JavaScriptSerializer UTC DateTime вопросы

Наш клиент хотел показать значения даты и времени в браузере точно так же, как они есть в базе данных, и мы сохраняем их как UTC в базе данных.

Сначала у нас были некоторые проблемы с сериализацией и стороной Javascript. Значения DateTime менялись дважды - сначала для соответствия локальному часовому поясу машины, а затем для соответствия часовому поясу в браузере. Мы исправили это, добавив пользовательский конвертер в JavaScriptSerializer. Мы отметили, что DateTime имеет значение DateTimeKind.Utc в переопределении Serialize. Было немного сложно передать данные из Serialize, но мы обнаружили некоторый Uri-хак, который помог вернуть значения DateTime в тот же формат JavaScriptSerializer/Date (286769410010)/format, но без перехода в локальное время. На стороне Javascript мы закрепили библиотеку JS KendoUI, чтобы компенсировать построенные объекты Date(), чтобы они отображались так, как будто они являются UTC.

Затем мы начали работать на другой стороне, десериализации. Опять же, нам пришлось корректировать наш код, чтобы использовать пользовательский stringify вместо JSON.stringify, который снова смещает данные при преобразовании с локального времени в UTC. Пока все было хорошо.

Но посмотрите на этот тест:

    public void DeserialiseDatesTest()
    {
        var dateExpected = new DateTime(1979, 2, 2,
            2, 10, 10, 10, DateTimeKind.Utc);

        // this how the Dates look like after serializing
        // anothe issue, unrelated to the core problem, is that the "\" might get stripped out when dates come back from the browser
        // so I have to add missing "\" or else Deserialize will break
        string s = "\"\\/Date(286769410010)\\/\"";

        // this get deserialized to UTC date by default
        JavaScriptSerializer js = new JavaScriptSerializer();

        var dateActual = js.Deserialize<DateTime>(s);
        Assert.AreEqual(dateExpected, dateActual);
        Assert.AreEqual(DateTimeKind.Utc, dateActual.Kind);

        // but some Javascript components (like KendoUI) sometimes use JSON.stringify 
        // for Javascript Date() object, thus producing the following:
        s = "\"1979-02-02T02:10:10Z\"";

        dateActual = js.Deserialize<DateTime>(s);
        // If your local computer time is not UTC, this will FAIL!
        Assert.AreEqual(dateExpected, dateActual);

        // and the following fails always
        Assert.AreEqual(DateTimeKind.Utc, dateActual.Kind); 
    }

Почему JavaScriptSerializer десериализует строки \/Date(286769410010)\/ по времени UTC, но 1979-02-02T02:10:10Z - по местному времени?

Мы попытались добавить метод Deserialize к нашему пользовательскому JavascriptConverter, но проблема в том, что Deserialize никогда не вызывается, если наш JavascriptConverter имеет следующие типы:

    public override IEnumerable<Type> SupportedTypes
    {
        get { return new List<Type>() { typeof(DateTime), typeof(DateTime?) }; }
    }

Я думаю, Deserialize вызывается только в том случае, если SupportedTypes содержит типы некоторых сложных объектов, у которых есть поля DateTime.

Итак, JavaScriptSerializer и JavascriptConverter имеют два несоответствия:

  • Serialize учитывает простые типы в SupportedTypes для каждого элемента данных, но Deserialize игнорирует его для простых типов.
  • Deserialize десериализует некоторые даты как UTC, а некоторые - как местное время.

Есть ли простой способ исправить эти проблемы? Мы немного боимся заменить JavaScriptSerializer на какой-то другой сериализатор, потому что, возможно, некоторые из сторонних библиотек, которые мы используем, полагаются на некоторые определенные "функции/ошибки" JavaScriptSerializer.

Ответ 1

JavaScriptSerializer и DataContractJsonSerializer пронизаны ошибками. Вместо этого используйте json.net. Даже Microsoft сделала этот переключатель в ASP.Net MVC4 и других последних проектах.

Формат /Date(286769410010)/ является собственностью и составлен Microsoft. У этого есть проблемы, и он не получил широкого распространения. Вы должны использовать формат 1979-02-02T02:10:10Z везде. Это определено в ISO8601 и RF3339. Он как машинный, так и удобный для человека, лексически сортируемый, культурный инвариант и недвусмысленный.

В JavaScript, если вы можете гарантировать, что вы будете работать в новых браузерах, используйте:

date.toISOString()

Ссылка здесь.

Если вы хотите использовать поддержку кросс-браузера и более старого браузера, используйте moment.js.

UPDATE

В стороне, если вы действительно хотите использовать JavaScriptSerializer, вы можете десериализоваться на DateTimeOffset, который сохранит правильное время. Затем вы можете получить UTC DateTime, следующим образом:

// note, you were missing the milliseconds in your example, I added them here.
s = "\"1979-02-02T02:10:10.010Z\"";

dateActual = js.Deserialize<DateTimeOffset>(s).UtcDateTime;

Теперь ваш тест пройдет.