Как использовать момент-часовой пояс для отображения с 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 могут помочь вам с доступными опциями.