【问题标题】:Java Calendar: 1am switchover to standard time (from DST)Java 日历:凌晨 1 点切换到标准时间(从 DST 开始)
【发布时间】:2012-10-28 02:14:40
【问题描述】:

我有一些代码使用 Calendar.set() 返回给定日期值的小时开始。我在 2012 年 11 月 4 日星期日(东部时区 - EDT 到 EST 切换)遇到了以下问题:

public void testStartOfHourDST1() {
    
    Calendar cal = Calendar.getInstance();
    
    long time = 1352005200000L; // Nov 4, 2012, 1AM EDT
    cal.setTimeInMillis(time);
        
    cal.set(Calendar.MILLISECOND, 0);
    cal.set(Calendar.SECOND, 0);
    cal.set(Calendar.MINUTE, 0);

    System.out.println(new Date(time));
    System.out.println(new Date(cal.getTimeInMillis()));
    System.out.println(System.getProperty("java.version"));
            
    assertEquals(cal.getTimeInMillis(), time); // fails
    
    return;
}

输出:

2012 年 11 月 4 日星期日 01:00:00 EDT

2012 年 11 月 4 日星期日 01:00:00 EST

1.6.0_35

也许这不是使用日历的正确方法,但在下一小时(或前一小时)运行相同的测试可以正常工作。这是 JVM 问题吗?

谢谢

【问题讨论】:

    标签: java calendar


    【解决方案1】:

    这不是一个真正的问题,因为它是确定性的并且按照它的程序来做。如果您希望它选择两个凌晨 1 点中较早的一个,这是一个问题!

    更改日历上的字段后,它拥有的唯一信息是“美国/东部时间凌晨 1 点”。好吧,你的时区那天有两个凌晨 1 点,应该选择哪一个? OpenJDK 的作者做出了一个决定,当出现这种歧义时,他们总是将其解释为标准时间的后者。此评论来自 java.util.GregorianCalendar OpenJDK 6:

        // 2. The transition out of DST.  Here, a designated time of 1:00 am - 1:59 am
        //    can be in standard or DST.  Both are valid representations (the rep
        //    jumps from 1:59:59 DST to 1:00:00 Std).
        //    Again, we assume standard time.
    

    如果您打印出数字的实际值,您将看到 cal.getTimeInMillis() 实际上比 time 的值改变了一个小时。

    【讨论】:

    • 好点。因此,Calendar 似乎不支持消除这两个重叠小时的歧义(使用 setTime/setTimeInMillis 或 @Nambari 提到的使用 setTimeZone 除外。
    • 日历是使用最近的时间还是在创建日历时应用默认时区(如果程序在已发生夏令时切换)...我不知道答案。
    • @jahroy java.util.GregorianCalendar 代码中的 cmets 表明它将始终将当天的 1:00-1:59 解释为标准时间,而不是夏令时任意约定。 " ...在这里,指定时间 1:00 am - 1:59 am 可以是标准时间或 DST。两者都是有效的表示(代表从 DST 1:59:59 跳转到 1:00:00 Std)。同样,我们假设标准时间。”
    【解决方案2】:

    获取正确的时区非常重要。例如,我确信您知道系统当前时间(以毫秒为单位)是从 1970 年 1 月 1 日午夜开始测量的。这是有据可查的。 Date 构造函数 JavaDoc 说:

    公开日期(长日期)

    分配一个 Date 对象并初始化它以表示指定的 自标准基准时间以来的毫秒数,称为“ 纪元”,即 1970 年 1 月 1 日 00:00:00 GMT。

    这很好,但是一开始这个程序的输出有点难以理解:

    import java.util.Date;
    import java.util.TimeZone;
    import java.text.SimpleDateFormat;
    
    public class Demo {
        public static void main(String[] args) {
            TimeZone tz = TimeZone.getTimeZone("Europe/London");
            Date d = new Date(-3600000);
    
            SimpleDateFormat df = new SimpleDateFormat("MMMM dd, yyyy HH:mm:ss.SSS z");
            df.setTimeZone(tz);
            System.out.println(String.format("%8dms -> %s",
                        Long.valueOf(d.getTime()) ,df.format(d)));
    
            d.setTime(0);
            System.out.println(String.format("%8dms -> %s",
                        Long.valueOf(d.getTime()) ,df.format(d)));
       }
    }
    

    输出是:

    -3600000ms -> January 01, 1970 00:00:00.000 GMT
           0ms -> January 01, 1970 01:00:00.000 GMT
    

    如您所见,纪元显然偏移了一个小时!

    除非它不是。如果您明确使用“GMT”时区,一切都很好。 “欧洲/伦敦”时区就是这么棘手。

    使用日历时,请了解您正在使用的时区,否则会被抓到。

    【讨论】:

    • 感谢您提供信息,但这个示例有点令人困惑,因为日期格式会针对不同的时区打印“GMT”。如果您使用“Z”输出格式,它将显示 +0100(很清楚)。因此,您的示例的问题不在于欧洲/伦敦“棘手”,而在于字符串输出不明确。我的错误是没有意识到 set() 函数会导致 Calendar 从头开始​​重新评估其 UTC 时间(因此在必须弄清楚要使用哪个 '1am' 之间感到困惑)。最初的问题只发生在支持 DST 偏移的时区。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-14
    • 2021-04-15
    • 1970-01-01
    • 2020-04-13
    • 2023-03-06
    相关资源
    最近更新 更多