How are dates encoded and decoded in JSON, and why are they 31 years off?

2.5.2 · L1 · Interview

Predict first

What does the default encoding write for the Unix epoch?

let e = JSONEncoder()
print(String(decoding: try e.encode(Date(timeIntervalSince1970: 0)), as: UTF8.self))
  • 0
  • -978307200
  • "1970-01-01T00:00:00Z"

Predict first

What does this print?

let e = JSONEncoder()
e.dateEncodingStrategy = .iso8601
print(String(decoding: try e.encode(["d": Date(timeIntervalSince1970: 0)]), as: UTF8.self))
  • {"d":0}
  • {"d":"1970-01-01T00:00:00Z"}

Mental model. A date in JSON is just a number or a string. The strategy is the agreement between both sides about what that number means.

JSON has no date type, so you choose a dateEncodingStrategy and dateDecodingStrategy. The default, .deferredToDate, writes a Double of seconds since the 2001 reference date, not the 1970 Unix epoch. A server that expects Unix seconds then reads dates about 31 years early. Pick a strategy deliberately: .iso8601, .secondsSince1970, .millisecondsSince1970, .formatted(_), or .custom.

ISO 8601 is the safest wire format because it carries the zone. Be careful with fractional seconds (…:00.123Z): older Foundation’s .iso8601 rejected them, newer versions accept them. If you must handle both, write a .custom strategy that tries each formatter, and test on your oldest supported OS.

The default is seconds since 2001, so the 1970 epoch is 978,307,200 seconds before it.

With .iso8601 the date becomes a UTC string with a zone designator.

Common traps

  • Leaving the default and talking to a Unix-epoch server. Everything is shifted by 978,307,200 seconds.
  • Assuming ISO 8601 means one format. With or without fractional seconds and zone offsets are different strings.

They might follow up with

  • Which strategy preserves millisecond precision? .millisecondsSince1970, or ISO 8601 with fractional seconds.
  • Where do you set it? On the encoder and decoder instances, so both ends agree. See the DateFormatter question for parsing the strings.

Related questions

  • 2.5.3 Which JSONEncoder and JSONDecoder options matter beyond keys and dates?
  • 2.2.8 What goes wrong with DateFormatter, and how do you parse a fixed format safely?

← What does keyDecodingStrategy do, and when does convertFromSnakeCase…   Which JSONEncoder and JSONDecoder options matter beyond keys and… →