Trismegistos
Trismegistos

Reputation: 3882

Milliseconds from 1970 to 2020.01.01

I was trying to get milliseconds from epoch until 2020.01.01. I used old method with Date and I also wanted to use new sexy LocalDate but two results I got are different:

long millisecondsInTheDay = 24 * 60 * 60 * 1000;
long millis1 = LocalDate.of(2020, Month.JANUARY, 1).toEpochDay() * millisecondsInTheDay; // 1577836800000
long millis2 = new Date(2020 - 1900, 0, 1).toInstant().toEpochMilli(); // 1577833200000

Difference is exactly one hour (3600_000 milliseconds). Why I get different result?

Upvotes: 2

Views: 1595

Answers (5)

Luis Colorado
Luis Colorado

Reputation: 12708

Your problem is here:

long millis1 = LocalDate.of(2020, Month.JANUARY, 1).toEpochDay() * millisecondsInTheDay; // 1577836800000

You use the LocalDate class, which gets for you the local time (in your timezone) while time in Java (in millisec) is the amount of time elapsed between 01.01.1970 UTC (Universal Coordinated Time) this is (at the date you requested, 01.01.2020 00:00:00 UTC):

1577836800000

The difference you get is due to the time offset observed at your local time (one hour, probably you are in central european time --CET--)

Edit:

By the way, I've seen in the answers (and in your code) that you use:

new Date(2020 - 1900, 0, 1);

This is very bad code. You are assuming that the above is equivalent to the difference in milliseconds that will be between the dates 2020.1.1 and 1900.1.1 and indeed, it represents the timestamp at date 120.1.1 this is the timestamp at the first of january of year one hundred and twenty (a.C) There's no distributive property between dates in new Date() operator. And if the years were all the same duration, this could be true... but they are not. A good way would be to use:

long millis = new Date(2020, 0, 1).getTime() - new Date(1900, 0, 1).getTime();

but the later is not equivalent to what is written above.

Upvotes: 0

Anonymous
Anonymous

Reputation: 86389

Different time zones

Why I get different result?

Joachim Sauer said it already: This is because of different time zones. millis1 is the count of milliseconds until January 1, 2020 at 00:00 in UTC. millis2 counts until January 1, 2020 at 00:00 in your local time zone, presumably Europe/Warsaw. In winter Poland is at offset +01:00 from UTC, which explains the difference of 1 hour between the two. Everything agrees nicely. The epoch is one point in time and independent of time zone. It’s usually defined as January 1, 1970 at 00:00 in UTC.

That said I agree with Andy Turner that both ways to calculate are problematic.

A good calculation with java.time

Here’s my go, of course using java.time, the modern Java date and time API:

    ZoneId targetZone = ZoneOffset.UTC;
    long millis = LocalDate.of(2020, Month.JANUARY, 1).atStartOfDay(targetZone)
            .toInstant()
            .toEpochMilli();
    System.out.println(millis);

Output:

1577836800000

If you did want your own time zone, just change the first line:

    ZoneId targetZone = ZoneId.of("Europe/Warsaw");

1577833200000

Upvotes: 3

Basil Bourque
Basil Bourque

Reputation: 340230

new Date( y , m , d ) uses default time zone

Some of the other Answers are correct and very useful. But I want to make very plain and simple where your code went wrong:

➥ The deprecated constructor of java.util.Date for year-month-day arguments implicitly applies your JVM’s current default time zone.

Take the first part of the key line in your code:

new Date(2020 - 1900, 0, 1).toInstant()

… where an Instant is always in UTC (an offset of zero hours-minutes-seconds), by definition. On my machine the current default time zone in my JVM is America/Los_Angeles. On your date and time, this zone was eight hours behind UTC.

So let's try these three lines of code code:

System.out.println(
        ZoneId.systemDefault()
);
System.out.println(
        new Date(2020 - 1900, 0, 1)
);
System.out.println(
        new Date(2020 - 1900, 0, 1).toInstant()
);

When run, we see indeed that the moment represented by new Date is the first moment of that day as seen in the time zone America/Los_Angeles, colloquially known as PST. That zone on that date is eight hours behind UTC. We can see this fact in the third line, when calling toInstant has adjusted to UTC where the time-of-day is 8 AM.

America/Los_Angeles

Wed Jan 01 00:00:00 PST 2020

2020-01-01T08:00:00Z

diagram summarizing that first moment of 2020-01-01 in L.A. time is simultaneously 8 AM in UTC

Avoid Date

In the bigger picture, stop using Date class!

There are no benefits to be had by studying the behavior of java.util.Date. That class is absolutely terrible, written by people who did not understand date-time handling. Along with Calendar, java.sql.Date, and SimpleDateFormat, these classes should never be used.

These legacy classes were supplanted years ago by the java.time classes, defined in JSR 310. Sun, Oracle, and the JCP community unanimously gave up on these classes. So should you.

Upvotes: 1

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 79620

The key is to use the same timezone (e.g. UTC) for both, the legacy and the modern API.

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneOffset;
import java.util.Locale;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd", Locale.ENGLISH);
        sdf.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
        long millisUsingJavaUtilDate =  sdf.parse("2020.01.01")
                                            .getTime();

        long millisUsingJavaTime = LocalDate.of(2020, Month.JANUARY, 1)
                                            .atStartOfDay(ZoneOffset.UTC)
                                            .toInstant()
                                            .toEpochMilli();

        System.out.println(millisUsingJavaUtilDate);
        System.out.println(millisUsingJavaTime);
    }
}

Output:

1577836800000
1577836800000

Let's try with another timezone, America/New_York:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;
import java.util.Locale;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd", Locale.ENGLISH);
        sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        long millisUsingJavaUtilDate =  sdf.parse("2020.01.01")
                                            .getTime();

        long millisUsingJavaTime = LocalDate.of(2020, Month.JANUARY, 1)
                                            .atStartOfDay(ZoneId.of("America/New_York"))
                                            .toInstant()
                                            .toEpochMilli();

        System.out.println(millisUsingJavaUtilDate);
        System.out.println(millisUsingJavaTime);
    }
}

Output:

1577854800000
1577854800000

Learn more about the modern date-time API from Trail: Date Time.

Note that the legacy date-time API (java.util date-time types and their formatting API, SimpleDateFormat) are outdated and error-prone. It is recommended to stop using them completely and switch to java.time, the modern date-time API* .


* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring and How to use ThreeTenABP in Android Project.

Upvotes: 1

Andy Turner
Andy Turner

Reputation: 140544

I don't want to comment on why you get a difference because I think that both of the original approaches are problematic. You need to pay close attention to things like time zones; and you really should avoid doing any sort of arithmetic on numerical values representing dates.

You need to pay special care to specify the points you are measuring between: if you want a number of milliseconds, presumably you really want to specify those points as instants in time. "1970" isn't an instant, it's a year-long period; "2020-01-01" isn't an instant either, but a period whose meaning shifts depending on time zone - there's roughly 48h-worth of instants where somewhere on the planet it is considered to be that date.

The correct way to do this (assuming you want milliseconds between epoch and the start of the day in your preferred timezone) is:

Duration between =
    Duration.between(
        Instant.EPOCH,
        LocalDate.of(2020, Month.JANUARY, 1).atStartOfDay(zoneId));

long betweenMillis = between.toMillis(); // If you must - better to keep the type information that this is a Duration.

Note that you need to specify the zoneId, e.g. ZoneId.of("Europe/Warsaw"), because that affects when the start of the day is, and hence how many milliseconds.

Upvotes: 10

Related Questions