如何在不进行转换的情况下将Unix时间戳转换为LocalDate(Time) [英] How to convert a unix timestamp to LocalDate(Time) without conversion

查看:115
本文介绍了如何在不进行转换的情况下将Unix时间戳转换为LocalDate(Time)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

结果让我有些惊讶.看完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进行了调用,并得到与未提供任何时区相同的结果.

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

我观察到的一个邪恶场景是,时间戳应该只是一个日期(没有时间).在这种情况下,时间戳记可以是时间设置为00:00:00的任何日期.当我使用与上面示例中的LocalDateTime相同的方式使用LocalDate时,它将时间戳转换为日期+时间(当然),并且只是减少了时间.但是,如果日期是15-JUL-2014T00:00:00UTC,并且我结尾处的结果与我其他示例中的结果偏移了一个小时,则结果变为14-JUL-2014T23:00:00,并随之移至日期2014年7月14日!这实际上是一场灾难,绝不可能发生!

那么你们中的任何人都知道为什么LocalDate(Time)会那样吗?或者我背后的概念是什么,我可能会误解.还是如何保证不会发生转换?

解决方案

tl; dr

您的问题令人困惑,但您似乎声称数字3_600_000L表示自1970年1月19日UTC 1970-01-01T00:00Z的纪元参考以来的毫秒数.

因此解析为 Instant .

  Instant//以UTC表示的时刻,偏移量为零小时-分钟-秒..ofEpochMilli(3_600_000L)//解析自1970-01-01T00:00Z以来的毫秒数.返回一个Instant对象..toString()//使用标准ISO 8601格式生成表示该值的文本. 

如UTC所示,结果是1970年第一天凌晨1点.最后的 Z 表示UTC.

1970-01-01T01:00:00Z

获取日期部分(如UTC所示).

  Instant//以UTC表示的时刻,偏移量为0小时-分钟-秒..ofEpochMilli(3_600_000L)//解析自1970-01-01T00:00Z以来的毫秒数..atOffset(//从`Instant`(总是UTC,偏移量为零)转换为`OffsetDateTime`,可以有任何偏移量.ZoneOffset.UTC//表示零时分分钟秒的偏移量的常数,即UTC本身.)//返回`OffsetDateTime`对象..toLocalDate()//提取日期部分,不包含日期时间和UTC偏移量..toString()//使用标准ISO 8601格式生成表示该值的文本. 

1970-01-01

调整从UTC到时区 Europe/Berlin 的那一刻.

  Instant//以UTC表示的时刻,偏移量为零小时-分钟-秒..ofEpochMilli(3_600_000L)//解析自1970-01-01T00:00Z以来的毫秒数..atZone(//从UTC转换为特定时区.ZoneId.of("Europe/Berlin")//时区是特定区域的人们过去,现在和将来对UTC偏移量的历史记录.)//返回一个"ZonedDateTime"对象..toString()//使用明智扩展的标准ISO 8601格式生成表示此值的文本,以将时区的名称附加在方括号中. 

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

请注意,该结果在柏林地区的凌晨2点不同于我们在UTC看到的凌晨1点.当时欧洲/柏林时区比世界协调时间早一小时,因此,凌晨1点前一小时是凌晨2点-同一时刻,时间轴上的同一点,不同的挂钟时间

从那一刻起获得仅日期部分,如 Europe/Berlin 所示.

  Instant//以UTC表示的时刻,偏移量为零小时-分钟-秒..ofEpochMilli(3_600_000L)//解析自1970-01-01T00:00Z以来的毫秒数..atZone(//从UTC转换为特定时区.ZoneId.of("Europe/Berlin")//时区是特定区域的人们过去,现在和将来对UTC偏移量的历史记录.)//返回`ZonedDateTime`对象..toLocalDate()//仅提取日期,而不提取时间和时区.返回一个LocalDate对象..toString()//使用标准ISO 8601生成表示该值的文本. 

1970-01-01

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

java.time

显然,您使用的术语"Unix timestamp"表示自1970 UTC的第一刻1970-01-01T00:00Z起的毫秒数.

将该数字解析为一个 Instant 对象.


关于 java.time

> ThreeTen-Extra 项目扩展了java.time与其他班级.该项目为将来可能在java.time中添加内容提供了一个试验场.您可能会在这里找到一些有用的类,例如 间隔 年周" YearQuarter 更多

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):

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.

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.

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!

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?

解决方案

tl;dr

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.

So parse as an 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-01-01T01:00:00Z

Get the date portion, as seen in 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

Adjust that moment from UTC to the time zone 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]

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.

Get the date-only portion from that moment as seen in 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

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.

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.

Parse that number into an Instant object. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).

Instant instant = Instant.ofEpochMilli( 3_600_000L ) ;

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

So very simple: An Instant is always in UTC, always a moment, a point on the timeline.

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.

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 France is 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.

Specify a proper time zone name in 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 not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "America/Montreal" ) ;  

Adjust your UTC value (Instant) to another time zone by applying a ZoneId to generate a ZonedDateTime.

ZonedDateTime zdt = instant.atZone( z ) ;  

From there we can extract the date-only portion as a LocalDate object.

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).

ZonedDateTime zdtStartOfDay = ld.atStartOfDay( z ) ;

If you want to see that same moment as UTC, simply extract a Instant.

Instant instant = zdtStartOfDay.toInstant() ;

The java.time classes also have a LocalDateTime class. Understand that this class LocalDateTime does not represent a moment! It does not represent 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:

  • 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.

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.time classes.



About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project 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.

这篇关于如何在不进行转换的情况下将Unix时间戳转换为LocalDate(Time)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆