Как преобразовать временной интервал с помощью смещения

У меня есть веб-приложение MVC. Я храню время UTC в базе данных. (Не datetime, а просто время). В C # Когда я извлекаю это время из базы данных, я возвращаю объект временного интервала. У меня также есть смещение в минутах. Например.

double offset = 600;

Как использовать это смещение для преобразования временного интервала в местное datetime.

Обратите внимание: я не хочу использовать метод DateTime.ToLocalTime (), потому что он будет использовать часовой пояс сервера.

UPDATE1
Я использую метод Javascript new Date().getTimezoneOffset() для получения смещения клиента, и у меня есть значение смещения, хранящееся на сервере. Затем у меня также есть раскрывающийся список, который показывает время как 12:00, 12:30, 1:00 и т. Д. Выпадающий список привязан к свойству модели SelectedDateTime типа DateTime. Идея состоит в том, чтобы преобразовать время, выбранное пользователем, в UTC, а затем в UTC в местное время в зависимости от смещения. Допустим, у меня есть смещение 300 minitues, которое будет 300/60 = 5 hours

double offset = 5.00; // this is available on the server

Когда пользователь выбирает время в раскрывающемся списке, я получаю объект datetime на сервере, игнорируя часть даты, которую я хочу сохранить в базе данных UTC. Вот как я конвертирую время в UTC.

TimeSpan utcTime = SelectedDateTime.AddHours(offset).TimeOfday;

Я сохраняю этот utcTime в базе данных. Теперь я хочу преобразовать временной интервал UTC в дату и время клиента. Я предполагаю, что сейчас у меня есть вычитание смещения

var newLocalTimeSpan = utcTime.Subtract(TimeSpan.FromHours(offset));
var newLocalDateTime = new DateTime(newLocalTimeSpan.Ticks, DateTimeKind.Local);

Однако это вызывает ошибку:

Тики должны быть между DateTime.MinValue.Ticks и DateTime.MaxValue.Ticks. \ R \ nИмя параметра: тики

Например, с дополнительными 5 часами. Если пользователь выберет 8:00 PM, он будет преобразован в UTC и будет сохранен как 01:00:00.0000000 в базе данных. Когда я получаю значение UTC из базы данных, это «1:00:00 AM». Затем я вычитаю 5 часов из этого TimeSpan, который сейчас равен `-4 ', и если я передаю Ticks в DateTime..i получаю ошибку выше.

ПРИМЕЧАНИЯ. Если вам интересно, почему свойство модели - DateTime, а не TimeSpan, это потому, что я использую Kendo TimePicker, которому нужен тип DateTime.

ОБНОВЛЕНИЕ 2
Я очень благодарен всем за вашу помощь. Я просмотрел все статьи, опубликованные @Matt Johnson, и похоже, что мне не следует использовать смещение для расчета времени UTC. В основном из-за экономии дневного света. Но вместо этого я должен использовать часовой пояс. Итак, у меня есть 3 варианта определения часового пояса клиента:

1 ›Использование JavaScript для определения часового пояса
В JavaScript я могу сделать new Date().toString(), который возвращает дату и время как Sun May 22 2016 02:12:36 GMT-0500 (Central Daylight Time). Затем я могу проанализировать строку, чтобы получить« Центральное летнее время »и отправить ее на сервер. . Однако на сервере для .net «Центральное летнее время» не является действительным идентификатором часового пояса Windows.
Вопрос
Это правильный подход? Возвращает ли JavaScript идентификатор зоны IANA? Всегда ли он будет возвращать идентификатор зоны IANA?

Если JavaScript возвращает IANA Id, я могу использовать статью Мэтта здесь, чтобы получить идентификатор часового пояса Windows

2 ›Используйте http://momentjs.com/ для определения часового пояса клиента
Вопрос
Возвращает ли momentjs IANA идентификатор зоны?
Если momentjs возвращает IANA идентификатор зоны, я могу использовать статью Мэтта выше, чтобы получить идентификатор зоны Windows. Одна из причин, по которой мне не нравится этот подход, заключается в том, что мне приходится использовать две сторонние библиотеки momentjs и Noda Time.

3 ›Предоставьте пользователю раскрывающийся список с помощью TimeZoneInfo.GetSystemTimeZones () и позвольте пользователю выбрать часовой пояс.
Пользователь выберет время и часовой пояс, а затем на сервере я конвертирую его в UTC. используя выбранный часовой пояс и сохраните его в БД. Однако я должен показать это время на некоторых других страницах, поэтому мне снова нужен часовой пояс. Это означает, что я должен поместить раскрывающийся список в такое место в пользовательском интерфейсе, где он будет доступен все время. Нравится верхнее меню.

(Я, конечно, могу сохранить часовой пояс в БД вместе со временем, однако, если пользователь отправится в другое место, он все равно будет видеть время в первоначально выбранном часовом поясе. Что мне не нужно)

Это правильные подходы? Я что-то упускаю?

Вопрос
Предположим, что я реализую выбор часового пояса, используя один из описанных выше подходов, и у меня есть правильный часовой пояс клиента с идентификатором часового пояса Windows на сервере в некоторой переменной.
Теперь допустим, что пользователь выбирает 6:00 PM (Центральное летнее время, UTC -5), который будет преобразован в UTC как 23:00:00. Пока мы находимся в центральном летнем времени, преобразование из UTC в местное будет отображаться в 18:00. Как только мы перейдем к Central Standard Time, который является UTC -6, будет ли преобразование по-прежнему отображаться в 18:00 или 17:00?
Я планирую использовать метод TimeZoneInfo.ConvertFromUtc(datetimevalue, timezone) для преобразования UTC в местное


person LP13    schedule 19.05.2016    source источник
comment
можешь привести мне пример?   -  person LP13    schedule 19.05.2016
comment
пожалуйста, посмотрите мое обновление 1 выше   -  person LP13    schedule 19.05.2016
comment
Прочтите вики-страницу о тегах часовых поясов, особенно о часовом поясе! = Смещение. Затем поищите другие похожие вопросы. Я закрою это как дубликат позже, когда смогу потратить некоторое время, чтобы выяснить, какой дубликат является наиболее подходящим из тех, о которых много раз спрашивали раньше.   -  person Matt Johnson-Pint    schedule 20.05.2016
comment
Пожалуйста, не отмечайте дубликаты. Обновим мои данные. Спасибо   -  person LP13    schedule 20.05.2016
comment
Прочтите это, это и this. Если у вас все еще есть вопросы, дайте мне знать. Я скажу, что вы действительно должны понимать, что преобразование времени без даты не имеет никакого смысла, потому что вы должны знать, какое смещение действует. Вы также не можете просто взять смещение от одного момента времени (из браузера) и применить его к любому моменту времени на вашем сервере. Смещение могло измениться.   -  person Matt Johnson-Pint    schedule 21.05.2016
comment
Например, для Тихоокеанского времени США стандартное смещение - UTC-8, а смещение дневного света - UTC-7. Также учтите, что даже стандартные смещения менялись со временем для некоторых часовых поясов, и есть часовые пояса, у которых есть четыре перехода между двумя или тремя смещениями за один год.   -  person Matt Johnson-Pint    schedule 21.05.2016
comment
@MattJohnson Большое спасибо. См. Мое обновление 2 выше   -  person LP13    schedule 22.05.2016


Ответы (2)


В общем, есть только два жизнеспособных подхода:

  1. Передавать клиенту только дату и время в формате UTC и выполнять все преобразования в местное время в браузере с помощью JavaScript.

    • Use this approach when you don't care what the time zone actually is, but you just want it to match the browser's local time.
    • Объект Date может это сделать, но вам может быть проще использовать такую ​​библиотеку, как moment.js, которая дает вам больше среди прочего, контроль формата вывода.
  2. Примените часовой пояс (а не только смещение) к дате и времени UTC на стороне сервера, чтобы получить правильное значение местного времени.

    • Use this approach when the time zone affects an entire application, and needs to be known in server-side business logic.
    • Вы можете попытаться угадать часовой пояс пользователя, используя jsTimeZoneDetect или moment.tz.guess() в момент-часовой пояс. Однако это всего лишь предположение, и это всегда идентификатор часового пояса IANA (например, America/Los_Angeles).
    • Спросить пользователя об их часовом поясе из списка - хорошая идея. Обычно это размещается на странице пользовательских настроек или профиля. Вы можете использовать сделанное ранее предположение, чтобы выбрать значение по умолчанию из списка.
    • Вам действительно нужно будет использовать Noda Time на сервере, если вы используете часовые пояса IANA на клиенте.
    • Some applications choose to list Windows time zones instead, which is a much simpler approach as you can get everything from the TimeZoneInfo class. However, recognize that there are limitations with this approach including:
      • Localization issues, as you cannot easily get at display name strings other than the ones matching the operating system's default language, not .NET's globalization and localization features.
      • Проблемы с ремонтопригодностью, поскольку вы уступаете управление операционной системе для обновления данных часового пояса. Это может показаться более удобным, но вы можете обнаружить, что у вас связаны руки, когда вы не отставаете от изменения часового пояса в кратчайшие сроки. Это особенно проблематично, если у вас нет контроля над тем, как и когда обновления применяются к ОС, например, с помощью службы приложений Microsoft Azure.
      • Проблемы совместимости, поскольку часовые пояса Windows обычно не распознаются за пределами Windows. Если вы когда-нибудь откроете настройку часового пояса пользователя в API, у вас, скорее всего, возникнут проблемы с переводом для звонящих с других платформ.

Теперь перейдем к вашим конкретным моментам:

Я использую метод javascript new Date (). GetTimezoneOffset (), чтобы получить смещение клиента ...

Это дает вам текущее смещение клиента. У вас нет гарантий, что это правильный часовой пояс для подачи заявки на произвольную дату и время.

Если вы хотите применить фиксированное смещение к UTC DateTime в C #, лучше всего использовать DateTimeOffset.

DateTime utc = new DateTime(2016, 12, 31, 0, 0, 0, DateTimeKind.Utc);
DateTimeOffset dto = new DateTimeOffset(utc); // DateTimeKind matters here
TimeSpan offset = TimeSpan.FromMinutes(-300); // The offset is inverse of JavaScript's
DateTimeOffset result = dto.ToOffset(offset);

Но учтите, что это только для фиксированного смещения часового пояса. Для истинного часового пояса вы должны использовать класс TimeZoneInfo, если вы используете часовые пояса Windows, или вы должны использовать класс NodaTime DateTimeZone для часовых поясов IANA.

В JavaScript я могу сделать new Date (). ToString (), который возвращает дату и время как Sun 22 мая 2016 02:12:36 GMT-0500 (Центральное летнее время). Затем я могу проанализировать строку, чтобы получить «Центральное летнее время» и опубликовать это к серверу.

Нет, этот подход не рекомендуется по нескольким причинам:

  • Нет гарантии, что вы получите результат в каком-либо конкретном формате из функции JavaScript toString. Результаты зависят от реализации и будут различаться в зависимости от браузера и платформы.

  • Обычно они предназначены для отображения целей. Когда действует летнее время, они показывают дневное имя, а когда действует стандартное время, они показывают стандартное имя.

  • Они часто локализованы для языка пользователя: английского, французского, китайского и т. Д.

Единственный собственный API, который может возвращать часовой пояс пользователя:

Intl.DateTimeFormat().resolvedOptions().timeZone

Это часть ECMAScript Internationalization API. К сожалению, в настоящее время он работает только в нескольких браузерах. И jsTimeZoneDetect, и moment.tz.guess() будут использовать этот API, если он доступен, а в противном случае вернутся к своей собственной логике предположений.

Предположим, что я реализую выбор часового пояса, используя один из описанных выше подходов, и у меня есть правильный часовой пояс клиента с идентификатором часового пояса Windows на сервере в некоторой переменной. Теперь предположим, что пользователь выбирает 18:00 (Центральное летнее время, UTC -5), который преобразуется в UTC как 23:00:00. Пока мы находимся в центральном летнем времени, преобразование из UTC в местное будет отображаться в 18:00. Как только мы перейдем в центральное стандартное время, которое будет UTC -6, будет ли преобразование по-прежнему отображаться в 18:00 или 17:00? Я планирую использовать метод TimeZoneInfo.ConvertFromUtc(datetimevalue, timezone) для преобразования UTC в Local

Как вы сказали ранее, "Central Daylight Time" не является допустимым идентификатором часового пояса Windows. Ваш пользователь этого не выберет. Вы бы отобразили список, созданный из TimeZoneInfo.GetSystemTimeZones(), показывая DisplayName пользователю и используя Id для значения. Id будет "Central Standard Time", что действительно является правильным идентификатором для Центрального времени США, включая CST и CDT, несмотря на наличие в строке слова «Standard».

person Matt Johnson-Pint    schedule 23.05.2016
comment
Я также укажу вам на это видео на YouTube, в котором рассматриваются некоторые из этих проблем. - person Matt Johnson-Pint; 23.05.2016
comment
Большое спасибо. Это помогло мне принять решение. - person LP13; 23.05.2016
comment
Asking the user for their time zone from a list is a good idea. Usually one would place this on a user settings or profile page. You can use the guess made earlier to pick a default value from the list. Однако, если я показываю часовые пояса Windows в раскрывающемся списке ... я предполагаю, что я не могу использовать предположение momentjs для выбора значения по умолчанию, потому что нет сопоставления 1-1 с идентификатором зоны Windows. Это означает, что мне все еще нужно использовать noda. Это правильное предположение? - person LP13; 23.05.2016
comment
Верный. Вам нужно будет угадать зону IANA, а затем перевести ее в зону Windows с помощью Noda Time. А если у вас есть Noda Time, тогда вы можете в первую очередь использовать зоны IANA. Возможно, в конце концов мы добавим поддержку зон Windows в часовой пояс, но в целом предпочтительнее использовать зоны IANA. (Обратите внимание, что даже сама Windows работает над переходом к зонам IANA, о чем свидетельствуют такие API WinRT, как Windows.Globalization.Calendar и Windows.Globalization.DateTimeFormatting.DateTimeFormatter.) - person Matt Johnson-Pint; 23.05.2016

Вам необходимо преобразовать TimeSpan в DateTime, используя текущий год, месяц и день. Если вы вычтете из TimeSpan без этого, это может привести к недостижимой дате.
Кроме того, в вашем обновлении я заметил, что вы оставили результаты в DateTime, поэтому я сделал то же самое. Этот код показывает вам время, если время UTC было 1:00 AM, как указано в вашей проблеме.

        double offset = 5.00;
        TimeSpan utcTime = new TimeSpan(1,0,0); //setting manually to your representation of 1 am.
        DateTime newLocalDateTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, utcTime.Hours, utcTime.Minutes, utcTime.Seconds);
        newLocalDateTime = newLocalDateTime.Subtract(TimeSpan.FromHours(offset));
person Joe    schedule 19.05.2016
comment
У меня нет даты и времени. У меня есть временной интервал, в котором указано время в формате UTC - person LP13; 19.05.2016
comment
Я отредактировал свой ответ. TimeSpan s - это представление возвращаемого вам TimeSpan. Обратите внимание, что TimeSpan.FromMinutes может иметь значение FromSeconds или что-то еще, что лучше всего подходит для вашего проекта. Значение, которое вы передаете, также может быть отрицательным, что приведет к вычитанию времени из промежутка времени. Тем не менее, я немного смущен тем, почему вы должны возвращать временной интервал для представления текущего времени сервера. Но это должно помочь. - person Joe; 19.05.2016
comment
Позвольте мне немного объяснить интервал времени и дату и время. TimeSpan - это количество времени. Например, 5 часов (5: 00,0). DateTime - это представление определенного времени 19.05.2016, 17:36. Я думаю, что вам не хватает того, что 1: 00.0 представляет собой час, а не 1 час ночи. Когда вы пытаетесь перейти до этого времени, он не откатывается к предыдущему дню, как объект datetime. Однако мы могли бы решить эту проблему, создав объект datetime из этого TimeSpan, затем увеличивая или вычитая время и конвертируя обратно в промежуток времени. - person Joe; 20.05.2016
comment
Все в порядке. Я довольно сильно отредактировал свой ответ. Я продемонстрировал создание объекта DateTime, позволяющего вычесть смещение. В моем примере показано, как вручную установить TimeSpan, с которого вы начинаете, на 1:00, что было проблемой, с которой вы столкнулись. Я думаю, что это довольно хорошо решает ваши проблемы, хотя я все же сказал бы, что лучше всего сохранить время UTC как DateTime, чтобы вам не приходилось конвертировать обратно вот так. Но это потребуется, если вы собираетесь использовать такой TimeSpan. Дайте мне знать, если вам понадобится что-нибудь еще или дополнительные объяснения. - person Joe; 20.05.2016
comment
Вы действительно не хотите делать какое-либо сложение или вычитание для учета часового пояса. Взгляните на DateTimeOffset, TimeZoneInfo и Noda Time - person Matt Johnson-Pint; 20.05.2016