Can't get local and utc Instant - java

I need to get local time and utc time in seconds. I read some posts in StackOverflow and found some solution, which is correct as mentioned:
Instant time = Instant.now();
OffsetDateTime utc = time.atOffset(ZoneOffset.UTC);
int utcTime = (int) utc.toEpochSecond();
int localTime = (int) time.getEpochSecond();
System.out.println("utc " + utcTime + " local " + localTime);
But result is not what I expected. It is utc time. The output:
utc 1593762925
local 1593762925
After debugging I found that Instant.now() is already utc. I can't find how to get time in current time zone, i.e. my system zone.
I found some solution in API but got error:
OffsetDateTime utc = time.atOffset(ZoneOffset.of(ZoneOffset.systemDefault().getId()));
Exception in thread "main" java.time.DateTimeException: Invalid ID for ZoneOffset, invalid format: Europe/Astrakhan
at java.base/java.time.ZoneOffset.of(ZoneOffset.java:241)
UPD: My question is How to get current time in seconds in local time zone and in UTC? I.e. the number of seconds since 1970-01-01T00:00:00 GMT+4 and 1970-01-01T00:00:00 GMT+0
UPD2: I have some device that needs response with utc time in seconds from 1970 and sender local time in seconds. Why? I don't know. It is black box for me.

I think you need to take the Instant, create a ZonedDateTime (OffsetDateTime may be suitable as well) by applying a ZoneId.of("UTC") and then take that ZonedDateTime and use it to shift the locale:
public static void main(String[] args) {
Instant now = Instant.now();
ZonedDateTime utcZdt = now.atZone(ZoneId.of("UTC"));
ZonedDateTime localZdt = utcZdt.withZoneSameLocal(ZoneId.systemDefault());
System.out.println(utcZdt.toEpochSecond() + " <== " + utcZdt);
System.out.println(localZdt.toEpochSecond() + " <== " + localZdt);
}
On my system, this outputs
1593765852 <== 2020-07-03T08:44:12.070Z[UTC]
1593758652 <== 2020-07-03T08:44:12.070+02:00[Europe/Berlin]
Two hours difference are affecting the sixth digit of the epoch seconds.

Found solution here:
TimeZone tz = TimeZone.getDefault();
Instant instant = Instant.now();
int offsetFromUtc = tz.getOffset(instant.getEpochSecond()) / 1000;
Or as wrote #deHaar:
int offsetFromUtc = Instant.now().atZone(ZoneOffset.systemDefault()).getOffset().getTotalSeconds();
It gives 14400 s that is correct for my timezone. I can add this to utc.

TL;DR: Your expectations are wrong. Your results are correct.
The results you are getting is the count of seconds since the Unix/Java epoch. This is also known as a Unix timestamp. The epoch is one point in time and is independent of time zone. It’s the same point in time in all time zones. Therefore the count of seconds is the same in all time zones too.
The epoch is 1970-01-01T00:00:00 GMT+0. In some time zones (yours?) this point in time would be given as 1970-01-01T04:00:00 GMT+4. Please note the time of day is 4 AM, not 00:00.
In case someone else was wrong
UPD2: I have some device that needs response with utc time in seconds
from 1970 and sender local time in seconds. Why? I don't know. It is
black box for me.
It’s a possibility, of course, that the designers of that device misunderstood and probably inadvertently invented their own way of counting seconds. It doesn’t sound very likely, though, so I would at least double-check and triple-check this piece of information. If it turns out to be correct, I would do something like:
LocalDateTime misunderstoodEpoch = LocalDate.EPOCH.atStartOfDay();
ZoneId zone = ZoneId.of("Europe/Astrakhan");
long secondsLong = ChronoUnit.SECONDS
.between(misunderstoodEpoch.atZone(zone), ZonedDateTime.now(zone));
int seconds = Math.toIntExact(secondsLong);
System.out.println(seconds);
Output when running just now:
1593881344
Also if your device insists on using an int for your seconds, at least use Math.toIntExact() for the conversion. This will throw an exception in case of int overflow so that in January 2038 (just 17 years 6 months from now) you and your users will be made aware of the fact that your device is no longer working.

Related

Why is the time difference off when subtracting the same date from two different time zones?

The following is an example that attempts to calculate the difference in hours between time zones.
import java.util.*;
import java.text.*;
public class ForPosting
{
public static void main (String[] args)
{
try {
String myDateString = "07-13-2021 11:00:00";
SimpleDateFormat localDateTime = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss");
SimpleDateFormat utcDateTime = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss");
localDateTime.setTimeZone(TimeZone.getTimeZone("EET"));
System.out.println(localDateTime.parse(myDateString));
utcDateTime.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(utcDateTime.parse(myDateString));
float diff = ((localDateTime.parse(myDateString).getTime() - utcDateTime.parse(myDateString).getTime() ) / 3600000);
System.out.println("EET & UTC Time Difference : " + diff);
localDateTime.setTimeZone(TimeZone.getDefault());
System.out.println(localDateTime.parse(myDateString));
utcDateTime.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(utcDateTime.parse(myDateString));
diff = ((localDateTime.parse(myDateString).getTime() - utcDateTime.parse(myDateString).getTime() ) / 3600000);
System.out.println("Default & UTC Time Difference : " + diff);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
The above code generates the following output.
The Default & UTC Time Difference : 4.0 was confirmed by checking the current dates and it confirms there is a 4 hour difference.
Doing the same check for EET & UTC is different.
EET & UTC Time Difference : -3.0
The output shows a 3 hour difference yet the following shows there is only 2.
Why does the EET & UTC actual time show 2 hour difference yet the Java code shows a 3 hour difference?
EET is not a time zone
EET is a common abbreviation of some 20 time zones in Europe and Northern Africa that are at offset +02:00 from UTC during standard time. Many, not all of them are using summer time (daylight saving time) abbreviated EEST for Eastern European Summer Time and hence are at offset +03:00 during the 7 “summer” months. So what you get when asking for EET, I can’t tell. So don’t do that. Use a proper time zone ID like Europe/Uzhgorod, always in region/city format. So that you know what you get.
So to answer your question:
Why does the EET & UTC actual time show 2 hour difference yet the Java
code shows a 3 hour difference?
Because they interpret EET differently. Both interpretations are fully valid and none is authoritative.
This is nothing special for EET. Many two, three, four and five letter time zone abbreviations are in common use. Very many of them are ambiguous, like EET. And very many of them are not time zones. It may be that there are situations where you can use one for informal communication to the user, for example just to distinguish between EET and EEST in a situation where the user knows well enough what each of those means in the context. You certainly cannot rely on them for defining a time zone in your program.
java.time
I recommend that you use java.time, the modern Java date and time API, for your date and time work. To find the difference between some time zone and UTC at a moment in time:
ZoneId zone = ZoneId.of("Europe/Uzhgorod");
Instant when = Instant.now();
ZoneOffset differenceFromUtc = when.atZone(zone).getOffset();
System.out.format("%s & UTC Time Difference : %s%n", zone, differenceFromUtc);
Output when running just now:
Europe/Uzhgorod & UTC Time Difference : +03:00
It’s summer now in Europe/Uzhgorod, and the difference of 3 hours comes from that time zone using summer time. Try with an instant in winter:
Instant when = Instant.parse("2022-01-01T00:00:00Z");
Europe/Uzhgorod & UTC Time Difference : +02:00
As I said, some Eastern European time zones do not use summer time. Try with one of those:
ZoneId zone = ZoneId.of("Europe/Kaliningrad");
Instant when = Instant.now();
Europe/Kaliningrad & UTC Time Difference : +02:00
Minor comments to your code
While UTC works for defining a time zone, the canonical ID is Etc/UTC. Etc is a pseudo-region for time zones that don’t belong in a well-defined geographical region including Etc/UTC, Etc/GMT and also the IDs for mere whole-hour offsets like Etc/GMT-2 (for offset +02:00, the sign is inverted).
Assigning the result of your division to a float does not help you. You are dividing a long values by an int value, and the result is always truncated to an long number. When assigning to a float you will still always have a whole number. If you wanted a fraction as result, you would need to convert at least one of the operands to float (or double) before dividing.
And a funny detail, your code measures the difference in hours between when it’s 11 o’clock in EET and 11 o’clock in UTC. You get a result exactly because there are 3 hours between those two moments. What I am not so pleased about is that you are not measuring the difference at a well-defined point in time. Instead in my code I am choosing one point in time and finding the difference at that point.
Link
Oracle tutorial: Date Time explaining how to use java.time.

Converting a separate date, hours, minutes, seconds, and AM/PM to a java.time.Instant

From the front end I am receiving a separate LocalDate (variable name is date), along with separate Integers for hours, minutes, seconds, and an "AM" or "PM" String, and I need to combine these into a java.time.Instant object to store in the database. I tried to construct a LocalTime as follows, adding 12 hours if this is a PM time and then constructing an Instant:
LocalTime time = LocalTime.of("pm".equals(amPm) ? hours + 12: hours, minutes, seconds);
Instant instant = date.atTime(time).toInstant(ZoneOffset.UTC);
But when I store and reload the page, though the date is always intact, the time is always being changed. If I set the date to 1/29/1900 and the time to 07:01:01 AM, the Instant I am creating and storing has the value: 1900-01-29T07:01:01Z when I debug, which appears correct, but when the page reloads, the time says 02:01:01 AM, and that is the time that is stored in the database.
Am I constructing the time or the instant incorrectly?
There’s hardly any doubt that your unexpected observations are due to one or more time zone issues.
So the first thing you need to do is make sure you know which time zones are involved.
Which time zone is your front end using for sending date and time to you?
Which time zone is your database using for storing the date and time and displaying them to you when you check them? (UTC would be recommended for storing the times.)
Once you know this you can check:
Is the conversion from 1/29/1900 07:01:01 AM from the front end in some time zone to an Instant of 1900-01-29T07:01:01Z correct? The Instant displays its time in UTC (denoted by the trailing Z).
Is the conversion from the Instant to 02:01:01 AM in the database time zone correct?
Is the time being fetched correctly from the database? I am assuming you are fetching it back into Java?
Is the time you’ve got in Java being converted correctly to 02:01:01 AM on the front end? Again I am assuming that on page reload your are displaying the time fetched from the database, but I don’t think you have told us, so I could be wrong.
To answer your question:
Am I constructing the time or the instant incorrectly?
It depends; it’s certainly possible.
Your construction of the time is assuming that pm is always in lower case and that 12 o’clock (midnight or noon) is given as 0. On one hand I find both assumptions more or less unlikely, on the other hand they cannot account for the discrepancy of 5 hours that you observed. 12 would conventionally be given as 12 (not 0) on a 12 hour clock. And your question gives PM in upper case.
Your construction of the Instant assumes that the front end sent the time in UTC. To me this sounds unlikely too, and it may be the reason or one of the reasons why you observed an incorrect time being displayed back after page reoload.
Code example
In the following snippet I am making the opposite assumptions: 12 is given as 12, AM/PM may be in any case, and the front end time zone is America/New_York. It’s probably way off, but there may be a detail that you can pick and use for your purpose.
DateTimeFormatter timeFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive() // Accept all of am, AM, aM and Am
.appendPattern("h:m:sa")
.toFormatter(Locale.US);
ZoneId zone = ZoneId.of("America/New_York");
LocalDate date = LocalDate.of(1900, Month.JANUARY, 29);
int hours = 7;
int minutes = 1;
int seconds = 1;
String amPm = "AM";
String constructedTimeString
= "" + hours + ':' + minutes + ':' + seconds + amPm;
LocalTime time = LocalTime.parse(constructedTimeString, timeFormatter);
Instant instant = date.atTime(time).atZone(zone).toInstant();
System.out.println(instant);
Output is:
1900-01-29T12:01:01Z
Geeky section: avoiding formatting time into a string and parsing it back
I couldn’t help thinking about whether it would be possible to have java.time parse the AM/PM string without having to construct a string for the time of day and parse it. It is possible, but we need to use the low-level TemporalAccessor interface, which is otherwise usually unnecessary.
DateTimeFormatter amPmFormatter = new DateTimeFormatterBuilder()
.parseCaseInsensitive() // Accept all of am, AM, aM and Am
.appendPattern("a")
.toFormatter(Locale.US);
int hours = 7;
int minutes = 1;
int seconds = 1;
String amPm = "AM";
TemporalAccessor parsedAmPm = amPmFormatter.parse(amPm);
LocalTime time = LocalTime.of(0, minutes, seconds)
.with(ChronoField.AMPM_OF_DAY, parsedAmPm.get(ChronoField.AMPM_OF_DAY))
.with(ChronoField.CLOCK_HOUR_OF_AMPM, hours);
System.out.println(time);
07:01:01
Construction of the Instant proceeds as before.

Java millis time with hourly resolution

How do I get java time millis in UTC ignoring the minutes and seconds.
For instance :
If it is October 10 2019, 1:10:59 AM , it should get the Time or millis for
October 10 2019, 1 AM.
Summary:
Instant
.now()
.truncatedTo(
ChronoUnit.HOURS
)
.toEpochMilli()
1570600800000
java.time, the modern Java date and time API has got exactly the method you need: many of the classes have a truncatedTo method for needs like yours.
Instant now = Instant.now();
System.out.println("Rough milliseconds: " + now.toEpochMilli());
Instant currentWholeHour = now.truncatedTo(ChronoUnit.HOURS);
System.out.println("Milliseconds ignoring minutes and seconds: "
+ currentWholeHour.toEpochMilli());
When running this snippet just now the output was:
Rough milliseconds: 1570604053787
Milliseconds ignoring minutes and seconds: 1570600800000
I know very well that the first line is what you asked not to have. I only included it for you to see the difference.
The truncation happens in UTC. If you are in a time zone whose offset is not a whole number of hours from UTC, the results may not be as you had expected. Examples of such time zones include Asia/Kathmandu, America/St_Johns some of the year also Australia/Lord_Howe.
Link: Oracle tutorial: Date Time
You can use LocalDate#atTime:
LocalDate.now().atTime(LocalDateTime.now().getHour(), 0, 0);
This will give you current date with hour and minutes and seconds set to 0.
And to get milliseconds in UTC:
LocalDate.now().atTime(LocalDateTime.now().getHour(), 0, 0).toInstant(ZoneOffset.UTC).toEpochMilli();
Jon Skeet notices, that calling now might give unexpected results in corner cases. To be sure, we can call it once and then convert it to LocalDate with mentioned solution:
var currentTime = LocalDateTime.now();
var currentDate = currentTime.toLocalDate();
Or the other way around - get LocalDate first and use LocalDate#atStartOfDay.
Given that you're interested in UTC milliseconds, and there are a whole number of milliseconds per hour, you can do this with simple arithmetic. For most calendrical computations I really wouldn't recommend that, but in this case I think it's the simplest approach. Something like this:
private static final long MILLISECONDS_PER_HOUR = TimeUnit.HOURS.toMillis(1);
// Injecting a clock makes the method testable. You can use Clock.systemUTC()
// for the system clock.
public static long truncateMillisToHour(Clock clock) {
long millisSinceEpoch = clock.millis();
// Truncate to the nearest hour
long hoursSinceEpoch = millisSinceEpoch / MILLISECONDS_PER_HOUR;
// Then multiply up again
return hoursSinceEpoch * MILLISECONDS_PER_HOUR;
}
Note that if the clock is for before the epoch, this will round up to the nearest hour, but if you're taking the genuine "current time" then that's unlikely to be a problem.
(I wrote this answer before seeing Ole V.V.'s answer with truncatedTo, which is a very nice approach.)

Convert UTC java.sql.Time to java.time.localtime with correct DST

I have a problem to convert a java.sql.Time (UTC) which is fetched from a database to a java.time.LocalTime (GMT+1 DST). It is always missing the DST hour. So like a Time of 03:00 is only converted to a LocalTime of 04:00 instead of 05:00.
//Saved UTC time in DB: 03:00
LocalTime.ofInstant(Instant.ofEpochMilli(sqlTime.getTime()), ZoneId.of("Europe/Berlin"));
=> 04:00 //expected 05:00
I guess the problem is that java.sql.Time saves the time with a default date of 1970-01-01 and in 1970 there was no DST in Germany. But of course the time should be shown for today and not for 1970.
So how can I get the correct time for this example?
Assuming that you are using at least JDBC 4.2, you should be able to retrieve a LocalTime from your result set:
LocalTime timeInUtc = yourResultSet.getObject(yourTimeColumn, LocalTime.class);
Then there’s no need bother with the outdated and poorly designed java.sql.Time class. The time you get will still be in UTC, of course. Here’s how to convert:
LocalTime timeInUtc = LocalTime.of(3, 0);
ZoneId zone = ZoneId.of("Europe/Berlin");
LocalTime timeInGermany = OffsetDateTime.now(ZoneOffset.UTC)
.with(timeInUtc)
.atZoneSameInstant(zone)
.toLocalTime();
System.out.println("Zeit heute in Deutschland: " + timeInGermany);
When I ran the code today I got the output you expected:
Zeit heute in Deutschland: 05:00
Edit: If there’s no way you can avoid getting a java.sql.Time, convert it to LocalTime first. Assuming that the Time is in UTC and we don’t want to rely on a fragile JVM time zone setting for conversion, you are correct that we need the getTime method:
Time sqlTimeInUtc = // Get from database
LocalTime timeInUtc
= LocalTime.MIDNIGHT.plus(sqlTimeInUtc.getTime(), ChronoUnit.MILLIS);
If you could rely on the JVM time zone setting also being UTC, the following would be nicer:
LocalTime timeInUtc = sqlTimeInUtc.toLocalTime();
In both cases the rest is as above.
In all cases there are some corner cases around the question whether you want “today in UTC” or “today in Europe/Berlin time zone” when you say “the time should be shown for today”. There’s also a corner case if the time is between 2 and 3 AM and today is the last Sunday in March, where the clocks are turned forward from 2 to 3 to initiate summer time (DST) in Germany. Please think these corner cases through and decide what you want.
By the way your diagnosis is completely correct: Time.getTime returns the time of day on Jan 1, 1970, so when you feed this into an Instant, you are converting the time of day on this date, that is, without summer time.
As far as I understand it your question is: Given a time in UTC convert it to local time according to the current time offset. This time offset is different depending of whether DST is in effect or not.
A possible approach is to determine the current offset using TimeZone:
TimeZone tz = TimeZone.getTimeZone("Europe/Berlin");
int timeZoneOffsetMillis = tz.getOffset(new Date().getTime());
Now timeZoneOffsetMillis contains the number of milliseconds you have to add to your UTC time to get local time.
You can get a LocalTime like this:
LocalTime localTime = LocalTime.ofNanoOfDay((sqlTime.getTime() + timeZoneOffsetMillis) * 1000000L);
If your time is only accurate to seconds instead of nanoseconds anyway you might want to use LocalTime.ofSecondOfDay.

How do I get POSIX time (UTC) from a serial value local date-time with the Java 8 DateTime API

I have a timestamp that is similar to POSIX Time with the sole exception that it is not reckoned in UTC.
Instead, it is the number of milliseconds that have elapsed since midnight, Jan 1 1970 in a particular local time zone. In order to make this value into an Instant, I must first know its offset (in milliseconds) to UTC/GMT.
So the problem is this: knowing the local time zone id, eg. "America/Chicago" and a count of milliseconds since the local Epoch, how do I make an Instant (which must be constructed with milliseconds since the POSIX Epoch)?
It does not seem that any of the java.time API constructors accept a millisecond parameter in a local Epoch.
I have a solution in which I first convert the local millisecond date-time into the local Gregorian calendar date-time (from which I can then construct a LocalDateTime and get the offset to UTC), but this seems like a lot of churning for what seems like it ought to be pretty simple.
Calculate the instant of your modified epoch:
ZoneId tz = ZoneId.of("America/Chicago");
Instant modifiedEpoch = ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, tz).toInstant();
Then add your millis:
Instant instant = modifiedEpoch.plusMillis(millis);
Wrong Way To Track Date-Time
First I have to say this use of count-from-epoch integers for date-time values in various time zones rather than in UTC is a really, really bad idea. I’ve seen some bad ways to handle date-time including inventing one or two bad ways myself. But this one is the worst. Whoever thought this up should be sentenced to a year of daily readings of StackOverflow answers marked "java", "date", and "Jon Skeet".
Using count-from-epoch to handle date-time in your app code is like using bit arrays to handle text. We have classes/interfaces such as CharSequence, String, StringBuilder, Printer, Reader and so on to handle the nitty-gritty complicated details of text, characters, character encoding, collations, and such for us to make writing apps easier. Imagine trying to debug, troubleshoot, and log textual data as bit arrays. Crazy, right? Trying to debug, troubleshoot, and log date-time data as long integers is crazy too.
Ditto for date-time, where we had Joda-Time and now have its successor java.time (Tutorial) built into Java 8 and later.
Secondly, implicitly adjusting a count-from-epoch into a time zone and then losing that fact makes a bad practice even worse.
Fix
The way to fix this is to get that count-from-epoch in some arbitrary time zone translated into a local date and local time where local means the wall-clock time as seen by people in than time zone.
With that local date-time in hand, we create a date-time object that has the assigned time zone, a ZonedDateTime. A ZonedDateTime is basically an Instant (a point on the timeline in UTC) plus a ZoneId (a time zone).
Since the author of the Question failed to supply any sample data, let's create a value in this screwy fashion. Get the current moment in Chicago time zone. Get a legitimate count-from-epoch, adjusting from nanosecond resolution to millisecond. Then arbitrarily add/subtract the offset from UTC for that time zone.
In this example we use the time zone America/Chicago. It's offset for our sample, with Daylight Saving Time, is -05:00. In milliseconds, 5 * 60 * 60 * 1,000 = 18,000,000.
// First, create sample data, a count-from-epoch but not in UTC, instead adjusted for the time zone’s offset.
ZoneId zoneId = ZoneId.of( "America/Chicago" );
// 2015-09-19T12:34:56.000-05:00[America/Chicago]
ZonedDateTime zdtTemp = ZonedDateTime.of( 2015 , 9 , 19 , 12 , 34 , 56 , 0 , zoneId );
long millisecondsFromEpoch = zdtTemp.toInstant().toEpochMilli(); // Loosing data, goin from nanosecond
long offsetInMillisecondsForChicagoInDaylightSavingTime = 18_000_000L; // Offset of `-05:00` is in milliseconds, 5 * 60 * 60 * 1,000 = 18,000,000.
long input = ( millisecondsFromEpoch - offsetInMillisecondsForChicagoInDaylightSavingTime );
Dump to console.
System.out.println( "zoneId : " + zoneId );
System.out.println( "zdtTemp : " + zdtTemp );
System.out.println( "millisecondsFromEpoch : " + millisecondsFromEpoch );
System.out.println( "offsetInMillisecondsForChicagoInDaylightSavingTime : " + offsetInMillisecondsForChicagoInDaylightSavingTime );
System.out.println( "input : " + input );
Now, do the job. Take that screwy input number, pretending it is in UTC even though we know it is not, to produce an Instant. From the Instant, get a LocalDateTime. Now push that LocalDateTime into a time zone to get what we finally want, a ZonedDateTime.
// With example data in hand, proceed to convert to a valid date-time object.
Instant instantPretendingToBeInUtcButNotReally = Instant.ofEpochMilli( input );
LocalDateTime localDateTimeOfPretendInstant = LocalDateTime.ofInstant( instantPretendingToBeInUtcButNotReally , ZoneOffset.UTC );
ZonedDateTime zdt = localDateTimeOfPretendInstant.atZone( zoneId );
Dump to console.
System.out.println( "instantPretendingToBeInUtcButNotReally : " + instantPretendingToBeInUtcButNotReally );
System.out.println( "localDateTimeOfPretendInstant : " + localDateTimeOfPretendInstant );
System.out.println( "zdt : " + zdt );
When run.
zoneId : America/Chicago
zdtTemp : 2015-09-19T12:34:56-05:00[America/Chicago]
millisecondsFromEpoch : 1442684096000
offsetInMillisecondsForChicagoInDaylightSavingTime : 18000000
input : 1442666096000
instantPretendingToBeInUtcButNotReally : 2015-09-19T12:34:56Z
localDateTimeOfPretendInstant : 2015-09-19T12:34:56
zdt : 2015-09-19T12:34:56-05:00[America/Chicago]
CAVEAT I did this in rush. Please comment or fix any errors.
Because chronological time units are interconvertible, at first blush it might seem that you could have a local date-time in the following double precision format:
57272.5
where...
57272 is the day number reckoned from the modified Julian day number epoch (Nov 17, 1858).
0.5 is local time expressed as a fraction of one day, e.g. 0.5 = 12:00 noon local time.
There is nothing wrong with expressing a local date-time in this manner. However, numbers are numbers and so instead of a count of days since the modified Julian day number epoch, one can convert this to a count of milliseconds since the POSIX epoch (seemingly) very simply as:
localMillis = ( dayNumber - POSIX_EPOCH_AS_MJD) / (86400.0 * 1000.0);
This is where the notion of "milliseconds since the local epoch" has come from in this case. The mistake here, though, is that there IS NOT a simple one-to-one correspondence between POSIX Epoch millis and "local" epoch millis (the definition of POSIX Time requires that the count be milliseconds from the Epoch in UTC). This is because the local number contains one or more local offsets from UTC that are not guaranteed to be historically consistent (depending on legislation, etc).
These "local" millis can be used as a local time stamp, but they need to be adjusted for historical daylight savings and time zone offsets with the same care that any other time stamp should be. So why use them? I can't think of a reason. Having a time stamp in this format was a mistake.
The solution to this problem that has been employed:
Convert the "local millis" to a modified Julian day number with the local time expressed as a fraction of one day
Transform the modified Julian day number to a local Gregorian calendar date and time (algorithm adapted from "Astrological Algorithms", 2nd Ed. by J. Meeus).
Create a LocalDateTime instance from the local calendar date-time obtained above
Combine the LocalDateTime with the local ZoneId to contruct a ZonedDateTime, from which an Instant is obtained
POSIX time as UTC milliseconds from the POSIX Epoch is obtained from the Instant
A code example for this procedure follows:
public static long epochMillisFromLocalMillis(ZoneId tz, long millis) {
double dayNum = (millis / (86400.0 * 1000.0)) + POSIX_EPOCH_AS_MJD;
int[] ymd_hmsm = toVectorFromDayNumber(dayNum);
LocalDateTime ldt = LocalDateTime.of (
ymd_hmsm[MJD.YEAR],
ymd_hmsm[MJD.MONTH],
ymd_hmsm[MJD.DAY],
ymd_hmsm[MJD.HOURS],
ymd_hmsm[MJD.MINUTES],
ymd_hmsm[MJD.SECONDS],
ymd_hmsm[MJD.MILLIS] * 1000000);
long utcMillis = ZonedDateTime
.of(ldt, tz)
.toInstant()
.toEpochMilli();
return utcMillis;
}
Thanks to Basil Bourque and assylias for their insights on this peculiar problem.

Categories