如何在不转换的情况下将 unix 时间戳转换为 LocalDate(Time)

Bla*_*oid 5 java jodatime unix-timestamp

我正在使用 org.joda.time.LocalDate 和 LocalDateTime。我从外部来源获得了一个 Unix 时间戳,并希望从中生成一个 LocalDate(Time)。关键是,它在该外部系统的界面中定义,所有日期/时间都在 UTC 时区。所以我想避免从该时间戳到本地系统的任何默认时区的任何隐式转换,这可能与 UTC 不同。对于此类事情,有一个 LocalDateTime 的构造函数,所以我尝试了(例如):

System.out.println(new LocalDateTime(3600000L));
  --> 1970-01-01T02:00:00.000

System.out.println(new LocalDateTime(3600000L, DateTimeZone.UTC));
  --> 1970-01-01T01:00:00.000
Run Code Online (Sandbox Code Playgroud)

结果有点出乎我的意料。查看 JavaDoc,第一个构造函数评估时间戳“在默认区域中使用 ISO 年表”。根据定义,Unix 时间戳是从 01-JAN-1970T00:00:00UTC 开始的秒数(这里是毫秒)!因此,如果将值 3600000(= 恰好 2 小时(以毫秒为单位)添加到该基数中,它将变为 01-JAN-1970T02:00:00UTC。我的本地系统设置为欧洲/柏林 (CET) 时区,即 UTC+1。准确地说,我们现在有夏令时,所以它甚至应该是 UTC+2,但让我们假设我们现在处于 UTC+1。因此,如果时间戳根据定义是 UTC,那么我希望结果时间是 01:00:00,如果它将时间戳的值解释为转换为 UTC 的 CET,或者 03:00:00 如果它正确地期望时间戳具有转换为 CET 的 UTC 值。但它实际上显示了一个未转换的时间戳,正好偏离基地 2 小时。第二个构造函数应该评估“在指定区域中使用 ISO 年表”的时间戳。(来自 JavaDoc)因此,如果我明确指定 UTC 时区,我根本不会期望任何转换,而是 02:00:00 的时间。一个基于 UTC 的时间戳会导致一个本身被声明为 UTC 的时间,结果应该是这样,但结果是 01:00:00!只是为了仔细检查,我用 CET 显式调用它并得到相同的结果,就好像我不提供任何时区一样。(来自 JavaDoc)因此,如果我明确指定 UTC 时区,我根本不会期望任何转换,而是 02:00:00 的时间。一个基于 UTC 的时间戳会导致一个本身被声明为 UTC 的时间,结果应该是这样,但结果是 01:00:00!只是为了仔细检查,我用 CET 显式调用它并得到相同的结果,就好像我不提供任何时区一样。(来自 JavaDoc)因此,如果我明确指定 UTC 时区,我根本不会期望任何转换,而是 02:00:00 的时间。一个基于 UTC 的时间戳会导致一个本身被声明为 UTC 的时间,结果应该是这样,但结果是 01:00:00!只是为了仔细检查,我用 CET 显式调用它并得到相同的结果,就好像我不提供任何时区一样。

所以看起来时间戳不被认为是UTC,而是在本地时区。创建 LocalDateTime 接受它并将本地时区的转换应用到目标时区(构造函数的第二个参数)。首先我想知道,这是否真的可以。其次,我必须保证我的代码中没有发生这种转换。所以我可以相信,保留第二个参数并使用默认时区可以解决问题,但这能保证吗?或者,如果我们从/更改为夏令时,是否有可能发生一些奇怪的转换?即使更改本地时区也不会产生任何后果,这就是为什么我们从外部系统获得的所有时间都已转换为 UTC。

我观察到的一个邪恶场景是,时间戳应该只是一个日期(没有时间)。在这种情况下,时间戳将是时间设置为 00:00:00 的任何日期。当我以与上面示例中使用 LocalDateTime 相同的方式使用 LocalDate 时,它​​将时间戳转换为日期 + 时间(当然)并简单地切断时间。但是,如果日期是 15-JUL-2014T00:00:00UTC,并且我最后的结果与我的另一个示例中相同的一小时移动,则变为 14-JUL-2014T23:00:00 并随之变为日期 14-JUL-2014!这实际上是一场灾难,绝对不能发生!

那么你们有没有人知道为什么 LocalDate(Time) 会这样?或者我背后的概念是什么,我可能会误解。或者如何保证不发生转换?

Bas*_*que 7

tl;博士

您的问题令人困惑,但您似乎声称数字 3_600_000L 表示自 UTC 1970 年第一个时刻的纪元参考以来的毫秒数,1970-01-01T00:00Z。

所以解析为Instant.

Instant                         // Represent a moment in UTC, an offset of zero hours-minutes-seconds.
.ofEpochMilli( 3_600_000L  )    // Parse a count of milliseconds since 1970-01-01T00:00Z. Returns a `Instant` object.
.toString()                     // Generate text representing this value, using standard ISO 8601 format.
Run Code Online (Sandbox Code Playgroud)

结果是 1970 年第一天的凌晨 1 点(UTC 时间)。在Z上月底表示UTC。

1970-01-01T01:00:00Z

获取日期部分,如 UTC 所示。

Instant                           // Represent a moment in UTC, an offset of zero hours-minutes-seconds.
.ofEpochMilli( 3_600_000L  )      // Parse a count of milliseconds since 1970-01-01T00:00Z.
.atOffset(                        // Convert from `Instant` (always in UTC, an offset of zero) to `OffsetDateTime` which can have any offset.
    ZoneOffset.UTC                // A constant representing an offset of zero hours-minutes-seconds, that is, UTC itself.
)                                 // Returns a `OffsetDateTime` object.
.toLocalDate()                    // Extract the date portion, without the time-of-day and without the offset-from-UTC.
.toString()                       // Generate text representing this value, using standard ISO 8601 format.
Run Code Online (Sandbox Code Playgroud)

1970-01-01

将那个时刻从 UTC 调整到时区Europe/Berlin

Instant                           // Represent a moment in UTC, an offset of zero hours-minutes-seconds.
.ofEpochMilli( 3_600_000L  )      // Parse a count of milliseconds since 1970-01-01T00:00Z.
.atZone(                          // Convert from UTC to a particular time zone.
    ZoneId.of( "Europe/Berlin" )  // A time zone is a history of the past, present, and future changes to the offset-from-UTC used by the people of a particular region. 
)                                 // Returns a `ZonedDateTime` object.
.toString()                       // Generate text representing this value, using standard ISO 8601 format wisely extended to append the name of the time zone in square brackets.
Run Code Online (Sandbox Code Playgroud)

1970-01-01T02:00+01:00[欧洲/柏林]

请注意该结果如何具有不同的时间,即柏林地区的凌晨 2 点,而不是我们在 UTC 中看到的凌晨 1 点。该Europe/Berlin时区是在那一刻比UTC的运行小时,然后,所以凌晨1点的提前半小时是凌晨2点-同一时刻,在时间轴上的相同点,不同的挂钟时间。

从那一刻获取仅日期部分,如 中所示Europe/Berlin

Instant                           // Represent a moment in UTC, an offset of zero hours-minutes-seconds.
.ofEpochMilli( 3_600_000L  )      // Parse a count of milliseconds since 1970-01-01T00:00Z.
.atZone(                          // Convert from UTC to a particular time zone.
    ZoneId.of( "Europe/Berlin" )  // A time zone is a history of the past, present, and future changes to the offset-from-UTC used by the people of a particular region. 
)                                 // Returns a `ZonedDateTime ` object.
.toLocalDate()                    // Extract the date only, without the time-of-day and without the time zone. Returns a `LocalDate` object.
.toString()                       // Generate text representing this value, using standard ISO 8601.
Run Code Online (Sandbox Code Playgroud)

1970-01-01

在这种情况下,柏林地区的日期与 UTC 中的日期相同。但在其他情况下,日期可能会有所不同。例如,UTC 时间 1 月 23 日的晚上 9 点(21:00)同时是日本东京的“明天”24 日。

时间

显然,您使用术语“Unix 时间戳”来表示自 1970 UTC 第一个时刻 1970-01-01T00:00Z 以来的毫秒数。

将该数字解析为一个Instant对象。该Instant级表示时间轴上的时刻UTC,分辨率为纳秒(最多小数的9个位数)。

Instant instant = Instant.ofEpochMilli( 3_600_000L ) ;
Run Code Online (Sandbox Code Playgroud)

Instant.toString(): 1970-01-01T01:00:00Z

非常简单:AnInstant始终是 UTC,始终是时间轴上的某个时刻。

当时间戳应该只是一个日期(没有时间)。

为此,请使用LocalDate该类。该LocalDate级表示没有时间一天和不同时区的日期,唯一的价值。

时区对于确定日期至关重要。对于任何给定时刻,日期因地区而异。例如,在法国巴黎午夜过后几分钟是新的一天,而在魁北克蒙特利尔仍然是“昨天” 。

如果未指定时区,JVM 会隐式应用其当前默认时区。该默认值可能随时更改,因此您的结果可能会有所不同。最好将您想要/预期的时区明确指定为参数。

以、、 或等格式指定正确的时区名称。永远不要使用 3-4 个字母的缩写,例如或因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。continent/regionAmerica/MontrealAfrica/CasablancaPacific/AucklandESTIST

ZoneId z = ZoneId.of( "America/Montreal" ) ;  
Run Code Online (Sandbox Code Playgroud)

调整你的UTC值(Instant通过应用)到另一个时区ZoneId来生成ZonedDateTime

ZonedDateTime zdt = instant.atZone( z ) ;  
Run Code Online (Sandbox Code Playgroud)

从那里我们可以提取仅日期部分作为LocalDate对象。

LocalDate ld = zdt.toLocalDate() ;
Run Code Online (Sandbox Code Playgroud)

如果您想要该日期当天的第一时刻,则必须指定时区的上下文。对于任何给定时刻,日期在全球各地因时区而异。当新的一天在印度破晓时,在法国仍然是“昨天”。

始终让 java.time 确定一天中的第一个时刻。不要假设 00:00。在某些日期的某些区域中,由于夏令时 (DST) 等异常情况,一天可能会在其他时间开始,例如 01:00。

ZonedDateTime zdtStartOfDay = ld.atStartOfDay( z ) ;
Run Code Online (Sandbox Code Playgroud)

如果您想看到与 UTC 相同的时刻,只需提取一个Instant.

Instant instant = zdtStartOfDay.toInstant() ;
Run Code Online (Sandbox Code Playgroud)

java.time类也有LocalDateTime类。记者了解到,这个类LocalDateTime不能代表一个时刻!它并不能代表在时间轴上的一个点。除非您将其置于时区的上下文中,否则它没有真正的意义。这个类仅用于两个含义:

  • 区域/偏移量未知(糟糕的情况)。
  • 每个/任何区域/偏移都是有意的。例如,“圣诞节在 2018 年 12 月 25 日 00:00 开始”,这意味着不同地方的不同时刻。第一个圣诞节发生在基里巴斯。然后连续的圣诞节在每个连续的午夜之后开始,向西穿过亚洲,然后是印度,然后继续到欧洲/非洲,最终到达美洲。所以圣诞老人至少需要 26 小时才能送出所有礼物。

希望一旦您理解了核心概念并使用了优秀的精心设计的java.time类,您就会发现这项工作一点也不令人困惑。


Java 中所有日期时间类型的表,包括现代的和传统的


关于java.time

java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date, Calendar, & SimpleDateFormat

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

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

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

从哪里获得 java.time 类?

哪个 java.time 库与哪个版本的 Java 或 Android 一起使用的表

ThreeTen-额外项目与其他类扩展java.time。该项目是未来可能添加到 java.time 的试验场。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多


小智 3

你为什么不:

timeStamp.toLocalDateTime().toLocalDate(); // JAVA 8
Run Code Online (Sandbox Code Playgroud)

  • 调用“TimeZone.setDefault”只能作为绝望的最后选择。它会立即影响 JVM 内所有应用程序的所有线程中的所有代码。 (2认同)