How to store repeating dates keeping in mind Daylight Savings Time

Dave picture Dave · Oct 28, 2013 · Viewed 13.4k times · Source

I'm storing events in my database. I have 'start' and 'end' date times, 'tickets_start' and 'tickets_end' (for when ticket sales actually start/end - as opposed to the start/end of the actual event).

So far, I've built methods that do all the fun stuff like converting the date/times to GMT before saving, then back to their respective timezone for display.

I'm storing the timezone in a varchar field with values like "America/New_York".

But - now I need to start dealing with if the user wants to allow repeating events. I've done it before, and it's not that big a deal, but never across multiple timezones.

At first, I thought it'd be no big deal, but then realized that - if the initial start date was in July (as example), and it repeats every month for a year, at some point, Daylight Savings Time will make it so that the conversion from GMT will change a time differently. One month, when converting 12:00, it would change it to -5, and the next, it would change it to -4 because of DST.

My current thought is that I'll store a 'dst' tinyint(1) for whether the start/end dates were entered during DST, and then make a method to alter the time by an hour if/when necessary.

But - figured I'd ask here in hopes maybe there's a "normal" for this or an easy something that I'm not thinking of.

(cakephp 2.4.x)

Answer

Matt Johnson-Pint picture Matt Johnson-Pint · Oct 28, 2013

First, please recognize that in modern terminology you should say UTC instead of GMT. They are mostly equivalent, except that UTC is more precisely defined. Reserve the term GMT to refer to the portion of the time zone in the United Kingdom that is in effect during the winter months having the offset UTC+0.

Now to your question. UTC is not necessarily the best way to store all date and time values. It works particularly well for past events, or for future absolute events, but it doesn't work so great for future local events - especially future recurring events.

I wrote about this on another answer recently, and is one of the few exception cases where local time makes more sense than UTC. The main argument is the "alarm clock problem". If you set your alarm clock by UTC, you'll be waking up an hour early or late on the day of the DST transitions. This is why most folks set their alarm clocks by local time.

Of course, you can't just store a local time if you are working with data from all over the world. You should store a few different things:

  • The local time of the recurring event, such as "08:00"
  • The time zone that the local time is expressed in, such as "America/New_York"
  • The recurrence pattern, in whatever format makes sense for your application, such as daily, bi-weekly, or the third Thursday of the month, etc.
  • The next immediate UTC date and time equivalent, to the best that you can project it.
  • Perhaps, but not always, a list of future event UTC date and times, projected out some predefined period into the future (perhaps a week, perhaps 6 months, perhaps a year or two, depending on your needs).

For the last two, understand that the UTC equivalent of any local date/time can change if the government responsible for that time zone decides to change anything. Since there are multiple time zone database updates every year, you will want to have a plan to subscribe to announcements of updates and update your timezone database regularly. Whenever you update your timezone data, you'll need to recalculate the UTC equivalent times of all future events.

Having the UTC equivalents is important if you plan to show any kind of list of events spanning more than one time zone. Those are the values you'll query to build that list.

Another point to consider is that if an event is scheduled for a local time that occurs during a DST fall-back transition, you will have to decide whether the event occurs on the first instance (usually), or the second instance (sometimes), or both (rarely), and build into your application a mechanism to ensure the event doesn't fire twice unless you want it to.

If you were looking for a simple answer - sorry, but there isn't one. Scheduling future events across time zones is a complicated task.

Alternative Approach

I've had a few people show me a technique where they do use UTC time for scheduling, which is that they pick a starting date in local time, convert that to UTC to be stored, and store the time zone ID as well. Then at runtime, they apply the time zone to convert the original UTC time back to local time, then use that local time to compute the other recurrences, as if it were the one originally stored as above.

While this technique will work, the drawbacks are:

  • If there's a time zone update that changes the local time before the first instance is run, it will throw the whole schedule off. This can be mitigated by choosing a time in the past for the "first" instance, such that the second instance is really the first one.

  • If the time is really a "floating time" that should follow the user around (such as in an alarm clock on a cell phone), you'd still have to store time zone information for the zone it was originally created in - even if that's not the zone you want to run in.

  • It adds additional complexity without any benefit. I'd reserve this technique for situations where you may have had a UTC-only scheduler that you're trying to retrofit time zone support into.