Convert UnixTime to DateTimeOffset with a custom System.Text.Json Converter

Convert UnixTime to DateTimeOffset with a custom System.Text.Json Converter

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!


Comments

Twitter Facebook LinkedIn WhatsApp