【问题标题】:Convert microseconds string to date in Java or Scala在 Java 或 Scala 中将微秒字符串转换为日期
【发布时间】:2016-09-24 11:31:52
【问题描述】:

如何在 Java/Scala 中将时间戳(以微秒为单位)字符串转换为日期。 我的目标是比较两个时间戳并找出它们之间的差异。 我正在使用 java 8,示例时间戳字符串是 1474457086337977。 我想将其转换为 Date 或 Timestamp 实例。

【问题讨论】:

  • 为什么不直接获取当前日期和时间?
  • @NoOne 因为当前日期和时间不会帮助您获得两个时间戳之间的差异,就像问题清楚地解释了:/
  • 实际上我会从外部资源获取时间戳作为我的数据的一部分,我需要比较这个时间戳并做一些逻辑。
  • 我真的不明白,为什么这个问题被否决了。如果您觉得答案很简单,请提供答案,否则请注明投反对票的原因。
  • 据我所知,每个日期库都有一种方法可以从毫秒(只要)创建日期,因此您只需将字符串解析为 long,然后创建日期(我推荐 joda DateTime - 它有很好的方法来比较日期)

标签: java scala


【解决方案1】:

tl;博士

Instant.EPOCH.plus( 
    Duration.ofNanos( 
        TimeUnit.MICROSECONDS.toNanos( 
            Long.parse( "1474457086337977" ) ) ) )

java.time

java.time 类支持nanoseconds 的分辨率,对于您的microseconds 来说绰绰有余。

解析数字字符串

将字符串解析为long,以获取纪元的微秒计数。

long micros = Long.parse( "1474457086337977" );

当然,您总是可以使用整数文字。注意 L 附加到整数文字。

long micros = 1_474_457_086_337_977L ;

将 long 转换为 Instant

我们希望将微秒计数从 UTC 1970 年初的 epoch (1970-01-01T00:00:00Z) 转换为 InstantInstant 类代表UTC 时间线上的一个时刻,分辨率为nanoseconds。这意味着最多九位小数。

Instant 类具有方便的静态方法,用于从 count of whole secondscount of whole seconds plus a fractional second in nanosecondscount of milliseconds 进行转换。但不幸的是,没有这样的方法可以计算微秒或纳秒。

作为一种解决方法,我们可以定义一个Duration 并将其添加到已经定义为常量的纪元参考日期。我们可以将Duration 实例化为纳秒数。为了获得纳秒,我们将您的微秒乘以一千。请注意使用 64 位 long 而不是 32 位 int

Duration duration = Duration.ofNanos( micros * 1_000L );  
Instant instant = Instant.EPOCH.plus( duration );

instant.toString(): 2016-09-21T11:24:46.337977Z

或者,您可以使用TimeUnit 枚举将微秒转换为纳秒,而无需硬编码“幻数”。

Duration duration = Duration.ofNanos( TimeUnit.MICROSECONDS.toNanos( micros ) );  

要调整到其他偏移量或时区,请在 StackOverflow 中搜索 Java 类 OffsetDateTimeZonedDateTime

转换为旧的日期时间类型

您应该避免与最早的 Java 版本捆绑在一起的旧日期时间类型。事实证明,它们设计不良、混乱且麻烦。现在被 java.time 类型所取代。

但如果您必须与尚未更新为 java.time 类型的旧代码进行交互,您可以转换为 java.time 或从 java.time 转换。查看添加到旧类的新方法。

java.sql.Timestamp ts = java.sql.Timestamp.from( instant );

从 java.time 类型转换为 java.util.Datejava.util.Calendar 时请注意数据丢失。这些类型仅解析为milliseconds。从纳秒到毫秒的截断是静默执行的,去掉小数秒的最后六个(九个)可能的数字。

java.util.Date utilDate = java.util.Date.from( instant );  // Caution: Data loss in truncating nanoseconds to milliseconds.

关于java.time

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

Joda-Time 项目现在位于maintenance mode,建议迁移到 java.time。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。

大部分 java.time 功能在ThreeTen-Backport 中向后移植到Java 6 和7,并进一步适应ThreeTenABP 中的Android(请参阅How to use…)。

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。

【讨论】:

  • 有趣的是,在您的回答中,TimeUnit.MICROSECONDS.toNanosTimeUnit.MICROSECONDS.toMillis 存在相同的问题。只是在相反的方向。 toMillis 转换可能会出现下溢,而在您的回答中,toNanos 转换可能会出现溢出。例如,如果您有 microsecs = Long.MAX_VALUE.
  • @EdwinDalorzo 为什么会有一个日期时间值作为 Long.MAX_VALUE 微秒的纪元计数?
  • 您在我的回答中留下了关于我使用TimeUnit.convert 时可能丢失数据的评论。我认为强调这个解决方案也不能解决问题是公平的。我以Long.MAX_VALUE 为例,但您无需达到该数字即可获得溢出。在哪些情况下这有意义?有关系吗?当您突出显示答案时,我可以问同样的问题关于我的答案中精度的损失。但仅举一个例子,天体计算很容易超过这个数字。
  • @EdwinDalorzo Your Answer 涉及将纳秒级截断到毫秒级。我指出了实际问题,即 always 意味着数据丢失(除了零的偶然性)。我还指出了解决该问题并改进示例代码的解决方案。您关于 Long.MAX_VALUE 和天体计算的观点超出了 java.time 类的意图,并且正在深入研究荒谬。
【解决方案2】:

那么,如何将这些微秒转换为毫秒,然后创建一个时间戳对象?

long microsecs = 1474457086337977L;
long millis = TimeUnit.MILLISECONDS.convert(microsecs, TimeUnit.MICROSECONDS);
Timestamp time = new Timestamp(millis);

这样不行吗?

--编辑

解决答案中留下的cmets:

关于 Java 8 的新日期时间 API

首先,既然您提到您正在使用 Java 8,我完全同意更好的方法是使用新的 Java 8 日期/时间 API。然而,即使在使用 Java 8 时,这也是一种奢侈,因为您可能仍在与仍然使用旧 Java 日期/时间类的旧 API 进行交互,或者仅仅是因为您的 API 的其余部分仍然使用它们并且你不想开始混合。

如果您已经知道这一点,您的问题不清楚,您似乎确定要使用java.util.Datejava.sql.Timestamp,我没有质疑,我只是解决了您的参数问题。

显然,新的 Java 日期/时间 API 比旧的要好得多,但仍有数百万行代码仍在使用旧的 API,而且它们仍然有效。再一次,我认为这超出了答案的范围,看来您在这里已经有了其他好的答案来解决这个问题。

关于可能的数据丢失

一条评论提到答案可能会导致数据丢失。我认为在 Java 中,所有整数算术都会受到潜在的下溢或溢出的影响。我的错误可能是没有提到它。

确实,TimeUnit.convert 方法在某些情况下最终可能会导致上溢或下溢。它记录在方法中。

  • 纳秒是十亿分之一秒 (1/1000000000)
  • 一微秒是百万分之一秒 (1/1000000)。
  • 一毫秒是千分之一秒 (1/1000)

这意味着,一旦表示为 long,毫秒数应该比微秒数小得多,对吧?

TimeUnit.convert使用的公式如下

final long MICROS = 1000000L;
final long MILLIS = 1000L;

long microsecs = 1474457086337977L;
long millisecs = microsecs / (MICROS / MILLIS)

这意味着只有当您的微秒是非常小的数字时,您才会遇到数据丢失,例如如果您的时间少于 1,000 微秒。你应该验证你的代码永远不会进入这样的场景。

此答案中留下的一条评论认为,正确的答案可能应该使用纳秒,但是再一次,纳秒长值将比您的微秒大得多,因此,在转换为纳秒期间,您可能仍然会遇到溢出。

例如,想想如果你有 Long.MAX_VALUE 微秒会发生什么,你怎么能只使用 Java 长算法将其转换为纳秒而不会溢出,因为纳秒应该比你的 Long.MAX_VALUE 微秒大得多?

我的观点是,无论您使用 Java 8 日期时间还是旧版 Java 日期时间 API,您都需要一个 long 值来表示时间线中的一个瞬间,但 long 在过去或未来你可以走多远,当你在单位之间进行转换时,该算术会出现下溢和溢出,并且没有办法解决它,你应该意识到这一点以避免非常讨厌的错误。

再一次,我认为这是一个既定的问题,超出了问题的范围,我提出这个问题只是因为我对这个遗漏投了一些反对票。

【讨论】:

  • 知道java.sql.Timestamp 是一个糟糕的类,扩展java.util.Date 但假装没有。建议在需要将数据传入/传出数据库时使用,但不建议用于业务逻辑。这个类是麻烦的旧日期时间类的一部分,现在被 java.time 类所取代。 java.time 类可以处理nanoseconds(甚至比微秒更精细)。
  • 此答案的代码导致数据丢失,将微秒截断为毫秒。虽然使用 java.time 是一条更好的路线,但如果您坚持使用这种方法,那么您可能需要通过调用 setNanos 来进行额外的数学运算以设置此 Timestamp 对象的 nano 字段。
  • @BasilBourque 我怀疑是否有办法在 Java 中进行整数/长整数运算,而不会在某些时候出现溢出或下溢。我的错误可能是没有提到它。据我所知,纳秒级的转换会遇到同样的精度问题。
【解决方案3】:

您可以尝试以下代码,它将时间戳作为字符串:

 BigInteger b = new BigInteger("1474457086337977");
 b=b.divide(new BigInteger("1000"));

 String x =b.toString();



DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");

long milliSeconds= Long.parseLong(x);
System.out.println(milliSeconds);

Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(milliSeconds);
System.out.println(formatter.format(calendar.getTime())); 

或者为了更准确,您可以使用 BigDecimal:

BigDecimal b = new BigDecimal("1474457086337977");
b=b.divide(new BigDecimal("1000"));
String x =b.toString();

【讨论】:

  • 您的毫秒数过大。输入如下:1474717464404
  • 哦,您的字符串以微秒为单位。然后将其转换为毫秒。
  • 时间单位是微秒而不是毫秒。
  • @No One,我的时间戳字符串不是毫秒而是微秒。
猜你喜欢
  • 1970-01-01
  • 2012-09-12
  • 1970-01-01
  • 2012-01-15
  • 1970-01-01
  • 2020-12-11
  • 1970-01-01
  • 1970-01-01
  • 2011-07-11
相关资源
最近更新 更多