如何使用XMLGregorianCalendar处理CST到CDT,反之亦然

时间:2018-04-03 07:38:40

标签: java date websphere-8 websphere-7 xmlgregoriancalendar

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

按预期从Was8.5服务器2018-03-11-05.00(UTC-5)获取输入,但是当涉及到WAS7服务器时,以下方法返回Sun Mar 10 00.00.00 CST 2018而不是Sun 3月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的时间改变或反之亦然。如何重写代码以在Java中将XMLGregorianCalendar转换为java.util.Date

有点像, 如果传入请求是CST(UTC-6),则toDate(XMLGregorianCalendar日历)返回CDT(UTC-5)。那么我想要Date()应该返回CST(UTC-6)。

同样的方式,

如果传入请求是CDT(UTC-5),则toDate(XMLGregorianCalendar日历)将返回CST(UTC-6)。那么我想要Date()应该返回CDT(UTC-5)。

2 个答案:

答案 0 :(得分:2)

java.util.Date没有时区。它只有一个long值,表示自unix epoch以来的毫秒数。

您看到的内容(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时区,这是一个zone that uses 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 :3月10日UTC-06:00。

那是因为在2018年,Daylight Saving Time starts only at 2 AM of March 11th。在午夜,DST还没有开始,所以偏移量仍然是UTC-06:00。

无论如何,您的转换代码是正确的,因为Date只代表一个时间点(自纪元以来经过的时间计数)并且没有附加时区。也许问题出在某个地方,检查毫秒值可能会帮助你理解正在发生的事情(我的猜测是XmlGregorianCalendar将时间设置为午夜,当它不存在时,这可以解释结果 Sun Mar 10 00.00.00 CST 2018 )。

如果有帮助,发生DST转换的确切UTC时刻(2018年3月11日凌晨2点,UTC-06:00)对应于毫秒值1520755200000。如果您在2018年3月的日期值低于此值,则表示他们在夏令时开始之前已经过,并且他们将在CST。

答案 1 :(得分:0)

我的第一个建议是你不需要你要求的东西。在我看来,你有一个日期和一个UTC偏移,我并没有真正看到偏移添加任何有用的信息。只需要约会。我相信所发生的事情是,在3月11日过渡到夏令时之后的某个时间点被剥夺了时间,但UTC偏移量无论出于何种原因或者根本没有任何理由而被保留。在白天开始时(00:00)给出时间时,偏移量与您在美国/芝加哥(或中央时区)的时区不一致,但 region / city 格式的ID是明确的并且建议)。

不要将java.util.Date用于您的约会对象。那堂课已经过时了。今天我们在java.time,现代Java日期和时间API方面做得更好。此外,它的LocalDate类更适合没有时间的日期,因为这正是它的本质,而Date实际上是一个时间点,即一个完全不同的故事。根据品味转换XMLGregorianCalendar可以通过两种方式进行。

直接的方式

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

使用XMLGregorianCalendar 2018-03-11-05:00,结果为LocalDate 2018-03-11

通过GregorianCalendarZonedDateTime的间接方式:

    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(见the other answer)类似,我得到了Sat Mar 10 23:00:00 CST 2018。这是正确的时间点。正如iolus还解释的那样,这是Date.toString以这种方式呈现时间点。 Date对象本身没有时区或UTC偏移量。所以我应该说你的代码是正确的。只是你被toString方法搞糊涂了。很多人都在你面前,好的解决方案是完全避免Date课程。此外,我认为您的观察与WAS 7和WAS 8.5之间的任何差异无关。