7

I have an enum TimeFrame that has values like: Yesterday, LastWeek, NextMonth etc.

I'm trying to write a method that takes in a TimeFrame and returns the start and end dates of that period of time.

I looked into the new Java 8 Period class which can take a start time and end time but it doesn't seem there's any clean way to retrieve those values afterwards.

How could I return the start and end date at once cleanly without using a List (seems like the wrong data structure) or some ugly datetime arithmetic?

Jesper
  • 202,709
  • 46
  • 318
  • 350
Mike Medina
  • 97
  • 1
  • 6
  • 1
    Look at [`Interval`](http://www.threeten.org/threeten-extra/apidocs/org/threeten/extra/Interval.html) in the [ThreeTen-Extra](http://www.threeten.org/threeten-extra/) project that extends the [java.time](http://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html) framework. – Basil Bourque Jun 22 '16 at 20:16
  • Do you want date-only or date-time? – Basil Bourque Jun 22 '16 at 22:32
  • I'm trying to capture the start `datetime` and end `datetime` in one data structure – Mike Medina Jun 23 '16 at 10:50
  • For a pair of date-time values, use the [`Interval`](http://www.threeten.org/threeten-extra/apidocs/org/threeten/extra/Interval.html) class found in the [ThreeTen-Extra](http://www.threeten.org/threeten-extra/) library which you can add to your project to extend the java.time classes built into Java. See [my Answer](http://stackoverflow.com/a/37979510/642706) for example. – Basil Bourque Jun 23 '16 at 21:33
  • It seems you are looking for calendar date intervals. If so then my library Time4J with the class [DateInterval](http://time4j.net/javadoc-en/net/time4j/range/DateInterval.html) might be interesting for you. It is also fully interoperable with `java.time`-package. – Meno Hochschild Jun 27 '16 at 14:23

2 Answers2

16

No, a Period doesn't record a start/end - it just represents the amount of time between them, in a calendrical sense (rather than the "precise amount of time" represented by a Duration).

It sounds like basically you should create your own class that has a start date and an end date. You can still use Period to implement that, but you'd have getStartDate() and getEndDate() returning LocalDate values. You shouldn't need to do any heavy date/time arithmetic yourself - just work out the start date and add the appropriate period, or the end date and subtract the appropriate period, depending on the time frame.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    Ended up writing my own DatePair class. I guess sometimes it's just better to do it yourself ^^ thanks! – Mike Medina Jun 22 '16 at 18:02
  • 2
    @MikeMedina: I wouldn't just call it `DatePair`, personally - I'd call it a `DateInterval`, validate that the start is earlier than (or the same as) the end, possibly provide a `contains` method etc. (That means considering whether the start and end are inclusive or exclusive, of course.) Basically, provide some semantics beyond "here are two dates". – Jon Skeet Jun 22 '16 at 18:03
  • Good input! Thanks – Mike Medina Jun 22 '16 at 20:28
1

Time marches on

Be careful about passing around enum values for “yesterday”, “tomorrow”, and so on. That raises a couple of issues, time zone and midnight.

Dates and day-of-week only have meaning in the context of a time zone. For any given moment, the date varies around the globe. For example, a few minutes after midnight in Paris is still “yesterday” in Montréal. So when ever you intend “yesterday” and such, always specify the desired/expected time zone as well.

Each non-atomic command and line of code executes separately from the previous. Each execution takes a moment of time, however brief. At any of those moments midnight in the specified time zone may be rolling over into a new day. At that stroke of midnight, your relative-time flag such as “yesterday” suddenly takes on a whole new meaning. That meaning is likely different that was intended by the earlier code given that conditions (the date) were different when that code began.

So it makes more sense to me to be passing around Instant objects, or perhaps OffsetDateTime or ZonedDateTime objects. These date-time values are frozen, representing a specific moment on the timeline. Your earlier original code can verify the meaning of that value, check that the date is indeed a Friday or some such. After such verification, the value can be passed on to other code. Now you need not worry about strange occasional bugs occurring at midnight.

java.time

You don't really need to build a class or enum to express your intention of relative time such as “yesterday”. The java.time classes built into Java 8 and later have plain-reading methods for adding and subtracting days, weeks, months. These are basically one-liners, so just call these plus… and minus… methods directly.

ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime yesterday = now.minusDays( 1 );
ZonedDateTime weekLater = now.plusWeeks( 1 );

That code is implicitly applying the JVM’s current default time zone. Better to specify.

ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime now = ZonedDateTime.now( zoneId );

You may want the date-only without time-of-day and without time zone. Use LocalDate.

LocalDate weekPrior = now.toLocalDate().minusWeeks( 1 );

You may want to get first moment of the day. Not always the time of 00:00:00.0.

ZonedDateTime yesterdayStart = now.minusDays( 1 ).toLocalDate().atStartOfDay( zoneId );

If you want to represent the span of time defined by a pair of date-time values, look at the Interval class found in the ThreeTen-Extra project that extends the java.time framework. This class tracks a pair of Instant objects which are moments on the timeline in UTC. You can extract an Instant from your ZonedDateTime by calling toInstant.

Interval intervalYesterday = Interval.of( yesterdayStart.toInstant() , yesterdayStart.plusDays( 1 ).toInstant() );

To get from an Instant back to a zoned date-time, apply a ZoneId.

ZonedDateTime zdt = ZonedDateTime( instant , zoneId );

For a date-only interval, you'll need to build your own class that stores a pair of LocalDate objects. I would call it something like DateRange.

Tip: Search "Half-Open" to learn about the practice of tracking spans of time where beginning in inclusive while the ending is exclusive.

Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • Brilliant response, thank you for your input! I have to use terms like `Yesterday` and `LastWeek` because I'm working with Amazon's Alexa which is a voice controlled device which requires the terms that will be spoken to it be predefined. – Mike Medina Jun 24 '16 at 02:10