如何在Java中使用尾随小数点将十进制时间戳转换为日期 [英] How to convert decimal timestamp to date in Java with trailing decimals
问题描述
我一直在尝试找出如何将时间戳转换为日期,但末尾带有小数点的方式,例如: 时间戳-C50204EC EC42EE92相当于2004年9月27日UTC.
时间戳格式包括前32位无符号秒,该字段跨度为136年,而32位小数字段解析232 皮秒.在时间戳格式中,当所有位均为零时,原始时期或时代0的基准日期为1900 UTC 1月1日0 h.
这是我到目前为止为代码编写的内容:
BigDecimal bi = new BigDecimal("1096255084000");
double decimal_timestamp = bi.doubleValue();
DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss.SSS");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(decimal_timestamp);
String date = formatter.format(calendar.getTime());
System.out.println(decimal_timestamp + " = " + date);
我的想法是日历可能无法实现,因此我必须从头开始,但是我不知道该怎么做.
java.time
使用说明中的示例:
时间戳-C50204EC EC42EE92相当于2004年9月27日 03:18:04.922896299 UTC.
Instant epoch = OffsetDateTime.of(1900, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant();
BigInteger timeStamp = new BigInteger("C50204ECEC42EE92", 16);
// To get the whole part and the fraction right, divide by 2^32
double secondsSince1900 = timeStamp.doubleValue() / 0x1_0000_0000L;
// Convert seconds to nanos by multiplying by 1 000 000 000
Instant converted = epoch.plusNanos(Math.round(secondsSince1900 * 1_000_000_000L));
System.out.println(converted);
输出为:
2004-09-27T03:18:04.922896384Z
关闭了85纳秒.更好的浮点算法可能会做得更好.由于原始时间戳的分辨率为2 ^ -32秒,是精度的一点点,这是不可避免的,这是Instant
的纳秒(10 ^ -9秒)分辨率的4倍以上.>
您尝试使用的Calendar
类总是设计不佳,现在已经过时了.取而代之的是,我按照其中的建议,我使用的是java.time,这是现代的Java日期和时间API.为了进行比较,Calendar
具有毫秒级的分辨率,因此充其量只会给您带来精度上的隐患.
更精确的数学
我不能让85纳秒成为现实.这是一个尽可能保持精度并提供预期结果的版本:
BigDecimal timeStamp = new BigDecimal(new BigInteger("C50204ECEC42EE92", 16));
// To get the whole part and the fraction right, divide by 2^32
BigDecimal bit32 = new BigDecimal(0x1_0000_0000L);
BigDecimal secondsSince1900 = timeStamp.divide(bit32);
// Convert seconds to nanos by multiplying by 1 000 000 000; round to long
long nanosSince1900 = secondsSince1900.multiply(new BigDecimal(TimeUnit.SECONDS.toNanos(1)))
.setScale(0, RoundingMode.HALF_UP)
.longValueExact();
Instant converted = epoch.plusNanos(nanosSince1900);
2004-09-27T03:18:04.922896300Z
1纳米太多吗?这是因为我在对setScale
的调用中使用了上舍入四舍五入.相反,如果我截断(使用RoundingMode.FLOOR
),则可以从解释中获得确切的结果.因此,我的版本不会比他们的版本失去更多的精度.
链接
Oracle教程:日期时间解释了如何使用java.time./p>
I have been trying to figure out how to convert a timestamp to a date but with the trailing decimals at the end, so for example: Timestamp - C50204EC EC42EE92 is equivalent to Sep 27, 2004 03:18:04.922896299 UTC.
The timestamp format includes the first 32-bit unsigned seconds as a field spanning 136 years and the 32-bit fraction field resolving 232 picoseconds. In the timestamp formats, the prime epoch, or base date of era 0, is 0 h 1 January 1900 UTC, when all bits are zero.
This is what I have written for my code so far:
BigDecimal bi = new BigDecimal("1096255084000");
double decimal_timestamp = bi.doubleValue();
DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss.SSS");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(decimal_timestamp);
String date = formatter.format(calendar.getTime());
System.out.println(decimal_timestamp + " = " + date);
My thought is that it is probably not possible with calendar, so I'll have to do it from scratch, but I have no idea how to go about doing that.
java.time
Using the example from the explanation:
Timestamp - C50204EC EC42EE92 is equivalent to Sep 27, 2004 03:18:04.922896299 UTC.
Instant epoch = OffsetDateTime.of(1900, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant();
BigInteger timeStamp = new BigInteger("C50204ECEC42EE92", 16);
// To get the whole part and the fraction right, divide by 2^32
double secondsSince1900 = timeStamp.doubleValue() / 0x1_0000_0000L;
// Convert seconds to nanos by multiplying by 1 000 000 000
Instant converted = epoch.plusNanos(Math.round(secondsSince1900 * 1_000_000_000L));
System.out.println(converted);
Output is:
2004-09-27T03:18:04.922896384Z
It’s off by 85 nanoseconds. Likely better floating-point arithmetic can do even better. Edit: A little loss of precision is unavoidable since the original time stamp has a resolution of 2^-32 seconds, which is more than 4 times as fine as the nanosecond (10^-9 second) resolution of Instant
.
The Calendar
class that you were trying to use was always poorly designed and is now long outdated. Instead I do as Amongalen suggested in a comment, I am using java.time, the modern Java date and time API. Edit: For comparison Calendar
has millisecond resolution, so would at best give you a substabtial loss of precision.
Edit: More precise math
I couldn’t let the 85 nanoseconds be. Here’s a version that preserves precision as far as possible and gives the expected result:
BigDecimal timeStamp = new BigDecimal(new BigInteger("C50204ECEC42EE92", 16));
// To get the whole part and the fraction right, divide by 2^32
BigDecimal bit32 = new BigDecimal(0x1_0000_0000L);
BigDecimal secondsSince1900 = timeStamp.divide(bit32);
// Convert seconds to nanos by multiplying by 1 000 000 000; round to long
long nanosSince1900 = secondsSince1900.multiply(new BigDecimal(TimeUnit.SECONDS.toNanos(1)))
.setScale(0, RoundingMode.HALF_UP)
.longValueExact();
Instant converted = epoch.plusNanos(nanosSince1900);
2004-09-27T03:18:04.922896300Z
1 nano too much? This is because I used half-up rounding in the call to setScale
. If instead I truncate (using RoundingMode.FLOOR
), I get the exact result from the explanation. So my version doesn’t lose more precision than theirs.
Link
Oracle tutorial: Date Time explaining how to use java.time.
这篇关于如何在Java中使用尾随小数点将十进制时间戳转换为日期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!