I'm attempting to get today's date in nanos since Epoch at midnight (e.g. 13 Feb 2021 00:00:00).
Using GregorianCalendar/Date appears to get the right result.
The way I'm using Instant/ChronoUnit is giving me tomorrow at midnight (e.g 14 Feb 2021 00:00:00).
What is wrong here?
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));
}
}
It appears that trucatedTo
is rounding up. The javadocs says it rounds down.
For example, truncating with the MINUTES unit will round down to the nearest minute, setting the seconds and nanoseconds to zero.
These give different results because you are "truncating to the day" in different timezones. Note that "truncating to the day" gives you different answers when you are in different timezones, because the instant of "midnight" is different in different timezones. Some timezones don't even have the time 00:00:00 due to DST changes.
In the Calendar
version, you are truncating to the day in your system timezone. Note that the setTimeZone
call happens after the truncation, so it doesn't actually do anything useful.
In the Instant
version, you are truncating to the day in UTC, because Instant
s don't have the concept of a "timezone" in them.
Instant
is only "wrong" because you expected it to truncate to a day in your system timezone. Instant
isn't really meant to do that. You should use a 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);
On the other hand, if you want Calendar
to truncate in the UTC timezone, put the setTimeZone
call before you truncate:
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);
But I would still suggest you use java.time
whenever you can :)
What you should do now is decide which "midnight" you mean (midnight in which timezone?).