Tips for dealing with time
It’s happened. You have to write some code that uses dates and times. You head to the bathroom to have a little cry in private only to realize a coworker was in there the whole time and now you have to explain yourself. Sorry, I lost my train of thought. You’re back from the bathroom, your coworker is back-channeling stories about your little cry, and you’re about to start writing date/time code.
If this is your first rodeo, or your second rodeo, or really it doesn’t matter how much rodeo experience you have, footguns await you (metaphorical footguns unless your code is being used in some kind of self-firing gun which is not a good idea, please stop working on that project). Here are some tips to help you avoid a few of those footguns. You’re still going to shoot yourself in the foot (again, likely metaphorically) because of timezones and daylight saving time, but hopefully these tips will reduce the number of shots to those precious piggies.
Include units in your variable names
You have some variables that will hold periods of time. Maybe timeout
, retryTime
, activationDelay
, etc. When you write the code, you’ll know what units of time those variables hold (e.g. seconds, milliseconds, days). But the next dev to come along, or your future self, won’t know. You’ll have to track down the code where the value is eventually used and hope that it gives some indication of the units. But it isn’t always that clear and either way it’s a waste of time tracking down that logic.
To avoid this, you should always include the units of time in your variable names. For example, timeoutMs
, timeoutSeconds
, retryTimeMinutes
, activationDelayDays
. That way it’s always clear what units of time those variables hold.
You may also be able to use something like branded types to guarantee units. Though that can add some complexity to your app that might not be necessary if you use good naming conventions.
This is good for timestamps too
It’s worth noting that including units in your variable names is also very helpful with timestamps. Since milliseconds and seconds are both used for timestamps, it can be challenging to know which is used in a particular variable. Including the units in your timestamp variables names (even in your database field names) will clear up this ambiguity. For example, createAtMs
, modifiedAtSeconds
, etc.
Include conversions in comments
You have some magic numbers in your code; something like:
const activationDelaySeconds = 10800;
But how long is 10,800 seconds? Well, let’s leave a comment:
const activationDelaySeconds = 10800; // 3 hours
You’ve probably seen comments like this before. The problem is, what if someone comes along and changes the value but forgets to change the comment:
const activationDelaySeconds = 14400; // 3 hours
Unless you can recognize that 14,400 seconds is not 3 hours, you’ll never know that the comment is wrong. Whoever looks at this code will think the activation delay is 3 hours and very likely waste time figuring out why activation isn’t happening at the expected time.
Instead of just commenting the value with an easier-to-read format, include the entire conversion in the comment:
const activationDelaySeconds = 10800; // 10800 seconds = 3 hours
That way if someone comes along and changes the value without changing the comment:
const activationDelaySeconds = 14400; // 10800 seconds = 3 hours
It will be clear to whoever looks at it that 14,400 seconds is not 3 hours. It will also make it harder for the person who made the original change to forget to change the comment.
Some people use multiplication to make the value more clear:
const activationDelaySeconds = 3 * 60 * 60;
This is more clear than a magic number, but it’s slightly harder to scan; you have to pause and perform the math at least a bit before understanding what the value is. It’s also error-prone. It’s easy to mess up the math and hard to notice it’s messed up. I would still leave a comment when using this method to indicate intent.
Use variables for magic numbers
Another option for documenting magic numbers is to do something like this:
const threeHoursInSeconds = 3 * 60 * 60; const activationDelaySeconds = threeHoursInSeconds;
This is self-documenting and it’s unlikely someone will change the value of threeHoursInSeconds
directly because then it won’t be “three hours.”
This isn’t necessarily better than leaving conversion comments. It’s just another option.
Use UTC until you can’t
Working with timezones opens up an exotic variety of opportunities to make mistakes. The easiest way to narrow the surface area for those problems is to not deal with timezones until you absolutely have to. A simple way to do this is to keep your dates in the UTC timezone unless you’re displaying information to the user. At that point, convert it to the user’s local timezone.
This can be a challenge in Javascript because the input and output of the Date object are usually in the “local” timezone. To get around this, it’s often helpful to prefer timestamps over Date objects. Timestamps also have the benefit of being easily serialized and used by other programming languages.
Remember daylight saving time
If you’re ever working with periods of time (comparing dates, scheduling events, etc), remember that daylight saving time exists.
If your date comparison is over a long enough period, you might lose or gain an hour to DST.
If you’re scheduling an event, there’s a chance the event could happen twice if DST falls back an hour, or never happen if DST springs forward an hour.
If you’re writing a unit test that deals with time, it might pass in October and fail in December because of DST.
DST does not start and end on the same dates each year. DST starts on the second Sunday in March at 2am and ends on the first Sunday in November at 2am. Remember that since the clock moves forward by an hour at the beginning of DST, the times between 2:00am and 2:59am never happen on that day (2am becomes 3am immediately). And because the clock moves back an hour at the end of DST, the times between 1:00am and 1:59am happen twice on that day (2am becomes 1am).
You may be able to completely avoid these issues by using dates/times in the UTC timezone.
Be careful around boundaries
If you’re working with a date/time that might end up close to the boundary between one day and the next, be careful. It’s easy to be off by one hour, minute, second, and end up on the “wrong” side of that boundary. The problem with bugs like this is you might not notice it unless you’re testing at the exact right time. A couple of ways to help prevent issues around boundaries is to include unit tests for those boundary times, or to simply think, “How will this work if it runs at 23:59, just before midnight? Or at 0:01, just after midnight?”
Conclusion
As we part company in this tenuously together journey, remember that you will shoot yourself in the foot around dates and times (please don’t build that self-firing gun), and in those moments of suffering it will be difficult to remember that you will have shot yourself in the foot far fewer times with all this togetherly garnered knowledge and so put all your effort into remembering because therein lies some simple hope for a bright side and what better thing to shove in the face of your gossipy coworker (a real Janet, a real Chad) than a bloody foot that very well could have been bloodier.