DataContractJsonSerializer DateTime неявное преобразование часового пояса

У меня есть дата и время в базе данных, и я извлекаю ее из базы данных с помощью Entity Framework, а затем передаю данные через JSON API через DataContractJsonSerializer.

Время в поле даты, кажется, было скорректировано в соответствии с местным часовым поясом сервера во время обработки в DataContractJsonSerializer. Выраженное время эпохи на 1 час опережает ожидаемое время. Тип DateTime - это UTC, но раньше он был не указан, и у меня была такая же проблема.

В моем приложении я хочу явно преобразовывать часовые пояса на стороне клиента, а не на сервере, поскольку это имеет больше смысла. Я удивлен этой неявной функциональностью, поскольку мои значения datetime должны быть простыми значениями, такими как целые числа.

Благодарность


person krisdyson    schedule 15.04.2011    source источник


Ответы (1)


DataContractJsonSerializer будет выводить часть часового пояса (+ zzzz), если ваш DateTime.Kind равен Local OR Unspecified. Это поведение отличается от XmlSerializer, который выводит только часть часового пояса, если Kind равно Unspecified.

Если интересно, посмотрите исходный код JsonWriterDelegator, который содержит следующий метод:

 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);
    }

Я провел следующий тест на своей машине

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"));

        }

который дает ожидаемый результат:

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

Таким образом, в какой-то момент ваша Дата должна быть Неуказанной или Местной.

После извлечения из БД преобразуйте вид из Unspecified в Utc, вызвав

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

и не забудьте присвоить возвращаемое значение SpecifyKind обратно вашему объекту, как у меня

person wal    schedule 19.04.2011
comment
Моя дата и время сериализовано до 1303500600000 + 0000 - person krisdyson; 19.04.2011
comment
Ваш образец сериализуется в \ / Date (1303220156217) \ /, тогда как мой сериализуется в \ / Date (1303500600000 + 0000) \ /. Я не знаю, почему он включает +0000 - person krisdyson; 19.04.2011
comment
вы уверены, что DateTimeKind = UTC? Я просто попробовал еще раз на своей машине, и когда DateTimeKind НЕ равен UTC (либо Local, либо Unspecified), выводится часовой пояс. Я только что просмотрел исходный код, и часовой пояс не будет выводиться, если DateTimeKind == UTC - person wal; 19.04.2011
comment
не могли бы вы запустить мой образец на своей стороне, чтобы убедиться, что вы не получаете результат + zzzzz? - person wal; 19.04.2011
comment
о да, извините, я думал, это произошло автоматически. Это произошло из-за того, что Kind в моем DateTime не был указан, поэтому сериализатор решил добавить спецификатор часового пояса, который затем заставил последующую десериализацию DataContractJsonSerializer подумать, что ему нужно настроить значение времени на местное время. На самом деле я хочу, чтобы мои значения datetime не зависели от часового пояса. Я не знаю, почему М.С. сделал это таким запутанным; было бы просто, если бы MS исключила такое неявное поведение. - person krisdyson; 20.04.2011