Daylight saving time and time zone best practices [closed]
I am hoping to make this question and the answers to it the definitive guide to dealing with daylight saving time, in particular for dealing with the actual change overs.
If you have anything to add, please do
Many systems are dependent on keeping accurate time, the problem is with changes to time due to daylight savings - moving the clock forward or backwards.
For instance, one has business rules in an order taking system that depend on the time of the order - if the clock changes, the rules might not be as clear. How should the time of the order be persisted? There are of course an endless number of scenarios - this one is simply an illustrative one.
- How have you dealt with the daylight saving issue?
- What assumptions are part of your solution? (looking for context here)
As important, if not more so:
- What did you try that did not work?
- Why did it not work?
I would be interested in programming, OS, data persistence and other pertinent aspects of the issue.
General answers are great, but I would also like to see details especially if they are only available on one platform.
Summary of answers and other data: (please add yours)
Do:
- Whenever you are referring to an exact moment in time, persist the time according to a unified standard that is not affected by daylight savings. (GMT and UTC are equivalent with this regard, but it is preferred to use the term UTC. Notice that UTC is also known as Zulu or Z time.)
- If instead you choose to persist a (past) time using a local time value, include the local time offset for this particular time from UTC (this offset may change throughout the year), such that the timestamp can later be interpreted unambiguously.
- In some cases, you may need to store both the UTC time and the equivalent local time. Often this is done with two separate fields, but some platforms support a
datetimeoffset
type that can store both in a single field. - When storing timestamps as a numeric value, use Unix time - which is the number of whole seconds since
1970-01-01T00:00:00Z
(excluding leap seconds). If you require higher precision, use milliseconds instead. This value should always be based on UTC, without any time zone adjustment. - If you might later need to modify the timestamp, include the original time zone ID so you can determine if the offset may have changed from the original value recorded.
- When scheduling future events, usually local time is preferred instead of UTC, as it is common for the offset to change. See answer, and blog post.
- When storing whole dates, such as birthdays and anniversaries, do not convert to UTC or any other time zone.
- When possible, store in a date-only data type that does not include a time of day.
- If such a type is not available, be sure to always ignore the time-of-day when interpreting the value. If you cannot be assured that the time-of-day will be ignored, choose 12:00 Noon, rather than 00:00 Midnight as a more safe representative time on that day.
- Remember that time zone offsets are not always an integer number of hours (for example, Indian Standard Time is UTC+05:30, and Nepal uses UTC+05:45).
- If using Java, use java.time for Java 8 and later.
- Much of that java.time functionality is back-ported to Java 6 & 7 in the ThreeTen-Backport library.
- Further adapted for early Android (< 26) in the ThreeTenABP library.
- These projects officially supplant the venerable Joda-Time, now in maintenance-mode. Joda-Time, ThreeTen-Backport, ThreeTen-Extra, java.time classes, and JSR 310 are led by the same man, Stephen Colebourne.
- If using .NET, consider using Noda Time.
- If using .NET without Noda Time, consider that
DateTimeOffset
is often a better choice thanDateTime
. - If using Perl, use DateTime.
- If using Python, use pytz or dateutil.
- If using JavaScript, use moment.js with the moment-timezone extension.
- If using PHP > 5.2, use the native time zones conversions provided by
DateTime
, andDateTimeZone
classes. Be careful when usingDateTimeZone::listAbbreviations()
- see answer. To keep PHP with up to date Olson data, install periodically the timezonedb PECL package; see answer. - If using C++, be sure to use a library that uses the properly implements the IANA timezone database. These include cctz, ICU, and Howard Hinnant's "tz" library. In C++20 the latter is adopted into the standard
<chrono>
library.- Do not use Boost for time zone conversions. While its API claims to support standard IANA (aka "zoneinfo") identifiers, it crudely maps them to POSIX-style data, without considering the rich history of changes each zone may have had. (Also, the file has fallen out of maintenance.)
- If using Rust, use chrono.
- Most business rules use civil time, rather than UTC or GMT. Therefore, plan to convert UTC timestamps to a local time zone before applying application logic.
- Remember that time zones and offsets are not fixed and may change. For instance, historically US and UK used the same dates to 'spring forward' and 'fall back'. However, in 2007 the US changed the dates that the clocks get changed on. This now means that for 48 weeks of the year the difference between London time and New York time is 5 hours and for 4 weeks (3 in the spring, 1 in the autumn) it is 4 hours. Be aware of items like this in any calculations that involve multiple zones.
- Consider the type of time (actual event time, broadcast time, relative time, historical time, recurring time) what elements (timestamp, time zone offset and time zone name) you need to store for correct retrieval - see "Types of Time" in this answer.
- Keep your OS, database and application tzdata files in sync, between themselves and the rest of the world.
- On servers, set hardware clocks and OS clocks to UTC rather than a local time zone.
- Regardless of the previous bullet point, server-side code, including web sites, should never expect the local time zone of the server to be anything in particular. see answer.
- Prefer working with time zones on a case-by-case basis in your application code, rather than globally through config file settings or defaults.
- Use NTP services on all servers.
- If using FAT32, remember that timestamps are stored in local time, not UTC.
- When dealing with recurring events (weekly TV show, for example), remember that the time changes with DST and will be different across time zones.
- Always query date-time values as lower-bound inclusive, upper-bound exclusive (
>=
,<
).
Don't:
- Do not confuse a "time zone", such as
America/New_York
with a "time zone offset", such as-05:00
. They are two different things. See the timezone tag wiki. - Do not use JavaScript's
Date
object to perform date and time calculations in older web browsers, as ECMAScript 5.1 and lower has a design flaw that may use daylight saving time incorrectly. (This was fixed in ECMAScript 6 / 2015). - Never trust the client's clock. It may very well be incorrect.
- Don't tell people to "always use UTC everywhere". This widespread advice is shortsighted of several valid scenarios that are described earlier in this document. Instead, use the appropriate time reference for the data you are working with. (Timestamping can use UTC, but future time scheduling and date-only values should not.)
Testing:
- When testing, make sure you test countries in the Western, Eastern, Northern and Southern hemispheres (in fact in each quarter of the globe, so 4 regions), with both DST in progress and not (gives 8), and a country that does not use DST (another 4 to cover all regions, making 12 in total).
- Test transition of DST, i.e. when you are currently in summer time, select a time value from winter.
- Test boundary cases, such as a timezone that is UTC+12, with DST, making the local time UTC+13 in summer and even places that are UTC+13 in winter
- Test all third-party libraries and applications and make sure they handle time zone data correctly.
- Test half-hour time zones, at least.
Reference:
- The detailed
timezone
tag wiki page on Stack Overflow - Olson database, aka Tz_database
- IETF draft procedures for maintaining the Olson database
- Sources for Time Zone and DST
- ISO format (ISO 8601)
- Mapping between Olson database and Windows Time Zone Ids, from the Unicode Consortium
- Time Zone page on Wikipedia
- StackOverflow questions tagged
dst
- StackOverflow questions tagged
timezone
- Dealing with DST - Microsoft DateTime best practices
- Network Time Protocol on Wikipedia
Other:
- Lobby your representative to end the abomination that is DST. We can always hope...
- Lobby for Earth Standard Time
I'm not sure what I can add to the answers above, but here are a few points from me:
Types of times
There are four different times you should consider:
- Event time: eg, the time when an international sporting event happens, or a coronation/death/etc. This is dependent on the timezone of the event and not of the viewer.
- Television time: eg, a particular TV show is broadcast at 9pm local time all around the world. Important when thinking about publishing the results (of say American Idol) on your website
- Relative time: eg: This question has an open bounty closing in 21 hours. This is easy to display
- Recurring time: eg: A TV show is on every Monday at 9pm, even when DST changes.
There is also Historic/alternate time. These are annoying because they may not map back to standard time. Eg: Julian dates, dates according to a Lunar calendar on Saturn, The Klingon calendar.
Storing start/end timestamps in UTC works well. For 1, you need an event timezone name + offset stored along with the event. For 2, you need a local time identifier stored with each region and a local timezone name + offset stored for every viewer (it's possible to derive this from the IP if you're in a crunch). For 3, store in UTC seconds and no need for timezones. 4 is a special case of 1 or 2 depending on whether it's a global or a local event, but you also need to store a created at timestamp so you can tell if a timezone definition changed before or after this event was created. This is necessary if you need to show historic data.
Storing times
- Always store time in UTC
- Convert to local time on display (local being defined by the user looking at the data)
- When storing a timezone, you need the name, timestamp and the offset. This is required because governments sometimes change the meanings of their timezones (eg: the US govt changed DST dates), and your application needs to handle things gracefully... eg: The exact timestamp when episodes of LOST showed both before and after DST rules changed.
Offsets and names
An example of the above would be:
The soccer world cup finals game happened in South Africa (UTC+2--SAST) on July 11, 2010 at 19:00 UTC.
With this information, we can historically determine the exact time when the 2010 WCS finals took place even if the South African timezone definition changes, and be able to display that to viewers in their local timezone at the time when they query the database.
System Time
You also need to keep your OS, database and application tzdata files in sync, both with each other, and with the rest of the world, and test extensively when you upgrade. It's not unheard of that a third party app that you depend on did not handle a TZ change correctly.
Make sure hardware clocks are set to UTC, and if you're running servers around the world, make sure their OSes are configured to use UTC as well. This becomes apparent when you need to copy hourly rotated apache log files from servers in multiple timezones. Sorting them by filename only works if all files are named with the same timezone. It also means that you don't have to do date math in your head when you ssh from one box to another and need to compare timestamps.
Also, run ntpd on all boxes.
Clients
Never trust the timestamp you get from a client machine as valid. For example, the Date: HTTP headers, or a javascript Date.getTime()
call. These are fine when used as opaque identifiers, or when doing date math during a single session on the same client, but don't try to cross-reference these values with something you have on the server. Your clients don't run NTP, and may not necessarily have a working battery for their BIOS clock.
Trivia
Finally, governments will sometimes do very weird things:
Standard time in the Netherlands was exactly 19 minutes and 32.13 seconds ahead of UTC by law from 1909-05-01 through 1937-06-30. This time zone cannot be represented exactly using the HH:MM format.
Ok, I think I'm done.
This is an important and surprisingly tough issue. The truth is that there is no completely satisfying standard for persisting time. For example, the SQL standard and the ISO format (ISO 8601) are clearly not enough.
From the conceptual point of view, one usually deals with two types of time-date data, and it's convenient to distinguish them (the above standards do not) : "physical time" and "civil time".
A "physical" instant of time is a point in the continuous universal timeline that physics deal with (ignoring relativity, of course). This concept can be adequately coded-persisted in UTC, for example (if you can ignore leap seconds).
A "civil" time is a datetime specification that follows civil norms: a point of time here is fully specified by a set of datetime fields (Y,M,D,H,MM,S,FS) plus a TZ (timezone specification) (also a "calendar", actually; but lets assume we restrict the discussion to Gregorian calendar). A timezone and a calendar jointly allow (in principle) to map from one representation to another. But civil and physical time instants are fundamentally different types of magnitudes, and they should be kept conceptually separated and treated differently (an analogy: arrays of bytes and character strings).
The issue is confusing because we speak of these types events interchangeably, and because the civil times are subject to political changes. The problem (and the need to distinguish these concepts) becomes more evident for events in the future. Example (taken from my discussion here.
John records in his calendar a reminder for some event at datetime
2019-Jul-27, 10:30:00
, TZ=Chile/Santiago
, (which has offset GMT-4,
hence it corresponds to UTC 2019-Jul-27 14:30:00
). But some day
in the future, the country decides to change the TZ offset to GMT-5.
Now, when the day comes... should that reminder trigger at
A) 2019-Jul-27 10:30:00 Chile/Santiago
= UTC time 2019-Jul-27 15:30:00
?
or
B) 2019-Jul-27 9:30:00 Chile/Santiago
= UTC time 2019-Jul-27 14:30:00
?
There is no correct answer, unless one knows what John conceptually meant
when he told the calendar "Please ring me at 2019-Jul-27, 10:30:00
TZ=Chile/Santiago
".
Did he mean a "civil date-time" ("when the clocks in my city tell 10:30")? In that case, A) is the correct answer.
Or did he mean a "physical instant of time", a point in the continuus line of time of our universe, say, "when the next solar eclipse happens". In that case, answer B) is the correct one.
A few Date/Time APIs get this distinction right: among them, Jodatime, which is the foundation of the next (third!) Java DateTime API (JSR 310).