使用 Instant/ChronoUnit 获取今天午夜的 nanos-since-epoch 时间比使用 GregorianCalendar/Date 提前 24 小时
Using Instant/ChronoUnit to get the time in nanos-since-epoch at midnight today is 24 hours ahead of using GregorianCalendar/Date
我正在尝试从大纪元午夜开始以纳米为单位获取今天的日期(例如 2021 年 2 月 13 日 00:00:00)。
使用 GregorianCalendar/Date 似乎得到了正确的结果。
我使用 Instant/ChronoUnit 的方式是明天午夜给我(例如 2021 年 2 月 14 日 00:00:00)。
这里有什么问题?
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Calendar;
import java.util.TimeZone;
class scratch {
public static void main(String[] args) {
Calendar c = new GregorianCalendar();
c.setTime(new Date());
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
c.setTimeZone(TimeZone.getTimeZone("UTC"));
long dateNanos = c.getTimeInMillis() * 1000000L;
Instant i = Instant.now().truncatedTo(ChronoUnit.DAYS);
long instantNanos = ChronoUnit.NANOS.between(Instant.EPOCH, i);
System.out.println("dateMillis = " + dateNanos);
System.out.println("instantMillis = " + instantNanos);
System.out.println("instantMillis - dateMillis = " + (instantNanos - dateNanos));
}
}
看来 trucatedTo
正在四舍五入。 javadocs 说它向下舍入。
For example, truncating with the MINUTES unit will round down to the nearest minute, setting the seconds and nanoseconds to zero.
这些会给出不同的结果,因为您在不同的时区“截断到一天”。请注意,当您处于不同的时区时,“截断到一天”会给您不同的答案,因为“午夜”的时刻在不同的时区是不同的。由于 DST 更改,某些时区甚至没有时间 00:00:00。
在 Calendar
版本中,您将截断到系统时区中的日期。请注意,setTimeZone
调用发生在截断之后,因此它实际上并没有做任何有用的事情。
在 Instant
版本中,您将截断到 UTC 中的日期,因为 Instant
s 中没有“时区”的概念。
Instant
只是“错误”,因为您希望它截断为系统时区中的一天。 Instant
并不是真的要那样做。你应该使用 ZonedDateTime
:
ZonedDateTime now = ZonedDateTime.now(); // this gets the current ZonedDateTime, you can also specify a specific zone
System.out.println(now.truncatedTo(ChronoUnit.DAYS).toEpochSecond() * 1_000_000_000L);
另一方面,如果您希望 Calendar
在 UTC 时区中截断,请在截断之前调用 setTimeZone
:
Calendar c = new GregorianCalendar();
c.setTime(new Date());
c.setTimeZone(TimeZone.getTimeZone("UTC"));
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
但我仍然建议您尽可能使用 java.time
:)
您现在应该做的是确定您指的是哪个“午夜”(哪个时区的午夜?)。
我正在尝试从大纪元午夜开始以纳米为单位获取今天的日期(例如 2021 年 2 月 13 日 00:00:00)。
使用 GregorianCalendar/Date 似乎得到了正确的结果。
我使用 Instant/ChronoUnit 的方式是明天午夜给我(例如 2021 年 2 月 14 日 00:00:00)。
这里有什么问题?
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Calendar;
import java.util.TimeZone;
class scratch {
public static void main(String[] args) {
Calendar c = new GregorianCalendar();
c.setTime(new Date());
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
c.setTimeZone(TimeZone.getTimeZone("UTC"));
long dateNanos = c.getTimeInMillis() * 1000000L;
Instant i = Instant.now().truncatedTo(ChronoUnit.DAYS);
long instantNanos = ChronoUnit.NANOS.between(Instant.EPOCH, i);
System.out.println("dateMillis = " + dateNanos);
System.out.println("instantMillis = " + instantNanos);
System.out.println("instantMillis - dateMillis = " + (instantNanos - dateNanos));
}
}
看来 trucatedTo
正在四舍五入。 javadocs 说它向下舍入。
For example, truncating with the MINUTES unit will round down to the nearest minute, setting the seconds and nanoseconds to zero.
这些会给出不同的结果,因为您在不同的时区“截断到一天”。请注意,当您处于不同的时区时,“截断到一天”会给您不同的答案,因为“午夜”的时刻在不同的时区是不同的。由于 DST 更改,某些时区甚至没有时间 00:00:00。
在 Calendar
版本中,您将截断到系统时区中的日期。请注意,setTimeZone
调用发生在截断之后,因此它实际上并没有做任何有用的事情。
在 Instant
版本中,您将截断到 UTC 中的日期,因为 Instant
s 中没有“时区”的概念。
Instant
只是“错误”,因为您希望它截断为系统时区中的一天。 Instant
并不是真的要那样做。你应该使用 ZonedDateTime
:
ZonedDateTime now = ZonedDateTime.now(); // this gets the current ZonedDateTime, you can also specify a specific zone
System.out.println(now.truncatedTo(ChronoUnit.DAYS).toEpochSecond() * 1_000_000_000L);
另一方面,如果您希望 Calendar
在 UTC 时区中截断,请在截断之前调用 setTimeZone
:
Calendar c = new GregorianCalendar();
c.setTime(new Date());
c.setTimeZone(TimeZone.getTimeZone("UTC"));
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
但我仍然建议您尽可能使用 java.time
:)
您现在应该做的是确定您指的是哪个“午夜”(哪个时区的午夜?)。