Reputation: 20837
I haven't used the java.time
API very much because mostly my work involves capturing, storing, and displaying dates and times without manipulating them in any way. But occasionally, I have to do something like:
I've also decided that, based upon other unrelated experiences, humans tend to consider "30 days in the past" to actually mean "the start of the day 30 days in the past" not just "now minus 30*24*60*60*1000
milliseconds in the past" which is super-easy to do with just System.currentTimeMillis and arithmetic.
So my requirement is:
If I were to do this with the old API, I would do this:
TimeZone zone = ...; // However I get my time zone
Locale locale = ...; // However I get my locale
Calendar now = Calendar.getInstance(zone, locale);
// Reset to midnight
now.set(Calendar.HOUR, 0);
now.set(Calendar.MINUTE, 0);
now.set(Calendar.SECOND, 0);
now.set(Calendar.MILLISECOND, 0);
// Now go back 30 days
now.add(Calendar.DAY, -30);
// done
Aside from the repeated calls to blank-out the time part of the Calendar
, it's pretty straightforward. It uses only 3 classes, and two of them are the TimeZone
and Locale
classes, only one of which is really necessary (TimeZone
).
This time around, I decided to use java.time
because, well, it's "better." I really don't know my way around it, so I did a lot of internet searching to figure out how to do various things, and I finally came up with the code below. Isn't this supposed to be "easier?"
TimeZone zone = ...; // However I get my time zone
ZoneId zid = zone.toZoneId(); // Convert to new kind of time zone
ZonedDateTime date = ZonedDateTime.now(zid);
date = date.minus(Period.ofDays(30)); // Rewind 30 days
date = date.truncatedTo(ChronoUnit.DAYS);
Instant instant = date.toInstant(); // Convert to Instant for comparisons
Now I'm using 6 different classes. Okay, 2 of them are constant-type things so we can ignore them. I have to use Instant
because all the dates I have to use for comparison are java.util.Date
, and Instant
is the class that bridges all the gaps.
Is the problem that this is a "simple problem" and java.time
is intended to solve problems that are far more complicated?
Upvotes: 1
Views: 710
Reputation: 338266
ZoneId zTokyo = ZoneId.of( "Asia/Tokyo" ) ; // Date varies around the globe by time zone.
LocalDate todayTokyo = LocalDate.now( zTokyo ) ; // Capture the current date as seen in a particular time zone.
ZonedDateTime firstMomentOfDateInTokyo = todayTokyo.atStartOfDay( zTokyo ) ; // Determine the first moment of that date in that zone.
Instant firstMomentOfDateInTokyoAsSeenInUtc = firstMomentOfDateInTokyo.toInstant() ;
See this code run live at Ideone.com.
2022-05-24
2022-05-24T00:00+09:00[Asia/Tokyo]
2022-05-23T15:00:00Z
As shown in correct Answer by Thilo, if you want the current date without a time-of-day and without a time zone or offset-from-UTC, use LocalDate
.
LocalDate today = LocalDate.now() ;
The now
method implicitly uses your JVM's current default time zone to determine the date. Be aware that for any given moment, the date varies around the globe by time zone. Right now it is "tomorrow" in Tokyo Japan while simultaneously "yesterday" in Toledo Ohio US.
Best to specify your desired/expected time zone.
ZoneId zTokyo = ZoneId.of( "Asia/Tokyo" ) ;
LocalDate todayTokyo = LocalDate.now( zTokyo ) ;
If you want the first moment of the day on that date as seen in that time zone, use ZonedDateTime
class. Do not assume the day starts at 00:00. Some dates in some time zones may start at another time such as 01:00. Let java.time determine the first moment.
ZonedDateTime firstMomentOfDateInTokyo = todayTokyo.atStartOfDay( zTokyo ) ;
If you want to see what that moment simultaneously looks like with an offset of zero hours-minutes-seconds from UTC, extract an Instant
.
Instant firstMomentOfDateInTokyoAsSeenInUtc = firstMomentOfDateInTokyo.toInstant() ;
You said:
Instant is the class that bridges all the gaps.
You should not be mixing the legacy date-time classes with the modern java.time classes. The old classes are terrible, bloody awful, a masterclass in how not to do object-oriented programming.
If you must interoperate with old code not yet updated for java.time, convert to-and-fro using new to…
/from…
/valueOf
conversion methods added to the old classes. But do not mix legacy & modern within your logic.
Is the problem that this is a "simple problem" and java.time is intended to solve problems that are far more complicated?
No, java.time is appropriate to all business and personal oriented date-time work (perhaps not all scientific or academic work). The legacy classes are a disaster that should be avoided entirely.
The java.time classes are actually utterly simple in the entities they represent. You just need to get real clear on what your needs are, what goals are you trying to accomplish.
Instant
LocalDate
ZonedDateTime
.OffsetDateTime
. This class maps to TIMESTAMP WITH TIME ZONE
in standard SQL.LocalDateTime
. This class maps to TIMESTAMP WITHOUT TIME ZONE
in standard SQL.The legacy classes have equivalents for only two of those: (a) java.util.Date
is replaced by Instant
, and (b) java.util.Calendar
(well, actually java.util.GregorianCalendar
) is replaced by ZonedDateTime
. The legacy classes neglected to represent a date-only, a date with offset, and a date and time without zone/offset. The java.sql.Date
class pretends to represent a date-only, but does not. And java.sql.Timestamp
, well, I never could figure out what that beast was trying to do.
FYI, an offset is merely a number of hours-minutes-seconds ahead or behind UTC. 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, as decided by their politicians.
Upvotes: 2
Reputation: 8983
The purpose of the multiple different classes in java.time
is exactly to model many of the things you mentioned in your question.
humans tend to consider "30 days in the past" to actually mean "the start of the day 30 days in the past"
Right, you don't care about the time, so you don't need a DateTime
, just a LocalDate
.
LocalDate prev = LocalDate.now().minusDays(30);
If you have to convert to an Instant
, then you need to specify a timezone.
Instant asInstant = prev.atStartOfDay(zone).toInstant();
However if you don't care about the time then you probably shouldn't be converting to Instant, just stick with LocalDate
.
I have to use Instant because all the dates I have to use for comparison are java.util.Date
Date
includes a time part. Are you truncating those to the start of day as well? One way to do that and get back a LocalDate
is:
date.toInstant().atZone(zone).toLocalDate();
Upvotes: 6
Reputation: 262474
Get the date 30 days in the past
sounds like
java.time.LocalDate.now().minusDays(30)
Upvotes: 8