DataContractJsonSerializer DateTime implicit timezone conversion
Asked Answered
V

1

7

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

Variance answered 15/4, 2011 at 11:7 Comment(1)
The solution to this problem is actually a-lot simpler than the accepted answer future readers can see it here: https://mcmap.net/q/790114/-datacontractjsonserializer-deserializing-datetime-within-list-lt-object-gtFlax
O
5

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

Ounce answered 19/4, 2011 at 10:25 Comment(5)
My datetime is serialized to 1303500600000+0000Variance
Your sample serializes to "\/Date(1303220156217)\/" whereas mine serializes to "\/Date(1303500600000+0000)\/". I don't know why it's including the +0000Variance
are you sure DateTimeKind=UTC? I just tried it again on my machine and when DateTimeKind does NOT equal UTC (either Local or Unspecified) the timezone is output. I've just looked at the source code as well and the timezone will not be output if DateTimeKind==UTCOunce
could you run my sample on your side to check you don't get the +zzzzz output?Ounce
oh yes, sorry, I thought it did it automatically. It was all because the Kind on my DateTime was unspecified, so the serializer decided to put on a timezone specifier, which then made the subsequent deserializing DataContractJsonSerializer think that it needed to adjust the time value to Local time. In reality I want my datetime values to be timezone agnostic. I don't know why MS made it so convoluted; it would be simple if MS had left out such implicit behaviour.Variance

© 2022 - 2024 — McMap. All rights reserved.