Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DataContractJsonSerializer DateTime implicit timezone conversion

I have a date time in the database and I retrieve it from the database using Entity Framework, I then pass out the data via JSON API through the DataContractJsonSerializer.

The time in the date time field appears to have been adjusted according to the local timezone of the server whilst being processed in DataContractJsonSerializer. The epoch expressed time is 1 hour ahead of the time expected. The DateTime Kind is UTC, but previously it was Unspecified and I had the same issue.

In my application, I wish to convert between timezones explicitly and on the client side, not the server, as this makes more sense. I'm surprised at this implicit functionality as my datetime values should be simple values just like an integer.

thanks

like image 714
krisdyson Avatar asked Apr 15 '11 11:04

krisdyson


1 Answers

DataContractJsonSerializer will output the timezone portion (+zzzz) if your DateTime.Kind is equal to Local OR Unspecified. This behaviour differs from the XmlSerializer which only outputs the timezone portion if Kind equals Unspecified.

If curious check out the source for JsonWriterDelegator which contains the following method:

 internal override void WriteDateTime(DateTime value) 
    {
        // ToUniversalTime() truncates dates to DateTime.MaxValue or DateTime.MinValue instead of throwing 
        // This will break round-tripping of these dates (see bug 9690 in CSD Developer Framework)
        if (value.Kind != DateTimeKind.Utc)
        {
            long tickCount = value.Ticks - TimeZone.CurrentTimeZone.GetUtcOffset(value).Ticks; 
            if ((tickCount > DateTime.MaxValue.Ticks) || (tickCount < DateTime.MinValue.Ticks))
            { 
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( 
                    XmlObjectSerializer.CreateSerializationException(SR.GetString(SR.JsonDateTimeOutOfRange), new ArgumentOutOfRangeException("value")));
            } 
        }

        writer.WriteString(JsonGlobals.DateTimeStartGuardReader);
        writer.WriteValue((value.ToUniversalTime().Ticks - JsonGlobals.unixEpochTicks) / 10000); 

        switch (value.Kind) 
        { 
            case DateTimeKind.Unspecified:
            case DateTimeKind.Local: 
                // +"zzzz";
                TimeSpan ts = TimeZone.CurrentTimeZone.GetUtcOffset(value.ToLocalTime());
                if (ts.Ticks < 0)
                { 
                    writer.WriteString("-");
                } 
                else 
                {
                    writer.WriteString("+"); 
                }
                int hours = Math.Abs(ts.Hours);
                writer.WriteString((hours < 10) ? "0" + hours : hours.ToString(CultureInfo.InvariantCulture));
                int minutes = Math.Abs(ts.Minutes); 
                writer.WriteString((minutes < 10) ? "0" + minutes : minutes.ToString(CultureInfo.InvariantCulture));
                break; 
            case DateTimeKind.Utc: 
                break;
        } 
        writer.WriteString(JsonGlobals.DateTimeEndGuardReader);
    }

I've run the following test on my machine

var jsonSerializer = new DataContractJsonSerializer(typeof(DateTime));
var date = DateTime.UtcNow;
        Console.WriteLine("original date = " + date.ToString("s"));
        using (var stream = new MemoryStream())
        {
            jsonSerializer.WriteObject(stream, date);

            stream.Position = 0;
            var deserializedDate = (DateTime)jsonSerializer.ReadObject(stream);
            Console.WriteLine("deserialized date = " + deserializedDate.ToString("s"));

        }

which produces the expected output:

original date = 2011-04-19T10:24:39
deserialized date = 2011-04-19T10:24:39

Thus at some point your Date must be Unspecified or Local.

After pulling it out of the DB convert the kind from Unspecified to Utc by calling

 entity.Date = DateTime.SpecifyKind(entity.Date, DateTimeKind.Utc);

and don't forget to assign the return value of SpecifyKind back into your object like I have

like image 121
wal Avatar answered Nov 15 '22 17:11

wal