java 如何在不转换的情况下将 Unix 时间戳转换为 LocalDate(Time)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25384680/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to convert a unix timestamp to LocalDate(Time) without conversion
提问by BlackDroid
I'm using org.joda.time.LocalDate and LocalDateTime. From an external source I get a Unix timestamp and want to make a LocalDate(Time) out of it. The point is, it is defined in the interface of that external system, that all dates/times are in UTC timezone. So I want to avoid any implicit conversion from that timestamp to any default timezone of the local system which might be different from UTC. There is a constructor of LocalDateTime for such things, so I tried (as an example):
我正在使用 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
The result surprises me a bit. Having a look into the JavaDoc, the first constructor evaluates the timestamp "using ISO chronology in the default zone." By definition, the Unix timestamp is the number of seconds (here milliseconds) from 01-JAN-1970T00:00:00UTC! So if the value 3600000 (= exactly 2 hours in millis) is add to that base, it would come to 01-JAN-1970T02:00:00UTC. My local system is set to timezone Europe/Berlin (CET) which is UTC+1. Precisely, we have daylight saving right now, so it should even be UTC+2, but lets pretend we're at UTC+1 now. So if the timestamp is by definition UTC, then I would expect that the resulting time is either 01:00:00, if it interprets the value of the timestamp to be in CET which is converted to UTC, or 03:00:00 if it correctly expects the timestamp to have a UTC value which is converted to CET. But it actually shows an unconverted timestamp, exactly 2 hours off the base. The second constructor is supposed to evaluate the timestamp "using ISO chronology in the specified zone." (from JavaDoc) So if I specify UTC timezone explicitly, I would not expect any conversion at all, but a time of 02:00:00. A UTC based timestamp which results in a time which itself is declared to be UTC should result in exactly that, but the result is 01:00:00! Just to double-check, I called it with CET explicitly and got the same result as if I don't provide any timezone.
结果有点出乎我的意料。查看 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 显式调用它并得到相同的结果,就好像我不提供任何时区一样。
So it looks like, that the timestamp is not considered to be UTC, but to be in the local timezone. Creating a LocalDateTime takes it and applies a conversion from your local timezone to the target one (second parameter of the constructor). First of all I'm wondering, if this is really ok. Secondly I have to guarantee that no such conversion is happening in my code. So I could believe, leaving the second parameter and using the default timezone does the trick, but is that guaranteed? Or might there be a chance that some strange conversion happens if we change from/to daylight saving? Even changing the local timezone must not have any consequence, this is why all times we get as a timestamp from that external system are already converted to UTC.
所以看起来时间戳不被认为是UTC,而是在本地时区。创建 LocalDateTime 接受它并将本地时区转换为目标时区(构造函数的第二个参数)。首先我想知道,这是否真的可以。其次,我必须保证我的代码中没有发生这种转换。所以我可以相信,保留第二个参数并使用默认时区可以解决问题,但这能保证吗?或者,如果我们从/更改为夏令时,是否有可能发生一些奇怪的转换?即使更改本地时区也不会产生任何后果,这就是为什么我们从外部系统获得的所有时间都已转换为 UTC。
One evil scenario I observed was, when a timestamp was supposed to be just a date (without time). In this case, the timestamp would be any date with time set to 00:00:00. When I use LocalDate the same way I used LocalDateTime in the example above, it converts the timestamp into date + time (of course) and simply cuts the time off. BUT, if the date was 15-JUL-2014T00:00:00UTC, and the result at my end is shifted the same one hour as in my other example, that turns to 14-JUL-2014T23:00:00 and therewith to the date 14-JUL-2014! This is actually a disaster and must not happen!
我观察到的一个邪恶场景是,时间戳应该只是一个日期(没有时间)。在这种情况下,时间戳将是时间设置为 00:00:00 的任何日期。当我像在上面的示例中使用 LocalDateTime 一样使用 LocalDate 时,它将时间戳转换为日期 + 时间(当然)并简单地切断时间。但是,如果日期是 15-JUL-2014T00:00:00UTC,并且我最后的结果与我的另一个示例中相同的一小时移动,则变为 14-JUL-2014T23:00:00 并随之变为日期 14-JUL-2014!这实际上是一场灾难,绝对不能发生!
So does anyone of you have a clue why LocalDate(Time) behaves like that? Or what is the concept behind I which I might misinterpret. Or how to guarantee that no conversion happens?
那么你们有没有人知道为什么 LocalDate(Time) 会这样?或者我背后的概念是什么,我可能会误解。或者如何保证不发生转换?
回答by BlackDroid
Why don't you:
你为什么不:
timeStamp.toLocalDateTime().toLocalDate(); // JAVA 8
回答by Basil Bourque
tl;dr
tl;博士
Your Question is confusing, but you seem to claim the number 3_600_000L represents a count of milliseconds since the epoch reference of first moment of 1970 in UTC, 1970-01-01T00:00Z.
您的问题令人困惑,但您似乎声称数字 3_600_000L 表示自 UTC 时间 1970 年第一个时刻的纪元参考 1970-01-01T00:00Z 以来的毫秒数。
So parse as an Instant
.
所以解析为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.
The result is 1 AM on the first day of 1970 as seen in UTC. The Z
on the end means UTC.
结果是 1970 年第一天的凌晨 1 点(UTC 时间)。在Z
上月底表示UTC。
1970-01-01T01:00:00Z
1970-01-01T01:00:00Z
Get the date portion, as seen in UTC.
获取日期部分,如 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.
1970-01-01
1970-01-01
Adjust that moment from UTC to the time zone Europe/Berlin
.
将那个时刻从 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.
1970-01-01T02:00+01:00[Europe/Berlin]
1970-01-01T02:00+01:00[欧洲/柏林]
Notice how that result has a different time-of-day, 2 AM in Berlin area rather than the 1 AM we saw in UTC. The Europe/Berlin
time zone was running an hour ahead of UTC at that moment then, so an hour ahead of 1 AM is 2 AM — same moment, same point on the timeline, different wall-clock time.
请注意该结果如何具有不同的时间,即柏林地区的凌晨 2 点,而不是我们在 UTC 中看到的凌晨 1 点。该Europe/Berlin
时区是在那一刻比UTC的运行小时,然后,所以凌晨1点的提前半小时是凌晨2点-同一时刻,在时间轴上的相同点,不同的挂钟时间。
Get the date-only portion from that moment as seen in Europe/Berlin
.
从那一刻获取仅限日期的部分,如 中所示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.
1970-01-01
1970-01-01
In this case, the date in Berlin area is the same as in UTC. But in other cases the date may vary. For example, 9 PM (21:00) on the 23rd of January in UTC is simultaneously “tomorrow” the 24th in Tokyo Japan.
在这种情况下,柏林地区的日期与 UTC 中的日期相同。但在其他情况下,日期可能会有所不同。例如,UTC 时间的 1 月 23 日晚上 9 点(21:00)同时是日本东京的“明天”24 日。
java.time
时间
Apparently, you use the term “Unix timestamp” to mean a count of milliseconds since first moment of 1970 UTC, 1970-01-01T00:00Z.
显然,您使用术语“Unix 时间戳”来表示自 1970 UTC 第一个时刻 1970-01-01T00:00Z 以来的毫秒数。
Parse that number into an Instant
object. The Instant
class represents a moment on the timeline in UTCwith a resolution of nanoseconds(up to nine (9) digits of a decimal fraction).
将该数字解析为一个Instant
对象。该Instant
级表示时间轴上的时刻UTC,分辨率为纳秒(最多小数的9个位数)。
Instant instant = Instant.ofEpochMilli( 3_600_000L ) ;
instant.toString(): 1970-01-01T01:00:00Z
Instant.toString(): 1970-01-01T01:00:00Z
So very simple: An Instant
is always in UTC, always a moment, a point on the timeline.
非常简单:AnInstant
始终是 UTC,始终是时间轴上的某个时刻。
when a timestamp was supposed to be just a date (without time).
当时间戳应该只是一个日期(没有时间)。
For this, use the LocalDate
class. The LocalDate
class represents a date-only value without time-of-day and without time zone.
为此,请使用LocalDate
类。该LocalDate
级表示没有时间一天和不同时区的日期,唯一的价值。
A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris Franceis a new day while still “yesterday” in Montréal Québec.
时区对于确定日期至关重要。对于任何给定时刻,日期因地区而异。例如,在法国巴黎午夜过后几分钟是新的一天,而在魁北克蒙特利尔仍然是“昨天” 。
If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment, so your results may vary. Better to specify your desired/expected time zone explicitly as an argument.
如果未指定时区,JVM 会隐式应用其当前默认时区。该默认值可能随时更改,因此您的结果可能会有所不同。最好将您想要/预期的时区明确指定为参数。
Specify a proper time zone namein the format of continent/region
, such as America/Montreal
, Africa/Casablanca
, or Pacific/Auckland
. Never use the 3-4 letter abbreviation such as EST
or IST
as they are nottrue time zones, not standardized, and not even unique(!).
以、、 或等格式指定正确的时区名称。永远不要使用 3-4 个字母的缩写,例如或因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。continent/region
America/Montreal
Africa/Casablanca
Pacific/Auckland
EST
IST
ZoneId z = ZoneId.of( "America/Montreal" ) ;
Adjust your UTC value (Instant
) to another time zone by applying a ZoneId
to generate a ZonedDateTime
.
调整你的UTC值(Instant
通过应用)到另一个时区ZoneId
来生成ZonedDateTime
。
ZonedDateTime zdt = instant.atZone( z ) ;
From there we can extract the date-only portion as a LocalDate
object.
从那里我们可以提取仅日期部分作为LocalDate
对象。
LocalDate ld = zdt.toLocalDate() ;
If you want the first moment of the day on that date, you must specify the context of a time zone. For any given moment, the date varies around the globe by time zone. When a new day dawns in India, it is still “yesterday” in France.
如果您想要该日期当天的第一时刻,则必须指定时区的上下文。对于任何给定时刻,日期在全球各地因时区而异。当印度迎来新的一天时,在法国仍是“昨天”。
Always let java.time determine the first moment of the day. Do not assume 00:00. In some zones on some dates, the day may start at another time such as 01:00 because of anomalies such as Daylight Saving Time (DST).
始终让 java.time 确定一天中的第一个时刻。不要假设 00:00。在某些日期的某些区域中,由于夏令时 (DST) 等异常情况,一天可能会在其他时间开始,例如 01:00。
ZonedDateTime zdtStartOfDay = ld.atStartOfDay( z ) ;
If you want to see that same moment as UTC, simply extract a Instant
.
如果您想看到与 UTC 相同的时刻,只需提取一个Instant
.
Instant instant = zdtStartOfDay.toInstant() ;
The java.timeclasses also have a LocalDateTime
class. Understand that this class LocalDateTime
does notrepresent a moment!It does notrepresent a point on the timeline. It has no real meaning until you place it in the context of a time zone. This class is only used for two meanings:
该java.time类也有LocalDateTime
类。记者了解到,这个类LocalDateTime
就不能代表一个时刻!它并不能代表在时间轴上的一个点。除非您将其置于时区的上下文中,否则它没有真正的意义。这个类仅用于两个含义:
- The zone/offset is unknown (a bad situation).
- Every/any zone/offset is intended. For example, "Christmas starts at 00:00 on December 25, 2018“, which means different moments in different places. The first Christmas happens in Kiribati. Then successive Christmases start after each successive midnight moving westward through Asia, then India, and onwards to Europe/Africa, and eventually the Americas. So it takes at least 26 hours for Santa to deliver all the presents.
- 区域/偏移量未知(糟糕的情况)。
- 每个/任何区域/偏移都是有意的。例如,“圣诞节从 2018 年 12 月 25 日 00:00 开始”,这意味着不同地方的不同时刻。第一个圣诞节发生在基里巴斯。然后连续的圣诞节在每个连续的午夜之后开始,向西穿过亚洲,然后是印度,然后继续到欧洲/非洲,最终到达美洲。所以圣诞老人至少需要 26 小时才能送出所有礼物。
Hopefully you can see this work is not at all as confusing once you understand the core concepts and use the excellent well-designed java.timeclasses.
希望一旦您理解了核心概念并使用了优秀的精心设计的java.time类,您就会发现这项工作一点也不令人困惑。
About java.time
关于java.time
The java.timeframework is built into Java 8 and later. These classes supplant the troublesome old legacydate-time classes such as java.util.Date
, Calendar
, & SimpleDateFormat
.
该java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date
, Calendar
, & SimpleDateFormat
。
The Joda-Timeproject, now in maintenance mode, advises migration to the java.timeclasses.
现在处于维护模式的Joda-Time项目建议迁移到java.time类。
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
要了解更多信息,请参阅Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范是JSR 310。
You may exchange java.timeobjects directly with your database. Use a JDBC drivercompliant with JDBC 4.2or later. No need for strings, no need for java.sql.*
classes.
您可以直接与数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC 驱动程序。不需要字符串,不需要类。java.sql.*
Where to obtain the java.time classes?
从哪里获得 java.time 类?
- Java SE 8, Java SE 9, and later
- Built-in.
- Part of the standard Java API with a bundled implementation.
- Java 9 adds some minor features and fixes.
- Java SE 6and Java SE 7
- Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
- Android
- Later versions of Android bundle implementations of the java.time classes.
- For earlier Android (<26), the ThreeTenABPproject adapts ThreeTen-Backport(mentioned above). See How to use ThreeTenABP….
- Java SE 8、Java SE 9及更高版本
- 内置。
- 具有捆绑实现的标准 Java API 的一部分。
- Java 9 添加了一些小功能和修复。
- Java SE 6和Java SE 7
- 多的java.time功能后移植到Java 6和7在ThreeTen-反向移植。
- 安卓
- 更高版本的 Android 捆绑实现 java.time 类。
- 对于早期的 Android(<26),ThreeTenABP项目采用了ThreeTen-Backport(上面提到过)。请参阅如何使用ThreeTenABP ...。
The ThreeTen-Extraproject extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval
, YearWeek
, YearQuarter
, and more.
该ThreeTen-额外项目与其他类扩展java.time。该项目是未来可能添加到 java.time 的试验场。你可能在这里找到一些有用的类,比如Interval
,YearWeek
,YearQuarter
,和更多。
回答by Aaron Digulla
new LocalDateTime(3600000L, DateTimeZone.UTC)
doesn't really make sense: LocalDateTime
is, by definition, in your time zone. So what this does is: It assumes the timestamp was taken using your local timezone and you want to know what this timestamp would be in the UTC timezone. This is exactly the opposite conversion that you want to do.
new LocalDateTime(3600000L, DateTimeZone.UTC)
没有意义:LocalDateTime
根据定义,在您的时区。所以它的作用是:它假设时间戳是使用您的本地时区获取的,并且您想知道这个时间戳在 UTC 时区中是什么。这与您想要进行的转换完全相反。
Try new DateTime(3600000L).toLocalDateTime()
to convert a UTC timestamp to a local time.
尝试new DateTime(3600000L).toLocalDateTime()
将 UTC 时间戳转换为本地时间。
[EDIT]You're right, my suggestion above is misleading. The docs say:
[编辑]你说得对,我上面的建议具有误导性。文档说:
LocalDateTime is an unmodifiable datetime class representing a datetime without a time zone.
LocalDateTime 是一个不可修改的日期时间类,表示没有时区的日期时间。
So this thing is local to the "current time zone" - whatever that may be. When you create a formatter, it implicitly gets a time zone (the default one). So when you format such a local time, it will "move" into your time zone since it doesn't have one itself.
所以这个东西是本地的“当前时区”——不管是什么。创建格式化程序时,它会隐式获取时区(默认时区)。因此,当您格式化这样的本地时间时,它会“移动”到您的时区,因为它本身没有时区。
You can use this type to represent the concept of "12:00" without a time zone. If you add to a date with Singapore, it will inherit the time zone of Singapore. So you can use this for date calculations like "I want to get a DateTime
for "9:00" in various cities in the world."
您可以使用这种类型来表示没有时区的“12:00”的概念。如果您添加到新加坡的日期,它将继承新加坡的时区。所以你可以用它来计算日期,比如“我想DateTime
在世界上不同的城市得到一个“9:00”。
DateTime
, on the other hand, has a fixed time zone which doesn't change depending on the context. If you don't give it one, the current time zone of the Java VM will be the default.
DateTime
,另一方面,有一个固定的时区,不会根据上下文而改变。如果你不给它一个,Java VM 的当前时区将是默认的。
With that knowledge, new DateTime(3600000L, DateTimeZone.UTC).toLocalDateTime()
obviously has to result in 1970-01-01T01:00:00.000
.
有了这些知识,new DateTime(3600000L, DateTimeZone.UTC).toLocalDateTime()
显然必须导致1970-01-01T01:00:00.000
.
First, we create a DateTime
with a fixed time zone (UTC). When you format this alone, the formatter sees "oh, this has a time zone, so I can use that." Now you convert it into a local time which strips the time zone info and the formatter will use the default.
首先,我们创建一个DateTime
固定时区 (UTC)。当你单独格式化它时,格式化程序会看到“哦,它有一个时区,所以我可以使用它。” 现在您将其转换为去除时区信息的本地时间,并且格式化程序将使用默认值。
To solve your problem, use this code:
要解决您的问题,请使用以下代码:
new DateTime(3600000L, DateTimeZone.UTC).withTimeZone(DateTimeZone.getDefault())
which should be the same as:
这应该与以下内容相同:
new DateTime(3600000L)
since all time stamps are relative to 1970-01-01T00:00:00Z
(Z
== UTC time zone).
因为所有时间戳都相对于1970-01-01T00:00:00Z
( Z
== UTC 时区)。