In my application I retrieve from a webservice an unix timestamp (between 0 and 15minutes in the future) and I display a countdown to that time in the form of XXm-XXs.
So I simply do System.currentTimeMillis() - timestamp and I convert the result in a human readable date.
Everything works fine but it seems that with certain timezones, my timer is 30 minutes off, because System.currentTimeMillis() return a value 1800000 millis lower than expected because Calendar returns wrong value of minutes when I request minutes with it.
The timezone is the GMT+8 of Kuala Lumpur (Malasya). Using another GMT+8 timezone works normally.
Example:
long till = requestTimeFromWebService()
long now=System.currentTimeMillis();
long remaining_time = till - now;
Calendar c=Calendar.getInstance();
c.setTimeInMillis(remaining_time);
int minutes=c.get(Calendar.MINUTE);
System.out.println(""+minutes+"m");
int seconds=c.get(Calendar.SECOND);
System.out.println(""+seconds+"s");
With this code System.out.println(""+minutes+"m"); prints (e.g) 5m if GMT+2 Rome Timezone is set and 35m if GMT+8 Kuala Lumpur Timezone is set.
Is this a known bug?
I found this: http://www.objectdb.com/database/forum/363 that seems to confirm an issue.
I also found this: https://en.wikipedia.org/wiki/Time_in_Malaysia
Blockquote At 2330 hrs local time of 31 December 1981, people in Peninsular Malaysia adjusted their clocks and watches ahead by 30 minutes to become 00:00 hours local time of 1 January 1982, to match the time in use in East Malaysia, which is UTC+08:00.
This could explain where the bug comes off.
Any advice?
A date-time != span-of-time
You are abusing the date-time class java.util.Calendar (or java.util.Date) to inappropriately track a span of time. That class tracks time as a count of milliseconds since the epoch of start of 1970 in UTC (1970-01-01T00:00:00Z) plus an assigned time zone.
So when you instantiate with a count of milliseconds of 5 minutes, you are actually creating a date-time of 5 minutes after start of 1970, 1970-01-01T00:05:00Z for a java.util.Date and adding a time zone for java.util.Calendar.
When you applied a time zone for Malaysia you end up getting the old-style Malaysia time rules for 1970, not today’s post-1981 Malaysia rules.
So, no bugs, just a misuse of features.
Lesson learned: Do not use a date-time value to represent a span-of-time.
java.time
Another problem: You are using the notoriously troublesome old legacy date-time classes, now supplanted by the java.time classes.
If by “unix timestamp” you meant a count of milliseconds from an epoch of 1970 in UTC such as 1_473_738_754_764L, then use the Instant class. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds.
First, we simulate some input data as described in Question as being in the future up to 15 minutes.
Instant instantNow = Instant.now ();
long seconds = TimeUnit.MINUTES.toSeconds ( 10 );
String input = Long.toString ( instantNow.plusSeconds ( seconds ).toEpochMilli () ); // 10 minutes in the future.
To process that String input, we convert to a long primitive value, and feed it to a Instant factory method.
Instant instantLater = Instant.ofEpochMilli ( Long.valueOf ( input ) );
Span-of-time
To capture the elapsed time, use the Duration class.
Duration duration = Duration.between ( instantNow , instantLater );
System.out.println ( "instantNow: " + instantNow + " | instantLater: " + instantLater + " | duration: " + duration );
When run. Note the standard ISO 8601 format for a duration PnYnMnDTnHnMnS where P marks the beginning and the T separates the years-months-days portion from the hours-minutes-seconds portion. So PT10M is “ten minutes”. Always use this format for textual representation of elapsed time rather than ambiguous clock-style (HH:MM:SS). The Duration and Period classes in java.time can parse and generate such strings directly with no need to specify a formatting pattern.
instantNow: 2016-09-13T19:16:33.913Z | instantLater: 2016-09-13T19:26:33.913Z | duration: PT10M
Note that none of the above code cared about time zones. All the values were in UTC. Much of your business logic, data storage, and data exchange should be in UTC. Only use zoned values where necessary or for presentation to the user.
Zoned
Your Questions asked about zoned values for Rome and for Malaysia. Apply a ZoneId to get a ZonedDateTime. Specify a proper time zone name. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).
ZoneId zMontreal = ZoneId.of ( "America/Montreal" );
ZoneId zRome = ZoneId.of ( "Europe/Rome" );
ZoneId zKualaLumpur = ZoneId.of ( "Asia/Kuala_Lumpur" );
ZonedDateTime zdtMontreal = instantNow.atZone ( zMontreal );
ZonedDateTime zdtRome = instantNow.atZone ( zRome );
ZonedDateTime zdtKualaLumpur = instantNow.atZone ( zKualaLumpur );
System.out.println ( "instantNow: " + instantNow + " | zdtMontreal: " + zdtMontreal + " | zdtRome: " + zdtRome + " | zdtKualaLumpur: " + zdtKualaLumpur );
instantNow: 2016-09-13T20:23:34.280Z | zdtMontreal: 2016-09-13T16:23:34.280-04:00[America/Montreal] | zdtRome: 2016-09-13T22:23:34.280+02:00[Europe/Rome] | zdtKualaLumpur: 2016-09-14T04:23:34.280+08:00[Asia/Kuala_Lumpur]
While I still don't know why the original code doesn't work, I can resolve my specific problem simply using
Calendar c=Calendar.getInstance(TimeZone.getTimeZone("GMT"));
instead than
Calendar c=Calendar.getInstance();
So I can always compare timestamp with the UTC TimeZone that is what I'm interested in.
Btw, Calendar should work in my case even setting the locale timezone (that is what happens when no argument is passed to getInstance()), and it does for most of the timezones, but apparently not for everyone.
Related
We received time as hour =11, minutes=29,seconds=54,milliseonds=999 along with timezone information.
How to convert this time to unix epoch milliseconds with no date part.
I have tried this code :
ZoneId zoneId = ZoneId.of("America/New_York");
LocalDate now = LocalDate.now(zoneId);
long epochMilli = ZonedDateTime.of(LocalDate.now(zoneId).atTime(11, 29, 20, 999 * 1000 * 1000), zoneId).toInstant().toEpochMilli();
long unixEpocSeconds = epochMilli % (24 * 60 * 60 * 1000); //86400000
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone(zoneId));
calendar.setTimeInMillis(unixEpocSeconds);
System.out.println("( = " + (calendar.get(Calendar.HOUR)==11));
System.out.println("( = " + (calendar.get(Calendar.MINUTE)==29));
System.out.println("( = " + (calendar.get(Calendar.SECOND)==20));
System.out.println("( = " + (calendar.get(Calendar.MILLISECOND)==999));
How to get the unix epoch seconds without the date component i.e.how to get the milliseconds in UTC zone /rather than as give zoneid. Above code runs find if zoneId=UTC
tl;dr
Duration.ofHours( 11L )
.plusMinutes( 29L )
.plusSeconds( 54L )
.plusMillis( 999L )
.toMillis()
41394999
Span-of-time versus Time-of-day
Your Question is confused. A time-of-day without a date makes no sense in comparison to UTC. A count of milliseconds since the Unix epoch reference date of 1970-01-01T00:00:00Z is for tracking the date and the time-of-day.
I suspect you are actually dealing with a span of time, and mishandling that as a time-of-day. One is meant for the timeline, the other is not.
Duration
The java.time classes bundled with Java 8 and later include Duration for handling such spans of time unattached to the timeline.
These methods take long data type, hence the trailing L.
Duration d = Duration.ofHours( 11L ).plusMinutes( 29L ).plusSeconds( 54L ).plusMillis( 999L ) ;
Count of milliseconds
You asked for a count of milliseconds, so here you go. Beware of data loss, as a Duration carries a finer resolution of nanoseconds, so you would be lopping off any finer fraction of a second when converting to milliseconds.
long millis = d.toMillis() ; // Converts this duration to the total length in milliseconds.
41394999
But I suggest you not represent spans of time nor moments on the timeline using a count-of-milliseconds. Better to use objects or standardized text; read on.
ISO 8601
The ISO 8601 standard defines practical unambiguous formats for representing date-time values as text.
This includes representation of durations. The format is PnYnMnDTnHnMnS where the P marks the beginning while the T separates any years-months-days portion from any hours-minutes-seconds portion.
The java.time classes use the standard formats by default in their parse and toString methods.
String output = d.toString() ;
PT11H29M54.999S
See this code run live at IdeOne.com.
You can directly parse such strings in java.time.
Duration d = Duration.parse( "PT11H29M54.999S" ) ;
I suggest using this format whenever possible, certainly when exchanging data between systems.
While working inside Java, pass Duration objects around rather than mere text.
Timeline
You can perform date-time math with the Duration objects. For example, take the current moment in your particular time zone, and add the eleven and a half hours.
ZoneId z = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime now = ZonedDateTime.now( z ) ;
ZonedDateTime later = now.plus( d ) ;
now.toString(): 2017-09-27T07:23:31.651+13:00[Pacific/Auckland]
later.toString(): 2017-09-27T18:53:26.650+13:00[Pacific/Auckland]
For UTC values, call toInstant. The Instant class represents a moment on the timeline in UTC with resolution of nanoseconds (finer than milliseconds).
Instant instant = later.toInstant() ;
So I tried now for about hours to convert a Timestamp to a local date (CEST).
Date date = new Date(stamp*1000);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("CEST"));
String myDate = simpleDateFormat.format(date);
It's not working whatever I tried and looked up in Internet I always get back the UTC time......
for better understanding: stamp is a variable timestamp with type long which I will receive from a service
tl;dr
String output = ZonedDateTime.ofInstant ( Instant.ofEpochSecond ( 1_468_015_200L ) , ZoneId.of ( "Europe/Paris" ) ).toString();
Details
A few issues:
You are not using proper time zone names.
Proper names are in continent/region format.
The 3-4 letter abbreviations so commonly seen in the media such as CEST are not true time zones. Avoid them. They are neither standardized nor unique(!).
You are using old outmoded date-time classes that are poorly designed and confusing. They have been supplanted by the java.time framework.
If by CEST you meant 2 hours ahead of UTC in the summer, then let's take Europe/Paris as an example time zone. Your Question lacks example data, so I'll make up this example.
Apparently your input is a count of whole seconds from the epoch of first moment of 1970 in UTC. That value can be used directly, no need to multiply.
The ZoneId class represents the time zone. An Instant is a point on the timeline in UTC with a resolution up to nanoseconds. A ZonedDateTime is the Instant adjusted into the ZoneId.
ZoneId zoneId = ZoneId.of ( "Europe/Paris" );
long input = 1_468_015_200L; // Whole seconds since start of 1970.
Instant instant = Instant.ofEpochSecond ( input );
ZonedDateTime zdt = ZonedDateTime.ofInstant ( instant , zoneId );
Dump to console.
System.out.println ( "input: " + input + " | instant: " + instant + " | zdt: " + zdt );
input: 1468015200 | instant: 2016-07-08T22:00:00Z | zdt: 2016-07-09T00:00+02:00[Europe/Paris]
Your TimeZone id is likely to be incorrect (well, not recognized by Java). It seems that instead of throwing an exception the TimeZone is evaluated to UTC in that case.
Try this instead:
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("ECT"));
Here is a page giving some information about Java's TimeZone and a list of timezone ids.
Following is the code that I have used to get the 00 hour of the current day (in long format).
I am running the below code in android.
The method returns the value properly most of the time. But once in a while it returns the value of System.currentTimeMillis().
import java.text.SimpleDateFormat;
import java.util.Date;
public static final SimpleDateFormat SD_FORMAT_DAY_MONTH_YEAR = new SimpleDateFormat("dd/MM/yyyy");
public static long getLongForCurrent00hr() {
Date date = new Date();
String time = SD_FORMAT_DAY_MONTH_YEAR.format(date);
long value;
try {
Date date2 = SD_FORMAT_DAY_MONTH_YEAR .parse(time);
value = date2.getTime();
} catch (ParseException e) {
value = 0;
}
return value;
}
Why is it returning the System.currentTimeMillis()?
How can I solve the issue?
I am more interested in knowing WHY..
As I was ruuning this code today, I checked it by putting Logs:
Most of the time it returns: 1462386600000
And few times System.currentTimeMillis() like 1462430867302.
Your example code works
I see no problem with your code, as you frame it (read below for criticism).
Nearly your exact code is shown here. Two changes:
I made your format constant a local variable. (simply to make this demo easier, one block of code that can be copy-pasted)
I added a couple calls to get an Instant, the current moment in UTC. Similar to java.util.Date, but Instant::toString creates a string showing UTC rather than confusingly applying the JVM’s current time zone. So you can more clearly see that you are indeed getting the first moment of the day of your JVM’s current default time zone. In my case when running this code, my JVM’s current default time zone is America/Los_Angeles, currently in Daylight Saving Time (DST) for an offset-from-UTC for -07:00 (seven hours behind UTC).
Example code.
Date date1 = new Date ();
SimpleDateFormat SD_FORMAT_DAY_MONTH_YEAR = new SimpleDateFormat ( "dd/MM/yyyy" );
String time = SD_FORMAT_DAY_MONTH_YEAR.format ( date1 );
Date date2 = null;
long value;
try {
date2 = SD_FORMAT_DAY_MONTH_YEAR.parse ( time );
value = date2.getTime ();
} catch ( ParseException e ) {
value = 0;
}
System.out.println ( "date1: " + date1 + " date2: " + date2 + " value: " + value + " | instant 1: " + date1.toInstant () + " | instant 2: " + date2.toInstant () );
When run.
date1: Thu May 05 16:55:40 PDT 2016 date2: Thu May 05 00:00:00 PDT 2016 value: 1462431600000 | instant 1: 2016-05-05T23:55:40.907Z | instant 2: 2016-05-05T07:00:00Z
Working too hard
Your Question is confusing, but it seems that you are trying to capture the first moment of the day. You are going about it the wrong way, and are working too hard.
Time Zone
Your code appears to be working with the java.util.Date class. That class represents a moment on the timeline in UTC.
But you are not getting the first moment of the day in UTC. When you parse that date-only string to generate a new java.util.Date (a date plus time-of-day value, despite the misleading name), your JVM’s current default time zone is applied implicitly. Very confusing to have time zones invisibly injected into the process.
Instead you should consciously consider time zones, and always make the time zone explicit is your coding (as seen below).
java.time
The old java.util.Date/.Calendar classes have proven to be poorly designed, confusing, and troublesome. They are now legacy, supplanted by the java.time framework built into Java 8 and later. Much of the java.time functionality is back-ported to Java 6 & 7 and further adapted for Android.
For a date-only value without time-of-day and without time zone, use LocalDate class. While not storing a time zone, determining a date such as “today” requires a time zone. If omitted, your JVM’s current default time zone is applied (beware, that default can change at any moment during runtime).
ZoneId zoneId = ZoneId.of( "America/Montreal" );
LocalDate today = LocalDate.now( zoneId );
You seem to want the first moment of the day. Do not assume the time of that moment is 00:00:00.0. While often true, in some time zones an anomaly such as Daylight Saving Time may shift to another time. Let java.time determine the correct time. Calling [atStartOfDay][2] generates a ZonedDateTime for the first moment appropriate to the specified time zone.
ZonedDateTime zdt = today.atStartOfDay( zoneId );
I strongly recommend against using handling date-time values as a count-from-epoch. That is like using an array of ints of Unicode code points rather than using the String-related classes for handling text. But if you insist, you can convert. But beware data loss as the java.time classes have a finer resolution of nanoseconds whereas you are asking for milliseconds (one of many reasons to avoid handling date-time as a count-from-epoch). First extract an Instant, a moment on the timeline in UTC with a resolution of nanoseconds.
Instant instant = zdt.toInstant();
long millisecondsFromEpoch = instant.toEpochMilli(); // WARNING: Possible data loss (going from nanoseconds to milliseconds).
UTC
If you did want the first moment of the day in UTC, that too is easy.
You could specify UTC as the time zone, using the constant ZoneOffset.UTC. (That constant happens to be in ZoneOffset, a subclass of ZoneId.)
ZonedDateTime zdt = today.atStartOfDay( ZoneOffset.UTC );
But that may not be the most appropriate route. A time zone is an offset-from-UTC plus a set of rules for anomalies such as Daylight Saving Time (DST). UTC has no such anomalies by definition. So more appropriate would be the OffsetDateTime rather than ZonedDateTime.
OffsetTime ot = OffsetTime.of( 0 , 0 , 0 , 0 , ZoneOffset.UTC );
OffsetDateTime odt = today.atTime( ot );
I have stored date in a string. Now I want to get minutes from the date string. How can I convert it into minutes?
Here is how I stored in a class:
public String fromDate;
public String toDate;
I have set getter and setter methods. I have saved the date value now I want to retrive the value and convert to minutes.
Retriving Like this:
Calendar c = Calendar.getInstance();
String datefrom = eventData.getFromDate();
I tried using this calendar instance:
c.set(Calendar.HOUR, hour);
c.set(Calendar.MINUTE, minute);
c.set(Calendar.DATE,day);
Date datefrom = c.getTime();
startTime = String.valueOf(datefrom);
int hour = c.get(Calendar.HOUR);
int totalMinutes = hour * 60;
But this I can get from Date object. I have stored date in String format. How can I convert this?
Use Joda-Time:
String fromDate;
String toDate;
DateTimeFormatter format = new DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
DateTime fromDT = format.parseDateTime(fromDate);
DateTime toDT = format.parseDateTime(toDate);
Duration duration = new Duration(fromDT, toDT);
int minutes = duration.getStandardMinutes();
To import in Android Studio, update your build.gradle file:
apply plugin: 'android'
dependencies {
compile 'joda-time:joda-time:2.4'
compile 'joda-time:joda-time:2.2'
}
To convert a String to Date in Java you would have to use the DateFormat like the sample below:
String string = "January 26, 2016";
DateFormat format = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH);
Date date = format.parse(string);
System.out.println(date); // Tue Jan 26 00:00:00 GMT 2016
then you can go ahead with your Calendar implementation.
Usually i'd suggest to parse the time with a SimpleDateFormat, but I think in this case (since the dates seem to have a defined form and there might be problems with the timezones) i'll suggest to retrieve the information yourself:
String date = "Wed Jan 27 07:25:29 GMT+05:30 2016";
String[] times = date.substring(11, 16).split(":");
int minutes = Integer.parseInt(times[0]) * 60 + Integer.parseInt(times[1]);
System.out.println(minutes);
The part date.substring(11, 16) extracts the hours and minutes part from the string ("07:25").
The part .split(":"); splits the string "07:25" into two strings: "07" and "25".
after that you just parse those numbers to integers with Integer.parseInt(...) and calculate the number of minutes!
To get the minutes from a String is possible to use a DateFormat to convert the string to a Date and after use your code.
Your Question is really two questions:
How to parse a String to get a date-time object
How to get number of minutes since start-of-day from a date-time object
The first one, parsing a String into a date-time, has been covered at least 1,845 times on Stack Overflow, so I will skip it. The second Question is addressed below.
Please try to make your questions more clear. And focus on a single topic as narrowly as possible, as that is the intention for Stack Overflow.
Minutes-Of-Day
What you seem to want is called “Minutes-Of-Day”, the number of minutes since the start of the day.
Be careful and thoughtful here as there are two different definitions for minutes-of-day. You can get the actual number of minutes for a specific day in a specific time zone. Or you can calculate for a generic 24-hour day. Because of Daylight Saving Time (DST) and other anomalies, a day is not necessarily 24 hours long. For example, in most of the United States the use of DST means a day may be 23, 24, or 25 hours long.
The Question’s code and other Answers ignore the crucial issue of time zone (a common mistake in date-time work). If you do not specify a time zone, your JVM’s current default time zone is silently applied. Not good… that default can change at any moment, even during runtime! Better to always specify the time zone you expect/desire.
Avoid Old Date-Time Classes
The old date-time classes bundled with the earliest versions of Java are notoriously troublesome. Avoid them. Instead use the java.time framework built into Java 8 and later (see Tutorial). If that technology is not available to you, use the Joda-Time library (which inspired java.time). Examples below are in java.time in Java 8 Update 66.
java.time
Let’s look at March 3rd, 2015. This day was the "Spring ahead" DST changeover day for most of the United States. The clock jumped from 2 AM to 3 AM. So 03:00:00.0 on this day meant two hours (120 minutes) actually elapsed since the start of the day. If we treat this as a generic 24-hour day, we would say three hours (180 minutes) elapsed. The java.time classes can calculate minutes-of-day in both definitions.
First we get 3 AM on that changeover day. We use one of the time zones which recognized DST.
ZoneId zoneId = ZoneId.of ( "America/Los_Angeles" );
ZonedDateTime zdt = ZonedDateTime.of ( 2015 , 3 , 8 , 3 , 0 , 0 , 0 , zoneId );
Generic 24-Hour Day
Next we get the minutes since start of day assuming a generic 24-hour day. The ChronoField enum provides many ways to access TemporalField values such as MINUTE_OF_DAY.
long minutesOfDayForGeneric24HourDay = zdt.get ( ChronoField.MINUTE_OF_DAY );
Actual Day
To get the actual number of minutes elapsed since the start of this particular day for this particular time zone in which DST was changing over, we must do a bit more work. We have to determine the first moment of the day from which we can calculate elapsed time. To get that first moment, we must go through the LocalDate class which is a date-only value without time-of-day nor time zone. On that LocalDate object we call atStartOfDay to adjust back into a date-time value (a ZonedDateTime). You might think you could skip this by assuming the day starts at 00:00:00.0 but that is not always true.
ZonedDateTime zdtStart = zdt.toLocalDate ().atStartOfDay ( zoneId );
Now calculate elapsed time. The Duration class represents a span of time as hours, minutes, and seconds. From that Duration we can ask the total number of minutes, converting hours to minutes.
Duration duration = Duration.between ( zdtStart , zdt );
long minutesOfDayForActualDay = duration.toMinutes ();
Dump to console. Note how the generic ChronoField approach says 180 minutes while the actual Duration approach yields 120 minutes.
System.out.println ( "zdt: " + zdt + " | minutesOfDayForGeneric24HourDay: " + minutesOfDayForGeneric24HourDay + " | duration: " + duration + " | minutesOfDayForActualDay: " + minutesOfDayForActualDay );
zdt: 2015-03-08T03:00-07:00[America/Los_Angeles] | minutesOfDayForGeneric24HourDay: 180 | duration: PT2H | minutesOfDayForActualDay: 120
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.