Спецификация DateTime.Now и культура/Timezone
Наше приложение было разработано для работы с пользователем из другого географического местоположения.
Мы не можем определить, какое текущее конечное пользовательское время и часовой пояс. Они выбирают другую культуру, такую как sv-se, en-us, ta-In даже они получают доступ из часовой пояс в Европе/Лондоне..
Мы размещали его на сервере хостинга в США, пользователи приложений от Norway/Denmark/Sweden/UK/USA/India
Проблема заключается в том, что мы использовали DateTime.Now
для хранения созданной/обновленной даты записи и т.д.
Поскольку Сервер работает в США, все пользовательские данные сохраняются как время США: (
После исследования в SO мы решили сохранить все даты истории в БД как DateTime.UtcNow
Проблема:
![enter image description here]()
Существует запись, созданная на 29 Dec 2013, 3:15 P.M Swedish time
.
public ActionResult Save(BookingViewModel model)
{
Booking booking = new Booking();
booking.BookingDateTime = model.BookingDateTime; //10 Jan 2014 2:00 P.M
booking.Name = model.Name;
booking.CurrentUserId = (User)Session["currentUser"].UserId;
//USA Server runs in Pacific Time Zone, UTC-08:00
booking.CreatedDateTime = DateTime.UtcNow; //29 Dec 2013, 6:15 A.M
BookingRepository.Save(booking);
return View("Index");
}
Мы хотим показать одно и то же время истории для пользователя, который вошел в систему в Индии/Швеции/США.
В настоящее время мы используем текущего пользователя культуры, зарегистрированного и выбираем часовой пояс из файла конфигурации и используем для преобразования с классом TimeZoneInfo
<appSettings>
<add key="sv-se" value="W. Europe Standard Time" />
<add key="ta-IN" value="India Standard Time" />
</appSettings>
private DateTime ConvertUTCBasedOnCuture(DateTime utcTime)
{
//utcTime is 29 Dec 2013, 6:15 A.M
string TimezoneId =
System.Configuration.ConfigurationManager.AppSettings
[System.Threading.Thread.CurrentThread.CurrentCulture.Name];
// if the user changes culture from sv-se to ta-IN, different date is shown
TimeZoneInfo tZone = TimeZoneInfo.FindSystemTimeZoneById(TimezoneId);
return TimeZoneInfo.ConvertTimeFromUtc(utcTime, tZone);
}
public ActionResult ViewHistory()
{
List<Booking> bookings = new List<Booking>();
bookings=BookingRepository.GetBookingHistory();
List<BookingViewModel> viewModel = new List<BookingViewModel>();
foreach (Booking b in bookings)
{
BookingViewModel model = new BookingViewModel();
model.CreatedTime = ConvertUTCBasedOnCuture(b.CreatedDateTime);
viewModel.Add(model);
}
return View(viewModel);
}
Просмотр кода
@Model.CreatedTime.ToString("dd-MMM-yyyy - HH':'mm")
ПРИМЕЧАНИЕ. Пользователь может изменить культуру/язык до входа в систему. Это приложение на основе локализации, работающее на американском сервере.
Я видел NODATIME
, но я не мог понять, как это может помочь в веб-приложении с несколькими культурами, размещенном в другом месте.
Вопрос
Как я могу показать дату создания такой же даты записи 29 Dec 2013, 3:15 P.M
для пользователей, зарегистрированных в INDIA/USA/Anywhere`?
На данный момент моя логика в ConvertUTCBasedOnCuture
основана на регистрации пользователя в культуре. Это должно быть независимо от культуры, поскольку пользователь может войти в систему, используя любую культуру из Индии/США.
КОЛОНКА БАЗЫ ДАННЫХ
CreatedTime: SMALLDATETIME
ОБНОВЛЕНИЕ: ПОПЫТНОЕ РЕШЕНИЕ:
DATABASE COLUMN TYPE: DATETIMEOFFSET
Пользовательский интерфейс
Наконец, я отправляю текущее локальное время пользователя, используя нижеуказанный код Momento.js в каждом запросе
$.ajaxSetup({
beforeSend: function (jqXHR, settings) {
try {
//moment.format gives current user date like 2014-01-04T18:27:59+01:00
jqXHR.setRequestHeader('BrowserLocalTime', moment().format());
}
catch (e) {
}
}
});
ПРИМЕНЕНИЕ
public static DateTimeOffset GetCurrentUserLocalTime()
{
try
{
return
DateTimeOffset.Parse(HttpContext.Current.Request.Headers["BrowserLocalTime"]);
}
catch
{
return DateTimeOffset.Now;
}
}
после чего вызывается
model.AddedDateTime = WebAppHelper.GetCurrentUserLocalTime();
В представлении
@Model.AddedDateTime.Value.LocalDateTime.ToString("dd-MMM-yyyy - HH':'mm")
В поле отображается локальное время для пользователя, однако я хочу видеть как dd-MMM-yyyy CET/PST
(2 часа назад).
Это 2 часа назад должно рассчитываться с локального времени пользователя. Точно так же, как вопрос, созданный/отредактированный, с отображением часового пояса и расчетами локальных пользователей.
Пример: answered Jan 25 '13 at 17:49 CST (6 hours/days/month ago)
Таким образом, другой просмотр пользователя из США/Индии может действительно понять, что эта запись была создана ровно через 6 часов с текущего времени в Индии/США
Почти я думаю, что я достиг всего, кроме формата отображения и расчета. Как я могу это сделать?
Ответы
Ответ 1
Похоже, вам нужно сохранить DateTimeOffset
вместо DateTime
. Вы можете просто сохранить локальный DateTime
для пользователя, создающего это значение, но это означает, что вы не можете выполнять какие-либо операции заказа и т.д. Вы не можете просто использовать DateTime.UtcNow
, так как это не будет хранить ничего, чтобы указать локальный дата/время пользователя, когда запись была создана.
В качестве альтернативы вы можете сохранить момент времени вместе с пользовательским часовым поясом - это труднее достичь, но даст вам больше информации, так как тогда вы сможете сказать такие вещи, как "Какое местное время пользователя один час позже?"
Хостинг сервера должен быть неактуальным - вы никогда не должны использовать часовой пояс сервера. Тем не менее, вам нужно будет знать соответствующее UTC-смещение (или часовой пояс) для пользователя. Это невозможно сделать на основе только культуры - вы захотите использовать Javascript на пользовательской машине, чтобы определить смещение UTC в то время, когда вас интересует (не обязательно "сейчас" ).
Как только вы определили, как сохранить это значение, получить его просто: если вы уже сохранили время UTC и смещение, вы просто применяете это смещение, и вы вернетесь в исходное местное время пользователя, Вы не сказали, как вы конвертируете значения в текст, но просто должны просто просто отказаться - просто отформатируйте значение, и вы должны получить исходное местное время.
Если вы решили использовать Noda Time, вы просто используете OffsetDateTime
вместо DateTimeOffset
.
Ответ 2
Стандартный подход заключается в том, чтобы всегда хранить данные времени в формате UTC, если важный момент времени имеет важное значение. На это время не влияют изменения и культуры часовых поясов.
Наиболее распространенный подход к показу времени с часовым поясом - сохранение времени в формате UTC и преобразование в текущую комбинацию пользовательской культуры/часового пояса при отображении значения. Для этого подхода требуется только одно время, указанное в хранилище.
Обратите внимание, что для веб-кейсов (например, ASP.Net) вам может понадобиться сначала определить культуру пользователя/часовой пояс и отправить его на сервер (поскольку эта информация не требуется в запросах GET) или форматирование времени в браузер.
В зависимости от того, что "показывает одно и то же время истории", вам может потребоваться хранить дополнительную информацию, например текущую культуру и/или текущее смещение. Если вам нужно показать время точно так же, как это сделал его оригинальный пользователь, вы также можете сохранить строковое представление (потому что форматы/переводы могут измениться позже, а значение будет выглядеть по-другому, также это необычно).
Примечание. Культура и часовой пояс не связаны друг с другом, поэтому вам нужно решить, как вам нужно обрабатывать такие случаи, как IN-IN culture в часовом поясе PST США.
Ответ 3
Меня немного смущает формулировка вашего вопроса, но кажется, что вы хотите определить часовой пояс своего пользователя.
-
Вы просили расспросить их? Во многих приложениях пользователь выбирает свой часовой пояс в пользовательских настройках.
-
Вы можете выбрать из раскрывающегося списка или пару списков (страна, затем часовой пояс внутри страны) или управление выбором часовых поясов на основе карт.
-
Вы можете принять предположение и использовать это как значение по умолчанию, если ваш пользователь не изменит его.
Если вы спуститесь по этому маршруту, вам нужно будет использовать часовые пояса IANA/Olson, где находится Noda Time в игру. Вы можете получить к ним доступ из DateTimeZoneProviders.Tzdb
.
Место размещения не имеет значения, если вы используете UTC. Это хорошая вещь.
Кроме того, если вы используете Noda Time, то вам, вероятно, следует использовать SystemClock.Instance.Now
вместо DateTime.UtcNow
.
См. также здесь и здесь.
Кроме того, альтернативным решением было бы просто передать время UTC браузеру и загрузить его в объект JavaScript Date
. Браузер может преобразовать это в локальное время пользователя. Вы можете также использовать библиотеку, например moment.js, чтобы сделать это проще.
Update
Относительно вашего подхода к отображению кодов культуры в часовые пояса:
<appSettings>
<add key="sv-se" value="W. Europe Standard Time" />
<add key="ta-IN" value="India Standard Time" />
</appSettings>
Это не сработает по нескольким причинам:
-
Многие люди используют разные настройки культуры на своем компьютере, чем область, в которой они физически находятся. Например, я мог бы быть американским-американским докладчиком, живущим в Германии, мой код культуры, вероятно, все еще en-US
, а не de-DE
.
-
Код культуры, содержащий страну, используется для различения диалектов языка. Когда вы видите es-MX
, это означает "испанский, как говорят в Мексике". Это не означает, что пользователь фактически находится в Мексике. Это просто означает, что пользователь говорит о диалекте испанского языка по сравнению с es-ES
, что означает "испанский, как говорят в Испании".
-
Даже если страновая часть кода культуры может быть надежной, есть много стран, которые имеют несколько часовых поясов! Например, что бы вы поместили в свой список сопоставлений для en-US
? Вы не можете просто предположить, что все мы находимся на восточном стандартном времени.
Теперь я объяснил, почему ваш нынешний подход не будет работать, я настоятельно рекомендую вам взять мой первоначальный совет. Очень просто:
-
Определите часовой пояс пользователя, желательно, спросив их, возможно, с некоторой помощью одной из утилит, с которыми я связан выше.
-
Вы сохраняете UTC, поэтому просто перейдите в этот часовой пояс для отображения.
Использование временных зон Microsoft
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
DateTime localDatetime = TimeZoneInfo.ConvertTimeFromUtc(yourUTCDateTime, tz);
Использование временных зон IANA и времени Noda
DateTimeZone tz = DateTimeZoneProviders.Tzdb["Europe/Stockholm"];
Instant theInstant = Instant.FromDateTimeUtc(yourUTCDateTime);
LocalDateTime localDateTime = theInstant.InZone(tz);
Ответ 4
Мы столкнулись с аналогичной проблемой с приложением, над которым я работал в последнее время. Во время разработки каждый находился в одном и том же часовом поясе, и проблема не была замечена. И в любом случае было много устаревшего кода, который был бы болью для изменения, не говоря уже о преобразовании всей информации о дате, которая уже была в БД. Поэтому переход на DateTimeOffset не был вариантом. Но нам удалось добиться этого, переходя от времени сервера к пользовательскому времени на выходе и перехода от времени пользователя к серверному времени на пути. Важно также делать это с любыми сравнениями времени, которые были границей. Поэтому, если пользователь ожидал, что какое-то время истечет в полночь, тогда мы будем конвертировать это время в серверное время и делать все сравнения во время сервера. Это звучит как много работы, но гораздо меньше работы, чем преобразование всего приложения и БД для использования DateTimeOffsets.
Слух - это поток, который похож на несколько хороших решений для часового пояса.
Определите часовой пояс пользователя
Ответ 5
Если вы хотите показать согласованную историю даты и времени для пользователя, независимо от языкового стандарта, из которого они просматривают историю, выполните следующие действия:
- Во время
Save
сохраняйте не только дату/время создания "UTC", но и обнаруженную локаль
- Использовать сохраненный
saved from locale
для вычисления исходной даты/времени и испускать строку для отображения (т.е. не использовать текущую локаль пользователя, когда вы ее разворачиваете).
Если у вас нет возможности изменить ваше хранилище, возможно, вы можете изменить свою отправку, чтобы отправить "текущее клиентское время", сохранить его буквально (не конвертировать в UTC), а затем отобразить буквально (не конвертировать к обнаруженной культуре)
Но, как я говорю в своем комментарии по вашему вопросу, я не уверен, что правильно понял ваши требования.