48

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)

Dave
  • 28,833
  • 23
  • 113
  • 183
  • possible duplicate of [Daylight saving time and time zone best practices](http://stackoverflow.com/questions/2532729/daylight-saving-time-and-time-zone-best-practices) – vascowhite Oct 28 '13 at 07:54

1 Answers1

142

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.

Community
  • 1
  • 1
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • Is there a good way to deal with checking to see if the date passed, or other simple 'checks' like that? Previously, if stored all dates in UTC, it was simple because I could check against server time. But now, it seems to require additional steps... any advice? – Dave Oct 28 '13 at 14:59
  • 1
    If you store the next immediate projected UTC time, then you can use this to query if an event is upcoming or has passed. If passed, then you might want to write a new record for the past event in a different table and then update your UTC time to the next recurring instance. – Matt Johnson-Pint Oct 28 '13 at 15:26
  • You can have the recurring event scheduled with local time, and past instances of each occurrence in UTC. But try to think of these as separate entities in your domain. – Matt Johnson-Pint Oct 28 '13 at 15:28
  • 2
    Of the five items, the last two can always be calculated from the first three, so if you decide to store them, I would consider them as _cached_ values. – musiphil Aug 14 '15 at 02:14
  • @musiphil - Exactly. And the cache always expires if you update the tz rules. :) – Matt Johnson-Pint Aug 14 '15 at 02:29
  • Great Answer @MattJohnson, Would have given you +1000, if allowed. Thanks for clear explanation. – Mangu Singh Rajpurohit Sep 06 '16 at 10:24
  • @MattJohnson Everything is clear, but what do you mean when you say "The next immediate UTC date and time equivalent,...." ? – Legends Nov 07 '17 at 21:39
  • @Legends - as in, if the task is running at 12:00:00 PM Pacific daily, and today is `2017-11-07`, then the next run would be `2017-11-07T20:00:00Z`. – Matt Johnson-Pint Nov 07 '17 at 23:32
  • 1
    This is a very informative and well-structured post. Thank you @MattJohnson-Pint – Deep Jan 16 '20 at 05:45