强制将Java时区设为GMT/UTC

117

我需要将所有与时间相关的操作强制转换为GMT/UTC,无论机器上设置的时区是什么。有没有一种方便的方法可以在代码中实现这一点?

为了澄清,我正在使用数据库服务器时间进行所有操作,但它会根据本地时区格式化。

谢谢!


实际上,他的问题是我的一个子集,但我找到了解决方案。 - SyBer
看一下重复的问题,并搜索“Joda”和“DateTimeZone”。 - Basil Bourque
13个回答

144

根据该问题的回答,要更改正在运行的JVM实例的默认时区,请设置user.timezone系统属性:

java -Duser.timezone=GMT ... <main-class>

如果你需要从数据库ResultSet中检索Date/Time/Timestamp对象时设置特定的时区,那么请使用带有Calendar对象参数的getXXX方法的第二种形式:

Calendar tzCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
ResultSet rs = ...;
while (rs.next()) {
    Date dateValue = rs.getDate("DateColumn", tzCal);
    // Other fields and calculations
}

或者,在 PreparedStatement 中设置日期:

Calendar tzCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
PreparedStatement ps = conn.createPreparedStatement("update ...");
ps.setDate("DateColumn", dateValue, tzCal);
// Other assignments
ps.executeUpdate();

当数据库列不包含时区信息时,这些内容将确保数据库中存储的值是一致的。

java.util.Datejava.sql.Date 类在UTC中存储实际时间(毫秒)。要将它们格式化为另一个时区的输出,请使用 SimpleDateFormat。您还可以使用 Calendar 对象将时区与值关联起来:

TimeZone tz = TimeZone.getTimeZone("<local-time-zone>");
//...
Date dateValue = rs.getDate("DateColumn");
Calendar calValue = Calendar.getInstance(tz);
calValue.setTime(dateValue);

有用的参考资料

https://docs.oracle.com/javase/9/troubleshoot/time-zone-settings-jre.htm#JSTGD377

https://confluence.atlassian.com/kb/setting-the-timezone-for-the-java-environment-841187402.html


2
关于为整个JVM设置时区的一件事需要注意的是,它会影响所有东西,包括日志记录等。如果这是期望的效果,那么请记住这一点。 - Hermann Hans
没问题。只是想提一下,我实际上是直接在代码中设置了用户时区,而不是通过-D参数设置。 - SyBer
7
@SyBer:这个方法可行,但你必须确保在任何方法调用TimeZone.getDefault()方法之前设置它。否则,第一次执行后默认时区会被缓存,导致混淆。这也意味着如果你在API/库内部(不对外公开)进行此操作,可能会有问题 - 一定要详细记录你所做的操作。 - Kevin Brock

25

另外,如果您可以以这种方式设置JVM时区

System.setProperty("user.timezone", "EST");

或者在JVM参数中加入-Duser.timezone=GMT


System.setProperty("user.timezone" ...) 不起作用。 - wilmol
1
System.setProperty("user.timezone", "EST"); 这个很好用。谢谢。 - pravin kottawar
1
@wilmol和@pravin-kottavar,你们两个都是正确的-这取决于情况。正如@syber所指出的那样,只有在TimeZone.getDefault()之前从未被调用过的情况下,这个方法才有效。之后,系统属性将不再影响java.util.Date和其他传统的日期类对当前时区的解释。从那时起,只有通过TimeZone.setDefault()来改变它才有效。至于这是否是一个好主意,那是另外一个讨论。我们应该始终坚持使用适当的java.time.*工具-尊重环境,而不是试图在万不得已时去改变它。 - undefined

21

没有冒犯的意思,但是暂时修改全局静态状态听起来像是一个非常糟糕的想法... - G. Demecki
1
你可以这样做,但你不应该这样做。这是极其糟糕的建议。整个JVM的时区都会被改变。 - scot
7
有时候,这正是你想要实现的。例如,我曾见过基于Java的微服务始终使用UTC来避免时区问题。在这些系统中,数据库后端也以UTC运行,因此自然而然地需要“强制”JVM也使用UTC。重要的是:你必须知道自己在做什么以及可能带来的后果... - snorbi
1
绝对不行。如果你希望整个系统都使用UTC时间(非常好的想法),请将系统时区设置为UTC,而不要改变Java的时区设置。 - scot
11
除非您有不同要求的多个JVM,否则通常情况下只需要设置整个系统或单个JVM的时区。每个情况都是独特的,我们可以争论很久,有时需要设置整个系统的时区,有时只需设置一个JVM的时区。让我们高兴地看到今天的系统如此灵活,我们可以两种方式都实现。 - snorbi

13

12

简而言之

String sql = "SELECT CURRENT_TIMESTAMP ;
…
OffsetDateTime odt = myResultSet.getObject( 1 , OffsetDateTime.class ) ;

避免依赖主机操作系统或JVM的默认时区

建议您编写所有代码以明确指定所需/期望的时区,不需要依赖JVM当前的默认时区。请注意,JVM当前的默认时区可能会在运行时的任何时刻发生更改,并影响到任何应用程序中的任何线程中的任何代码调用TimeZone.setDefault。这样的调用会立即影响该JVM中的所有应用程序。

java.time

一些其他答案是正确的,但现在已经过时了。最早版本的Java捆绑的糟糕日期时间类存在缺陷,由于编写者不理解日期时间处理的复杂性和微妙之处。
遗留的日期时间类已被JSR 310中定义的java.time类所取代。
要表示时区,请使用ZoneId。要表示与UTC偏移量,请使用ZoneOffset。偏移量仅是相对于本初子午线提前或落后的小时-分钟-秒数。时区则远不止如此。时区是一个特定地区人民使用的偏移量的过去、现在和未来变化的历史记录。
引用:

我需要强制任何与时间有关的操作为GMT / UTC

对于零小时-分钟-秒的偏移量,请使用常量ZoneOffset.UTC

Instant

要在UTC中捕获当前时刻,请使用Instant。该类始终以UTC表示时刻,根据定义始终为UTC。
Instant instant = Instant.now() ;  // Capture current moment in UTC.

ZonedDateTime

为了以特定区域人们使用的挂钟时间查看同一时刻,需要调整到相应的时区。同一时刻,在时间线上相同的点,对应不同的挂钟时间。

ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

数据库

您提到了一个数据库。

要从数据库检索一个时刻,您的列应该是类似于SQL标准的TIMESTAMP WITH TIME ZONE数据类型。检索一个对象而不是一个字符串。在JDBC 4.2及更高版本中,我们可以与数据库交换java.time对象。JDBC需要OffsetDateTime,而InstantZonedDateTime是可选的。

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;

在大多数数据库和驱动程序中,我猜您将获得UTC时间。但如果不是这样,您可以通过以下两种方式之一进行调整:
  • 提取Instantodt.toInstant()
  • 从给定的偏移量调整为零偏移量:OffsetDateTime odtUtc = odt.withOffsetSameInstant( ZoneOffset.UTC ) ;`。

Table of date-time types in Java (both legacy and modern) and in standard SQL


关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,如java.util.DateCalendarSimpleDateFormat

要了解更多信息,请参阅Oracle Tutorial。并在Stack Overflow上搜索许多示例和解释。规范为JSR 310

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC驱动程序。无需字符串,也无需java.sql.*类。

如何获取java.time类?


8

使用系统属性:

-Duser.timezone=UTC

6
为什么要使用那个?请在你的回答中加入一些解释,以便其他人可以从中学习。 - Nico Haase

8

对我而言,只需要快速掌握SimpleDateFormat。

  private static final SimpleDateFormat GMT = new SimpleDateFormat("yyyy-MM-dd");
  private static final SimpleDateFormat SYD = new SimpleDateFormat("yyyy-MM-dd");
  static {
      GMT.setTimeZone(TimeZone.getTimeZone("GMT"));
    SYD.setTimeZone(TimeZone.getTimeZone("Australia/Sydney"));
  }

然后使用不同的时区格式化日期。

6
我会从数据库中以原始形式(长时间戳或Java的日期)检索时间,然后使用SimpleDateFormat进行格式化,或使用Calendar进行操作。 在两种情况下,在使用之前应设置对象的时区。
有关详细信息,请参见SimpleDateFormat.setTimeZone(..)和Calendar.setTimeZone(..)。

1
创建一对客户端/服务器,执行后,客户端服务器发送正确的时间和日期。然后,客户端询问服务器pm GMT,服务器返回正确答案。

1
希望下面的代码能对你有所帮助。
    DateFormat formatDate = new SimpleDateFormat(ISO_DATE_TIME_PATTERN);
    formatDate.setTimeZone(TimeZone.getTimeZone("UTC")); 
    formatDate.parse(dateString);

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