Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
c48a70d
Prepare for session local timezone support.
ueshin Dec 6, 2016
1d21fec
Make Cast TimeZoneAwareExpression.
ueshin Dec 6, 2016
0763c8f
Fix DateTimeUtilsSuite to follow changes.
ueshin Dec 6, 2016
449d93d
Make some datetime expressions TimeZoneAwareExpression.
ueshin Dec 8, 2016
b59d902
Fix compiler error in sql/core.
ueshin Dec 8, 2016
3ddfae4
Add constructors without zoneId to TimeZoneAwareExpressions for Funct…
ueshin Dec 9, 2016
f58f00d
Add DateTimeUtils.threadLocalLocalTimeZone to partition-reltated Cast.
ueshin Dec 13, 2016
8f2040b
Fix timezone for Hive timestamp string.
ueshin Dec 13, 2016
63c103c
Use defaultTimeZone instead of threadLocalLocalTimeZone.
ueshin Dec 13, 2016
7066850
Add TimeZone to DateFormats.
ueshin Dec 13, 2016
1aaca29
Make `CurrentBatchTimestamp` `TimeZoneAwareExpression`.
ueshin Dec 14, 2016
e5bb246
Add tests for date functions with session local timezone.
ueshin Dec 14, 2016
32cc391
Remove unused import and small cleanup.
ueshin Dec 16, 2016
f434378
Fix tests.
ueshin Dec 16, 2016
16fd1e4
Rename `zoneId` to `timeZoneId`.
ueshin Dec 19, 2016
009c17b
Use lazy val to avoid to keep creating a new timezone object (or doin…
ueshin Dec 19, 2016
a2936ed
Modify ComputeCurrentTime to hold the same date.
ueshin Dec 19, 2016
c5ca73e
Add comments.
ueshin Dec 19, 2016
b860379
Fix `Cast.needTimeZone()` to handle complex types.
ueshin Dec 19, 2016
6746265
Fix `Dataset.showString()` to use session local timezone.
ueshin Dec 19, 2016
4b6900c
Merge branch 'master' into issues/SPARK-18350
ueshin Dec 20, 2016
4f9cc40
Modify to analyze `ResolveTimeZone` only once.
ueshin Dec 24, 2016
2ca2413
Use session local timezone for Hive string.
ueshin Dec 24, 2016
c232854
Merge branch 'master' into issues/SPARK-18350
ueshin Dec 26, 2016
5b6dd4f
Merge branch 'master' into issues/SPARK-18350
ueshin Jan 5, 2017
1ca5808
Use `addReferenceMinorObj` to avoid adding member variables.
ueshin Jan 10, 2017
702dd81
Use Option[String] for timeZoneId.
ueshin Jan 10, 2017
33a3425
Update a comment.
ueshin Jan 10, 2017
5cc93e3
Fix overloaded constructors.
ueshin Jan 11, 2017
5521165
Fix session local timezone for timezone sensitive tests.
ueshin Jan 11, 2017
bd8275e
Remove `timeZoneResolved` and use `timeZoneId.isEmpty` instead in `Re…
ueshin Jan 11, 2017
183945c
Merge branch 'master' into issues/SPARK-18350
ueshin Jan 14, 2017
22a3b6e
Remove unused parameter.
ueshin Jan 16, 2017
30d51fa
Merge branch 'master' into issues/SPARK-18350
ueshin Jan 16, 2017
043ab52
Use Cast directly instead of dsl.
ueshin Jan 16, 2017
3ba5830
Merge branch 'master' into issues/SPARK-18350
ueshin Jan 22, 2017
9ab31f0
Revert unnecessary changes.
ueshin Jan 22, 2017
b954947
Use `@` binding to simplify pattern match.
ueshin Jan 22, 2017
dbb2604
Inline a `lazy val`.
ueshin Jan 22, 2017
186cd3e
Add some TODO comments for follow-up prs.
ueshin Jan 22, 2017
6631a69
Add a config document.
ueshin Jan 22, 2017
3610465
Use an overload version of `checkAnswer`.
ueshin Jan 22, 2017
c12e596
Fix CastSuite and add some comments to describe the tests.
ueshin Jan 22, 2017
8a04e80
Use None instead of null.
ueshin Jan 22, 2017
efe3aff
Add some comments to describe the tests.
ueshin Jan 22, 2017
cdbb266
Make TimeAdd/TimeSub/MonthsBetween TimeZoneAwareExpression.
ueshin Jan 22, 2017
328399a
Add comments to explain tests.
ueshin Jan 23, 2017
7352612
Modify a test.
ueshin Jan 23, 2017
b99cf79
Refine tests.
ueshin Jan 25, 2017
a85377f
Remove unnecessary new lines.
ueshin Jan 26, 2017
f0c911b
Add newDateFormat to DateTimeUtils and use it.
ueshin Jan 26, 2017
6fa1d6a
Parameterize some tests.
ueshin Jan 26, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Make TimeAdd/TimeSub/MonthsBetween TimeZoneAwareExpression.
  • Loading branch information
ueshin committed Jan 22, 2017
commit cdbb26651d574c908f8badcc945bddfedb6545be
Original file line number Diff line number Diff line change
Expand Up @@ -880,8 +880,10 @@ case class NextDay(startDate: Expression, dayOfWeek: Expression)
/**
* Adds an interval to timestamp.
*/
case class TimeAdd(start: Expression, interval: Expression)
extends BinaryExpression with ImplicitCastInputTypes {
case class TimeAdd(start: Expression, interval: Expression, timeZoneId: Option[String] = None)
extends BinaryExpression with TimeZoneAwareExpression with ImplicitCastInputTypes {

def this(start: Expression, interval: Expression) = this(start, interval, None)

override def left: Expression = start
override def right: Expression = interval
Expand All @@ -892,16 +894,20 @@ case class TimeAdd(start: Expression, interval: Expression)

override def dataType: DataType = TimestampType

override def withTimeZone(timeZoneId: String): TimeZoneAwareExpression =
copy(timeZoneId = Option(timeZoneId))

override def nullSafeEval(start: Any, interval: Any): Any = {
val itvl = interval.asInstanceOf[CalendarInterval]
DateTimeUtils.timestampAddInterval(
start.asInstanceOf[Long], itvl.months, itvl.microseconds)
start.asInstanceOf[Long], itvl.months, itvl.microseconds, timeZone)
}

override def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = {
val tz = ctx.addReferenceMinorObj(timeZone)
val dtu = DateTimeUtils.getClass.getName.stripSuffix("$")
defineCodeGen(ctx, ev, (sd, i) => {
s"""$dtu.timestampAddInterval($sd, $i.months, $i.microseconds)"""
s"""$dtu.timestampAddInterval($sd, $i.months, $i.microseconds, $tz)"""
})
}
}
Expand Down Expand Up @@ -967,8 +973,10 @@ case class FromUTCTimestamp(left: Expression, right: Expression)
/**
* Subtracts an interval from timestamp.
*/
case class TimeSub(start: Expression, interval: Expression)
extends BinaryExpression with ImplicitCastInputTypes {
case class TimeSub(start: Expression, interval: Expression, timeZoneId: Option[String] = None)
extends BinaryExpression with TimeZoneAwareExpression with ImplicitCastInputTypes {

def this(start: Expression, interval: Expression) = this(start, interval, None)

override def left: Expression = start
override def right: Expression = interval
Expand All @@ -979,16 +987,20 @@ case class TimeSub(start: Expression, interval: Expression)

override def dataType: DataType = TimestampType

override def withTimeZone(timeZoneId: String): TimeZoneAwareExpression =
copy(timeZoneId = Option(timeZoneId))

override def nullSafeEval(start: Any, interval: Any): Any = {
val itvl = interval.asInstanceOf[CalendarInterval]
DateTimeUtils.timestampAddInterval(
start.asInstanceOf[Long], 0 - itvl.months, 0 - itvl.microseconds)
start.asInstanceOf[Long], 0 - itvl.months, 0 - itvl.microseconds, timeZone)
}

override def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = {
val tz = ctx.addReferenceMinorObj(timeZone)
val dtu = DateTimeUtils.getClass.getName.stripSuffix("$")
defineCodeGen(ctx, ev, (sd, i) => {
s"""$dtu.timestampAddInterval($sd, 0 - $i.months, 0 - $i.microseconds)"""
s"""$dtu.timestampAddInterval($sd, 0 - $i.months, 0 - $i.microseconds, $tz)"""
})
}
}
Expand Down Expand Up @@ -1041,8 +1053,10 @@ case class AddMonths(startDate: Expression, numMonths: Expression)
3.94959677
""")
// scalastyle:on line.size.limit
case class MonthsBetween(date1: Expression, date2: Expression)
extends BinaryExpression with ImplicitCastInputTypes {
case class MonthsBetween(date1: Expression, date2: Expression, timeZoneId: Option[String] = None)
extends BinaryExpression with TimeZoneAwareExpression with ImplicitCastInputTypes {

def this(date1: Expression, date2: Expression) = this(date1, date2, None)

override def left: Expression = date1
override def right: Expression = date2
Expand All @@ -1051,14 +1065,18 @@ case class MonthsBetween(date1: Expression, date2: Expression)

override def dataType: DataType = DoubleType

override def withTimeZone(timeZoneId: String): TimeZoneAwareExpression =
copy(timeZoneId = Option(timeZoneId))

override def nullSafeEval(t1: Any, t2: Any): Any = {
DateTimeUtils.monthsBetween(t1.asInstanceOf[Long], t2.asInstanceOf[Long])
DateTimeUtils.monthsBetween(t1.asInstanceOf[Long], t2.asInstanceOf[Long], timeZone)
}

override def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = {
val tz = ctx.addReferenceMinorObj(timeZone)
val dtu = DateTimeUtils.getClass.getName.stripSuffix("$")
defineCodeGen(ctx, ev, (l, r) => {
s"""$dtu.monthsBetween($l, $r)"""
s"""$dtu.monthsBetween($l, $r, $tz)"""
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -792,9 +792,23 @@ object DateTimeUtils {
* Returns a timestamp value, expressed in microseconds since 1.1.1970 00:00:00.
*/
def timestampAddInterval(start: SQLTimestamp, months: Int, microseconds: Long): SQLTimestamp = {
val days = millisToDays(start / 1000L)
timestampAddInterval(start, months, microseconds, defaultTimeZone())
}

/**
* Add timestamp and full interval.
* Returns a timestamp value, expressed in microseconds since 1.1.1970 00:00:00.
*/
def timestampAddInterval(
start: SQLTimestamp,
months: Int,
microseconds: Long,
timeZone: TimeZone): SQLTimestamp = {
val days = millisToDays(start / 1000L, timeZone)
val newDays = dateAddMonths(days, months)
daysToMillis(newDays) * 1000L + start - daysToMillis(days) * 1000L + microseconds
start +
daysToMillis(newDays, timeZone) * 1000L - daysToMillis(days, timeZone) * 1000L +
microseconds
}

/**
Expand All @@ -808,10 +822,24 @@ object DateTimeUtils {
* 8 digits.
*/
def monthsBetween(time1: SQLTimestamp, time2: SQLTimestamp): Double = {
monthsBetween(time1, time2, defaultTimeZone())
}

/**
* Returns number of months between time1 and time2. time1 and time2 are expressed in
* microseconds since 1.1.1970.
*
* If time1 and time2 having the same day of month, or both are the last day of month,
* it returns an integer (time under a day will be ignored).
*
* Otherwise, the difference is calculated based on 31 days per month, and rounding to
* 8 digits.
*/
def monthsBetween(time1: SQLTimestamp, time2: SQLTimestamp, timeZone: TimeZone): Double = {
val millis1 = time1 / 1000L
val millis2 = time2 / 1000L
val date1 = millisToDays(millis1)
val date2 = millisToDays(millis2)
val date1 = millisToDays(millis1, timeZone)
val date2 = millisToDays(millis2, timeZone)
val (year1, monthInYear1, dayInMonth1, daysToMonthEnd1) = splitDate(date1)
val (year2, monthInYear2, dayInMonth2, daysToMonthEnd2) = splitDate(date2)

Expand All @@ -822,8 +850,8 @@ object DateTimeUtils {
return (months1 - months2).toDouble
}
// milliseconds is enough for 8 digits precision on the right side
val timeInDay1 = millis1 - daysToMillis(date1)
val timeInDay2 = millis2 - daysToMillis(date2)
val timeInDay1 = millis1 - daysToMillis(date1, timeZone)
val timeInDay2 = millis2 - daysToMillis(date2, timeZone)
val timesBetween = (timeInDay1 - timeInDay2).toDouble / MILLIS_PER_DAY
val diff = (months1 - months2).toDouble + (dayInMonth1 - dayInMonth2 + timesBetween) / 31.0
// rounding to 8 digits
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,46 +308,86 @@ class DateExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper {
}

test("time_add") {
checkEvaluation(
TimeAdd(Literal(Timestamp.valueOf("2016-01-29 10:00:00")),
Literal(new CalendarInterval(1, 123000L))),
DateTimeUtils.fromJavaTimestamp(Timestamp.valueOf("2016-02-29 10:00:00.123")))
val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
for (tz <- Seq(TimeZoneGMT, TimeZonePST, TimeZoneJST)) {
val timeZoneId = Option(tz.getID)
sdf.setTimeZone(tz)

checkEvaluation(
TimeAdd(Literal.create(null, TimestampType), Literal(new CalendarInterval(1, 123000L))),
null)
checkEvaluation(
TimeAdd(Literal(Timestamp.valueOf("2016-01-29 10:00:00")),
Literal.create(null, CalendarIntervalType)),
null)
checkEvaluation(
TimeAdd(Literal.create(null, TimestampType), Literal.create(null, CalendarIntervalType)),
null)
checkConsistencyBetweenInterpretedAndCodegen(TimeAdd, TimestampType, CalendarIntervalType)
checkEvaluation(
TimeAdd(
Literal(new Timestamp(sdf.parse("2016-01-29 10:00:00.000").getTime)),
Literal(new CalendarInterval(1, 123000L)),
timeZoneId),
DateTimeUtils.fromJavaTimestamp(
new Timestamp(sdf.parse("2016-02-29 10:00:00.123").getTime)))

checkEvaluation(
TimeAdd(
Literal.create(null, TimestampType),
Literal(new CalendarInterval(1, 123000L)),
timeZoneId),
null)
checkEvaluation(
TimeAdd(
Literal(new Timestamp(sdf.parse("2016-01-29 10:00:00.000").getTime)),
Literal.create(null, CalendarIntervalType),
timeZoneId),
null)
checkEvaluation(
TimeAdd(
Literal.create(null, TimestampType),
Literal.create(null, CalendarIntervalType),
timeZoneId),
null)
checkConsistencyBetweenInterpretedAndCodegen(
(start: Expression, interval: Expression) => TimeAdd(start, interval, timeZoneId),
TimestampType, CalendarIntervalType)
}
}

test("time_sub") {
checkEvaluation(
TimeSub(Literal(Timestamp.valueOf("2016-03-31 10:00:00")),
Literal(new CalendarInterval(1, 0))),
DateTimeUtils.fromJavaTimestamp(Timestamp.valueOf("2016-02-29 10:00:00")))
checkEvaluation(
TimeSub(
Literal(Timestamp.valueOf("2016-03-30 00:00:01")),
Literal(new CalendarInterval(1, 2000000.toLong))),
DateTimeUtils.fromJavaTimestamp(Timestamp.valueOf("2016-02-28 23:59:59")))
val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
for (tz <- Seq(TimeZoneGMT, TimeZonePST, TimeZoneJST)) {
val timeZoneId = Option(tz.getID)
sdf.setTimeZone(tz)

checkEvaluation(
TimeSub(Literal.create(null, TimestampType), Literal(new CalendarInterval(1, 123000L))),
null)
checkEvaluation(
TimeSub(Literal(Timestamp.valueOf("2016-01-29 10:00:00")),
Literal.create(null, CalendarIntervalType)),
null)
checkEvaluation(
TimeSub(Literal.create(null, TimestampType), Literal.create(null, CalendarIntervalType)),
null)
checkConsistencyBetweenInterpretedAndCodegen(TimeSub, TimestampType, CalendarIntervalType)
checkEvaluation(
TimeSub(
Literal(new Timestamp(sdf.parse("2016-03-31 10:00:00.000").getTime)),
Literal(new CalendarInterval(1, 0)),
timeZoneId),
DateTimeUtils.fromJavaTimestamp(
new Timestamp(sdf.parse("2016-02-29 10:00:00.000").getTime)))
checkEvaluation(
TimeSub(
Literal(new Timestamp(sdf.parse("2016-03-30 00:00:01.000").getTime)),
Literal(new CalendarInterval(1, 2000000.toLong)),
timeZoneId),
DateTimeUtils.fromJavaTimestamp(
new Timestamp(sdf.parse("2016-02-28 23:59:59.000").getTime)))

checkEvaluation(
TimeSub(
Literal.create(null, TimestampType),
Literal(new CalendarInterval(1, 123000L)),
timeZoneId),
null)
checkEvaluation(
TimeSub(
Literal(new Timestamp(sdf.parse("2016-01-29 10:00:00.000").getTime)),
Literal.create(null, CalendarIntervalType),
timeZoneId),
null)
checkEvaluation(
TimeSub(
Literal.create(null, TimestampType),
Literal.create(null, CalendarIntervalType),
timeZoneId),
null)
checkConsistencyBetweenInterpretedAndCodegen(
(start: Expression, interval: Expression) => TimeSub(start, interval, timeZoneId),
TimestampType, CalendarIntervalType)
}
}

test("add_months") {
Expand All @@ -371,28 +411,44 @@ class DateExpressionsSuite extends SparkFunSuite with ExpressionEvalHelper {
}

test("months_between") {
checkEvaluation(
MonthsBetween(Literal(Timestamp.valueOf("1997-02-28 10:30:00")),
Literal(Timestamp.valueOf("1996-10-30 00:00:00"))),
3.94959677)
checkEvaluation(
MonthsBetween(Literal(Timestamp.valueOf("2015-01-30 11:52:00")),
Literal(Timestamp.valueOf("2015-01-30 11:50:00"))),
0.0)
checkEvaluation(
MonthsBetween(Literal(Timestamp.valueOf("2015-01-31 00:00:00")),
Literal(Timestamp.valueOf("2015-03-31 22:00:00"))),
-2.0)
checkEvaluation(
MonthsBetween(Literal(Timestamp.valueOf("2015-03-31 22:00:00")),
Literal(Timestamp.valueOf("2015-02-28 00:00:00"))),
1.0)
val t = Literal(Timestamp.valueOf("2015-03-31 22:00:00"))
val tnull = Literal.create(null, TimestampType)
checkEvaluation(MonthsBetween(t, tnull), null)
checkEvaluation(MonthsBetween(tnull, t), null)
checkEvaluation(MonthsBetween(tnull, tnull), null)
checkConsistencyBetweenInterpretedAndCodegen(MonthsBetween, TimestampType, TimestampType)
val sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
for (tz <- Seq(TimeZoneGMT, TimeZonePST, TimeZoneJST)) {
val timeZoneId = Option(tz.getID)
sdf.setTimeZone(tz)

checkEvaluation(
MonthsBetween(
Literal(new Timestamp(sdf.parse("1997-02-28 10:30:00").getTime)),
Literal(new Timestamp(sdf.parse("1996-10-30 00:00:00").getTime)),
timeZoneId),
3.94959677)
checkEvaluation(
MonthsBetween(
Literal(new Timestamp(sdf.parse("2015-01-30 11:52:00").getTime)),
Literal(new Timestamp(sdf.parse("2015-01-30 11:50:00").getTime)),
timeZoneId),
0.0)
checkEvaluation(
MonthsBetween(
Literal(new Timestamp(sdf.parse("2015-01-31 00:00:00").getTime)),
Literal(new Timestamp(sdf.parse("2015-03-31 22:00:00").getTime)),
timeZoneId),
-2.0)
checkEvaluation(
MonthsBetween(
Literal(new Timestamp(sdf.parse("2015-03-31 22:00:00").getTime)),
Literal(new Timestamp(sdf.parse("2015-02-28 00:00:00").getTime)),
timeZoneId),
1.0)
val t = Literal(Timestamp.valueOf("2015-03-31 22:00:00"))
val tnull = Literal.create(null, TimestampType)
checkEvaluation(MonthsBetween(t, tnull, timeZoneId), null)
checkEvaluation(MonthsBetween(tnull, t, timeZoneId), null)
checkEvaluation(MonthsBetween(tnull, tnull, timeZoneId), null)
checkConsistencyBetweenInterpretedAndCodegen(
(time1: Expression, time2: Expression) => MonthsBetween(time1, time2, timeZoneId),
TimestampType, TimestampType)
}
}

test("last_day") {
Expand Down