Aws-sdk-net: DateTime does not support round trip serialization with the DocumentModel

Created on 2 Jul 2014  ·  3Comments  ·  Source: aws/aws-sdk-net

The DocumentModel Primitive does not properly support round trip serialization. It should use the format specifier "o" when serializing DateTime to ensure the timezone and milliseconds are preserved correctly.

I created a IPropertyConverter below to illustrate the correct serialization for DateTime and NullableDateTime.

using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using System.Globalization;

/// <summary>
/// The <see cref="IPropertyConverter"/> to properly support round-trip serialization of <see cref="DateTime"/>.
/// </summary>
public class RoundTripNullableDateTimeTypeConverter : IPropertyConverter
{
    /// <summary>
    /// Converts the <c>value</c> to a <see cref="Primitive"/>.
    /// </summary>
    /// <param name="value">The value to convert.</param>
    /// <returns>The primitive of the <c>value</c>.</returns>
    public DynamoDBEntry ToEntry(object value)
    {
        var date = value as DateTime?;
        return new Primitive 
        {
            Value = date.HasValue ? date.Value.ToString("o", CultureInfo.InvariantCulture) : null
        };
    }

    /// <summary>
    /// Converts the <c>entry</c> to <see cref="DateTime"/>.
    /// </summary>
    /// <param name="entry">The entry to convert.</param>
    /// <returns>The date time of the entry.</returns>
    public object FromEntry(DynamoDBEntry entry)
    {
        var entryString = entry.AsString();
        if (string.IsNullOrEmpty(entryString))
            return null;
        else
            return DateTime.ParseExact(entryString, "o", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
    }
}

/// <summary>
/// The <see cref="IPropertyConverter"/> to properly support round-trip serialization of <see cref="DateTime"/>.
/// </summary>
public class RoundTripDateTimeTypeConverter : IPropertyConverter
{
    /// <summary>
    /// Converts the <c>value</c> to a <see cref="Primitive"/>.
    /// </summary>
    /// <param name="value">The value to convert.</param>
    /// <returns>The primitive of the <c>value</c>.</returns>
    public DynamoDBEntry ToEntry(object value)
    {
        return new Primitive
        {
            Value = ((DateTime)value).ToString("o", CultureInfo.InvariantCulture)
        };
    }

    /// <summary>
    /// Converts the <c>entry</c> to <see cref="DateTime"/>.
    /// </summary>
    /// <param name="entry">The entry to convert.</param>
    /// <returns>The date time of the entry.</returns>
    public object FromEntry(DynamoDBEntry entry)
    {
        return DateTime.ParseExact(entry.AsString(), "o", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
    }
}

Some important references:

All 3 comments

Thank you for bringing this up. As you pointed out the current implementation uses ISO 8601 UTC format, which does not store the timezone and has a lower precision than is possible with the "o" format.

The original decision to use this format was made across all AWS SDKs that implement a high-level DynamoDB API (Table and DynamoDBContext in .NET, DynamoDBMapper in Java, etc.) Storing dates in ISO 8601 UTC format allows for consistent searching and sorting, which is not possible if timezones are specified: greater string values do not necessarily mean "greater" date values, and it is possible to have multiple representations for a single "unique" date. The latter can cause issues with all operations, not just searches (Query and Scan): if an item with DateTime as a key is stored with a specific timezone, it can only be retrieved if the same exact timezone is used. So depending on how an application is structured, a change in the timezone may cause all data calls to fail, since the key will not match.

Making the above change to the .NET SDK will be a breaking change for users who may be using multiple SDKs or different versions of the .NET SDK, since the non-updated clients will be unable to parse the new formats. But the provided converters are an excellent approach to storing higher-precision dates with timezones, as long as the data is consistently read and written using the same formatting and some of the mentioned considerations are taken into account.

@PavelSafronov, thanks for the response!

The reason I brought it up was an unexpected behavior of saving a UTC date then upon fetching the item it came back in local timezone. This is unexpected for those of us using the API to properly store and retrieve data using UTC. The API assumes incoming DateTime are NOT UTC so therefore, it assumes they should be local time on the deserialization. So it makes an inconsistent assumption.

I put together a Nuget package called AWSSDK.DynamoDBv2.Converters to help others needing consistent serialization behavior of dates. (The source is on GitHub.)

Hope this information helps the team.

The converters linked above should be used if you have a similar use-case as @radleta. For the main SDK, we will not be updating the conversions as that will introduce a breaking change.

Was this page helpful?
0 / 5 - 0 ratings