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

Мне нужно сделать иллюзию работы в выбранном пользователем часовом поясе. Проблема в том, что сервер и клиентский код придерживаются даты 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 часовых поясов. enter image description here

Почему у каждого из них много аббревиатур? Что значит пока? Все, что я хочу проверить, является ли время 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 ]

0 голосов
/ 08 января 2019

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

Словарь

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

Задача

Основная проблема, например когда человек вошел в систему на ПК, который находится в часовом поясе +3, но для его учетной записи установлено значение -6. Angularjs и кендо используются в проекте, а время кендо работает только с родной датой js. А родная js дата всегда находится в часовом поясе браузера, для этого примера +3. Но я должен настроить такие вещи в -6. F.E. Например, пользователь выбрал время 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 и летнее время верно для этой даты, но когда мы получаем дату 03.03.2014 от сервера, мы не знаем, было ли летнее время активным на 03.03.2014.

Ответ

В проекте на сервере .net, поэтому в базе данных хранятся идентификаторы часовых поясов окна. Я сделал это следующим образом: рассчитывать на летнее время сервера для диапазонов дат в диапазоне от минимальной даты на сервере и сохранить на клиенте в локальном хранилище. Мне нравится это в этом случае сервер является одним из источников правды, поэтому вычисления могут выполняться на любом клиенте без манипулирования часовыми поясами. И нет необходимости конвертировать между часовыми поясами IANA, Windows и rails. Но есть и проблема: необходимо предварительно рассчитать DST в диапазоне от 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 с сервера с помощью конвертера часового пояса и получить необходимое смещение непосредственно из момент-часового пояса .

0 голосов
/ 04 января 2019

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

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

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

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

  • Если в вашем .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()
    

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

...