diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java index 942be1587..46a35c1a4 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcTypeConverter.java @@ -43,6 +43,7 @@ import java.util.Arrays; import java.util.Calendar; import java.util.List; +import java.util.TimeZone; import java.util.concurrent.TimeUnit; /** Convenience class for converting values between Java, JDBC and Cloud Spanner. */ @@ -496,10 +497,18 @@ private static java.sql.Timestamp getOrSetTimestampInCalendar( Calendar newCal = Calendar.getInstance(cal.getTimeZone()); // set the millisecond time on this calendar from the timestamp newCal.setTimeInMillis(sqlTs.getTime()); - newCal.set(Calendar.MILLISECOND, 0); + + TimeZone timeZone = newCal.getTimeZone(); + long totalMillis = newCal.getTimeInMillis(); + // to calculate the offset for DST correctly, we need to add DST savings and check if + // given epoch milli is in daylight savings time. + if (getOrSet == GetOrSetTimestampInCalendar.GET) { + totalMillis += timeZone.getRawOffset() + timeZone.getDSTSavings(); + } + // then shift the time of the calendar by the difference between UTC and the timezone of the // given calendar - int offset = newCal.getTimeZone().getOffset(newCal.getTimeInMillis()); + int offset = newCal.getTimeZone().getOffset(totalMillis); newCal.add( Calendar.MILLISECOND, getOrSet == GetOrSetTimestampInCalendar.GET ? offset : -offset); // then use that to create a sql timestamp diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java index 26eaffe2d..db36b47fa 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcTypeConverterTest.java @@ -51,6 +51,8 @@ import java.sql.Time; import java.sql.Timestamp; import java.text.DecimalFormat; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -959,6 +961,29 @@ public void testDateToSqlTimestampWithCalendar() { .toSqlTimestamp()); } + @Test + public void testDateToSqlTimestampWithCalendarWithStartOfDST() { + TimeZone timeZone = TimeZone.getTimeZone("Europe/Oslo"); + + List zonedDateTimes = + Arrays.asList( + ZonedDateTime.of(2018, 3, 25, 2, 0, 0, 0, ZoneId.of("+01:00")), + ZonedDateTime.of(2018, 10, 28, 2, 0, 0, 0, ZoneId.of("+01:00"))); + + zonedDateTimes.forEach( + expected -> { + Timestamp expectedTimestamp = Timestamp.from(expected.toInstant()); + Calendar cal = Calendar.getInstance(timeZone); + Timestamp storeTimestamp = + JdbcTypeConverter.setTimestampInCalendar(expectedTimestamp, cal); + + Timestamp resultTimestamp = JdbcTypeConverter.getTimestampInCalendar(storeTimestamp, cal); + ZonedDateTime actual = resultTimestamp.toInstant().atZone(timeZone.toZoneId()); + + assertThat(actual).isEqualTo(expected.withZoneSameInstant(timeZone.toZoneId())); + }); + } + @Test public void testParseSqlTimeWithCalendar() { assertThat(