在 Java 中获取当前时区的日期tl;博士避免java.util.Date & .Calendar java.time区域与偏移关于 java.time 城大时间默认时区的来源

2022-08-31 21:28:58

在过去的几个小时里,我一直在网上搜索,以获得我的系统时区的日期时间。

当我使用它时,它总是返回我。理想情况下,它应该返回我当前的时区,即.calendar.getTimezone.getDefaultName()GMTIST

我正在尝试转换此字符串“2014-02-14T06:04:00:00”,这在我的时区日期时间中。它总是在.GMTGMT

我所看到的是,每个人都建议使用,即Timezone

dateFormatter.setTimezone("any_arbitary_timezone");

重点是我的应用程序将在不同的地理位置使用。我无法将其设置为特定时区。它应设置为系统时区,以便它可以在用户当前所在的任何时区显示。


答案 1

tl;博士

使用现代 java.time 类。

ZonedDateTime.now(           // Capture the current moment in the wall-clock time used by the people of a certain region (a time zone).
    ZoneId.systemDefault()   // Get the JVM’s current default time zone. Can change at any moment during runtime. If important, confirm with the user.
)                            // Renders a `ZonedDateTime` object. To see the same moment in UTC, extract a `Instant` object by calling `ZonedDateTime::getInstant`.

您可以省略对 的显式调用。(但我不建议这样做。ZoneId.systemDefault

ZonedDateTime.now()          // Capture the current moment in the JVM’s current default time zone.

将字符串解析为 LocalDateTime,并调整为所需的时区。

LocalDateTime.parse( "2014-02-14T06:04:00:00" )    // Parse a string lacking any indicator of time zone or offset-from-UTC. *Not* a specific point on the timeline.
             .atOffset( ZoneOffset.UTC )           // Apply UTC as we are certain that offset-from-UTC of zero was intended by the supplier of that input string. Returns a `OffsetDateTime` object.
             .atZoneSameInstant(                   // Adjust into another time zone. The `sameInstant` part means the same moment, different wall-clock time. 
                 ZoneId.of( "Africa/Tunis" )       // Specify the particular zone of interest to you.
             )                                     // Returns a `ZonedDateTime` object.

避免java.util.Date & .Calendar

众所周知,这些遗留类很麻烦。Sun/Oracle 在 Java 8 中添加了 java.time 包来取代它们。该软件包的灵感来自Joda-Time

遗留类的问题之一是这种令人困惑的行为:虽然java.util.Date没有时区信息,但它的实现在生成字符串时应用JVM的当前默认时区。因此,它误导了你,因为它似乎没有时区。toString

java.time

我正在尝试转换此字符串“2014-02-14T06:04:00:00”,...

您的输入字符串缺少任何时区或 UTC 偏移量的指示器。因此,我们解析为LocalDateTime,它缺少任何区域/偏移量的概念。

A代表一个时刻,不是时间轴上的一个点。这里的“本地”一词并不意味着特定的地方。它的意思是“根本没有具体的地方”。如果没有区域/偏移的上下文,它就没有真正的意义。LocalDateTime

LocalDateTime ldt = LocalDateTime.parse( "2014-02-14T06:04:00:00" ) ;

...这是在格林威治标准时间...

您说您确定该输入字符串的提供者将 UTC 作为上下文。我们可以应用零的 UTC 偏移量或 UTC 本身来获取对象。A一个时刻,时间轴上的一个点。我们可以指定使用 UTC 的常量 ZoneOffset.UTCOffsetDateTimeOffsetDateTimeZoneOffset

OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ) ;

...到我的时区日期时间

显然,您想将那一刻调整到另一个时区,以查看特定地区的人们使用的挂钟时间。我们需要应用一个时区()来获得ZonedDateTimeZoneId

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = odt.atZone( z ) ;

您可以向 JVM 询问其当前默认时区,而不是指定时区。注意:JVM的当前默认时区可以随时由该JVM中任何应用程序的任何线程中的任何代码更改。

ZoneId z = ZoneId.systemDefault() ;
ZonedDateTime zdt = odt.atZone( z ) ;

重点是我的应用程序将在不同的地理位置使用。

只需明确指定您想要/预期的时区。在我看来,这总是很好的做法。作为程序员,默认时区不受您的控制,这使得它不可靠。

以 的格式指定适当的时区名称,例如 美国/蒙特利尔非洲/卡萨布兰卡或 。切勿使用3-4个字母的缩写,例如或因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。continent/regionPacific/AucklandESTIST

另一个提示:在UTC中工作,思考,存储和交换。忘记你自己的教区时区,因为来回翻译到你的家乡会让你感到恶心。将 UTC 视为一个真实时间,而其他区域/偏移量只是变化。

Instant instant = Instant.now() ;  // Capture current moment in UTC.

ZoneId zAuckland = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdtAuckland = instant.atZone( zAuckland ) ;

ZoneId zKolkata = ZoneId.of( "Asia/Kolkata" ) ;  
ZonedDateTime zdtKolkata = instant.atZone( zKolkata ) ;

ZoneId zCasablanca = ZoneId.of( "Africa/Casablanca" ) ;  
ZonedDateTime zdtCasablanca = instant.atZone( zCasablanca ) ;

在那里,我们有四种方式(,,,)来观察时间轴上相同的同时时刻,同一点。instantzdtAucklandzdtKolkatazdtCasablanca

瞬视(): 2018-05-08T20:55:14.761721Z

zdtAuckland.toString(): 2018-05-09T08:55:14.761721+12:00[太平洋/奥克兰]

zdtKolkata.toString(): 2018-05-09T02:25:14.761721+05:30[亚洲/加尔各答]

zdtCasablanca.toString(): 2018-05-08T21:55:14.761721+01:00[非洲/卡萨布兰卡]

区域与偏移

与 UTC 的偏移量只是小时数、分钟数和秒数。仅此而已,仅此而已。任意数量的时区都可能在特定时刻共享特定的偏移量。

时区是特定地区人民使用的偏移量的过去、现在和未来变化的历史记录。例如,夏令时 (DST) 是一种做法,其中某个地区的人们(莫名其妙地)决定每年更改两次其偏移量。

因此,时区总是比单纯的偏移量更可取。拥有区域使我们能够以有意义的方式添加或减少时间,以解释该区域历史记录中偏移量的变化。


Table of all date-time types in Java, both modern and legacy


关于 java.time

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

要了解更多信息,请参阅 Oracle 教程。搜索 Stack Overflow 以获取许多示例和解释。规格是JSR 310

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

您可以直接与数据库交换 java.time 对象。使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序。不需要字符串,不需要类。Hibernate 5 & JPA 2.2 支持 java.timejava.sql.*

从哪里获取 java.time 类?

ThreeTen-Extra 项目通过其他类扩展了 java.time。这个项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuarter


城大时间

更新:Joda-Time项目现在处于维护模式。该团队建议迁移到 java.time 类。跳到此答案下面的java.time部分。

Joda-Time软件包对时区有很好的明确支持。与java.util.Date不同,Joda-Time DateTime确实知道自己分配的时区。如果未能指定时区,则会隐式分配 JVM 的当前缺省时区。

DateTime dateTime = DateTime.now(); // Applies JVM’s default time zone implicitly.

我建议不要隐式依赖默认时区。这样做会导致在执行日期时间工作时出现混淆和错误。

DateTime dateTime = DateTime.now( DateTimeZone.getDefault() ); // Explicitly using default time zone.

如果需要,您可以分配一个时区。

DateTime dateTimeKolkata = DateTime.now( DateTimeZone.forID( "Asia/Kolkata" ) ); // Specify a time zone.

对于服务器端工作,最佳做法是在 UTC 中执行业务逻辑和数据库存储。

DateTime dateTimeUtc = DateTime.now( DateTimeZone.UTC ); // Assign UTC (GMT) time zone.

您可以从分配的时区转换为另一个时区,包括 JVM 的当前缺省时区。

DateTime dateTime = dateTimeUtc.withZone( DateTimeZone.getDefault() );

对于线程安全,Joda-Time 使用不可变对象。不是修改对象,而是基于原始实例创建新实例等方法。withZone

解析字符串

若要将字符串分析为日期时间,必须注意字符串是否包含与 UTC 的偏移量和/或时区。你的没有。因此,您必须指定一个解释该字符串的时区。如果未指定,则在解析期间将使用 JVM 的当前缺省时区。

在您的问题中,您说字符串表示 UTCGMT) 中的日期时间。

DateTime dateTimeUtc = new DateTime( "2014-02-14T06:04:00:00", DateTimeZone.UTC );

解析后,如果需要,您可以分配另一个时区。在宇宙的时间线中,相同的时刻,但显示不同的挂钟时间

DateTime dateTimeDefaultZone = dateTimeUtc.withZone( DateTimeZone.getDefault() );

所以请注意,这是一个两步过程。首先,我们使用对字符串预期时区的外部知识来解析 String,因为它缺少该时区或偏移量的内部表示形式。其次,我们将时区调整为另一个时区(JVM默认区域)。

如果你的 String 包含了 偏移量 或 习惯 ,我们可以将这两个步骤折叠成一个。+00:00Z

DateTime dateTimeDefaultZone = new DateTime(  "2014-02-14T06:04:00:00Z", DateTimeZone.getDefault() ); // Apply time zone adjustment *after* parsing.

请注意,此 DateTime 构造函数看起来像上面的构造函数,但实际上完全不同。此时间区参数在解析应用,而不是在解析期间应用。此处,时区参数用于调整已解析的 DateTime。这最终会带来天壤之别。Z

默认时区的来源

JVM 最初从主机操作系统获取其默认时区。但请注意,程序员可以通过以下方式覆盖它:

执行此覆盖会影响在该 JVM 中运行的所有应用程序的所有线程。所以你应该知道JVM的默认时区通常与主机操作系统相同,但不一定相同。


答案 2

下面是获取与本地系统时钟偏移量匹配的 的 ID 的方法,TimeZone

Calendar cal = Calendar.getInstance();
long milliDiff = cal.get(Calendar.ZONE_OFFSET);
// Got local offset, now loop through available timezone id(s).
String [] ids = TimeZone.getAvailableIDs();
String name = null;
for (String id : ids) {
  TimeZone tz = TimeZone.getTimeZone(id);
  if (tz.getRawOffset() == milliDiff) {
    // Found a match.
    name = id;
    break;
  }
}
System.out.println(name);