
This article was written in 2023. Versions, tools and recommendations may have changed since then. Newer posts are collected under .NET.
Some APIs do not follow standards, ignore ISO8601 and return UnixTime. This is not nice, but can be easily fixed with a custom converter for System.Text.Json.
The converter is quite simple. It expects a number and converts it into a DateTimeOffset when reading - and vice versa when writing. For compatibility reasons, the converter should not only support the resolution of seconds, because some return Unix Time as milliseconds.
1/// <summary>
2/// Converts Unix time to nullable DateTimeOffset.
3/// </summary>
4public class UnixToNullableDateTimOffsetConverter : JsonConverter<DateTimeOffset?>
5{
6 /// <summary>
7 /// Minimum Unix time in seconds.
8 /// </summary>
9 private static readonly long s_unixMinSeconds = DateTimeOffset.MinValue.ToUnixTimeSeconds();
10
11 /// <summary>
12 /// Maximum Unix time in seconds.
13 /// </summary>
14 private static readonly long s_unixMaxSeconds = DateTimeOffset.MaxValue.ToUnixTimeSeconds();
15
16 /// <summary>
17 /// Determines if the time should be formatted as seconds. False if resolved as milliseconds.
18 /// </summary>
19 public bool FormatAsSeconds { get; init; } = true;
20
21 /// <summary>
22 /// Reads and converts the JSON to type T.
23 /// </summary>
24 public override DateTimeOffset? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
25 {
26 try
27 {
28 if (reader.TryGetInt64(out long time))
29 {
30 // If FormatAsSeconds is not specified, the correct type is derived depending on whether
31 // the value can be represented as seconds within the .NET DateTimeOffset min/max range 0001-1-1 to 9999-12-31.
32
33 // Since this is a 64-bit value, the Unixtime in seconds may exceed
34 // the 32-bit min/max restrictions 1/1/1970-1-1 to 1/19/2038-1-19.
35 if (FormatAsSeconds || !FormatAsSeconds && time > s_unixMinSeconds && time < s_unixMaxSeconds)
36 {
37 return DateTimeOffset.FromUnixTimeSeconds(time);
38 }
39
40 return DateTimeOffset.FromUnixTimeMilliseconds(time);
41 }
42 }
43 catch
44 {
45 // TryGetInt64 still can throw exceptions if valid is invalid (e.g. no number)
46
47 }
48
49 return null;
50 }
51
52 /// <summary>
53 /// Writes the converted value to JSON.
54 /// </summary>
55 public override void Write(Utf8JsonWriter writer, DateTimeOffset? value, JsonSerializerOptions options)
56 {
57 if (value is DateTimeOffset date)
58 {
59 if (FormatAsSeconds)
60 {
61 writer.WriteNumberValue(date.ToUnixTimeSeconds());
62 }
63 else
64 {
65 writer.WriteNumberValue(date.ToUnixTimeMilliseconds());
66 }
67 }
68 else
69 {
70 writer.WriteNullValue();
71 }
72 }
73}The obligatory test
1public class UnixToNullableDateTimOffsetConverterTests
2{
3 private readonly UnixToNullableDateTimOffsetConverter _converter = new();
4
5 [Fact]
6 public void TestRead()
7 {
8 string json = "1619827200"; // Unix timestamp for 2021-05-01
9 Utf8JsonReader reader = new(System.Text.Encoding.UTF8.GetBytes(json));
10 reader.Read();
11
12 DateTimeOffset? result = _converter.Read(ref reader, typeof(DateTimeOffset?), new JsonSerializerOptions());
13
14 Assert.Equal(new DateTimeOffset(2021, 5, 1, 0, 0, 0, TimeSpan.Zero), result);
15 }
16
17 [Fact]
18 public void TestWrite()
19 {
20 ArrayBufferWriter<byte> buffer = new();
21 Utf8JsonWriter writer = new(buffer);
22 DateTimeOffset date = new(2021, 5, 1, 0, 0, 0, TimeSpan.Zero);
23
24 _converter.Write(writer, date, new JsonSerializerOptions());
25 writer.Flush();
26
27 string json = System.Text.Encoding.UTF8.GetString(buffer.WrittenSpan);
28 Assert.Equal("1619827200", json);
29 }
30}Have fun!
Related articles

- 11 min read
Containerizing .NET in 2026 Without a Dockerfile
Most .NET Dockerfiles repeat the same five steps: restore, publish, select the matching Microsoft runtime image, copy the output and start …

- 12 min read
Problem Details in ASP.NET Core and .NET 11
builder.Services.AddProblemDetails() registers infrastructure. It does not create an error contract. An ASP.NET Core request can fail during …

- 11 min read
Resilience in .NET with Microsoft.Extensions.Resilience
Retries are the most visible resilience strategy and the easiest one to misuse. Repeating a failing call can recover from a short network …
Let's Work Together
Looking for an experienced Platform Architect or Engineer for your next project? Whether it's cloud migration, platform modernization or building new solutions from scratch - I'm here to help you succeed.
