На самом деле, он делает именно то, что вы просили. С точки зрения сервера вы передали ему "неопределенное" 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