Java 日历:凌晨 1 点切换到标准时间(从 DST 开始)
Posted
技术标签:
【中文标题】Java 日历:凌晨 1 点切换到标准时间(从 DST 开始)【英文标题】:Java Calendar: 1am switchover to standard time (from 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 问题吗?
谢谢
【问题讨论】:
【参考方案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 偏移的时区。以上是关于Java 日历:凌晨 1 点切换到标准时间(从 DST 开始)的主要内容,如果未能解决你的问题,请参考以下文章