如何将Google Proto时间戳转换为Java LocalDate?

22

我们需要将Google Proto buffer时间戳转换为普通日期。在此情况下,是否有任何方法可以直接将Google Proto buffer时间戳转换为Java LocalDate


你是指 Java8日期 中的 LocalDate 吗?Date 本身是Java 1 :-) - LuCio
抱歉,这是一个打字错误。现在已更改问题@LuCio。 - Thirunavukkarasu
实际上,Google Proto Buffer 返回 com.google.protobuf.Timestamp 实例。在那个位置,我们需要将其转换为 LocalDate 或任何其他类型! - Thirunavukkarasu
3
使用 Instant.ofEpochSecond(yourTimestamp.getSeconds(), yourTimestamp.getNanos()) 方法获取一个Instant对象,并从获取到的 Instant 对象开始处理。如果不确定如何将 Instant 转换为 LocalDate,可以搜索相关内容,这方面的信息在很多地方都有描述。 - Ole V.V.
1
亲爱的投反对票者,请在您的投票中留下批评意见,以解释这个问题为什么不是有益、建设性或形式良好。在我看来,这是一个非常好的适合于 Stack Overflow 的问题。 - Basil Bourque
5个回答

32

简短概述

将一个UTC时间转换为java.time.Instant,然后应用一个时区以获取一个ZonedDateTime。提取仅包含日期的部分作为LocalDate

一行代码:

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

转换

第一步是将Timestamp对象的秒数和小数秒(纳秒)转换为java.time类。具体来说,是java.time.Instant。与Timestamp一样,Instant表示UTC中的一个瞬间,分辨率为纳秒。

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

确定日期需要一个时区。对于任何特定的时刻,由于时区的不同,日期在全球范围内会有所变化。
ZoneId应用于我们的Instant,就可以得到一个ZonedDateTime。同一时刻,在时间线上相同的点,不同的挂钟时间。
ZoneId z = ZoneId( "Pacific/Auckland" ) ; 
ZonedDateTime zdt = instant.atZone( z ) ;

提取日期部分作为{{LocalDate}}。{{LocalDate}}没有时间和时区。
LocalDate ld = zdt.toLocalDate() ;

注意:不要使用LocalDateTime类来实现此目的,正如另一个答案所不幸显示的那样。该类故意缺乏任何时区或UTC偏移量的概念。因此它不能表示一个瞬间,也不是时间轴上的一个点。请参阅类文档。

转换

最好完全避免可怕的旧日期时间类,包括DateCalendarSimpleDateFormat。但如果您必须与尚未更新为java.time的旧代码进行交互,您可以进行相互转换。调用添加到旧类中的新转换方法。

GregorianCalendar gc = GregorianCalendar.from( zdt ) ;

要将仅包含日期的值表示为GregorianCalendar,我们必须指定一天中的时间和时区。您可能希望使用一天中的第一个时刻作为时间组件。永远不要假设第一个时刻是00:00:00。夏令时等异常情况可能导致第一个时刻变成另一个时间,例如01:00:00。让java.time确定第一个时刻。

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及以上版本内置的。这些类替代了老旧的legacy日期时间类,如java.util.DateCalendarSimpleDateFormat

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

欲了解更多信息,请参阅Oracle Tutorial。您也可以在Stack Overflow上搜索许多示例和解释。规范为JSR 310

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

在哪里获取java.time类?

ThreeTen-Extra项目扩展了java.time的附加类。该项目是java.time可能未来增加的类的试验场。您可能会在这里找到一些有用的类,例如Interval, YearWeek, YearQuarter, 和more

感谢@Basil Bourque的建议。请问您能否帮我解决如何将其转换为日历实例(java.util.calendar)的问题? - Thirunavukkarasu
@Thirunavukkarasu 我添加了“转换”部分。 - Basil Bourque

1

首先提醒一下:Protobuf的TimeStamp比Java的LocalDate有更高的分辨率(秒和秒的分数),因此将TimeStamp转换为LocalDate会丢失一些信息。

请参阅JavaDoc中TimeStamp的摘录JavaDoc

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


这个JavaDoc告诉我们,该值是基于Epoch时间的表示,这意味着我们可以使用Java的LocalDateTime#ofEpochSeconds进行无损转换(因为LocalDateTime也存储时间),然后剥离时间以获得LocalDate
通过使用LocalDateTime(本地时间),我们可以确保使用与TimeStamp类相同的时区偏移量,即UTC(这也来自JavaDoc)。
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之后

查看Date的可能构造函数及其JavaDoc,我们发现:

分配一个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 = 1970年1月1日23时59分59秒 CET

秒数: 86400 = 1970年1月2日00时00分00秒 CET


还有一个问题,是否有办法将java.util.Date从proto时间戳转换而不是LocalDate? - Thirunavukkarasu
错误。在这里使用LocalDateTime是完全错误的。正如其类文档中所解释的那样,该类无法表示一个瞬间。该类表示一系列潜在的时刻,涵盖全球大约26-27个小时的时区范围。相反,应使用java.time.Instant来表示UTC中的瞬间。 - Basil Bourque
@BasilBourque 我同意 Instant 更加优雅,因为它隐藏了所有的时代/时区信息,但这对于 OP 来说并不是那么具体的解释。你说得对,LocalDateTime 可以 表示一个范围。但是通过指定时区偏移量,这些都无关紧要了。这就好比说不要使用 int 来表示 010 之间的数字,因为 int 也可以是 10.000 - ifloop
@BasilBourque,您能否提供一个例子,说明我的转换方法不起作用?因为我非常愿意学习新的东西,这将是对投票和粗体“不正确”标签的证明。提前感谢! - ifloop

1

0

最简单的方法是从缓冲时间戳中提取日期到一个字符串,以便可以多次使用,然后:

Timestamp timestamp;
String sDate1 = timestamp.toString();  
Date date1 = new SimpleDateFormat("dd/MM/yyyy").parse(sDate1);

如果你想将它转换为LocalDate,可以按照以下步骤进行:

Timestamp timestamp;
String sDate1 = timestamp.toString();    
LocalDate localDate = LocalDate.parse(sDate1);

如果您无法提取字符串时间戳,而是有毫秒数,您也可以从中创建LocalDate和Date对象,因为它们都有相应的构造函数。
    Timestamp timestamp;
    long timeInMilliSeconds = Timestamp.toMillis(timestamp);
    Date currentDate = new Date(timeInMilliSeconds);
    LocalDate date = Instant.ofEpochMilli(timeInMilliSeconds).atZone(ZoneId.systemDefault()).toLocalDate();

实际上,Google Proto Buffer 返回 com.google.protobuf.Timestamp 实例。在那个位置,我们需要将其转换为 LocalDate 或任何其他类型! - Thirunavukkarasu
@Thirunavukkarasu 必须有一种方法将时间戳转换为字符串。一旦你做到了,就可以轻松地对其进行任何操作。 - Sir. Hedgehog
1
实际上,Google Protobuf时间戳返回以下值 秒:988221139 纳秒:516000000。 - Thirunavukkarasu
1
我正在使用com.google.protobuf.Timestamp而不是com.google.protobuf.util.Timestamps。 - Thirunavukkarasu
1
根据文档,没有getTime方法。 - Ole V.V.
显示剩余5条评论

-1
PubsubMessage pubsubMessage = message.getPubsubMessage();
String data = pubsubMessage.getData().toStringUtf8();
//System.out.println(pubsubMessage.getMessageId());
Timestamp timestamp = pubsubMessage.getPublishTime();
//System.out.println(timestamp);
Instant instant =  Instant.ofEpochSecond(timestamp.getSeconds() , 
timestamp.getNanos());
ZoneId z = ZoneId.of("America/Montreal");
ZonedDateTime zdt = instant.atZone(z);
LocalDateTime ldt = zdt.toLocalDateTime();
System.out.println("today's date==> "+ldt.getDayOfMonth()); 

上述代码肯定可以运行。


1
感谢您想要做出贡献。您是否已经为其他答案所没有的内容做出了很多贡献?我们更需要的是解释,因为我们从中学到东西,如果您能添加一些解释,那就更好了。我相信您的代码是有效的。 - Ole V.V.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接