问题
How to convert epoch like 1413225446.92000
to ZonedDateTime
in java?
The code given expects long value hence this will throw NumberFormatException
for the value given above.
ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(dateInMillis)), ZoneId.of(TIME_ZONE_PST));
回答1:
Basil Bourque’s answer is a good one. Taking out the nanoseconds from the fractional part into an integer for nanoseconds may entail a pitfall or two. I suggest:
String dateInMillis = "1413225446.92000";
String[] secondsAndFraction = dateInMillis.split("\\.");
int nanos = 0;
if (secondsAndFraction.length > 1) { // there’s a fractional part
// extend fractional part to 9 digits to obtain nanoseconds
String nanosecondsString
= (secondsAndFraction[1] + "000000000").substring(0, 9);
nanos = Integer.parseInt(nanosecondsString);
// if the double number was negative, the nanos must be too
if (dateInMillis.startsWith("-")) {
nanos = -nanos;
}
}
ZonedDateTime zdt = Instant
.ofEpochSecond(Long.parseLong(secondsAndFraction[0]), nanos)
.atZone(ZoneId.of("Asia/Manila"));
System.out.println(zdt);
This prints
2014-10-14T02:37:26.920+08:00[Asia/Manila]
We don’t need 64 bits for the nanoseconds, so I am just using an int
.
Assumption: I have assumed that your string contains a floating-point number and that it may be signed, for example -1.50
would mean one and a half seconds before the epoch. If one day your epoch time comes in scientific notation (1.41322544692E9), the above will not work.
Please substitute your desired time zone in the region/city format if it didn’t happen to be Asia/Manila, for example America/Vancouver, America/Los_Angeles or Pacific/Pitcairn. Avoid three letter abbreviations like PST, they are ambiguous and often not true time zones.
回答2:
Split the number into a pair of 64-bit long integers:
- Number of whole seconds since the epoch reference date of first moment of 1970 in UTC
- A number of nanoseconds for the fractional second
Pass those numbers to the factory method Instant.ofEpochSecond(long epochSecond, long nanoAdjustment)
With an Instant
in hand, proceed to assign a time zone to get a ZonedDateTime
.
ZoneId z = ZoneId.of( "America/Los_Angeles" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;
回答3:
Expanding on Basil's and Ole's answers here, for the special case of a negative timestamp i.e. before epoch. Is that even possible? Here's what Jon Skeet writes in "All about java.util.Date":
The Date class uses “milliseconds since the Unix epoch” – that’s the value returned by getTime(), and set by either the Date(long) constructor or the setTime() method. As the moon walk occurred before the Unix epoch, the value is negative: it’s actually -14159020000.
The only real difference between Ole's answer (besides a few extra asserts) is that here, we do not reverse the sign on nanos if the date string starts with a negative sign. The reason for this is, when passing the nanos to the Instant constructor, that is an adjustment, so if we send the nanos as a negative, it will actually adjust the seconds back, and thus the entire ZonedDateTime value is off by the nanos.
This is from the JavaDoc, note the interesting behavior:
This method allows an arbitrary number of nanoseconds to be passed in. The factory will alter the values of the second and nanosecond in order to ensure that the stored nanosecond is in the range 0 to 999,999,999. For example, the following will result in the exactly the same instant:
Instant.ofEpochSecond(3, 1);
Instant.ofEpochSecond(4,-999_999_999);
Instant.ofEpochSecond(2, 1000_000_001);
So the 2nd argument, nanos, we are not setting the value, it is an adjustment. So just the same as for a positive timestamp (after epoch), we want to send in the actual nanos.
Taking Ole's code as a base and adding the above mentioned changes:
String strDateZoned = "Jul 20 1969 21:56:20.599 CDT"; // yes, should use America/Chicago here as Ole points out
DateTimeFormatter dtfFormatter = DateTimeFormatter.ofPattern("MMM dd yyyy HH:mm:ss.SSS zzz");
ZonedDateTime originalZoned = ZonedDateTime.parse(strDateZoned, dtfFormatter);
long epochSeconds = originalZoned.toInstant().getEpochSecond();
int nanoSeconds = originalZoned.toInstant().getNano();
String dateInMillis = epochSeconds + "." + nanoSeconds;
String[] secondsAndFraction = dateInMillis.split("\\.");
int nanos = 0;
if (secondsAndFraction.length > 1) { // there’s a fractional part
// extend fractional part to 9 digits to obtain nanoseconds
String nanosecondsString
= (secondsAndFraction[1] + "000000000").substring(0, 9);
nanos = Integer.parseInt(nanosecondsString);
}
ZonedDateTime zdt = Instant
.ofEpochSecond(Long.parseLong(secondsAndFraction[0]), nanos)
.atZone(ZoneId.of("America/Chicago"));
String formattedZdt = dtfFormatter.format(zdt);
System.out.println("zoneDateTime expected = " + strDateZoned);
System.out.println("zoneDateTime from millis = " + formattedZdt);
assertEquals("date in millis is wrong", "-14159020.599000000", dateInMillis);
assertEquals("date doesn't match expected",strDateZoned, dtfFormatter.format(zdt));
Output from code:
zoneDateTime expected = Jul 20 1969 21:56:20.599 CDT
zoneDateTime from millis = Jul 20 1969 21:56:20.599 CDT
If we reverse the sign on nanos for the case where the seconds part is negative, we can see the difference in the formatted ZonedDateTime:
org.junit.ComparisonFailure: date doesn't match expected
Expected :Jul 20 1969 21:56:20.599 CDT
Actual :Jul 20 1969 21:56:19.401 CDT
P.S. A few more thoughts from the 'All About Dates' post on what Jon Skeet calls "leniency", and elsewhere I have seen called 'normalization' which is perhaps due to POSIX influences:
It’s lenient for no obvious reason: “In all cases, arguments given to methods for these purposes need not fall within the indicated ranges; for example, a date may be specified as January 32 and is interpreted as meaning February 1.” How often is that useful?
来源:https://stackoverflow.com/questions/47975195/converting-epoch-to-zoneddatetime-in-java