Укажите часовой пояс даты и времени без изменения значения

Мне интересно, как изменить часовой пояс объекта DateTime без фактического изменения значения. Вот фон...

У меня есть сайт ASP.NET MVC, размещенный в AppHarbor, и на сервере установлено время UTC. Когда я отправляю форму со своего сайта, содержащую значение даты и времени, скажем, 17.09.2013, 4:00, она попадает на сервер с этим значением. Однако тогда, когда я это делаю:

public ActionResult Save(Entity entity)
{
    entity.Date = entity.Date.ToUniversalTime();
    EntityService.Save(entity);
}

... он неправильно сохраняет одно и то же время (4 часа ночи), потому что сервер уже использует время UTC. Таким образом, после преобразования в UTC и сохранения в базу данных значение базы данных равно 2013-09-17 04:00:00.000, хотя на самом деле оно должно быть 2013-09-17 08:00:00.000, поскольку браузер находится в режиме EST. Я надеялся, что после отправки формы и передачи значений в действие контроллера часовой пояс DateTime будет установлен на браузер (EST), а не на хост-сервер (UTC), однако этого не произошло.

В любом случае, теперь я застрял с объектом DateTime, который содержит правильное значение даты/времени, но неправильный часовой пояс, поэтому я не могу правильно преобразовать его в UTC. Я храню часовой пояс каждого пользователя, поэтому, если бы был способ установить часовой пояс для объекта DateTime без фактического изменения значения (я не могу сделать TimeZoneInfo.ConvertTime, потому что это изменит значение), тогда я мог бы правильное значение даты/времени И правильный часовой пояс, тогда я мог бы безопасно сделать date.ToUniversalTime. При этом я видел только, как изменить тип DateTime, а не TimeZone, без изменения его фактического значения.

Любые идеи?


person Justin    schedule 17.09.2013    source источник
comment
Посмотрите этот ответ: stackoverflow.com/questions/6682290/   -  person aleciten    schedule 17.09.2013
comment
Вы можете попытаться преобразовать значение даты клиента, например Date('yourdatehere').toISOString()   -  person Frank59    schedule 17.09.2013


Ответы (1)


На самом деле, он делает именно то, что вы просили. С точки зрения сервера вы передали ему "неопределенное" DateTime. Другими словами, просто год, месяц, день и т. д. без какого-либо контекста. Если вы посмотрите на свойство .Kind, вы увидите, что это действительно DateTimeKind.Unspecified.

Когда вы вызываете .ToUniversalTime() для неопределенного типа DateTime, .NET будет использовать контекст локального часового пояса компьютера, на котором выполняется код. Подробнее об этом можно прочитать в документации здесь. Поскольку ваш сервер настроен на UTC, то независимо от типа ввода все три вида дадут один и тот же результат. По сути, это неоперативка.

Итак, вы сказали, что хотите, чтобы время отражало часовой пояс пользователя. К сожалению, это информация, которой нет на сервере. Не существует волшебного HTTP-заголовка, содержащего сведения о часовом поясе.

Однако есть способы добиться этого эффекта, и у вас есть несколько вариантов.

Метод JavaScript

Это, вероятно, самый простой вариант, но он требует JavaScript.

  • Take the local date and time input from your user and parse it into a JavaScript Date class.
    • For easier parsing, you might consider using a moment instead, from the moment.js library.
  • Get the UTC date and time from the Date or moment, and pass it to your server.
    • This is relatively easy in JavaScript, so I'll spare you the details.
    • Есть много форматов, которые вы можете передать, но предпочтительным способом является временная метка ISO8601. Пример: 2013-09-17T08:00:00.000Z
  • Не забудьте Z в конце. Это означает, что время указано в формате UTC.
  • Поскольку он передается вам как UTC, вы можете просто сохранить его без преобразования.
  • При получении вы снова передаете UTC в браузер, загружаете его в Date или moment с помощью JavaScript, а затем выдаете локальную дату и время.
  • Я настоятельно рекомендую вам попробовать moment.js, если вы выберете этот подход. Это можно сделать и без него, но это может быть намного сложнее и чревато ошибками.

Метод .NET с использованием часовых поясов Windows

Если вы не собираетесь вызывать JavaScript, вам нужно будет запросить у пользователя часовой пояс. Это может хорошо работать в больших приложениях, например на странице профиля пользователя.

  • Use TimeZoneInfo.GetSystemTimeZones to build a drop-down list.
    • For each TimeZoneInfo item in the list, use the .Id for the value, and the .DisplayName for the text.
  • Затем вы можете использовать это значение, когда хотите преобразовать время.

    TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById(yourUsersTimeZone);
    DateTime utc = TimeZoneInfo.ConvertTimeToUtc(theInputDatetime, tz);
    
  • Это будет работать с часовыми поясами Windows, которые созданы Microsoft и имеют некоторые недостатки. Вы можете прочитать о некоторых из их недостатков в вики тега часового пояса.

Метод .NET с использованием часовых поясов IANA

Если вы хотите использовать более стандартные часовые пояса IANA, такие как America/New_York или Europe/London, вы можете использовать библиотеку, например Noda Time. Он предлагает гораздо лучший API для работы с датой и временем, чем встроенный фреймворк. Есть немного кривой обучения, но если вы делаете что-то сложное, это того стоит. Например:

DateTimeZone tz = DateTimeZoneProviders.Tzdb["America/New_York"];
var pattern = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = pattern.Parse("2013-09-17 04:00:00").Value;
ZonedDateTime zdt = tz.AtLeniently(dt);
Instant utc = zdt.ToInstant();


О переходе на летнее время

Независимо от того, какой из этих трех подходов вы выберете, вам придется иметь дело с проблемами, создаваемыми переходом на летнее время. Каждый из этих примеров демонстрирует «снисходительный» подход, при котором, если указанное вами местное время неоднозначно или недействительно, выполняется какое-то правило, поэтому вы все равно получаете некоторый действительный момент времени. Вы можете увидеть это непосредственно в подходе Noda Time, когда я позвонил AtLeniently. Но это происходит и с другими - просто неявно. В JavaScript правила могут различаться в зависимости от браузера, поэтому не ждите одинаковых результатов.

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

В .Net это можно проверить с помощью TimeZoneInfo.IsInvalidTime. и TimeZoneInfo.IsAmbiguousTime.

Пример перехода на летнее время см. здесь. При переходе «пружинный вперед» время во время перехода недействительно. В «обратном» переходе время во время перехода неоднозначно, то есть это могло произойти либо до, либо после перехода.

person Matt Johnson-Pint    schedule 17.09.2013
comment
TimeZoneInfo.ConvertTimeToUtc был именно тем, что мне было нужно, спасибо! - person Justin; 17.09.2013