Dvir
Dvir

Reputation: 97

Java - convert timestamp to Date and back to timestamp changes the date

I'm creating a string out from current time and I wanted to convert it to timestamp again, but the thing is, that it's subtracts 2 hours while converting.

This is the steps I'm doing -

    DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder().append(DateTimeFormatter.ofPattern("uuuu-MM-dd")).appendLiteral(" ")
                .append(DateTimeFormatter.ofPattern("HH:mm:ss")).parseLenient();
    long ts = Clock.systemUTC().millis();
    System.out.println(ts);
    Instant instant = Instant.ofEpochMilli(ts);
    ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);
    String str = zonedDateTime.format(dateTimeFormatterBuilder.toFormatter());


    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    try {
        long timestamp = simpleDateFormat.parse(str).getTime();
        System.out.println(timestamp);
    } catch (ParseException e) {
        e.printStackTrace();
    }

output - 1639065502667 1639058302000

(2021-12-09 15:58:22 2021-12-09 13:58:22)

why is the diff of the 2 hours? how can I parse it so that the outputs will be equal?

Upvotes: 0

Views: 1713

Answers (4)

Basil Bourque
Basil Bourque

Reputation: 338584

tl;dr

Trying to understand Date, Calendar, and SimpleDateFormat is a huge waste of time. Use only their replacements, the java.time classes.

LocalDateTime                        // Represent a date with time-of-day, but lacking the context of a time zone or offset-from-UTC. So *not* a moment, *not* a point on the timeline.
.parse(                              // Parse your text string input into a date-time object.
    "2021-12-09 15:58:22"            // Your input of date with time-of-day but no offset/zone.
    .replace( " " , "T" )            // Replace SPACE with a `T` to comply with standard ISO 8601 format.
)                                    // Returns a `LocalDateTime` object.
.atZone(                             // Place that date-with-time into the context a particular time zone.
    ZoneId.of( "America/Montreal" )  // Specify a time zone by its `Continent/Region` name.
)                                    // Returns a `ZonedDateTime` object, a date with time-of-day as seen through the wall-clock time used by the people of a particular region. This *does* represent a moment, *is* a specific point on the timeline.
.toInstant()                         // Adjust from time zone to UTC (an offset of zero hours-minutes-seconds). This represents the very same moment as the `ZonedDateTime` object above, but as seen through a different wall-clock time.
.toEpochMilli()                      // Get a count of milliseconds from first moment of 1970 in UTC (1970-01-01T00:00Z) to the moment of our `Instant` object (& `ZonedDateTime` object).

See this code run live at IdeOne.com. There you can click fork to make a copy, alter, and run.

1639083502000

Avoid legacy date-time classes

Regarding your specific question about a two hour difference, the obvious cause would be a time zone difference.

Parsing incomplete information

Your parsing, SimpleDateFormat("yyyy-MM-dd HH:mm:ss") is something of a wild card. The result will be a java.util.Date object, which represents a moment, a date with time-of-day as seen with an offset of zero. But your input lacks an indicator of offset or zone. As commented by Sotirios Delimanolis, you are parsing with partial input, with incomplete information.

So some default zone/offset will be applied. I do not know what zone or offset in particular, because I do not care. That terrible class is tragically flawed, and should be avoided.

Also, yet another problem with the Date class is that its toString method has the anti-feature of applying the JVM’s current default time zone to adjust away from the UTC value represented by that class. Very confusing, as this creates the illusion of that zone being a part of Date object but it is not. As I said, a terrible class, tragically flawed.

Use only java.time classes instead.

java.time

Understand that a date with time-of-day is inherently ambiguous, is not a moment.

If you are tracking 4 PM on the 9th, we do not know if that means 4 PM in Tokyo Japan, 4 PM in Toulouse France, or 4 PM in Toledo Ohio US — three very different moments that happen several hours apart.

LocalDateTime ldt = LocalDateTime.parse( "2021-12-09 16:00:00" ) ;

To track a moment, a point on the timeline, you must place ne date-with-time in the context of an offset from UTC or of a time zone.

An offset is merely a number of hours-minutes-seconds ahead or behind the baseline of modern timekeeping, the prime meridian at Royal Observatory, Greenwich.

A time zone is much more. A time zone is a named history of the past, present, and future changes to the offset used by the people of a particular region. Each zone has a name in format of Continent/Region such as Europe/Berlin or Asia/Tokyo.

To track moments as seen in UTC, with an offset of zero, use Instant.

Instant instant = Instant.now() ;

To see that same moment through the wall-clock time used by people in a region, apply a ZoneId to get a ZonedDateTime.

ZoneId z = ZoneId.of( "America/Edmonton" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;

As for your use of SimpleDateFormat, Date, and Calendar, don’t. Avoid these legacy date-time classes. Hey were designed by people who did not understand date-time handling. They were supplanted years ago by the modern java.time classes defined in JSR 310. Sun, Oracle, and the JCP community all gave up on those classes. I suggest you do the same.

In your code:

long ts = Clock.systemUTC().millis();
Instant instant = Instant.ofEpochMilli(ts);

That is the same as doing this:

Instant.now().truncatedTo( ChronoUnit.MILLISECONDS )

In your code:

ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);

(A) When working with mere offsets rather than time zones, use OffsetDateTime class. The ZonedDateTime class is for working with time zones.

(B) A briefer way to adjust from Instant to a zoned moment was shown above:

myInstant.atZone( z )

Upvotes: 3

diziaq
diziaq

Reputation: 7795

The dateTimeFomatter builder uses format without milliseconds and without timezone.
That's why the str value contain no information about them.

Then simpleDateFormat.parse(str) uses timezone of JVM which is UTC+02:00 in this case.

Trace what is going on:

Instant instant = Instant.now(); 
    // => 2021-12-09 15:58:22.798 +00:00

String str = zonedDateTime.format(dateTimeFormatterBuilder.toFormatter());
    // => "2021-12-09 15:58:22"

simpleDateFormat.parse(str);
    // => 2021-12-09 15:58:22.000 +02:00

You just need to fix the pattern (add millis .SSS and timezone XXX parts) to make the results consistent as expected:

DateTimeFormatter.ofPattern("HH:mm:ss.SSSXXX")

// and something similar for SimpleDateFormat if needed

Parsing Instant from a custom formatted string.

This example shows how to parse Instant from serialized time assuming that there is a fixed timezone for all cases.

var serializedDateTime = "2020-01-01 10:20:30";
var zoneId = ZoneOffset.UTC; // may be any other zone
    
var format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    
var instant = LocalDateTime
                 .parse(serializedDateTime, format)
                    // LocalDateTime is unzoned, just representation of (date + time) numbers in a single object 
                 .atZone(zoneId)  
                    // apply a zone to get ZonedDateTime (point in space-time on Earth)
                 .toInstant(); 
                    // convert to Instant (the same point adjusted to UTC+00:00)

Upvotes: 2

Dvir
Dvir

Reputation: 97

The answer was only setting the timezone to UTC -

DateTimeFormatterBuilder dateTimeFormatterBuilder = new DateTimeFormatterBuilder().append(DateTimeFormatter.ofPattern("uuuu-MM-dd")).appendLiteral(" ")
            .append(DateTimeFormatter.ofPattern("HH:mm:ss")).parseLenient();
long ts = Clock.systemUTC().millis();
System.out.println(ts);
Instant instant = Instant.ofEpochMilli(ts);
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC);
String str = zonedDateTime.format(dateTimeFormatterBuilder.toFormatter());


SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
    *******
    simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    *******
    long timestamp = simpleDateFormat.parse(str).getTime();
    System.out.println(timestamp);
} catch (ParseException e) {
    e.printStackTrace();
}

Upvotes: 2

talex
talex

Reputation: 20455

Let me guess, your timezone is UTC+2?

simpleDateFormat.parse(str) assume that your date in current system timezone, but it is in UTC.

Upvotes: 0

Related Questions