Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert a unix timestamp to LocalDate(Time) without conversion

I'm using org.joda.time.LocalDate and LocalDateTime. From an external source I get a Unix timestamp and want to make a LocalDate(Time) out of it. The point is, it is defined in the interface of that external system, that all dates/times are in UTC timezone. So I want to avoid any implicit conversion from that timestamp to any default timezone of the local system which might be different from UTC. There is a constructor of LocalDateTime for such things, so I tried (as an example):

System.out.println(new LocalDateTime(3600000L));
  --> 1970-01-01T02:00:00.000

System.out.println(new LocalDateTime(3600000L, DateTimeZone.UTC));
  --> 1970-01-01T01:00:00.000

The result surprises me a bit. Having a look into the JavaDoc, the first constructor evaluates the timestamp "using ISO chronology in the default zone." By definition, the Unix timestamp is the number of seconds (here milliseconds) from 01-JAN-1970T00:00:00UTC! So if the value 3600000 (= exactly 2 hours in millis) is add to that base, it would come to 01-JAN-1970T02:00:00UTC. My local system is set to timezone Europe/Berlin (CET) which is UTC+1. Precisely, we have daylight saving right now, so it should even be UTC+2, but lets pretend we're at UTC+1 now. So if the timestamp is by definition UTC, then I would expect that the resulting time is either 01:00:00, if it interprets the value of the timestamp to be in CET which is converted to UTC, or 03:00:00 if it correctly expects the timestamp to have a UTC value which is converted to CET. But it actually shows an unconverted timestamp, exactly 2 hours off the base. The second constructor is supposed to evaluate the timestamp "using ISO chronology in the specified zone." (from JavaDoc) So if I specify UTC timezone explicitly, I would not expect any conversion at all, but a time of 02:00:00. A UTC based timestamp which results in a time which itself is declared to be UTC should result in exactly that, but the result is 01:00:00! Just to double-check, I called it with CET explicitly and got the same result as if I don't provide any timezone.

So it looks like, that the timestamp is not considered to be UTC, but to be in the local timezone. Creating a LocalDateTime takes it and applies a conversion from your local timezone to the target one (second parameter of the constructor). First of all I'm wondering, if this is really ok. Secondly I have to guarantee that no such conversion is happening in my code. So I could believe, leaving the second parameter and using the default timezone does the trick, but is that guaranteed? Or might there be a chance that some strange conversion happens if we change from/to daylight saving? Even changing the local timezone must not have any consequence, this is why all times we get as a timestamp from that external system are already converted to UTC.

One evil scenario I observed was, when a timestamp was supposed to be just a date (without time). In this case, the timestamp would be any date with time set to 00:00:00. When I use LocalDate the same way I used LocalDateTime in the example above, it converts the timestamp into date + time (of course) and simply cuts the time off. BUT, if the date was 15-JUL-2014T00:00:00UTC, and the result at my end is shifted the same one hour as in my other example, that turns to 14-JUL-2014T23:00:00 and therewith to the date 14-JUL-2014! This is actually a disaster and must not happen!

So does anyone of you have a clue why LocalDate(Time) behaves like that? Or what is the concept behind I which I might misinterpret. Or how to guarantee that no conversion happens?

like image 640
BlackDroid Avatar asked Aug 19 '14 13:08

BlackDroid


People also ask

How do I convert timestamp to time in Unix?

The getTime method returns the number of milliseconds since the Unix Epoch (1st of January, 1970 00:00:00). To get a Unix timestamp, we have to divide the result from calling the getTime() method by 1000 to convert the milliseconds to seconds. What is this?

How do I switch from epoch to LocalDate?

To convert LocalDate to epoch milliseconds, we can use Instant. toEpochMilli() that converts this instant to the number of milliseconds from the epoch 1970-01-01T00:00:00Z. To get epoch milliseconds, first we will convert LocalDate to Instant and then will use its toEpochMilli() method.

Does LocalDateTime have timezone?

Remember that LocalDateTime class does not store or represent a time-zone. Instead, it is a description of the date, as used for birthdays, combined with the local time as seen on a wall clock. It cannot represent an instant on the time-line without additional information such as an offset or time-zone.

Should I use ZonedDateTime or LocalDateTime?

A LocalDateTime instance represents a point in the local timeline. It cannot represent an instant on the universal timeline without additional information such as an offset or time zone. A ZonedDateTime instance represents an instant in the universal timeline. It is the combination of date, time and zone information.


1 Answers

tl;dr

Your Question is confusing, but you seem to claim the number 3_600_000L represents a count of milliseconds since the epoch reference of first moment of 1970 in UTC, 1970-01-01T00:00Z.

So parse as an Instant.

Instant                         // Represent a moment in UTC, an offset of zero hours-minutes-seconds.
.ofEpochMilli( 3_600_000L  )    // Parse a count of milliseconds since 1970-01-01T00:00Z. Returns a `Instant` object.
.toString()                     // Generate text representing this value, using standard ISO 8601 format.

The result is 1 AM on the first day of 1970 as seen in UTC. The Z on the end means UTC.

1970-01-01T01:00:00Z

Get the date portion, as seen in UTC.

Instant                           // Represent a moment in UTC, an offset of zero hours-minutes-seconds.
.ofEpochMilli( 3_600_000L  )      // Parse a count of milliseconds since 1970-01-01T00:00Z.
.atOffset(                        // Convert from `Instant` (always in UTC, an offset of zero) to `OffsetDateTime` which can have any offset.
    ZoneOffset.UTC                // A constant representing an offset of zero hours-minutes-seconds, that is, UTC itself.
)                                 // Returns a `OffsetDateTime` object.
.toLocalDate()                    // Extract the date portion, without the time-of-day and without the offset-from-UTC.
.toString()                       // Generate text representing this value, using standard ISO 8601 format.

1970-01-01

Adjust that moment from UTC to the time zone Europe/Berlin.

Instant                           // Represent a moment in UTC, an offset of zero hours-minutes-seconds.
.ofEpochMilli( 3_600_000L  )      // Parse a count of milliseconds since 1970-01-01T00:00Z.
.atZone(                          // Convert from UTC to a particular time zone.
    ZoneId.of( "Europe/Berlin" )  // A time zone is a history of the past, present, and future changes to the offset-from-UTC used by the people of a particular region. 
)                                 // Returns a `ZonedDateTime` object.
.toString()                       // Generate text representing this value, using standard ISO 8601 format wisely extended to append the name of the time zone in square brackets.

1970-01-01T02:00+01:00[Europe/Berlin]

Notice how that result has a different time-of-day, 2 AM in Berlin area rather than the 1 AM we saw in UTC. The Europe/Berlin time zone was running an hour ahead of UTC at that moment then, so an hour ahead of 1 AM is 2 AM — same moment, same point on the timeline, different wall-clock time.

Get the date-only portion from that moment as seen in Europe/Berlin.

Instant                           // Represent a moment in UTC, an offset of zero hours-minutes-seconds.
.ofEpochMilli( 3_600_000L  )      // Parse a count of milliseconds since 1970-01-01T00:00Z.
.atZone(                          // Convert from UTC to a particular time zone.
    ZoneId.of( "Europe/Berlin" )  // A time zone is a history of the past, present, and future changes to the offset-from-UTC used by the people of a particular region. 
)                                 // Returns a `ZonedDateTime ` object.
.toLocalDate()                    // Extract the date only, without the time-of-day and without the time zone. Returns a `LocalDate` object.
.toString()                       // Generate text representing this value, using standard ISO 8601.

1970-01-01

In this case, the date in Berlin area is the same as in UTC. But in other cases the date may vary. For example, 9 PM (21:00) on the 23rd of January in UTC is simultaneously “tomorrow” the 24th in Tokyo Japan.

java.time

Apparently, you use the term “Unix timestamp” to mean a count of milliseconds since first moment of 1970 UTC, 1970-01-01T00:00Z.

Parse that number into an Instant object. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).

Instant instant = Instant.ofEpochMilli( 3_600_000L ) ;

instant.toString(): 1970-01-01T01:00:00Z

So very simple: An Instant is always in UTC, always a moment, a point on the timeline.

when a timestamp was supposed to be just a date (without time).

For this, use the LocalDate class. The LocalDate class represents a date-only value without time-of-day and without time zone.

A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.

If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment, so your results may vary. Better to specify your desired/expected time zone explicitly as an argument.

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. 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 z = ZoneId.of( "America/Montreal" ) ;  

Adjust your UTC value (Instant) to another time zone by applying a ZoneId to generate a ZonedDateTime.

ZonedDateTime zdt = instant.atZone( z ) ;  

From there we can extract the date-only portion as a LocalDate object.

LocalDate ld = zdt.toLocalDate() ;

If you want the first moment of the day on that date, you must specify the context of a time zone. For any given moment, the date varies around the globe by time zone. When a new day dawns in India, it is still “yesterday” in France.

Always let java.time determine the first moment of the day. Do not assume 00:00. In some zones on some dates, the day may start at another time such as 01:00 because of anomalies such as Daylight Saving Time (DST).

ZonedDateTime zdtStartOfDay = ld.atStartOfDay( z ) ;

If you want to see that same moment as UTC, simply extract a Instant.

Instant instant = zdtStartOfDay.toInstant() ;

The java.time classes also have a LocalDateTime class. Understand that this class LocalDateTime does not represent a moment! It does not represent a point on the timeline. It has no real meaning until you place it in the context of a time zone. This class is only used for two meanings:

  • The zone/offset is unknown (a bad situation).
  • Every/any zone/offset is intended. For example, "Christmas starts at 00:00 on December 25, 2018“, which means different moments in different places. The first Christmas happens in Kiribati. Then successive Christmases start after each successive midnight moving westward through Asia, then India, and onwards to Europe/Africa, and eventually the Americas. So it takes at least 26 hours for Santa to deliver all the presents.

Hopefully you can see this work is not at all as confusing once you understand the core concepts and use the excellent well-designed java.time classes.


Table of all date-time types in Java, both modern and legacy


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

  • Java SE 8, Java SE 9, and later
    • Built-in.
    • Part of the standard Java API with a bundled implementation.
    • Java 9 adds some minor features and fixes.
  • Java SE 6 and Java SE 7
    • Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
  • Android
    • Later versions of Android bundle implementations of the java.time classes.
    • For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….

Table of which java.time library to use with which version of Java or Android

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

like image 108
Basil Bourque Avatar answered Oct 11 '22 14:10

Basil Bourque