Проверьте, пожалуйста, ответ Мэтта Джонсона на этот вопрос . Это было очень полезно для меня.
Словарь
- 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 с сервера с помощью конвертера часового пояса и получить необходимое смещение непосредственно из момент-часового пояса .