【问题标题】:Java XMLGregorianCalendar is changing the time - Strange behaviorJava XMLGregorianCalendar 正在改变时间 - 奇怪的行为
【发布时间】:2021-08-20 07:30:42
【问题描述】:

我有一个日期作为输入 = 2021-03-12T10:42:01.000Z.... 我想转换成这种格式:

String pattern = "yyyy-MM-dd'T'HH:mm:ssZ";


public String getDate(XMLGregorianCalendar input) {
    DateFormat f = new SimpleDateFormat(pattern);
    input.toGregorianCalendar().setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC));
    String output = f.format(input.toGregorianCalendar().getTime());
    System.out.println(output);
}

2021-03-12T12:42:01+0200

基本上,它增加了 2 小时。可能和时区有关,我没有在另一台电脑上测试过。我有两个问题:

  • 为什么会这样
  • 我可以做些什么来避免它?这是一个旧版应用,所以我不想做大的改变

谢谢

【问题讨论】:

  • 也不是input.toGregorianCalendar() 很可能(取决于实现)每次调用都会为您提供一个新实例。因此,您设置时区的声明可能不会改变任何内容。但是,它不是输出中不同小时的来源。
  • 您想要哪个结果? 2021-03-12T10:42:01+0000?您的字符串采用 UTC 格式,由结尾的 Z 表示。是否总是这样,或者您可以使用其他偏移量,例如 -03-0100+02:00
  • 你的输入和输出都在ISO 8601 format,那么你的转换真的有必要吗?如果你的字符串的接收者接受 ISO 8601,我建议你给他们input.toString()

标签: java xmlgregoriancalendar


【解决方案1】:

Answer by Jon Skeet 是正确且聪明的。您似乎只是看到了时区调整。您的两个字符串2021-03-12T10:42:01.000Z2021-03-12T12:42:01+0200 代表同一时刻。中午 12 点(如果比 UTC 早两个小时)与上午 10 点相同,与 UTC 的偏移量为零时分秒。

而且,正如另一个答案中提到的,您确实应该避免使用与 Java 的最早版本捆绑在一起的糟糕的日期时间类。

tl;博士

myXMLGregorianCalendar     // Legacy class, representing a moment as seen in some time zone.
.toGregorianCalendar()     // Another legacy class, also representing a moment as seen in some time zone.
.toZonedDateTime()         // A modern *java.time* class, representing a moment as seen in some time zone.
.toInstant()               // Another *java.time* class, for representing a moment as seen in UTC.
.truncatedTo(              // Lopping off some smaller part of the date-time value.
    ChronoUnit.SECONDS     // Specifying whole seconds as our granularity of truncation, so lopping off any fractional second.
)                          // Returns another `Instant` object, rather than altering (mutating) the original, per immutable objects pattern.
.toString()                // Generating text representing the content of our `Instant` object, using standard ISO 8601 format.

java.time

现代方法使用 java.time 类,该类在几年前取代了 SimpleDateFormatXMLGregorianCalendar GregorianCalendar 等。

转换传统现代

您可以轻松地从旧类型转换为 java.time。在旧类上寻找新的 to/from 方法。

ZonedDateTime zdt = myXMLGregorianCalendar.toGregorianCalendar().toZonedDateTime() ;

调整到零偏移

通过提取Instant 将任何时区调整为UTC。此类表示以 UTC 表示的时刻,始终以 UTC 表示。

Instant instant = zdt.toInstant() ; 

了解zdtinstant 都代表同一时刻,时间轴上的同一点,但它们的挂钟时间不同。

截断

鉴于您的问题中看到的格式模式,您似乎希望使用整秒的粒度。要截断任何小数秒,请截断为秒。

Instant truncated = instant.truncatedTo( ChronoUnit.SECONDS ) ;

ISO 8601

您所需的文本格式在ISO 8601 标准中定义。 java.time 中默认使用该标准来解析/生成字符串。所以不需要指定任何格式模式。

String output = truncated.toString() ;

【讨论】:

  • 这是前进的方向。 OP 需要 yyyy-MM-dd'T'HH:mm:ssZ 格式的字符串。如果您添加有关格式的部分,这将对他和未来的访问者有用。
  • @ArvindKumarAvinash 感谢您提供建议。但是我不知道你具体是什么意思。除了我的最后一段,还有什么要说的?
  • @ArvindKumarAvinash 谢谢你的建议。我添加了代码和散文来解释截断整秒。
【解决方案2】:

基本上,它增加了 2 小时

不是真的。它会在同一时刻为您提供输出,但在您的系统本地时区 - 因为您正在创建 SimpleDateFormat 而不指定时区(或文化):

DateFormat f = new SimpleDateFormat(pattern);

我个人建议完全避免使用java.text.SimpleDateFormat,而更喜欢java.time 类型和格式化程序。但是,如果您确实想使用 SimpleDateFormat,只需确保将时区设置为 UTC(假设您始终需要 UTC)并最好也设置文化(例如 Locale.ROOT)。

【讨论】:

  • 我想我在下一行设置时区
  • @Julio:你没有在 formatter 上设置它。您提供给格式化程序的值只是一个java.util.Date,它没有关联的时区——它只是一个瞬间。格式化程序使用格式化程序上设置的任何时区来表示该时刻。
  • 啊好吧,有道理。非常感谢
  • @Julio 你需要f.setTimeZone(TimeZone.getTimeZone(ZoneOffset.UTC));
  • @tquadrat 是的,input 上的时区很可能已经定义,因为显示了尾随 Z。这在假设实现(XMLGregorianCalendar 是抽象的)正确处理此问题的情况下是有效的。在 XML 类型中,没有时区的概念。这就是为什么我们可以显式设置它或者使用#toGregorianCalendar(TimeZone, ..., ...)方法在我们想要的时区进行转换。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-08
  • 1970-01-01
  • 1970-01-01
  • 2012-07-01
  • 2014-11-23
  • 2015-10-20
  • 1970-01-01
相关资源
最近更新 更多