2

I am updating a scheduling application with ability to repeat appointment dates into the future.

The appointment future appointment time is figured out by finding the amount of minutes between the original appointment start time and end time. So, for the instance the appointment is 120 mins long. In the below code, a book time is a period of time in which appointments can be made. So, the booktime and the appointments in it are getting copied. This is one iteration of appointment copying.

Calendar beginCalendar = Calendar.getInstance();
beginCalendar.setTime(newBookTime.getStartDate());
beginCalendar.add(Calendar.MINUTE, bookTimeDiffMinutes);

newAppointment.setStartDate(beginCalendar.getTime());
Calendar endCalendar = Calendar.getInstance();
endCalendar.setTime(newAppointment.getStartDate());
endCalendar.add(Calendar.MINUTE, appointmentDiffMinutes);
newAppointment.setStopDate(endCalendar.getTime());

The issue is that on the daylight savings day where CST turns into CDT ... if the start time of the appointment is say 11pm on the CST day and then end time on the CDT day say 2am ... my appointment ends an hour later or earlier (depending if they clocks are turning back or forward). This is because when I add X mins for the appointment ... there is really say 1 less hour that day, because we skip an hour.

So, when I print the dates I can see on the DST day that the time zone changes from CST to CDT:

The book time should be from 20:00-6:00

[STDOUT] The book time start date:Sat Mar 07 20:00:00 CST 2015
[STDOUT] The book time stop date:Sun Mar 08 07:00:00 CDT 2015

whereas on non-daylight savings change day we see:

[STDOUT] The book time start date:Sun Mar 08 20:00:00 CDT 2015
[STDOUT] The book time stop date:Mon Mar 09 06:00:00 CDT 2015

I want to know how I can compensate for this and ensure my appointment and booktime are the correct length on the day that the timezone changes from CST to CDT. If I had a smart way of detecting the change I could add or subtract 60 mins.

Looking for input.

Matt
  • 1,167
  • 1
  • 15
  • 26
  • 1
    I'm confused, do you mean that appointments should be the same duration, or rather that they should end at the same time... If the goal is to have a 10 hour appointment, you have that in both cases in your example... Is the goal that the appointments always end and start at the same times, regardless of which time zone the start or end in? – Rob Marrowstone Jan 11 '12 at 22:21
  • Let's us look at the booktimes above ... you can see on march 8 /9 they book time is from 20:00-6:00 and on the DST switch day the booktime is 20:00-7:00 it is an hour longer. It starts at the right time but ends at a later time. This because this day is actually an hour shorter than all days. But, I want the booktime to end at 6:00. So when I am on this day I should add less minutes to achieve the same thing. Hmmmm, actually now that I think of it, maybe it should be a end later because the time should be the same. Maybe this is working the way I need it to. I'll update later. – Matt Jan 12 '12 at 03:11
  • Hoons you are correct. Answer the question and I'll accept it. Thanks! I had my head buried so deeply in the code that I didn't actually think of the real world. And, yes of course you would want the same appointment duration not the same end time! – Matt Jan 12 '12 at 03:14

3 Answers3

2

Date/time capabilities built in to Java are notoriously horrible. Switch to a much better library, like JodaTime, and you won't have to worry about things like this. Also, JodaTime makes calculations with time much simpler. It almost makes time-related programming "fun".

cdeszaq
  • 30,869
  • 25
  • 117
  • 173
  • I am updating an enterprise application and unfortunately do not have the option of switching, I will definitely looking at it for future use. Thanks for the input! – Matt Jan 12 '12 at 03:07
2

It appears that the goal is to have an appointment of the same duration, regardless of whether the appointment occurs during daylight or standard time, or happens to straddle a time change. Given that, then your code works just fine, the output is reflecting the fact that it's printing with changed offsets: a 10 hour appointment that started at 20:00:00 CST the night before DST takes effect should end at 07:00:00 CDT the next day, since the time zone will have "sprung forward" an hour.

Rob Marrowstone
  • 1,224
  • 8
  • 15
0

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.

Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154