chood.net/blog/

Go’s clever time format strings

Monday, November 25, 2024

Many programming languages have specialized format strings which make it easier to specify how time and date strings should be parsed or generated. I find Go’s approach particularly clever and practical.

The most familiar format string is probably that of the Unix date command [ref.]:

date '+%a %Y-%m-%d %H:%M:%S'
# Mon 2024-11-25 13:01:09

This format has been adopted in various environments, sometimes with small changes or enhancements. Python is one such language [ref.]:

print(datetime.now().strftime("%a %Y-%m-%d %H:%M:%S"))
# Mon 2024-11-25 13:01:09

Other languages have their own format strings. For example, Java [ref.]:

LocalDateTime date = LocalDateTime.now();
String pattern = "E yyyy-MM-dd HH:mm:ss";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
System.out.Println(date.format(formatter));
// Mon 2024-11-25 13:01:09

PHP [ref.]:

echo date('D Y-m-d H:i:s');
// Mon 2024-11-25 13:01:09

These languages’ format strings provide a concise way to specify the format of a generated time/date string. The alternative of specifying dates with a list of tagged unions would be more cumbersome, albeit more type-safe.

Go’s standard library also uses strings to specify how time and dates should be displayed. However, the language does not use special characters in its format strings like other languages do. Instead, the designers of the time package chose a special timestamp which programmers must write in the format they want for all dates.

The package’s special timestamp is Monday, January 6, 2006, 3:04:05 pm MST.

This is what it looks like in action:

fmt.Println(time.Now().Format("Mon 2006-01-02 15:04:05"))
// Mon 2024-11-25 13:01:09

This 2006 date was chosen very specifically so that every component of the layout can be uniquely identified. For example, Americans generally prefer to write dates in the order month–day–year, whereas most of the rest of the world uses day–month–year. This means that a date written like 3/10/2024 is ambiguous: am I referring to March 10th or October 3rd?

Every numerical component of Go’s special 2006 date has a distinct value:

  1. Month (January)
  2. Day
  3. Hour (3 pm)
  4. Minute
  5. Second
  6. Year (2006)
  7. Time zone (MST, i.e. UTC-07)

The hour is pm so that we can differentiate between 12-hour and 24-hour (“military”) time. By choosing 3 pm, the hour is written as either 3 or 15, thus disambiguating.

Because all the numbers are a single digit, it is perfectly clear when we want single-digit values to be zero-prefixed (e.g., 1/1/2025 vs. 01/01/2025). Exception: you will always have two-digit hours when using 24-hour time.

Go’s package documentation provides a full description of how these layouts are understood.

Go’s approach is advantageous because the format is written out in the source code the same way it will be displayed. Like all format specifiers, you can’t do everything—and Go is constraining itself much more than other languages where you can always have more special character sequences. But for my purposes, what Go supports is more than enough, and it’s a lot more convenient than having to look up every sequence in the documentation.

Go’s approach to formatting timestamps is a good example of the language’s philosophy, where simplicity takes strong precedence over extensibility.