Avoid legacy date-time classes
The old legacy date-time classes are poorly designed, confusing, and troublesome. Avoid them. Now supplanted by the java.time classes.
Work in UTC
Your core problem is trying to store date-time values in a zoned date-time. Generally speaking, the best practice is to store date-time values in UTC rather than any time zone.
The length of the appointments, the span of time not yet attached to the timeline, should be handled as a Duration
object. Then you can apply that to various moments by calling plus
and minus
methods for date-time math.
Duration duration = Duration.ofHours( 8 );
The start time of each expected appointment can be handled as a LocalTime
object, a class representing a time-of-day without a date and without a time zone.
LocalTime startTime = LocalTime.of( 20 , 0 ); // 20:00:00 (8 PM).
Each date for which you want to create an appointment can be represented as a LocalDate
, a class for a date-only value without a time-of-day and without a time zone.
LocalDate startDate = LocalDate.of( 2015 , Month.MARCH , 7 ); // 2015-03-07
Lastly, to create an appointment we need a time zone. The pieces above, start time, date, and duration, all have no time zone and so have no meaning. For example, 8 PM in Auckland NZ is not at all the same moment as 8 PM in Kolkata IN, and different again than 8 PM in Paris FR.
You apparently intend these appointments for the time zone of central North America. Never use the 3-4 letter abbreviations for time zones as these are not true time zones, not standardized, and are not even unique(!). Use proper time zone names in the format of continent/region
. Perhaps America/Chicago
in your case.
ZoneId z = ZoneId.of( "America/Chicago" );
These are what you should store as your core data. Note that we do not need to store the ending time.
LocalDate
– When the appointment should start
LocalTime
– When the appointment should start
ZoneID
– Time zone in which that date and time makes sense
Duration
– How long the appointment should be
The rules for time zones change often, quite often. Politicians have a surprising predilection for re-defining Daylight Saving Time (DST), or otherwise re-defining their time zone, and often do so with little advance warning. So determining appointments into the future is tricky business. Best to delay determining the appointments until you really need to. For example, wait until generating a calendar to present to the user.
When your are ready to determine an appointment’s start and stop times as seen through the lens of some region’s wall-clock time, combine those parts above to generate a ZonedDateTime
object.
ZonedDateTime zdtStart = ZonedDateTime.of( startDate , startTime , z ); // 8 PM
ZonedDateTime zdtStop = zdtStart.plus( duration ); // 4 AM, or 3 AM, or 5 AM depending on DST.
The key to the solution here is that the ending time fell out naturally. The ZonedDateTime
class handles the issue of determining if the time-of-day should be 4 AM on most days, or 5 AM when Daylight Saving Time (DST) engages, or 3 AM when DST disengages. Determining that particular time of day, 4 AM, 3 AM, or 5 AM, is a result, an output. The appointments are all 8 hours long, all have the same duration, regardless of the wall-clock time seen at each end. In other words, do not focus on the ending time-of-day.
Power tip: Stop thinking in any particular time zone. Think of UTC as the “One True Time”. We apply a time zone only to report a particular region’s wall-clock time to the user. Applying a time zone is kind of like localization - we need it for presentation to the user, not for tracking core data.
If you want to track this pair as an interval of time, add the ThreeTen-Extra library to your project to use its Interval
class. The interval handles a pair of Instant
objects rather than the ZonedDateTime
objects seen above.
The Instant
class represents a moment on the timeline in UTC with a resolution of nanoseconds. That means up to nine (9) digits of a decimal fraction. Think of an Instant
as a ZonedDateTime
stripped of it assigned time zone. In other words, conceptually you can think of it this way: ZonedDateTime
= ( Instant
+ ZoneId
). We can extract an Instant
from each of our pair of ZonedDateTime
objects.
Interval interval = Interval.of( zdtStart.toInstant() , zdtStop.toInstant() );
Note that common practice in date-time work is to define a span of time like this Interval
using the Half-Open approach where the beginning is inclusive while the ending is exclusive. So in your example the appointment starts at 8 PM and runs up to, but does not include, 4 AM the next day. This avoid the problem of determining the last inclusive moment with a fractional second.
The Interval
class methods you may find quite handy such as comparing for overlaps
, abuts
, encloses
, and contains
.
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the troublesome old date-time classes such as java.util.Date
, .Calendar
, & java.text.SimpleDateFormat
.
The Joda-Time project, now in maintenance mode, advises migration to java.time.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations.
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP (see How to use…).
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval
, YearWeek
, YearQuarter
, and more.