Search code examples
c#datetimeparsingtimestampunix-timestamp

C# DateTime.Parse method losses nanosecond precision of the timestamp string


I need to convert an RFC-3339 formatted timestamp (e.g. "2024-01-04T14:30:00.119919872Z") to a unix nanosecond timestamp (e.g. 1704378600119919872). However, the C# DateTime structures maximum precision is only 100 nanoseconds. For example:

string timestamp = "2024-01-04T14:30:00.119919872Z";
DateTime dt = DateTime.Parse(timestamp);
Console.WriteLine(dt.ToString("O")); // outputs: 2024-01-04T09:30:00.1199199-05:00

So the fractional seconds components of the timestamp get rounded to - 1199199 - the nearest 100 nanoseconds. Apparently, 100 nanoseconds is the DateTime structures precision.

To avoid this loss of precision, I've written my own parsing method, but it's very ugly -

public static long ToUnixNanoseconds(string str)
{
    string dateTimeStr = str[..19];
    DateTime utcDateTime = DateTime.Parse(dateTimeStr + "Z");
    long unixTimeSeconds = (new DateTimeOffset(utcDateTime)).ToUnixTimeSeconds();
    string fractionalSecondsStr = str[19..(str.Length - 1)];
    decimal fractionalSeconds = decimal.Parse(fractionalSecondsStr);
    long unixNano = (long)((unixTimeSeconds + fractionalSeconds) * 1000000000);
    return unixNano;
}

What better ways are there to convert an RFC-3339 timestamp to a unix timestamp with nanosecond precision in C#?


Solution

  • Your parsing method seems okay, if you always expect that same input format and don't want to include a full library just for this one use case (although Noda Time is really good.)

    You could do a little tweaking. Here's my approach:

    public static long ToUnixNanoseconds(ReadOnlySpan<char> timestamp)
    {
        var seconds = DateTimeOffset.Parse(timestamp[0..19], CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).ToUnixTimeSeconds();
        var fractionalSeconds = decimal.Parse(timestamp[19..^1], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture);
        return (long)((seconds + fractionalSeconds) * 1000000000);
    }