diff --git serde/src/java/org/apache/hadoop/hive/serde2/io/DateWritable.java serde/src/java/org/apache/hadoop/hive/serde2/io/DateWritable.java index 5289983..2b0b550 100644 --- serde/src/java/org/apache/hadoop/hive/serde2/io/DateWritable.java +++ serde/src/java/org/apache/hadoop/hive/serde2/io/DateWritable.java @@ -128,7 +128,10 @@ public static Date timeToDate(long l) { public static long daysToMillis(int d) { // Convert from day offset to ms in UTC, then apply local timezone offset. long millisUtc = d * MILLIS_PER_DAY; - return millisUtc - LOCAL_TIMEZONE.get().getOffset(millisUtc); + long tmp = millisUtc - LOCAL_TIMEZONE.get().getOffset(millisUtc); + // Between millisUtc and tmp, the time zone offset may have changed due to DST. + // Look up the offset again. + return millisUtc - LOCAL_TIMEZONE.get().getOffset(tmp); } public static int dateToDays(Date d) { diff --git serde/src/test/org/apache/hadoop/hive/serde2/io/TestDateWritable.java serde/src/test/org/apache/hadoop/hive/serde2/io/TestDateWritable.java index 75de0a6..8b5bfbd 100644 --- serde/src/test/org/apache/hadoop/hive/serde2/io/TestDateWritable.java +++ serde/src/test/org/apache/hadoop/hive/serde2/io/TestDateWritable.java @@ -10,6 +10,12 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.TimeZone; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; public class TestDateWritable { @@ -135,4 +141,61 @@ public static void setupDateStrings() { private static String getRandomDateString() { return dateStrings[(int) (Math.random() * 365)]; } + + public static class DateTestCallable implements Callable { + public DateTestCallable() { + } + + @Override + public String call() throws Exception { + // Iterate through each day of the year, make sure Date/DateWritable match + Date originalDate = Date.valueOf("2014-01-01"); + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(originalDate.getTime()); + for (int idx = 0; idx < 365; ++idx) { + originalDate = new Date(cal.getTimeInMillis()); + // Make sure originalDate is at midnight in the local time zone, + // since DateWritable will generate dates at that time. + originalDate = Date.valueOf(originalDate.toString()); + DateWritable dateWritable = new DateWritable(originalDate); + if (!originalDate.equals(dateWritable.get())) { + return originalDate.toString(); + } + cal.add(Calendar.DAY_OF_YEAR, 1); + } + // Success! + return null; + } + } + + @Test + public void testDaylightSavingsTime() throws InterruptedException, ExecutionException { + String[] timeZones = { + "GMT", + "UTC", + "America/Godthab", + "America/Los_Angeles", + "Asia/Jerusalem", + "Australia/Melbourne", + "Europe/London", + // time zones with half hour boundaries + "America/St_Johns", + "Asia/Tehran", + }; + + for (String timeZone: timeZones) { + TimeZone previousDefault = TimeZone.getDefault(); + TimeZone.setDefault(TimeZone.getTimeZone(timeZone)); + assertEquals("Default timezone should now be " + timeZone, + timeZone, TimeZone.getDefault().getID()); + ExecutorService threadPool = Executors.newFixedThreadPool(1); + try { + Future future = threadPool.submit(new DateTestCallable()); + String result = future.get(); + assertNull("Failed at timezone " + timeZone + ", date " + result, result); + } finally { + threadPool.shutdown(); TimeZone.setDefault(previousDefault); + } + } + } }