diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala index 43ca2cff5882..8f2877caeb83 100644 --- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala +++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/datetimeExpressions.scala @@ -402,23 +402,59 @@ case class DayOfMonth(child: Expression) extends UnaryExpression with ImplicitCa } } +// scalastyle:off line.size.limit @ExpressionDescription( - usage = "_FUNC_(date) - Returns the week of the year of the given date.", + usage = "_FUNC_(date[, format[, firstDayOfWeek]) - Returns the week of the year of the given date. Defaults to ISO 8601 standard: weeks start on Monday, and week 1 is defined as the first week in the new year that contains more than half of the days (Thursday in a Monday to Sunday week). Start of week can be overriden, and the week first week can be defined as the week that contains the first day of the new year by setting it to 'gregorian'.", extended = """ Examples: - > SELECT _FUNC_('2008-02-20'); - 8 + > SELECT _FUNC_('2011-01-01'); + 52 + > SELECT _FUNC_('2011-01-01', 'gregorian'); + 1 + > SELECT _FUNC_('2011-01-01', 'iso'); + 52 + > SELECT _FUNC_('2011-01-01', 'iso', 'monday'); + 52 + > SELECT _FUNC_('2011-01-01', 'iso', 'sunday'); + 1 + """) -case class WeekOfYear(child: Expression) extends UnaryExpression with ImplicitCastInputTypes { +// scalastyle:on line.size.limit +case class WeekOfYear(child: Expression, format: Expression, firstDayOfWeek: Expression) extends + UnaryExpression with ImplicitCastInputTypes { + + def this(child: Expression) = { + this(child, Literal("iso"), Literal("monday")) + } + + def this(child: Expression, format: Expression) = { + this(child, format, Literal("monday")) + } override def inputTypes: Seq[AbstractDataType] = Seq(DateType) override def dataType: DataType = IntegerType + @transient private lazy val minimalDays = { + if ("gregorian".equalsIgnoreCase(format.toString)) 1 else 4 + } + + @transient private lazy val startOfWeek = { + firstDayOfWeek.toString match { + case day if "monday".equalsIgnoreCase(day) => Calendar.MONDAY + case day if "tuesday".equalsIgnoreCase(day) => Calendar.TUESDAY + case day if "wednesday".equalsIgnoreCase(day) => Calendar.WEDNESDAY + case day if "thursday".equalsIgnoreCase(day) => Calendar.THURSDAY + case day if "friday".equalsIgnoreCase(day) => Calendar.FRIDAY + case day if "saturday".equalsIgnoreCase(day) => Calendar.SATURDAY + case day if "sunday".equalsIgnoreCase(day) => Calendar.SUNDAY + } + } + @transient private lazy val c = { val c = Calendar.getInstance(DateTimeUtils.getTimeZone("UTC")) - c.setFirstDayOfWeek(Calendar.MONDAY) - c.setMinimalDaysInFirstWeek(4) + c.setFirstDayOfWeek(startOfWeek) + c.setMinimalDaysInFirstWeek(minimalDays) c } @@ -435,8 +471,8 @@ case class WeekOfYear(child: Expression) extends UnaryExpression with ImplicitCa ctx.addMutableState(cal, c, s""" $c = $cal.getInstance($dtu.getTimeZone("UTC")); - $c.setFirstDayOfWeek($cal.MONDAY); - $c.setMinimalDaysInFirstWeek(4); + $c.setFirstDayOfWeek($startOfWeek); + $c.setMinimalDaysInFirstWeek($minimalDays); """) s""" $c.setTimeInMillis($time * 1000L * 3600L * 24L); diff --git a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala index 4ce68538c87a..679303de6943 100644 --- a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala +++ b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/DateExpressionsSuite.scala @@ -197,14 +197,29 @@ class DateExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper { } test("WeekOfYear") { - checkEvaluation(WeekOfYear(Literal.create(null, DateType)), null) - checkEvaluation(WeekOfYear(Literal(d)), 15) - checkEvaluation(WeekOfYear(Cast(Literal(sdfDate.format(d)), DateType, gmtId)), 15) - checkEvaluation(WeekOfYear(Cast(Literal(ts), DateType, gmtId)), 45) - checkEvaluation(WeekOfYear(Cast(Literal("2011-05-06"), DateType, gmtId)), 18) - checkEvaluation(WeekOfYear(Literal(new Date(sdf.parse("1582-10-15 13:10:15").getTime))), 40) - checkEvaluation(WeekOfYear(Literal(new Date(sdf.parse("1582-10-04 13:10:15").getTime))), 40) - checkConsistencyBetweenInterpretedAndCodegen(WeekOfYear, DateType) + checkEvaluation(WeekOfYear( + Literal.create(null, DateType), Literal("iso"), Literal("monday")), null) + checkEvaluation(WeekOfYear(Literal(d), Literal("iso"), Literal("monday")), 15) + checkEvaluation(WeekOfYear( + Cast(Literal(ts), DateType, gmtId), Literal("iso"), Literal("monday")), 45) + checkEvaluation(WeekOfYear( + Cast(Literal(sdfDate.format(d)), DateType, gmtId), Literal("iso"), Literal("monday")), 15) + checkEvaluation(WeekOfYear( + Literal(new Date(sdf.parse("1582-10-15 13:10:15").getTime)), + Literal("iso"), Literal("monday")), 40) + checkEvaluation(WeekOfYear( + Literal(new Date(sdf.parse("1582-10-04 13:10:15").getTime)), + Literal("iso"), Literal("monday")), 40) + checkEvaluation(WeekOfYear( + Cast(Literal("2011-01-01"), DateType, gmtId), Literal("iso"), Literal("monday")), 52) + checkEvaluation(WeekOfYear( + Cast(Literal("2011-01-01"), DateType, gmtId), Literal("gregorian"), Literal("monday")), 1) + checkEvaluation(WeekOfYear( + Cast(Literal("2011-01-01"), DateType, gmtId), Literal("iso"), Literal("sunday")), 52) + + checkConsistencyBetweenInterpretedAndCodegen( + (child: Expression) => WeekOfYear(child, Literal("iso"), Literal("monday")), DateType) + } test("DateFormat") {