0

我在白天更改 CST-CDT 重置期间遇到了以下问题。

我按预期从 Was8.5 服务器 2018-03-11-05.00 (UTC-5) 获取输入,但是对于 WAS7 服务器,以下方法返回 Sun Mar 10 00.00.00 CST 2018 而不是 Sun Mar 11 00.00 .00 CDT 2018

/*
 * Converts XMLGregorianCalendar to java.util.Date
 */
public static Date toDate(XMLGregorianCalendar calendar){
    if(calendar == null) {
        return null;
    }
    return calendar.toGregorianCalendar().getTime();
}

我知道服务器日期/时区重置没有正确进行,但如果我想在 CST 到 CDT 更改时获得正确的时间,反之亦然。如何重写要转换XMLGregorianCalendarjava.util.DateJava 的代码?

例如,如果传入请求是 CST(UTC-6),则 toDate(XMLGregorianCalendar 日历) 返回 CDT (UTC-5)。然后我想 toDate() 应该返回 CST (UTC-6)。

一样的方法,

如果传入请求是 CDT(UTC-5),则 toDate(XMLGregorianCalendar calendar) 返回 CST(UTC-6)。然后我想 toDate() 应该返回 CDT(UTC-5)。

4

2 回答 2

2

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 中。

于 2018-04-03T12:23:50.317 回答
0

我的第一个建议是你不需要你所要求的。正如我所看到的,您有一个日期和一个 UTC 偏移量,我并没有真正看到偏移量增加了任何有用的信息。就拿日期吧。我相信所发生的事情是,在 3 月 11 日过渡到夏令时之后的某个时间点被剥夺了一天中的时间,但是无论出于何种原因,或者可能根本没有任何原因,UTC 偏移量都被保留了。在一天的开始 (00:00) 给出时间时,偏移量与您的美国/芝加哥时区(或中央时区,但地区/城市格式的 ID 明确并推荐)不一致。

并且不要java.util.Date用于您的约会。那门课早就过时了。java.time今天,我们在现代 Java 日期和时间 API方面有了更好的表现。此外,它的LocalDate类更适合没有时间的日期,因为这正是它的本质,而 aDate实际上是一个时间点,即完全不同的故事。根据口味转换XMLGregorianCalendar可以通过两种方式发生。

直接的方式

    return LocalDate.of(calendar.getYear(), calendar.getMonth(), calendar.getDay());

与你XMLGregorianCalendar2018-03-11-05:00结果是一个LocalDate2018-03-11

通过GregorianCalendar和的间接方式ZonedDateTime

    return calendar.toGregorianCalendar().toZonedDateTime().toLocalDate();

结果是一样的。后者的优点是您不需要关心年、月和月日的各个字段。除其他外,这意味着您不会冒险将它们按错误的顺序排列。

如果您确实坚持保留时区或 UTC 偏移量,请至少采用偏移量。Sun Mar 11 00.00.00 CDT 2018 没有意义,因为 3 月 11 日 00:00 时 DST 尚未生效(从 02:00 开始)。这样一个不存在的时间只会让所有人感到困惑。将您的日历对象转换为OffsetDateTime

    return calendar.toGregorianCalendar().toZonedDateTime().toOffsetDateTime();

结果:2018-03-11T00:00-05:00。这个时间点存在。:-)

由于您calendar来自外部系统,因此您可能需要验证它,因为任何字段都可能未定义并返回DatatypeConstants.FIELD_UNDEFINED。使用 时LocalDate.of(),您可能会认为它的参数验证就足够了,因为它会反对DatatypeConstants.FIELD_UNDEFINED作为参数传递。toGregorianCalendar()另一方面会默认使用默认值,所以在使用它时我会认为验证是必不可少的。

你的代码出了什么问题?

我运行了您的代码,并且与 iolus 类似(请参阅其他答案),我得到了Sat Mar 10 23:00:00 CST 2018. 这是正确的时间点。正如 iolus 还解释的那样,这是以Date.toString这种方式呈现时间点。Date对象本身没有时区或 UTC 偏移量。所以我应该说你的代码是正确的。只是你对这种toString方法感到困惑。很多人都在你之前,最好的解决方案是完全避免Date上课。此外,我认为您的观察与 WAS 7 和 WAS 8.5 之间的任何区别无关。

于 2018-04-03T19:34:16.953 回答