如何将unix时间戳转换为LocalDate(时间)而不进行转换

时间:2014-08-19 13:28:48

标签: java jodatime unix-timestamp

我正在使用org.joda.time.LocalDate和LocalDateTime。从外部源我得到一个Unix时间戳,并希望从中获取一个LocalDate(时间)。关键是,它在该外部系统的接口中定义,所有日期/时间都是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

结果令我感到惊讶。看一下JavaDoc,第一个构造函数使用默认区域中的ISO年表来评估时间戳""根据定义,Unix时间戳是01-JAN-1970T00:00:00UTC的秒数(此处为毫秒)!因此,如果将值3600000(=以毫安为准2小时)添加到该基数,则将在01-JAN-1970T02:00:00UTC中进行。我的本地系统设置为时区Europe / Berlin(CET),即UTC + 1。确切地说,我们现在有夏令时,所以它甚至应该是UTC + 2,但我们现在假装我们在UTC + 1。因此,如果时间戳是按UTC定义的,那么我希望结果时间是01:00:00,如果它将时间戳的值解释为CET中转换为UTC,或者03:00:00 if它正确地期望时间戳具有转换为CET的UTC值。但它实际上显示了一个未转换的时间戳,距离基地只有2个小时。 第二个构造函数应该使用指定区域中的ISO年表来评估时间戳。" (来自JavaDoc)因此,如果我明确指定UTC时区,我根本不会期望任何转换,但是时间是02:00:00。基于UTC的时间戳导致其自身被声明为UTC的时间应该产生确切的结果,但结果是01:00:00!为了仔细检查,我明确地用CET打电话给它,并得到了相同的结果,好像我没有提供任何时区。

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

我观察到的一个邪恶场景是,时间戳应该只是一个日期(没有时间)。在这种情况下,时间戳将是时间设置为00:00:00的任何日期。当我使用LocalDate时,就像我在上面的例子中使用LocalDateTime一样,它将时间戳转换为日期+时间(当然),并简单地减少时间。但是,如果日期是15-JUL-2014T00:00:00UTC,并且我的结果在我的另一个例子中移动了相同的一小时,那么转到14-JUL-2014T23:00:00并且随后转到日期14-JUL-2014!这实际上是一场灾难,绝不可能发生!

那么你们中的任何人都有一个线索,为什么LocalDate(时间)表现得那样?或者我可能误解的背后的概念是什么。或者如何保证不发生转换?

3 个答案:

答案 0 :(得分:2)

为什么不要你:

timeStamp.toLocalDateTime().toLocalDate(); // JAVA 8

答案 1 :(得分:1)

new LocalDateTime(3600000L, DateTimeZone.UTC)没有任何意义:根据定义,LocalDateTime位于您的时区。这样做的原因是:它假设使用您的本地时区获取时间戳,并且您想知道此时间戳在UTC时区中的含义。这正是您想要做的相反的转换。

尝试new DateTime(3600000L).toLocalDateTime()将UTC时间戳转换为本地时间。

[编辑] 你是对的,我的上述建议具有误导性。文档说:

  

LocalDateTime是一个不可修改的日期时间类,表示没有时区的日期时间。

所以这个东西是“当前时区”的本地 - 无论可能是什么。创建格式化程序时,它会隐式获取时区(默认值)。因此,当您格式化这样的本地时间时,它将“移动”到您的时区,因为它本身没有。

您可以使用此类型来表示没有时区的“12:00”概念。如果您在新加坡添加日期,它将继承新加坡的时区。因此,您可以将此用于日期计算,例如“我希望在世界各个城市获得DateTime”9:00“。

另一方面,

DateTime具有固定的时区,根据上下文不会改变。如果您不给它一个,则Java VM的当前时区将是默认值。

有了这些知识,new DateTime(3600000L, DateTimeZone.UTC).toLocalDateTime()显然必须导致1970-01-01T01:00:00.000

首先,我们创建一个具有固定时区(UTC)的DateTime。当你单独格式化时,格式化程序会看到“哦,这有一个时区,所以我可以使用它。”现在您将其转换为剥离时区信息的本地时间,格式化程序将使用默认值。

要解决您的问题,请使用以下代码:

new DateTime(3600000L, DateTimeZone.UTC).withTimeZone(DateTimeZone.getDefault())

应该与:

相同
new DateTime(3600000L)

因为所有时间戳都相对于1970-01-01T00:00:00ZZ == UTC时区)。

答案 2 :(得分:0)

java.time

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

将该数字解析为Instant个对象。 Instant类代表UTC中时间轴上的一个时刻,分辨率为nanoseconds(小数部分最多九(9)位)。

Instant instant = Instant.ofEpochMilli( 3_600_000L ) ;
  

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

非常简单:Instant始终是UTC,总是片刻,时间轴上的一个点。

  

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

为此,请使用LocalDate类。 LocalDate类表示没有时间且没有时区的仅限日期的值。

时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因地区而异。例如,在Paris France午夜后的几分钟是新的一天,而Montréal Québec中仍然是“昨天”。

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

continent/region的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用诸如ESTIST之类的3-4字母缩写,因为它们不是真正的时区,不是标准化的,甚至不是唯一的(!)。

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

通过应用Instant生成ZoneId,将您的UTC值(ZonedDateTime)调整为其他时区。

ZonedDateTime zdt = instant.atZone( z ) ;  

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

LocalDate ld = zdt.toLocalDate() ;

如果您想要该日期的第一天,则必须指定时区的上下文。对于任何特定时刻,日期在全球各地按时区变化。当印度新的一天到来时,它在法国仍然是“昨天”。

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

ZonedDateTime zdtStartOfDay = ld.atStartOfDay( z ) ;

如果您希望看到与UTC相同的时刻,只需提取Instant

Instant instant = zdtStartOfDay.toInstant() ;

java.time 类也有一个LocalDateTime类。了解此类 LocalDateTime 代表片刻! not 代表时间轴上的一个点。在将它放在时区的上下文中之前,它没有任何实际意义。该类仅用于两个含义:

  • 区域/偏移量未知(情况不佳)。
  • 意图是每个/任何区域/偏移。例如,"圣诞节从2018年12月25日00:00开始“,这意味着不同地方的不同时刻。第一个圣诞节发生在Kiribati。然后连续的圣诞节在每个连续的午夜之后开始向西移动通过亚洲,然后是印度,然后是欧洲/非洲,最后是美洲。因此圣诞老人至少需要26个小时才能送出所有礼物。

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

关于 java.time

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

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

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

从哪里获取java.time类?

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