Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DateTime does not support round trip serialization with the DocumentModel #111

Closed
radleta opened this issue Jul 2, 2014 · 3 comments
Closed

Comments

@radleta
Copy link

radleta commented Jul 2, 2014

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:

@PavelSafronov
Copy link

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.

@radleta
Copy link
Author

radleta commented Jul 3, 2014

@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.

@PavelSafronov
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants