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

BEN ABT
medialesson
Published in
3 min readOct 17, 2023

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.

/// <summary>
/// Converts Unix time to nullable DateTimeOffset.
/// </summary>
public class UnixToNullableDateTimOffsetConverter : JsonConverter<DateTimeOffset?>
{
/// <summary>
/// Minimum Unix time in seconds.
/// </summary>
private static readonly long s_unixMinSeconds = DateTimeOffset.MinValue.ToUnixTimeSeconds();

/// <summary>
/// Maximum Unix time in seconds.
/// </summary>
private static readonly long s_unixMaxSeconds = DateTimeOffset.MaxValue.ToUnixTimeSeconds();

/// <summary>
/// Determines if the time should be formatted as seconds. False if resolved as milliseconds.
/// </summary>
public bool FormatAsSeconds { get; init; } = true;

/// <summary>
/// Reads and converts the JSON to type T.
/// </summary>
public override DateTimeOffset? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
try
{
if (reader.TryGetInt64(out long time))
{
// If FormatAsSeconds is not specified, the correct type is derived depending on whether
// the value can be represented as seconds within the .NET DateTimeOffset min/max range 0001-1-1 to 9999-12-31.

// Since this is a 64-bit value, the Unixtime in seconds may exceed
// the 32-bit min/max restrictions 1/1/1970-1-1 to 1/19/2038-1-19.
if (FormatAsSeconds || !FormatAsSeconds && time > s_unixMinSeconds && time < s_unixMaxSeconds)
{
return DateTimeOffset.FromUnixTimeSeconds(time);
}

return DateTimeOffset.FromUnixTimeMilliseconds(time);
}
}
catch
{
// TryGetInt64 still can throw exceptions if valid is invalid (e.g. no number)

}

return null;
}

/// <summary>
/// Writes the converted value to JSON.
/// </summary>
public override void Write(Utf8JsonWriter writer, DateTimeOffset? value, JsonSerializerOptions options)
{
if (value is DateTimeOffset date)
{
if (FormatAsSeconds)
{
writer.WriteNumberValue(date.ToUnixTimeSeconds());
}
else
{
writer.WriteNumberValue(date.ToUnixTimeMilliseconds());
}
}
else
{
writer.WriteNullValue();
}
}
}

The obligatory test

public class UnixToNullableDateTimOffsetConverterTests
{
private readonly UnixToNullableDateTimOffsetConverter _converter = new();

[Fact]
public void TestRead()
{
string json = "1619827200"; // Unix timestamp for 2021-05-01
Utf8JsonReader reader = new(System.Text.Encoding.UTF8.GetBytes(json));
reader.Read();

DateTimeOffset? result = _converter.Read(ref reader, typeof(DateTimeOffset?), new JsonSerializerOptions());

Assert.Equal(new DateTimeOffset(2021, 5, 1, 0, 0, 0, TimeSpan.Zero), result);
}

[Fact]
public void TestWrite()
{
ArrayBufferWriter<byte> buffer = new();
Utf8JsonWriter writer = new(buffer);
DateTimeOffset date = new(2021, 5, 1, 0, 0, 0, TimeSpan.Zero);

_converter.Write(writer, date, new JsonSerializerOptions());
writer.Flush();

string json = System.Text.Encoding.UTF8.GetString(buffer.WrittenSpan);
Assert.Equal("1619827200", json);
}
}

Have fun!

Autor

Benjamin Abt

Ben is a passionate developer and software architect and especially focused on .NET, cloud and IoT. In his professional he works on high-scalable platforms for IoT and Industry 4.0 focused on the next generation of connected industry based on Azure and .NET. He runs the largest german-speaking C# forum myCSharp.de, is the founder of the Azure UserGroup Stuttgart, a co-organizer of the AzureSaturday, runs his blog, participates in open source projects, speaks at various conferences and user groups and also has a bit free time. He is a Microsoft MVP since 2015 for .NET and Azure.

Originally published at https://schwabencode.com on October 17, 2023.

--

--