Java Joda时间、夏令时更改和日期时间解析

Java Joda时间、夏令时更改和日期时间解析,java,parsing,jodatime,dst,Java,Parsing,Jodatime,Dst,我在解析和生成日期和时间(DST)小时左右时遇到以下问题。下面是一个例子(请注意,2008年3月30日是意大利的夏时制变革): 我得到以下输出: 3 30/03/2008 03:00:00 4 30/03/2008 04:00:00 当我计算小时数时,我得到的小时数是3。在我的数据结构中,我存储了一天的午夜时间,然后一天中的每一小时都有一些值(0-23)。然后,当我写下日期时,我重新计算了完整的日期时间,即午夜加小时。当我把3个小时算到午夜的时候,我得到了04:00:00!如果我再次解析它,我

我在解析和生成日期和时间(DST)小时左右时遇到以下问题。下面是一个例子(请注意,2008年3月30日是意大利的夏时制变革):

我得到以下输出:

3
30/03/2008 03:00:00
4
30/03/2008 04:00:00
当我计算小时数时,我得到的小时数是3。在我的数据结构中,我存储了一天的午夜时间,然后一天中的每一小时都有一些值(0-23)。然后,当我写下日期时,我重新计算了完整的日期时间,即午夜加小时。当我把3个小时算到午夜的时候,我得到了04:00:00!如果我再次解析它,我会得到4小时

我的错在哪里?有什么方法可以让我在解析的时候得到2小时,或者打印出来的时候得到3小时

我还尝试手动生成输出:

String.format("%s %02d:00:00", date.toString("dd/MM/yyyy"), h);
但在本例中,对于小时2,我生成了30/03/2008 02:00:00,这不是一个有效的日期(因为小时2不存在),并且无法再进行解析

提前感谢您的帮助。
Filippo

您保存数据的数据结构对于夏令时的日子来说不是很理想。在这个特殊的日子里,你的一天应该只有23个小时

如果您这样做:

    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()方法来获得实际时间。我想你可能会通过以下方式逃脱:

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\u更改为\u测试,以查看它是否已超过夏令时

当我把3个小时算到午夜的时候,我得到了04:00:00!如果我再次解析它,我会得到4小时!我的错在哪里

您已经提到,这个日期正是时间改变的时候。所以没有错。2010年3月30日00:00 CEST(意大利时区)正是2010年3月29日23:00 UTC。添加3小时后,您将获得2010年3月30日UTC 02:00。但这是我们切换时间(发生在UTC 01:00)的后一刻,所以当您将时间转换为本地时区时,您将得到3月30日04:00。这是正确的行为

有什么方法可以让我在解析的时候得到2小时,或者打印出来的时候得到3小时

否,因为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。否02:xx小时在该特定日期存在

顺便说一句,在一周内你可以期待另一个“乐趣”。您能告诉我这指的是UTC的哪个日期吗

2010年10月31日02:15 CEST

好笑的是,我们不知道。它可以是2010年10月31日凌晨0:15 UTC(实际时间切换前)或2010年10月31日凌晨1:15 UTC(切换后)

这正是为什么您应该始终存储与UTC相关的日期和时间,并在显示之前将其转换为本地时区,否则可能会产生歧义


HTH.

基于PawełDyda和Knubo的正确答案

字符串格式的ISO 8601 您不应该以您提到的格式将()日期时间存储为字符串:
“30/03/2008 03:00:00”
。问题:

  • 省略时区
  • 日、月、年订单不明确
  • 应已转换为UTC时间
如果必须将日期时间值序列化为文本,请使用可靠的格式。显而易见的选择是标准格式。更好的方法是将本地时间转换为时区,然后转换为ISO 8601格式。像这样:
2013-11-01T04:48:53.044Z

没有午夜 Joda Time中的
midnight
方法被弃用,取而代之的是Joda Time方法
withTimeAtStartOfDay()
(请参阅)。有些日子的确如此

Joda Time 2.3中的示例代码 关于此源代码的一些评论:

    // © 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小时。请注意,指定了时区(罗马)

    // 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来存储日期时间的示例。然后在恢复日期时间对象后,调整到所需的时区

    // 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 );
运行时:

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

谢谢你的回复。遗憾的是,当我存储数据时,我没有小时(您的hour-TO-TEST属性),我从我解析的日期-时间字符串中得到它。因此,我唯一的解决办法(我认为)是每小时检查dst的变化,并在需要时进行修正。从这个意义上讲,我现在正在修改我的代码,它似乎可以工作。如果你有一个字符串,我想你可以使用hourOfDay作为那个字符串,对吗?提供字符串的人在某个时区提供字符串,对吗?如果该时区也受到夏令时的影响,那么这应该不是问题,因为制作人和作为消费者的您位于同一时区(那么将永远不会向您发送非法日期02:00)。如果您在其他时区(例如UTC)获取日期,则parseDateTime方法具有解析当前区域设置以外的其他时区的功能-请查阅文档。+1为了在不存储与UTC.FYI相关的日期时澄清此问题,项目现在已启动,团队建议迁移到类。看见
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