Как использовать момент-часовой пояс для отображения с C# TimeZoneInfo?

Мне нужно сделать иллюзию работы в выбранном пользователем часовом поясе. Проблема в том, что сервер и клиентский код придерживаются даты JavaScript. Таким образом, чтобы выполнить требование, я сделал отображение вручную от utc до даты на стороне клиента:

dateToServer(date) {
    const momentDate = moment(date);
    let serverDate = null;
    if (momentDate.isValid() && date) {
        const browserUtcOffset = momentDate.utcOffset();
        serverDate = momentDate
            .utc()
            .subtract(this.clientTimeZoneOffset, 'minutes')
            .add(browserUtcOffset, 'minutes')
            .toDate();
    }
    return serverDate;
}


dateToClient(date) {
    const momentDate = moment(date);
    let uiDate = null;
    if (momentDate.isValid() && date) {
        const browserUtcOffset = momentDate.utcOffset();
        uiDate = momentDate
            .utc()
            .subtract(browserUtcOffset, 'minutes')
            .add(this.clientTimeZoneOffset, 'minutes')
            .toDate();
    }
    return uiDate;
}

Я добавляю / вычитаю browserUtcOffset, потому что он автоматически добавляет / вычитает браузер, когда дата проходит между сервером и клиентом.

Это работало хорошо, но в этом решении отсутствует обработка DST. Я хотел бы проверить, активен ли DST для даты, а затем добавить смещение DST, если это необходимо.

Вот код C#, который может сделать это:

        string timeZone = "Central Standard Time";
        TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
        DateTime date = new DateTime(2011, 3, 14);
        Console.WriteLine(timeZoneInfo.BaseUtcOffset.TotalMinutes); // -360
        Console.WriteLine(timeZoneInfo.GetUtcOffset(date).TotalMinutes); // -300
        Console.WriteLine(timeZoneInfo.IsDaylightSavingTime(date)); // true
        Console.WriteLine(timeZoneInfo.DaylightName);
        Console.WriteLine(timeZoneInfo.SupportsDaylightSavingTime);

Я нашел isDST в тот момент, и когда у меня есть местный часовой пояс Windows к CST и проверить moment([2011, 2, 14]).isDST(); в консоли браузера я вижу правду. Как вы можете увидеть isDST Это зависит от местного времени браузера.

Следующим шагом попробуйте использовать момент-часовой пояс, чтобы сделать что-то, как я делал в C#. К сожалению, я не понимаю, как этого добиться. Первая проблема, которая в качестве отправной точки у меня есть: UTC time, Base offset(-360 in C# sample), timezone name: Central Standard Time Часовые пояса в момент-часовой пояс разные.

moment.tz.names().map(name => moment.tz.zone(name)).filter(zone => zone.abbrs.find(abbr => abbr === 'CST') != null) этот код возвращает 68 часовых поясов.

Почему у каждого из них много аббревиатур? Что значит пока? Все, что я хочу проверить, является ли время UTC в выбранном часовом поясе, который является "Центральным стандартным временем", активным для летнего дня. Смотрите пример C# еще раз:)

        string timeZone = "Central Standard Time";
        TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
        DateTime date = new DateTime(2011, 3, 14);
        Console.WriteLine(timeZoneInfo.BaseUtcOffset.TotalMinutes); //-360
        Console.WriteLine(timeZoneInfo.GetUtcOffset(date).TotalMinutes); //-300
        Console.WriteLine(timeZoneInfo.IsDaylightSavingTime(date)); //true, I have checked is daylightsavingtime for the date
        Console.WriteLine(timeZoneInfo.DaylightName);
        Console.WriteLine(timeZoneInfo.SupportsDaylightSavingTime);

Заявка была написана на angularjs 1.6.3.

2 ответа

Решение

Проверьте, пожалуйста, ответ Мэтта Джонсона на этот вопрос. Это было очень полезно для меня.

Запас слов

  • Дата пользовательского интерфейса - дата, которую пользователь видит при вводе даты в формате html. Дата пользовательского интерфейса является родной датой JS.
  • Дата сервера - дата UTC, которая хранится в базе данных.
  • timezone id - строка, идентифицирующая часовой пояс. Они различны для соглашений windows(.net), IANA(javascript) и rails.

проблема

Основная проблема, например, при входе человека на ПК, который находится в часовом поясе +3, но для его учетной записи установлено значение -6. Angularjs и кендо используются в проекте, а время кендо работает только с родной датой js. А родная js дата всегда находится в часовом поясе браузера, для этого примера +3. Но я должен настроить такие вещи в -6. Например, пользователь выбрал время 07:00, UTC будет 04:00 (07:00 - 3), но для его учетной записи часовой пояс равен -6, utc должен быть 13:00 (07:00 + 6)., И эти разговоры применяются автоматически, когда мы конвертируем собственную дату JS (UI date) в UTC и обратно. Итак, решение посчитать смещение на сервере и избавиться от смещения часового пояса браузера: utcTime = UItime + browserOffset - clientTimezoneBaseOffset - daylightSavingTimeOffset, Однако существует проблема, когда необходимо вернуть время пользовательского интерфейса, например, сегодня 06.06.2014, и DST является верным для этой даты, но когда мы получаем дату 03.03.2014 с сервера, мы не знаем, было ли DST активным на 03.03.2014.

Ответ

В проекте на сервере.net, поэтому в базе данных хранятся идентификаторы часовых поясов окна. Я сделал это следующим образом: рассчитывать на летнее время сервера для диапазонов дат в диапазоне от минимальной даты на сервере и сохранить на клиенте в локальном хранилище. Мне нравится, что в этом случае сервер является одним из источников правды, поэтому вычисления могут выполняться на любом клиенте, не манипулируя часовыми поясами. И нет необходимости конвертировать между часовыми поясами IANA, Windows и rails. Но есть и проблема: нужно пересчитать ТЛЧ в диапазоне от DateTime.MinValue в DateTime.MaxValue В настоящее время для ускорения я рассчитываю DST для диапазона от 01.01.2000 в DateTime.Now - этого достаточно при преобразовании значения из базы данных в пользовательский интерфейс, поскольку в базе данных минимальная дата указана в 2008 году, но недостаточно для ввода html, поскольку пользователь может выбрать значение больше DateTime.Now и ниже 01.01.2000, Чтобы это исправить я планирую с использованием TimeZoneConverter отправить клиенту IANATimezoneId и для случаев, когда указанная дата (пользовательский интерфейс или сервер) находится за пределами диапазона [01.01.2000, DateTime.Now] принадлежат на moment.utc(date).tz(IANATimezoneId).isDST(),

Это новый код на стороне сервера

    private class DaylightSavingTimeDescriptor
    {
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
        public bool IsDaylightSavingTime { get; set; }
    }

    private string GetDaylightSavingTimeDescriptorsJson(string timeZone)
    {
        string daylightSaveingTimeDescriptorsJson = String.Empty;
        if(timeZone != null)
        {
            List<DaylightSavingTimeDescriptor> dstList = new List<DaylightSavingTimeDescriptor>();
            TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone);
            DateTime startDate = new DateTime(2000, 1, 1);
            DateTime dateIterator = startDate;
            bool isDST = timeZoneInfo.IsDaylightSavingTime(startDate);
            while (dateIterator < DateTime.Now)
            {
                bool currDST = timeZoneInfo.IsDaylightSavingTime(dateIterator);
                if (isDST != currDST)
                {
                    dstList.Add(new DaylightSavingTimeDescriptor()
                    {
                        EndTime = dateIterator.AddDays(-1),
                        IsDaylightSavingTime = isDST,
                        StartTime = startDate
                    });
                    startDate = dateIterator;
                    isDST = currDST;
                }
                dateIterator = dateIterator.AddDays(1);
            }

            daylightSaveingTimeDescriptorsJson = Newtonsoft.Json.JsonConvert.SerializeObject(dstList);
        }
        return daylightSaveingTimeDescriptorsJson;
    }

И это модифицированный dateToServer, dateToClient на стороне клиента

export default class DateService{
constructor (authService) {
    const authData = authService.getAuthData();
    this.clientTimeZoneOffset = Number(authData.timeZoneOffset);
    this.daylightSavingTimeRanges = authData.daylightSavingTimeRanges ? JSON.parse(authData.daylightSavingTimeRanges) : [];
}

getDaylightSavingTimeMinutesOffset(utcDate) {
    const dstRange = this.daylightSavingTimeRanges.find(range => {
        const momentStart = moment(range.startTime).utc();
        const momentEnd = moment(range.endTime).utc();
        const momentDate = moment(utcDate).utc();
        return momentStart.isBefore(momentDate) && momentEnd.isAfter(momentDate);
    });
    const isDaylightSavingTime = dstRange ? dstRange.isDaylightSavingTime : false;
    return isDaylightSavingTime ? '60' : 0;
}

dateToClient(date) {
    const momentDate = moment(date);
    let uiDate = null;
    if (momentDate.isValid() && date) {
        const browserUtcOffset = momentDate.utcOffset();
        uiDate = momentDate
            .utc()
            .subtract(browserUtcOffset, 'minutes')
            .add(this.clientTimeZoneOffset, 'minutes')
            .add(this.getDaylightSavingTimeMinutesOffset(momentDate.utc()), 'minutes')
            .toDate();
    }
    return uiDate;
}

dateToServer(date) {
    const momentDate = moment(date);
    let serverDate = null;
    if (momentDate.isValid() && date) {
        const browserUtcOffset = momentDate.utcOffset();
        serverDate = momentDate
            .utc()
            .subtract(this.clientTimeZoneOffset, 'minutes')
            .add(browserUtcOffset, 'minutes')
            .subtract(this.getDaylightSavingTimeMinutesOffset(momentDate.utc()), 'minutes')
            .toDate();
    }
    return serverDate;
}
}

PS

Я хочу избавиться от смещений и использовать момент-часовой пояс на клиенте и преобразователь часового пояса на сервере, но это будет позже, если клиент спросит, потому что я никогда не пробовал это раньше, и я не уверен, что он будет работать хорошо и текущее решение работает. Также смещения в любом случае будут использоваться для компонентов dateinputs (angularjs), потому что они используют kendo-dateinput, ng-модель которого - JS Date в браузере по местному времени, но я предоставляю другой часовой пояс, поэтому необходимо преобразовать внутри компонента.

PPS Лучшее решение с моей точки зрения, если конвертер часового пояса и момент-часовой пояс будут работать как положено

В любом случае, все приложение использует JS Date, поэтому, когда пользователь в Москве с профилем в Америке выбирает дату и время в кендо-вводе, или видит настройку кендо-планировщика, или отображает дату в кендо-таблице, мне нужно манипулировать смещениями, Но вместо того, чтобы передавать непосредственно смещение клиента, я планирую передать идентификатор часового пояса IANA с сервера с помощью преобразователя часового пояса и получить необходимое смещение непосредственно из момента времени-пояса.

Несколько вещей:

  • Date объект в JavaScript отслеживает определенный момент времени в формате UTC. Вы можете увидеть эту временную метку с вызовом .valueOf() или же .getTime(), Только определенные функции и параметры конструктора работают с местным часовым поясом (например, .toString()). Этот местный часовой пояс применяется во время вызова функции. Нельзя заменить другой часовой пояс (за исключением объекта параметров, переданного в toLocaleString). Таким образом Date Объект не может быть преобразован из одного часового пояса в другой. Потому что обе ваши функции принимают Date возразить и вернуть Date объект, все, что вы делаете в середине, это выбор другого момента времени. Это также видно внутри функции, в которой вы используете add а также subtract методы Момент. Они манипулируют представленным моментом времени - они не меняют часовой пояс.

  • Мимоходом this.clientTimeZoneOffset, Похоже, вы связали часовой пояс со смещением часового пояса. Это отдельные концепции, поскольку часовой пояс может проходить через несколько различных смещений, как из-за перехода на летнее время, так и из-за изменений стандартного времени, когда они произошли в истории. См. Также "Часовой пояс!= Смещение" в теге часового пояса вики. Бесполезно передавать только смещение клиента, так как это смещение применяется только к одному моменту времени. Вы не можете использовать его для преобразования часового пояса, потому что он ничего не говорит вам о том, какие смещения используются для других моментов времени.

  • Вместо этого передайте идентификатор часового пояса. В Windows в.NET они выглядят как "Central Standard Time" (представляющие как стандартное, так и летнее время, несмотря на название), а также в JavaScript и большинстве других операционных систем используются имена часовых поясов IANA. Они похожи "America/Chicago", Это также описано в теге часового пояса вики.

  • Если ваш код.NET использует идентификаторы Windows, вы можете использовать мою библиотеку TimeZoneConverter для преобразования из Windows в IANA, а затем отправить эту строку в браузер в качестве часового пояса.

  • С Moment-Timezone вы можете просто проверить DST следующим образом:

    moment.tz([2011, 2, 14], 'America/Chicago').isDST()
    

    Опять же, вы должны использовать идентификаторы часовых поясов IANA, и TimeZoneConverter может предоставить их, если вы используете серверные часовые пояса Windows.

  • Вы можете рассмотреть возможность использования Noda Time на стороне сервера с его поставщиком часовых поясов TZDB. Это позволит вам использовать часовые пояса IANA с обеих сторон. Это также относится и к TimeZoneInfo при запуске на.NET Core в Linux или Mac OSX.

  • Причина, по которой вы видите так много записей, когда вы искали сокращения с CST в том, что сокращения часовых поясов неоднозначны. Возможно, вы имели в виду центральное стандартное время США, но вы могли также указать стандартное время Кубы, стандартное время Китая или другие места, в которых используется эта аббревиатура.

  • Учитывая untils массив, как правило, вам не нужно беспокоиться об этом. Это часть внутренних данных, которые Moment-Timezone использует для выбора правильного момента времени для выбора смещения и сокращения часового пояса.

  • Вы сказали в конце, что-то немного другое:

    Все, что я хочу проверить, является ли время UTC в выбранном часовом поясе, который является "Центральным стандартным временем", активным для летнего дня.

    Пример, который я привел ранее, предполагал, что вы начинаете с местного часового пояса. Если вы начинаете со времени UTC, то это так:

    moment.utc([2011, 2, 14]).tz('America/Chicago').isDST()
    

    Конечно, вы можете передавать различные другие поддерживаемые входные данные, где я передаю массив. Документы Moment могут помочь вам с доступными опциями.

Другие вопросы по тегам