Joda-Time, Daylight Saving Time change and date time parsing
我有以下问题使用Joda-Time解析和生成夏令时(DST)小时的日期和时间。这是一个例子(请注意,2008年3月30日是意大利的夏令时变化):
1 2 3 4 5 6 7 8 | DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss"); DateTime x = dtf.parseDateTime("30/03/2008 03:00:00"); int h = x.getHourOfDay(); System.out.println(h); System.out.println(x.toString("dd/MM/yyyy HH:mm:ss")); DateTime y = x.toDateMidnight().toDateTime().plusHours(h); System.out.println(y.getHourOfDay()); System.out.println(y.toString("dd/MM/yyyy HH:mm:ss")); |
我得到以下输出:
1 2 3 4 | 3 30/03/2008 03:00:00 4 30/03/2008 04:00:00 |
当我解析小时时,我得到小时为3.在我的数据结构中,我保存了存储午夜时间的那一天,然后我在一天中的每个小时(0-23)有一些值。然后,当我写出日期时,我会重新计算午夜加上小时的完整日期时间。当我总结3个小时到我的午夜时,我得到04:00:00!如果我再次解析它,我会得到4小时!
我的错误在哪里?有什么方法可以在我解析时获得第2小时或在打印时获得第3小时?
我也尝试手工构建输出:
1 |
但在这种情况下,小时2,我生成30/03/2008 02:00:00,这不是一个有效的日期(因为小时2不存在),不能再解析了。
预先感谢您的帮助。
菲利波
When I sum 3 hours to my midnight I get 04:00:00! And if I parse it again, I get hour 4! Where is my mistake?
您已经提到过这个日期恰好是时间的变化。所以没有错。 2010年3月30日00:00 CEST(意大利的时区)正好是2010年3月29日23:00 UTC。当您添加3小时后,您将获得2010年3月30日02:00 UTC。但是这是我们切换时间(发生在01:00 UTC)的时刻,因此当您将时间转换为当地时区时,您将获得3月30日04:00。这是正确的行为。
Is there some way to get hour 2 when I parse or get hour three when I print out?
不,因为2010年3月30日02:00 CEST不存在。正好在2010年3月30日01:00 UTC我们将时间从+1小时切换到+2小时与UTC相比,所以2010年3月30日00:59 UTC是2010年3月30日:01:59 CEST,但2010年3月30日01 :00 UTC成为2010年3月30日03:00 CEST。 No 02:在该特定日期存在xx小时。
BTW。在一个星期内,你可以期待另一个"有趣"。你能告诉UTC这个日期是指:
2010年10月31日02:15 CEST?
嗯,有趣的是,我们不知道。它可能是2010年10月31日00:15 UTC(实际时间切换之前)或2010年10月31日01:15 UTC(切换之后)。
这就是为什么你应该总是存储与UTC相关的日期和时间,并在显示之前将它们转换为本地时区,否则你可能会有歧义。
HTH。
在夏令时期间,保存数据的数据结构不是最佳选择。你这一天的特殊日子应该只有23个小时。
如果你这样做:
1 2 3 4 5 6 7 8 9 | DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss").withLocale(Locale.US); DateTime x = dtf.parseDateTime("30/03/2008 00:00:00"); DateTimeFormatter parser = DateTimeFormat.fullDateTime(); System.out.println("Start:"+parser.print(x)); DateTime y = x.plusHours(4); System.out.println("After add of 4:"+parser.print(y)); |
你得到了预期的结果,时间是05:00。
我建议您更改存储日期和使用日期的方式。如果没有,您必须在存储一天中的小时时处理夏令时。
你可能会这样做:
在我们将时间向前移动一小时的情况下,在这种情况下,您必须存储4而不是5作为5的时间。当您计算时间时,您应该使用plusHours()方法来获取实际时间。我想你可能会逃避:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class DateTest { private static final int HOUR_TO_TEST = 2; public static void main(String[] args) { DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss"); DateTime startOfDay = dtf.parseDateTime("30/03/2008 00:00:00"); /* Obtained from new DateTime() in code in practice */ DateTime actualTimeWhenStoring = startOfDay.plusHours(HOUR_TO_TEST); int hourOfDay = actualTimeWhenStoring.getHourOfDay(); int hourOffset = startOfDay.plusHours(hourOfDay).getHourOfDay(); System.out.println("Hour of day:" + hourOfDay); System.out.println("Offset hour:" + hourOffset); int timeToSave = hourOfDay; if (hourOffset != hourOfDay) { timeToSave = (hourOfDay + (hourOfDay - hourOffset)); } System.out.println("Time to save:" + timeToSave); /* When obtaining from db: */ DateTime recalculatedTime = startOfDay.plusHours(timeToSave); System.out.println("Hour of time 'read' from db:" + recalculatedTime.getHourOfDay()); } } |
......或基本上是这样的。如果你选择沿着这条路走,我会为它写一个测试。您可以更改HOUR_TO_TEST以查看它是否通过了夏令时。
建立在Pawe的正确答案之上?戴达& Knubo ...
ISO 8601字符串格式
您不应该以您提到的格式将日期时间存储(序列化)为字符串:
- 省略时区。
- 日,月,年订单含糊不清。
- 应该已经翻译成UTC时间。
如果必须将日期时间值序列化为文本,请使用可靠的格式。显而易见的选择是ISO 8601标准格式。更好的是将本地时间转换为UTC(Zulu)时区,然后转换为ISO 8601格式。像这样:
没有午夜
Joda-Time中的
Joda-Time 2.3中的示例代码
关于此源代码的一些评论:
1 2 3 4 5 6 7 8 9 10 11 | // ? 2013 Basil Bourque. This source code may be used freely forevery by anyone taking full responsibility for doing so. // Joda-Time - The popular alternative to Sun/Oracle's notoriously bad date, time, and calendar classes bundled with Java 7 and earlier. // http://www.joda.org/joda-time/ // Joda-Time will become outmoded by the JSR 310 Date and Time API introduced in Java 8. // JSR 310 was inspired by Joda-Time but is not directly based on it. // http://jcp.org/en/jsr/detail?id=310 // By default, Joda-Time produces strings in the standard ISO 8601 format. // https://en.wikipedia.org/wiki/ISO_8601 |
示例显示在意大利罗马的DST(夏令时)当天23小时,而后一天有24小时。请注意,指定了时区(对于罗马)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Time Zone list: http://joda-time.sourceforge.net/timezones.html org.joda.time.DateTimeZone romeTimeZone = org.joda.time.DateTimeZone.forID("Europe/Rome"); org.joda.time.DateTime dayOfDstChange = new org.joda.time.DateTime( 2008, 3, 30, 0, 0, romeTimeZone ) ; // Day when DST org.joda.time.DateTime dayAfter = dayOfDstChange.plusDays(1); // How many hours in this day? Should be 23 rather than 24 on day of Daylight Saving Time"springing ahead" to lose one hour. org.joda.time.Hours hoursObjectForDay = org.joda.time.Hours.hoursBetween(dayOfDstChange.withTimeAtStartOfDay(), dayAfter.withTimeAtStartOfDay()); System.out.println("Expect 23 hours, got:" + hoursObjectForDay.getHours() ); // Extract an int from object. // What time is 3 hours after midnight on day of DST change? org.joda.time.DateTime threeHoursAfterMidnightOnDayOfDst = dayOfDstChange.withTimeAtStartOfDay().plusHours(3); System.out.println("Expect 4 AM (04:00) for threeHoursAfterMidnightOnDayOfDst:" + threeHoursAfterMidnightOnDayOfDst ); // What time is 3 hours after midnight on day _after_ DST change? org.joda.time.DateTime threeHoursAfterMidnightOnDayAfterDst = dayAfter.withTimeAtStartOfDay().plusHours(3); System.out.println("Expect 3 AM (03:00) for threeHoursAfterMidnightOnDayAfterDst:" + threeHoursAfterMidnightOnDayAfterDst ); |
通过首先转换为UTC来存储日期时间的示例。然后在恢复日期时间对象时,调整到所需的时区。
1 2 3 4 5 6 7 8 9 10 11 12 13 | // Serialize DateTime object to text. org.joda.time.DateTimeZone romeTimeZone = org.joda.time.DateTimeZone.forID("Europe/Rome"); org.joda.time.DateTime dayOfDstChangeAtThreeHoursAfterMidnight = new org.joda.time.DateTime( 2008, 3, 30, 0, 0, romeTimeZone ).withTimeAtStartOfDay().plusHours(3); System.out.println("dayOfDstChangeAtThreeHoursAfterMidnight:" + dayOfDstChangeAtThreeHoursAfterMidnight); // Usually best to first change to UTC (Zulu) time when serializing. String dateTimeSerialized = dayOfDstChangeAtThreeHoursAfterMidnight.toDateTime( org.joda.time.DateTimeZone.UTC ).toString(); System.out.println("dateTimeBeingSerialized:" + dateTimeSerialized ); // Restore org.joda.time.DateTime restoredDateTime = org.joda.time.DateTime.parse( dateTimeSerialized ); System.out.println("restoredDateTime:" + restoredDateTime ); // Adjust to Rome Italy time zone. org.joda.time.DateTime restoredDateTimeAdjustedToRomeItaly = restoredDateTime.toDateTime(romeTimeZone); System.out.println("restoredDateTimeAdjustedToRomeItaly:" + restoredDateTimeAdjustedToRomeItaly ); |
运行时:
1 2 3 4 | dayOfDstChangeAtThreeHoursAfterMidnight: 2008-03-30T04:00:00.000+02:00 dateTimeBeingSerialized: 2008-03-30T02:00:00.000Z restoredDateTime: 2008-03-30T02:00:00.000Z restoredDateTimeAdjustedToRomeItaly: 2008-03-30T04:00:00.000+02:00 |