Aws-sdk-net: Prevent DynamoDB SDK converting the datetime in local timezone

Created on 7 Nov 2019  路  5Comments  路  Source: aws/aws-sdk-net

The DynamoDB SDK is converting the datetime property in local timezone while fetching documents from database, which has stored the datetime in UTC correctly.

Expected Behavior

DateTime should not be converted to local time zone

Current Behavior

The SDK is converting the date time into local time zone

Correct In Dyanmo DB Table (UTC)

image

While fetching records through SDK

"2019-11-07T12:40:28.445+05:30",
"2019-11-07T12:35:25.071+05:30",

Code to Reproduce :

async Task Main()
{
    var dynamo = new DynamoDbService();
    var result = await dynamo.GetDocumentsAsync<Documents>("abc");
    result.Dump();

}

public class DynamoDbService
{
    private static readonly string awsAccessKey = "access key here";
    private static readonly string awsSecretKey = "secret here";

    private static BasicAWSCredentials awsCredentials = new BasicAWSCredentials(awsAccessKey, awsSecretKey);
    private static AmazonDynamoDBClient client = new AmazonDynamoDBClient(awsCredentials, RegionEndpoint.USEast1);
    private static DynamoDBContext context = new DynamoDBContext(client);

    public async Task InsertDocumentAsync<T>(T document)
    {
        await context.SaveAsync<T>(document);
    }

    public async Task<List<T>> GetDocumentsAsync<T>(string id, int limit = 10)
    {
        QueryFilter filter = new QueryFilter();
        filter.AddCondition("doc_id", QueryOperator.Equal, id);
        QueryOperationConfig queryConfig = new QueryOperationConfig
        {
            Filter = filter,
            Limit = limit,
            BackwardSearch = true
        };
        var documents = await context.FromQueryAsync<T>(queryConfig, _operationConfig).GetNextSetAsync();
        return documents;
    }
}

[DynamoDBTable("Table1")]
public class Documents
{
    public string doc_id { get; set; }
    public string name { get; set; }
    public DateTime created { get; set; }
}

B bug duplicate moduldynamodb

Most helpful comment

We use a custom converter as a workaround for dates stored in UTC

public class DateTimeUtcConverter : IPropertyConverter
{
    public DynamoDBEntry ToEntry(object value) => (DateTime) value;

    public object FromEntry(DynamoDBEntry entry)
    {
        var dateTime = entry.AsDateTime();
        return dateTime.ToUniversalTime();
    }
}

Applied to properties like that:

public class Entity
{
    [DynamoDBProperty("ddb_field", typeof(DateTimeUtcConverter))]
    public DateTime Timestamp { get; set; }
}

All 5 comments

I'm also seeing this problem. Is there any workaround available?

Facing same issue, any solution?

We use a custom converter as a workaround for dates stored in UTC

public class DateTimeUtcConverter : IPropertyConverter
{
    public DynamoDBEntry ToEntry(object value) => (DateTime) value;

    public object FromEntry(DynamoDBEntry entry)
    {
        var dateTime = entry.AsDateTime();
        return dateTime.ToUniversalTime();
    }
}

Applied to properties like that:

public class Entity
{
    [DynamoDBProperty("ddb_field", typeof(DateTimeUtcConverter))]
    public DateTime Timestamp { get; set; }
}

Does anyone know how to use the converter when calling DynamoDBContext.LoadAsync()
I have data that I want to roundtrip using datetime as the rangekey, at this point in my code I only have UTC values, and it apears to store correctly, but when trying to use LoadAsync it doens't find the entry correctly.

Looks like there is a bug in SchemaV1.cs at DateTimeConverterV1.TryFrom method:

return DateTime.TryParseExact(
      p.StringValue, AWSSDKUtils.ISO8601DateFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out result);

DateTimeStyles should be not DateTimeStyles.AssumeUniversal but DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal

Proof:

using System;
using System.Globalization;

namespace DateTimeConverterV1Proof
{
    class Program
    {
        static void Main(string[] args)
        {
            var utcNow = DateTime.UtcNow;

            Console.WriteLine("UTC:\n" + utcNow);
            var isoString = utcNow.ToString("o");

            // Same string as AWSSDKUtils.ISO8601DateFormat except original fractional which is .fff
            const string format = "yyyy-MM-dd\\THH:mm:ss.fffffff\\Z";

            DateTime.TryParseExact(isoString,
                format, CultureInfo.InvariantCulture,
                DateTimeStyles.AssumeUniversal,
                out var dateTime);

            Console.WriteLine("[Original] DateTimeStyles.AssumeUniversal:\n" + dateTime);

            DateTime.TryParseExact(isoString,
                format, CultureInfo.InvariantCulture,
                DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal,
                out dateTime);

            Console.WriteLine("[Fixed] DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal:\n" + dateTime);
        }
    }
}

Output:

UTC:
9/21/2020 8:39:45 PM
[Original] DateTimeStyles.AssumeUniversal:
9/21/2020 11:39:45 PM
[Fixed] DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal:
9/21/2020 8:39:45 PM

Fix:

Use DynamoDBProperty attibute with type converter as @firenero suggested.

Was this page helpful?
0 / 5 - 0 ratings