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.UTC
。OffsetDateTime
OffsetDateTime
ZoneOffset
OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ) ;
...到我的时区日期时间
显然,您想将那一刻调整到另一个时区,以查看特定地区的人们使用的挂钟时间。我们需要应用一个时区()来获得ZonedDateTime
。ZoneId
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/region
Pacific/Auckland
EST
IST
另一个提示:在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 ) ;
在那里,我们有四种方式(,,,)来观察时间轴上相同的同时时刻,同一点。instant
zdtAuckland
zdtKolkata
zdtCasablanca
瞬视(): 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) 是一种做法,其中某个地区的人们(莫名其妙地)决定每年更改两次其偏移量。
因此,时区总是比单纯的偏移量更可取。拥有区域使我们能够以有意义的方式添加或减少时间,以解释该区域历史记录中偏移量的变化。
关于 java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧旧日期时间类,如java.util.Date
,Calendar
和SimpleDateFormat
。
要了解更多信息,请参阅 Oracle 教程。搜索 Stack Overflow 以获取许多示例和解释。规格是JSR 310。
Joda-Time 项目现在处于维护模式,建议迁移到 java.time 类。
您可以直接与数据库交换 java.time 对象。使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序。不需要字符串,不需要类。Hibernate 5 & JPA 2.2 支持 java.time。java.sql.*
从哪里获取 java.time 类?
ThreeTen-Extra 项目通过其他类扩展了 java.time。这个项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如Interval
,YearWeek
,YearQuarter
等。
城大时间
更新: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 的当前缺省时区。
在您的问题中,您说字符串表示 UTC (GMT) 中的日期时间。
DateTime dateTimeUtc = new DateTime( "2014-02-14T06:04:00:00", DateTimeZone.UTC );
解析后,如果需要,您可以分配另一个时区。在宇宙的时间线中,相同的时刻,但显示不同的挂钟时间。
DateTime dateTimeDefaultZone = dateTimeUtc.withZone( DateTimeZone.getDefault() );
所以请注意,这是一个两步过程。首先,我们使用对字符串预期时区的外部知识来解析 String,因为它缺少该时区或偏移量的内部表示形式。其次,我们将时区调整为另一个时区(JVM默认区域)。
如果你的 String 包含了 偏移量 或 习惯 ,我们可以将这两个步骤折叠成一个。+00:00
Z
DateTime dateTimeDefaultZone = new DateTime( "2014-02-14T06:04:00:00Z", DateTimeZone.getDefault() ); // Apply time zone adjustment *after* parsing.
请注意,此 DateTime 构造函数看起来像上面的构造函数,但实际上完全不同。此时间区参数在解析后应用,而不是在解析期间应用。此处,时区参数用于调整已解析的 DateTime。这最终会带来天壤之别。Z
默认时区的来源
JVM 最初从主机操作系统获取其默认时区。但请注意,程序员可以通过以下方式覆盖它:
执行此覆盖会影响在该 JVM 中运行的所有应用程序的所有线程。所以你应该知道JVM的默认时区通常与主机操作系统相同,但不一定相同。