如何将Google原型时间戳转换为Java LocalDate?tl;博士转换转换关于 java.time

2022-09-02 12:48:50

我们需要将Google Proto缓冲区时间戳转换为正常日期。在这种情况下,有没有办法将Google Proto缓冲区时间戳直接转换为Java?LocalDate


答案 1

tl;博士

作为 UTC 中的某个时刻,转换为 。然后应用时区以获取 .将仅日期部分解压缩为 .java.time.InstantZonedDateTimeLocalDate

单行:

Instant
.ofEpochSecond( ts.getSeconds() , ts.getNanos() ) 
.atZone( ZoneId.of( "America/Montreal" ) ) 
.toLocalDate() 

转换

第一步是将 Timestamp 对象的秒数和秒的小数部分(纳秒)计数转换为 java.time 类。具体来说,java.time.Instant。就像 一样,an 表示 UTC 中的一个时刻,分辨率为纳秒。TimestampInstant

Instant instant =  Instant.ofEpochSecond( ts.getSeconds() , ts.getNanos() ) ;

确定日期需要时区。在任何给定的时刻,日期在全球范围内因区域而异。

将 a 应用于我们的 ,以获得 .相同的时刻,时间轴上的相同点,不同的挂钟时间。ZoneIdInstantZonedDateTime

ZoneId z = ZoneId( "Pacific/Auckland" ) ; 
ZonedDateTime zdt = instant.atZone( z ) ;

将仅日期部分解压缩为 .A 没有时间,也没有时区。LocalDateLocalDate

LocalDate ld = zdt.toLocalDate() ;

警告: 不要将类用于此目的,不幸的是,如另一个答案所示。该类故意缺少时区或与 UTC 的偏移量的任何概念。因此,它不能代表一个时刻,不是时间轴上的一个点。请参阅类文档。LocalDateTime

转换

最好完全避免非常麻烦的遗留日期时间类,包括 , 、 .但是,如果您必须与尚未更新为java.time的旧代码进行互操作,则可以来回转换。调用添加到旧类的新转换方法。DateCalendarSimpleDateFormat

GregorianCalendar gc = GregorianCalendar.from( zdt ) ;

要将仅日期值表示为,我们必须指定一天中的时间和时区。您可能希望将一天中的第一个时刻用作一天中的时间组件。永远不要假设第一刻是 00:00:00。异常(如夏令时)意味着第一个时刻可能是另一个时间,如 01:00:00。让 java.time 决定第一个时刻。GregorianCalendar

ZonedDateTime firstMomentOfDay = ld.atZone( z ) ;
GregorianCalendar gc = GregorianCalendar.from( firstMomentOfDay ) ;

Table of date-time types in Java (both legacy and modern) and in standard SQL


关于 java.time

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

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

要了解更多信息,请参阅 Oracle 教程。搜索 Stack Overflow 以获取许多示例和解释。规格是JSR 310

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

从哪里获取 java.time 类?

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


答案 2

首先要注意:Protobuf的分辨率(秒和秒的分数)比Java的(Days)更高,因此您将通过从转换为TimeStampLocalDateTimeStampLocalDate

请参阅 TimeStamp 的 JavaDoc 摘录:

时间戳表示独立于任何时区或日历的时间点,在 UTC 纪元时间中以纳秒分辨率表示秒和秒的分数。


这个相同的JavaDoc告诉我们,该值是基于Epoch时间的表示,这意味着我们可以使用Java的LocalDateTime#ofEpochSeconds进行转换而不会丢失(因为还可以存储时间),并从那里剥离时间以获得.LocalDateTimeLocalDate

通过使用(em:Local),我们可以确保使用与类相同的时区偏移量,即UTC(这再次来自JavaDoc):LocalDateTimeTimeStamp

final Timestamp ts1 = Timestamp.newBuilder().setSeconds((60 * 60 * 24) - 1).build();
final Timestamp ts2 = Timestamp.newBuilder().setSeconds((60 * 60 * 24)).build();

final LocalDate ld1 = LocalDateTime.ofEpochSecond(ts1.getSeconds(), ts1.getNanos(), ZoneOffset.UTC).toLocalDate();
final LocalDate ld2 = LocalDateTime.ofEpochSecond(ts2.getSeconds(), ts2.getNanos(), ZoneOffset.UTC).toLocalDate();

System.out.println(ts1 + " = " + ld1);
System.out.println(ts2 + " = " + ld2);

输出

秒: 86399 = 1970-01-01

秒: 86400 = 1970-01-02


要求转换为后编辑java.util.Date

看看可能的构造函数和它们的JavaDoc,我们发现:Date

分配一个 Date 对象并将其初始化为表示自称为“纪元”的标准基时间(即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。

由于GMT与区域时间表示的旧标准相同,并且基本上是UTC +/- 0,因此此构造函数符合我们的需求:

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
// making sure the Date objects use UTC as timezone

final Timestamp ts1 = Timestamp.newBuilder().setSeconds((60 * 60 * 24) - 1).build();
final Timestamp ts2 = Timestamp.newBuilder().setSeconds((60 * 60 * 24)).build();

final Date d1 = new Date(ts1.getSeconds() * 1000);
final Date d2 = new Date(ts2.getSeconds() * 1000);

System.out.println(ts1 + " = " + d1);
System.out.println(ts2 + " = " + d2);

输出:

秒: 86399 = 星期四 一月 01 23:59:59 CET 1970

秒: 86400 = 星期五 一月 02 00:00:00 CET 1970