This site runs best with JavaScript enabled.

How to add time travel to your app


If you write code for a living, it’s likely that you’ve experienced the pain of writing business logic around time. Aside from perhaps international postal codes (which are total disaster), time can be one of the most annoying things to manage. Regardless of your language, the fundamental issues with time are as follows:

Why do we even need time zones?

Before mechanical clocks were invented, none of this time stuff mattered. It was common practice and sufficient to simply mark the time of day by just looking at the sun (e.g. sundial). This was known as “apparent solar time.” This worked well for millenia, even though time was different for every location.

When mechanical clocks arrived in the early 19th century, each city began to use “local mean solar time.” Apparent and mean solar time can differ by around 15 minutes because of the elliptical shape of the Earth’s orbit around the Sun (eccentricity) and the tilt of the Earth’s axis (obliquity). In other words, if our planet didn’t strut around the Sun like the main character in Grand Theft Auto, our lives would be slightly easier as devs.

Greenwich Mean Time (GMT) was established in 1675, as an aid to ships to determine longitude at sea, providing a standard reference time while each city in England kept a different local time.

For around 200 years, time was kind of a mess, but no one really cared. Clocks differed between places by amounts corresponding to the differences in their geographical longitudes, which varied by four minutes of time for every degree of longitude. For example, when it is solar noon in Bristol, it is about 10 minutes past solar noon in London. Again, none of this mattered to anyone though, because you couldn’t travel fast enough to know the difference. That all changed, however, when Great Britain developed its railway system in the mid 1850s. For the first time in history, humans could travel fast enough where time references mattered. Thus, the train companies started to keep GMT to keep their schedules in sync.

By the 1870s there was pressure both to establish a prime meridian for worldwide navigation purposes and to unify local times for railway timetables. Although, Great Britain’s trains were running swimmingly on GMT, the U.S. was a nightmare. Look at this 1857 timetable:

Comparitive Time Table

Fed up with the insanity, President Chester A. Arthur held the International Meridian Conference. in Washington in 1884. At this conference, some people got into a room and DECIDED a common prime meridian for time and longitude throughout the world.

Soon after the conference, the US adopted its five timezones. And by the turn of the century, most countries had some kind of hourly offset of astronomical time. However, it would take another 30 years for the rest of the world to officially adopt timezones based on standard GMT/UTC offsets.

How to structure time in your app

Regardless of your language, or it’s date handling (or lack thereof), it’s a good idea to abstract the entire concept of time throughout your entire application. Extraordinary amounts of pain and sadness can occur if you fail to do this. For example, let’s say you want to write some tests that your Black Friday sale promotion only shows up on Black Friday. If you wrote your app without some kind of time abstraction, it would be impossible to simulate this behavior until the day of. You’d have to fake it with some flags and booleans and hope for the best. Rather than thoughts and prayers, I like to create a single source of truth for time called TimeService in just about every application I write.

In Java, TimeService looks like this:

package com.example.api.services;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
@Singleton
public class TimeService {
private final ZoneId timezone;
private Instant now = null;
@Inject
public TimeService(@Named("timezone") ZoneId timezone) {
this.timezone = timezone;
}
public void setToday(LocalDate today) {
this.setNow(today.atStartOfDay(this.timezone).toInstant());
}
public void setNow(Instant now) {
this.now = now;
}
public Instant getNow() {
return this.now != null ? this.now : Instant.now();
}
public LocalDate getToday() {
return this.toDate(this.getNow());
}
public LocalDate toDate(Instant at) {
return at.atZone(this.timezone).toLocalDate();
}
public Instant toInstant(LocalDate date) {
return date.atStartOfDay(this.timezone).toInstant();
}
}

If I’m on the frontend, I use date-fns to build something almost identical. The only wrinkle is to make sure it works in parallel test environments.

Now, whenever any aspect of the application needs to do any kind of date math, you should inject/bind/reference/import time service.

Once you do this, you can now easily test past, present, and future dates by simply setting timeService.setNow().


Share article

Jared Palmer is a software developer based in New York City. He is the founder of Formium, a developer-focused form builder and also the author of popular open source software including Formik, TSDX, and Razzle.

PreviousGatsby vs. Next.jsNextHow to add AI to your app without knowing anything about AI