
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