Java网络服务中使用了错误的时区

3

我有一个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服务端拦截编组,以便在设置日期之前自己设置时区。
谢谢提前帮助!
Urf

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

6
你只需要记住,一个 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 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

总之:始终将日期对象视为数据,即自纪元以来的毫秒数。 (不要使用任何已弃用的方法,也不要使用toString(),除非您了解它显示的内容。)要显示、格式化、转换(添加减去时间等)日期/时间,请始终使用Calendar实例或DateFormat实现,很难出错。
正如Date的Javadoc所述:
“在JDK 1.1之前,Date类具有两个额外的功能。它允许将日期解释为年、月、日、小时、分钟和秒值。它还允许格式化和解析日期字符串。不幸的是,这些功能的API不适合国际化。从JDK 1.1开始,应该使用Calendar类在日期和时间字段之间进行转换,应该使用DateFormat类格式化和解析日期字符串。日期中的相应方法已被弃用。”
尝试使用Date、Calendar和Formatter进行实验,阅读Javadoc,它会变得更加清晰。
对于你问题的第一部分,你不需要设置Glassfish服务器的时区来适应你的数据。如果您想将时区数据与您的数据/时间值一起存储,则在对象中使用Calendar而不是Date。或者像我通常做的那样,一切都以UTC时间(在数据库和Date实例中)存储,并且仅在数据显示/输出或解析时使用时区。因此,当接收到数据时,请使用设置为+01:00的DateFormat或类似格式解析它(如果时间字符串附有时区,则它可能会自动获取该值,就像您的示例一样)。
我不知道你问题的第二部分,但是如果你的Web服务端正确处理日期并正确解析它们,则无需您的干预即可处理此问题。

谢谢。当你这样说时,一切似乎都很简单明了!昨天我在阅读JavaDocs时,开始陷入时间顺序、弃用和日历的泥潭中 :) 如果我可以投票给你多次,我会这样做的。 - mr_urf

4

要在Glassfish服务器中更改使用的UTC时间:

进入目录(C:\glassfish4\glassfish\bin)

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

3

我刚刚解决了我的问题,并希望帮助其他开发人员。请按照以下步骤操作:

在你的GlassFish中输入以下内容:

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

查看列表是否出现

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

如果出现问题,请查找domain.xml文件中的jvm-options选项,并添加命令行。

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

然后重新启动服务器,开心就好。


1

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

要将相同的值调整为某个时区的挂钟时间,请应用ZoneId以获取ZonedDateTime对象。时区是UTC偏移量加上处理异常情况(例如夏令时(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类型。

在直接支持之前,您可以使用一个适配器来处理java.time类型,如Blaise Doughan的这个答案中讨论的那样,该答案是针对问题JAXB能否处理java.time对象?。您可以使用此实现或者根据此类似于Joda-Time库的实现编写自己的适配器。

LocalDate

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接