从 1970 到 2020.01.01 的毫秒数

Milliseconds from 1970 to 2020.01.01

我试图获取从纪元到 2020.01.01 的毫秒数。我对 Date 使用了旧方法,我也想使用新的性感 LocalDate 但我得到的两个结果是不同的:

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

差异正好是一小时(3600_000 毫秒)。为什么我得到不同的结果?

我不想评论为什么你会有所不同,因为我认为这两种原始方法都有问题。您需要密切注意时区之类的事情;你真的应该避免对代表日期的数值进行任何类型的算术运算。

您需要特别注意指定要在其间测量的点:如果您想要毫秒数,大概您真的想将这些点指定为时间点。 “1970”不是一瞬间,而是一年的时间; “2020-01-01”也不是一个瞬间,而是一个时间段,其含义根据时区而变化 - 在地球上的某个地方,大约有 48 小时的瞬间被认为是那个日期。

执行此操作的正确方法(假设您希望在首选时区的纪元和一天的开始之间有毫秒数)是:

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.

请注意,您需要指定 zoneId,例如ZoneId.of("Europe/Warsaw"),因为这会影响一天的开始时间,从而影响多少毫秒。

不同时区

Why I get different result?

Joachim Sauer 已经说过:这是因为不同的时区millis1 是截至 2020 年 1 月 1 日在 00:00 (UTC)的毫秒数。 millis2 计算到 2020 年 1 月 1 日 00:00 在您当地的时区 ,大概是 Europe/Warsaw。在冬季,波兰与 UTC 的偏移量为 +01:00,这解释了两者之间 1 小时的差异。一切都很好。纪元是一个时间点,与时区无关。通常定义为 1970 年 1 月 1 日 00:00 UTC。

也就是说,我同意 Andy Turner 的观点,即两种计算方式都有问题。

用java.time

计算得很好

这是我的选择,当然使用 java.time,现代 Java 日期和时间 API:

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

输出:

1577836800000

如果您确实想要自己的时区,只需更改第一行:

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

1577833200000

关键是对传统和现代使用相同的时区(例如 UTC)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);
    }
}

输出:

1577836800000
1577836800000

我们换个时区试试,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);
    }
}

输出:

1577854800000
1577854800000

Trail: Date Time.

了解有关现代日期时间 API 的更多信息

请注意,旧版日期时间 API(java.util 日期时间类型及其格式 API、SimpleDateFormat)已过时且容易出错。建议完全停止使用,改用java.timemodern date-time API* .


* 无论出于何种原因,如果您必须坚持Java 6 或Java 7,您可以使用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

你的问题在这里:

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

您使用 LocalDate class,它为您获取当地时间(在您的时区),而 Java 中的时间(以毫秒为单位)是 [=14= 之间经过的时间量](世界协调时间)这是(在您请求的日期,01.01.2020 00:00:00 UTC):

1577836800000

您得到的差异是由于在您当地时间观察到的时间偏移(一小时,可能您处于欧洲中部时间 --CET--)

编辑:

顺便说一句,我在你使用的答案(和你的代码)中看到:

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

这是非常糟糕的代码。您假设以上内容等同于日期 2020.1.11900.1.1 之间的毫秒差,实际上,它表示日期 120.1.1 的时间戳,这是时间戳第一百二十年一月一日 (a.C) new Date() 运算符中的日期之间没有分配 属性。如果这些年的持续时间都相同,这可能是真的……但事实并非如此。一个好方法是使用:

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

但后面的不等同于上面写的

new Date( y , m , d ) 使用默认时区

其他一些答案是正确的,非常有用。但我想把你的代码出错的地方说得非常简单明了:

➥ 已弃用的 constructor of java.util.Date for year-month-day 参数隐式应用 JVM 当前的默认时区。

取代码中关键行的第一部分:

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

… 根据定义,其中 Instant 始终采用 UTC(零小时-分钟-秒的偏移量)。在我的机器上,我的 JVM 中的当前默认时区是 America/Los_Angeles。在您的日期和时间,该时区比 UTC 晚八小时。

那么让我们试试这三行代码代码:

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

当运行时,我们确实看到new Date所代表的时刻是那天在时区America/Los_Angeles中看到的第一个时刻,俗称[=18] =].该日期的该区域比 UTC 晚八小时。我们可以在第三行中看到这一事实,当调用 toInstant 时已调整为 UTC,其中时间为上午 8 点。

America/Los_Angeles

Wed Jan 01 00:00:00 PST 2020

2020-01-01T08:00:00Z

避免Date

从更大的角度来看,停止使用 Date class

研究 java.util.Date 的行为没有任何好处。 class 绝对是可怕的,由不懂日期时间处理的人写的。与 Calendarjava.sql.DateSimpleDateFormat 一起,绝不能使用这些 classes。

这些遗留 classes 在几年前被 java.time classes 取代,在 JSR 310 中定义。Sun、Oracle、 JCP 社区一致放弃了这些 classes。你也应该。