java.util.Date
没有时区。它只有一个long
值,表示自 unix 纪元以来的毫秒数。
您看到的 ( Sun Mar 10 00.00.00 CST 2018
) 是toString()
方法的结果,它使用 JVM 默认时区将long
值转换为该时区中的日期和时间。有关更多详细信息,请参阅本文:
https ://codeblog.jonskeet.uk/2017/04/23/all-about-java-util-date/
无论如何,真正了解正在发生的事情的一种方法是检查这个long
值:
long millis = calendar.toGregorianCalendar().getTimeInMillis();
然后你可以用 UTC 打印这个值:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss XXX");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(sdf.format(new Date(millis)));
或者,如果您使用 Java 8:
System.out.println(Instant.ofEpochMilli(millis));
这将告诉您Date
对应的 UTC 时刻,因此您可以比依赖Date::toString()
方法更好地调试代码,这会造成混淆和误导。
关于您的主要问题,我尝试重现(我使用的是 Java 8,因为它比使用更容易操作Date
)。首先,我在 UTC-05:00 创建了一个对应于 2018-03-11 的日期/时间,我假设时间是午夜:
// March 11th 2018, midnight, UTC-05:00
OffsetDateTime odt = OffsetDateTime.parse("2018-03-11T00:00-05:00");
然后我将其转换America/Chicago
为时区,这是一个使用 CST/CDT的区域:
// get the same instant in Central Time
ZonedDateTime zdt = odt.atZoneSameInstant(ZoneId.of("America/Chicago"));
然后我打印了这个:
// print the date/time with timezone abbreviation
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm xxx z", Locale.US);
System.out.println(zdt.format(fmt)); // 2018-03-10 23:00 -06:00 CST
请注意,结果是2018-03-10 23:00 -06:00 CST : UTC-06:00 的 3 月 10 日。
这是因为在 2018 年,夏令时仅在3 月 11 日凌晨 2 点开始。午夜时分,DST 还没有开始,所以偏移量仍然是 UTC-06:00。
无论如何,您的转换代码是正确的,因为Date
它只代表一个时间点(自纪元以来经过的时间计数)并且没有附加时区。也许问题出在某个地方,检查 millis 值可能会帮助您了解发生了什么(我的猜测是XmlGregorianCalendar
当它不存在时将时间设置为午夜,这将解释Sun Mar 10 00.00.00 CST 2018的结果)。
如果这有帮助,发生 DST 转换的确切 UTC 时刻(UTC-06:00 的 2018 年 3 月 11 日凌晨 2 点)对应于毫秒值1520755200000
。如果您在 2018 年 3 月的日期值低于此值,则表示它们在 DST 开始之前,并且它们将在 CST 中。