When I first started writing software to handle time, I went into it with a naive perspective that it couldn’t be that hard. After all, it’s just time, and I’ve understood how that worked since elementary school! It took my first daylight savings time transition to disabuse me of that notion. I began daydreaming that one day all systems would fully run on UTC and people would adapt to that as a standard.
- No more writing code to handle time zones in different regions.
- No urgent time zone library updates to account for new government legislation around daylight savings time.
- Being able to add and subtract time without having to account for crossing time zones.
It sounded great to me at the time, and sometimes when I’m neck-deep in tricky code, I feel that way still. In calmer moments though, I’ve learned that’s not a philosophy that serves me. When I talk to people about my birthday, a holiday, or give vague time measurements like “twice a day” or “first thing tomorrow,” I’m not speaking to them about timestamps. I’m conveying an idea that just happens to involve time. Time isn’t just a number; it is communication that is tied to our days and nights and the lived human experience.
A good software product handles, records, and displays time accurately. A great piece of software captures, stores, and displays time and date information in a format that conveys the full idea to the intended recipients. Good software works; great software communicates.
General Guidelines
Back when I first learned that handling time in software products was unexpectedly hard, some of my senior colleagues tried to explain the importance of analyzing and collecting time context, not just the data, but I admit I didn’t fully grasp the distinction. I did understand that working with time had a lot of edge cases, and I struggled with that complexity. To keep me out of the messiest troubles, I was given three basic tenants to follow:
- Do not DIY date/time representations and time manipulation. Rely on frameworks and libraries.
- Use ISO-8601 for all string representations of dates and times that aren’t presented to the user.
- Services should accept dates with any offset or time zone, immediately convert them to UTC, store them in UTC, and return them in UTC.
The first guideline helps prevent slippery slopes around unexpected edge cases. Adding milliseconds seems safe until the first time you encounter a daylight savings time transition.
The second introduces consistency. When working with an international team, the natural way to store and parse dates may differ between individuals. Using a standard reduces confusion and simplifies logic.
The third helps with consistency and avoids time zone complexities by declining to handle them unless strictly necessary. It puts the burden on the application that has the time zone and locale context of the user.
Admittedly, I’ve violated all three of these guidelines at various points.
Breaking the first two has frequently landed me in a pit of despair followed by regretful refactoring. There is nothing quite like realizing that the switch to standard daylight time occurs at midnight in some countries to trip up a piece of otherwise good code.
Breaking the third rule, however, has sometimes been necessary for the system to generate code that is semantically meaningful for the user. It’s easy to write code that accepts twice a day and converts that to 2 timestamps in a 24-hour period, but that isn’t what twice a day means. It could mean 12 hours apart, it could mean at breakfast and at dinner, it could mean when waking and when sleeping.
As I’ve hit many of these cases over the years (sometimes very unexpectedly), I’ve revised that third guideline to:
- Services should accept, store, and return dates in a standard, well-documented format that captures sufficient metadata to fulfill and represent the system or user’s intention.
Most of the time, this is functionally the same. Representing the date in UTC does create a meaningful standardized form of communication. However, the process of determining sufficient metadata is essential for gaining insight into business requests, revealing technical constraints, and creating more robust designs. It generally adds more fields in addition to that timestamp rather than superseding it.
Sufficient Metadata
I could write many examples of time computations getting tricky, but Tom Smith gives one of my favorite overviews of the descent into chaos in The Problem with Time & Time Zones: Computerphile. Some examples include rescheduling phone calls across international boundaries, countries changing their observance of daylight savings time with less than 30 days notice, medical events occurring during air travel for an infant. If it can happen, it may need to be recorded.
That being said, I frequently have to remind myself: Handling time in software is already confusing enough, don’t add unnecessary complexity.
Metadata is the information surrounding the timestamp that adds context to it. Maybe it is the location of the user or the location of the event. Maybe it’s a time zone or context about the action being performed, or maybe it’s a string representation of the time because of auditing requirements that must represent exactly what was entered by the user.
The hard part is finding the correct amount of metadata to record. Too much makes everything more confusing. Too little makes it hard or even impossible to give the user the information they need.
The first step is to determine if your situation is simple. If you’re in one of the following situations, a UTC timestamp is probably fine.
- Times in the system are generated and never displayed to the user.
- Times in the system are historical and not displayed to the user.
- An incorrect time will not significantly impact user experience or business outcomes.
Suppose you are working with times in the future, users who travel, or past dates that are used to derive other information or communicate critical information. In that case, you may need to think a bit more about what data is captured in addition to the timestamp.
I wish I could give a comprehensive guide to determine this, but generally, the best strategy is to ask questions–of yourself, the user, and the other people invested in the application.
Context Questions
Here are some of the key functional questions that I’ve asked to determine what metadata may need to be recorded with the timestamp.
- Why is this timestamp being recorded? Who is recording it?
- Who is reading this timestamp?
- Is the display format of the timestamp important?
- Is this timestamp being compared against other timestamps in the system?
- Is this timestamp used to calculate other timestamps in the system?
- What information had to be collected initially to determine the timestamp?
As an example, it is fairly common in business for a centralized scheduling service to take phone calls and schedule appointments. Sometimes the appointment is a single instance, but sometimes it will recur. They will frequently be scheduling for offices in multiple time zones. Their business pulls reports to see when appointments are scheduled and to create daily schedules for clients.
In this example, there are a lot of dates to consider. So, let’s run through and answer the questions from above.
- The timestamp of the action is recorded by the system for auditing. The timestamp of the appointment is recorded by the scheduler. It is in the time zone of their client, not necessarily the time zone of the scheduler.
- The timestamps are read by the scheduler when they make and confirm appointments. They are read on reports. They are read by the clients when viewing their schedule. They may be read by the customers on their own devices.
- The display format of the timestamp isn’t significant.
- These timestamps may be compared against other dates by the system when generating reports and schedules.
- These timestamps aren’t used to calculate other dates.
- For the appointment to be scheduled, the schedule and availability of the destination had to be known. The client’s location and schedule is unknown.
From this, we can determine that the timestamp of the action can be recorded in UTC without additional context. The system just needs to know when it occurred historically. For the appointment, we need to collect the timestamp and the time zone. The time zone must be recorded and returned because the appointment times need to be converted back to their initial time zone to be meaningful to the scheduler and on reports.
This can either be done directly by associating the appointment with the time zone or by associating the appointment with some location information that contains the time zone. Maybe this location is geographic, such as a city/region, or maybe it’s specific to the building of the appointment.
On most software systems, these are sufficient to properly handle time. But on critical infrastructure systems that are time-sensitive, they may not be.
Complications
We’ve left out the geopolitical aspect of time.
Time zones change in more radical ways and more often than developers might expect. To illustrate this, let’s say that the example system from before uses the IANA Timezone Database exemplar cities to store the time zone information. Let’s also go back in time to 2011.
In December 2011, Samoa switched time zones to the other side of the international date line. Prior to the switch, they were in Samoa Standard Time, which American Samoa also follows. They moved into West Samoa Time (WST) which is a full 24 hours ahead of their prior zone. During the switch, they skipped the full day of December 30, 2011.
This switch created complications for stored events that had a time and zone assigned but were not associated with some form of location data (American Samoa or Samoa).
In our example system, if we had decided to go with a simple exemplar city on the appointment, this would likely create difficulties. However, the more robust location model would likely be more flexible for the change.
This switch was extreme, but big changes do occur in applications with wide international usage. Time zone rules are set and changed by governments often enough that the TZ database typically has several releases in each calendar year.
When I was working on high-availability software, some of the additional metadata I collected at various times included:
- The physical location of the event and user in both GPS coordinates and IANA exemplar cities to use in the case of geopolitical changes
- Frequency descriptors–such as ‘twice a day’ or ‘at mealtimes’
- The format string for the date–to use when the system was generating certificates or auditing reports that had formatting requirements
- Format string for the date–to recreate locale or configuration-based formatting for the date/time
- The version of the time zone database used when the timestamp was recorded
Summary
Handling time and time zones in software can be difficult to conceptualize. The best way to control this complexity is to not do it. Whenever possible, lean on well-tested and supported libraries for your tech stack. If times do need to be converted into local representations or calculated, isolate that logic from other code. If the timestamp alone doesn’t provide appropriate context, ask questions to collect the additional information.
The majority of applications that I have worked in have not needed to store information beyond the time zone and locale. However, as they expanded to new markets, some needed to add location and auditing information as practicalities dictated. Being aware of the other time information that may need to be captured helped hold room in the architecture for later expansion when complexities arose, dates needed adjustments, or new requirements were introduced.
If you’re struggling with date/time complexities in your app, don’t hesitate to reach out. Our team at Keyhole is here to help you simplify and streamline your project. For more insights on navigating coding challenges, check out the Keyhole Dev Blog, where we regularly share tips and best practices from our team of seasoned professionals.