【问题标题】:SimpleDateFormat returns wrong time from Firestore Timestamp - why?SimpleDateFormat 从 Firestore 时间戳返回错误的时间 - 为什么?
【发布时间】:2019-08-11 17:52:42
【问题描述】:

当使用 SimpleDateFormat 从 Firebase Firestore Timestamp 对象生成字符串时,我得到一个不正确/无意义的结果。为什么?

我制作了一些时间戳并将它们发送到 Firestore。我在 Firebase/Firestore 控制台中看到了预期的结果。然后,我从 Firestore 获取文件,从文档中检索时间戳,并首先将它们转换为 Date 对象,然后使用 SimpleDateFormat 转换为字符串。最后一步产生不正确的字符串:特别是时间“18:08:00”而不是“18:28:53”和“19:08:64”而不是“19:28:53”(注意:秒> 59)

Firebase 控制台显示我期望的时间戳:

eventTimestamp August 10, 2019 at 6:28:53 PM UTC+1
collectedTimestamp August 10, 2019 at 7:28:53 PM UTC+1

但以下代码显示了问题(selectedMedEvent 是从检索到的文档中创建的对象,getEventTimestamp() 和 getCollectedTimestamp() 都返回 Firebase Timestamp 对象):

Date eDate = selectedMedEvent.getEventTimestamp().toDate();
Date cDate = selectedMedEvent.getCollectedTimestamp().toDate();
String eString = selectedMedEvent.getEventTimestamp().toString();
String cString = selectedMedEvent.getCollectedTimestamp().toString();

SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy HH:MM:SS");
String eventDateString = formatter.format(eDate);
String collectedDateString = formatter.format(cDate);
Log.d(TAG, "Event Date is '" + eDate + "' or " + eString + " but SimpleDateFormat gives '" + eventDateString + "'");
Log.d(TAG, "Collected Date is '" + cDate + "' or " + cString + " but SimpleDateFormat gives '" + collectedDateString + "'");

Logcat 显示按预期打印的两个 Date 对象(与 Firestore 控制台匹配),但在其他地方出现错误:

Event Date is 'Sat Aug 10 18:28:53 GMT+01:00 2019' or Timestamp(seconds=1565458133, nanoseconds=0) but SimpleDateFormat gives '10/08/2019 18:08:00'
Collected Date is 'Sat Aug 10 19:28:53 GMT+01:00 2019' or Timestamp(seconds=1565461733, nanoseconds=648000000) but SimpleDateFormat gives '10/08/2019 19:08:64'

注意非零纳秒。当我创建时间戳时,纳秒值始终为 0。

这是怎么回事,我该如何解决?

稍后:我创建了一个独立的例程来演示该问题。我插入了 Logcat 给我的秒和纳秒值,我得到了相同的意外 SimpleDateFormat 字符串。代码是:

private void showBug() {
    Timestamp ts1 = new Timestamp( 1565458133, 0);
    Timestamp ts2 = new Timestamp( 1565461733, 648000000);

    Date eDate = ts1.toDate();
    Date cDate = ts2.toDate();
    String eString = ts1.toString();
    String cString = ts2.toString();

    SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy HH:MM:SS");
    String eventDateString = formatter.format(eDate);
    String collectedDateString = formatter.format(cDate);
    Log.d(TAG, "Event Date is '" + eDate + "' or " + eString + " but SimpleDateFormat gives '" + eventDateString + "'");
    Log.d(TAG, "Collected Date is '" + cDate + "' or " + cString + " but SimpleDateFormat gives '" + collectedDateString + "'");
}

Logcat 输出与之前报告的相同。

【问题讨论】:

标签: java firebase google-cloud-firestore timestamp simpledateformat


【解决方案1】:

tl;博士

您的直接问题是格式模式中的错误代码。您更大的问题是使用了糟糕的遗留类。

从 Firestore Timestamp(不要与 java.sql.Timestamp 混淆)转换为现代的 java.time.Instant

Instant instant = Instant.of( timestamp.getSeconds() , timestamp.getNanoseconds() ) 

示例:Instant.of( 1_565_461_733L , 648_000_000L )

java.time.Instant 转换为 Firestore Timestamp

new Timestamp( instant.getEpochSecond() , instant.getNano() )

小心数据丢失: 显然Firestore is limited to microseconds 而不是nanoseconds 分辨率。为什么他们自己的 Timestamp 类解析为 nanos 超出了我的理解。 (我不使用 Firestore/Firebase。)

格式不正确

new SimpleDateFormat("dd/MM/yyyy HH:MM:SS");

您使用了不正确的格式模式。模式代码区分大小写。阅读 Javadoc 类,并搜索 Stack Overflow,因为这已被 thousands of times already 覆盖。 发帖前搜索 Stack Overflow。

而且你不应该使用那个类。

java.time

Java 中糟糕的日期时间类,例如 SimpleDateFormatDateTimestamp,在几年前已被现代 java.time 类所取代。 java.util.Datejava.sql.Timestamp 都被 java.time.Instant 替换(和 java.time.OffsetDateTime 替换 JDBC 工作)。

不幸的是,Firestore 似乎还没有更新到 java.time。幸运的是,您可以在传统类和现代类之间来回转换。查看旧类中的新 to…from… 方法。

java.time.Instant

自 1970 年 UTC 第一时刻的纪元参考以来,您似乎输入了整秒计数,加上小数秒作为纳秒计数。将它们传递给 Instant 中的工厂方法。

Instant instant = Instant.ofEpochSecond ( 1_565_461_733L , 648_000_000L );

instant.toString(): 2019-08-10T18:28:53.648Z

截断

Timestore 是 limited to microseconds 而不是 java.time 中的纳秒。因此,您可能需要养成截断一致性的习惯。

instant = instant.truncatedTo( ChronoUnit.MICROS ) ;

Firestore Timestamp

显然 Firestore 有自己的 Timestamp 类。它的构造函数采用java.util.Date 对象。因此,我们可以java.time.Instant 转换为 java.util.Date 并将其提供给 Firebase Timestamp。不幸的是,java.util.Date 仅限于毫秒,甚至比 Firestore 中数据类型的微秒更小。转换为Date 意味着数据丢失。

使用其他构造方法构造一个 Firestore Timestamp,在该方法中传递整秒数加上纳秒。

Timestamp ts = new Timestamp( instant.getEpochSecond() , instant.getNano() ) ;

检索 Firestore Timestamp 时,立即转换为 Instant

Instant instant = Instant.of( ts.getSeconds() , ts.getNanoseconds() ) ;

如果您想查看从 UTC 调整到某个时区的同一时刻,请应用 ZoneId 以获取 ZonedDateTime

ZoneId z = ZoneId.of( "Asia/Tokyo" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, same point on the timeline, different wall-clock time.

请注意,在上面的代码中,我们都没有在字符串中工作。所以不需要任何格式化模式

学习在 java.time 中完成所有业务逻辑。避免可怕的DateCalendarSimpleDateFormat 类。

【讨论】:

    【解决方案2】:

    Java 中的日期对象只有毫秒的分辨率。他们不能存储任何小于此的度量单位。 Firestore 时间戳可以存储小到纳秒的时间单位,这比一毫秒要小得多。

    因此,如果将 Date 转换为 Firestore Timestamp 对象,则纳秒组件将始终为 0,因为 Date 对象根本没有可用的纳秒。在这种情况下,0 只是默认值。

    更正式地说,将时间戳转换为日期是缩小转换,您会失去精度。将日期转换为时间戳是加宽转换,其中添加了新的精度,并且默认情况下丢失的数据设置为零。

    【讨论】:

      猜你喜欢
      • 2020-07-09
      • 1970-01-01
      • 2021-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-26
      • 2020-11-23
      • 2013-04-13
      相关资源
      最近更新 更多