【问题标题】:Wrong timezone being used in Java web serviceJava Web 服务中使用了错误的时区
【发布时间】:2010-12-03 01:49:49
【问题描述】:

我有一个用于更新数据库的 JAX-B java Web 服务。我正在更新的表中的每一行都由一个类似于下面的对象表示:-

public class Item {
    private String id;
    private Date startDate;
    private Date endDate;

    public Item() { }

    ...

}

这个类在一个单独的程序中被实例化,然后通过 SOAP 在类似于下面的消息中传递:-

...

<item>
    <id>D001IAAC030</id>
    <startDate>2009-09-17T00:00:00.000+01:00</startDate>
    <endDate>2009-10-01T00:00:00.000+01:00</endDate>
</item>

...

如您所见,由于 BST,UTC 时间有一个 +01:00 的偏移量。但是,当对象在服务器上编组(也在我的本地计算机上)时,它会恢复为 GMT 并从日期中减去 1 小时。

你能告诉我怎么做吗:-

  1. 将我的 Glassfish 服务器设置为正确的区域设置,以便将日期识别为 BST。
  2. 告诉我如何在 Web 服务端拦截编组,以便在设置日期之前自己设置时区。

TIA,

Urf

【问题讨论】:

  • java.util.Date 对象没有保留任何时区信息,那么“它恢复到 GMT 并从日期中减去 1 小时”是什么意思?
  • JAX-B 使用基于 XMLGregorianCalendar 的 XSDate 对象,据我所知。也许是 XSDate to Date 的解析导致了这个问题。我尝试改为解析为 JodaTime DateTime 对象,但这只是给出当前日期和时间,而不是我发送的日期和时间。

标签: java web-services date marshalling


【解决方案1】:

您只需要记住 Date 对象(始终)将日期/时间存储为自 UTC/GMT 时区中纪元以来的毫秒数。让人们感到困惑的是 Date.toString() 方法返回 JVM 默认时区中的文本表示(通过内部 Calendar 对象)。 (看一下JDK源代码。)

例如在我的机器上

Date now = new Date();
System.out.println(now.toString());
System.out.println(now.getTime())

会给

Fri Oct 02 06:56:24 EST 2009
1254430584531

毫秒数是自 GMT/UTC 时区纪元以来的实际毫秒数。

在操作/使用 Date 对象时,您应该始终使用 Date 格式化程序或 Calendar 实例。例如:

Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:MM:ss zzz yyyy");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(now.toString());
System.out.println(sdf.format(now));

给予

Fri Oct 02 06:56:24 EST 2009
Thu Oct 01 20:56:24 UTC 2009

总结:始终将 Date 对象仅视为数据,自纪元以来的毫秒数。 (不要使用任何已弃用的方法,也不要使用 toString() 除非您了解它所显示的内容。)要显示、格式化、转换(添加减去时间等)日期/时间,请始终使用 Calendar 实例或 DateFormat实施,很难出错。

正如 Javadoc 所说的日期:

'在 JDK 1.1 之前,Date 类有两个附加功能。它允许将日期解释为年、月、日、小时、分钟和秒值。它还允许格式化和解析日期字符串。不幸的是,这些函数的 API 不适合国际化。从 JDK 1.1 开始,应使用 Calendar 类在日期和时间字段之间进行转换,而应使用 DateFormat 类来格式化和解析日期字符串。不推荐使用 Date 中的相应方法。

自己试验一下日期、日历和格式化程序并阅读 Javadoc,它会变得更清晰一些。

对于您问题的第一部分,您不需要设置 Glassfish 服务器的时区来容纳您的数据。如果您想将时区数据与您的数据/时间值一起存储,请在您的对象中使用日历而不是日期。或者像我通常做的那样,所有内容都存储为 UTC 时间(在对象中的 db 和 Date 实例中),并且仅在显示/输出或解析数据时使用时区。因此,当收到您的数据时,使用 DateFormat 或将时区设置为 +01:00 的等价物对其进行解析(如果它附加了您的示例显示的时区,它可能会自动从时间字符串中提取该数据)。

我不具体了解您问题的第二部分,但如果您的网络服务端的实现正确处理日期并正确解析它们,它应该在没有您干预的情况下处理这个问题。

【讨论】:

  • 谢谢。当你这样说时,这一切似乎都非常简单!当我昨天阅读 JavaDocs 时,我开始沉迷于年表、弃用和日历 :) 如果我能多次投票给你,我会的。
【解决方案2】:

在 glassfish 服务器中更改使用时间 UTC:

C:\glassfish4\glassfish\bin)

asadmin create-jvm-options -Duser.timezone=UTC
asadmin restart-domain

【讨论】:

    【解决方案3】:

    我现在解决了我的问题,并打算帮助其他开发人员,一步一步来

    输入你的玻璃鱼

    c: \ glassfish3 \ bin \> asadmin list-jvm-options
    

    查看列表是否出现

    <jvm-options>-Duser.timezone=UTC</jvm-options>
    

    case 没有出现去 domain.xml 文件并搜索 jvm-options 并添加命令行

    <jvm-options>-Duser.timezone = Brazil / East </ jvm-options> substituting the Brazil / East timezone by country
    

    然后重启服务器就开心了

    【讨论】:

      【解决方案4】:

      Locale无关

      Locale 与时区无关。对于日期时间工作,Locale 确定了两件事:(a) 用于翻译日期名称、月份名称等的人类语言,以及 (b) 句号与逗号、句号排序等问题的文化规范元素(日期之前/之后的月份等)。

      不要更改默认时区

      不要按照另一个答案的建议更改 JVM 的当前默认时区。更改会影响该 JVM 内所有应用程序的所有线程中的所有代码,并且会立即在运行时期间这样做。

      java.time

      您正在使用旧的过时类。与 Java 的最早版本捆绑在一起的旧日期时间类已被证明设计不佳、混乱且麻烦。避开他们。现在被 Java 8 及更高版本中内置的 java.time 框架所取代。这些类取代了旧的麻烦的日期时间类,例如java.util.Date。见Oracle Tutorial。大部分功能已在 ThreeTen-Backport 中向后移植到 Java 6 和 7,并在 ThreeTenABP 中进一步适应 Android。

      ISO 8601

      您的日期时间字符串恰好符合ISO 8601 标准。在解析/生成表示日期时间值的字符串时,默认情况下在 java.time 类中使用此标准定义的这种格式。

      String input = "2009-09-17T00:00:00.000+01:00";
      OffsetDateTime odt = OffsetDateTime.parse( input );
      

      如果您想要时间轴上的同一时刻但在 UTC 中,请提取 Instant 对象。对于您的大部分业务逻辑以及数据交换和数据存储,最好坚持使用 UTC 和此类。

      Instant instant = odt.toInstant();
      

      要生成 ISO 8601 格式的字符串,请调用 toString

      String output = instant.toString();  // 2009-09-16T23:00:00Z
      

      要查看调整到某个时区的wall-clock time 的相同值,请应用ZoneId 以获取ZonedDateTime 对象。时区是与 UTC 的偏移量加上规则,用于处理 Daylight Saving Time (DST) 等异常情况。

      ZoneId zoneId = ZoneId.of( "Europe/London" );
      ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );
      

      另外,如果您需要在 UTC 中捕捉当前时刻:

      Instant instant = Instant.now();
      

      JAXB 适配器

      Java Architecture for XML Binding (JAXB) 可能还不直接支持 java.time 类型。

      在添加直接支持之前,您可以使用针对问题 Can JAXB handle java.time objects?this Answer by Blaise Doughan 中讨论的 java.time 类型的适配器。您可以使用this implementation 或通过对那个或这个similar one for Joda-Time 库进行建模来编写自己的。

      LocalDate

      00:00:00 的那些时间值让我想知道您是否真的在尝试表示仅日期值。在仅日期值中,您不关心时间或时区。如果是这种情况,请考虑使用LocalDate 类。

      【讨论】:

        猜你喜欢
        • 2012-08-07
        • 1970-01-01
        • 1970-01-01
        • 2018-08-20
        • 2017-02-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多