我们的应用程序旨在处理来自不同地理位置的用户。
我们无法检测当前最终用户当地时间和时间 时区对它进行操作。他们选择不同的文化,如sv-se, en-us,ta-In甚至他们从欧洲/伦敦时区进入..
我们将其托管在美国的托管服务器中,应用程序用户来自Norway/Denmark/Sweden/UK/USA/India
问题是我们使用DateTime.Now
来存储创建/更新日期等等。
由于 服务器在美国运行 ,所有用户数据都会保存为美国时间:(
在SO中进行研究后,我们决定将所有历史日期存储在DB中DateTime.UtcNow
问题:
在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 ConvertUTCBasedOnCulture(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 = ConvertUTCBasedOnCulture(b.CreatedDateTime);
viewModel.Add(model);
}
return View(viewModel);
}
查看代码
@Model.CreatedTime.ToString("dd-MMM-yyyy - HH':'mm")
注意:用户可以在登录前更改文化/语言。它是一个基于本地化的应用程序,运行在美国服
我见过NODATIME
,但我无法理解它如何帮助托管在不同位置的多文化网络应用程序。
问题
如何为登录INDIA / USA / Anywhere`的用户显示相同的记录创建日期29 Dec 2013, 3:15 P.M
?
截至目前,ConvertUTCBasedOnCulture
中的逻辑基于用户登录文化。这应该与文化无关,因为用户可以使用来自印度/美国的任何文化登录
DATABASE COLUMN
CreatedTime:SMALLDATETIME
更新:擅自解决方案:
DATABASE COLUMN TYPE: DATETIMEOFFSET
UI
最后,我在每个请求中使用以下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小时内创建的
几乎我认为我实现了一切,除了显示格式和显示格式计算。我怎样才能做到这一点?
答案 0 :(得分:14)
听起来您需要存储DateTimeOffset
而不是DateTime
。您可以只将本地DateTime
存储到创建值的用户,但这意味着您无法执行任何订购操作等。您不能只使用DateTime.UtcNow
,因为在创建记录时不会存储任何东西来指示用户的本地日期/时间。
或者,您可以及时存储用户的时区 - 这很难实现,但会提供更多信息,因为您可以说“用户当地时间是一小时”以后?“
服务器的托管应该是无关紧要的 - 您永远不应该使用服务器的时区。但是,您需要知道用户的相应UTC偏移(或时区)。 不能只根据文化来完成 - 您需要在用户的机器上使用Javascript来确定您感兴趣时的UTC偏移(不一定是“现在”)。 / p>
一旦你弄清楚了如何存储价值,检索它很简单 - 如果你已经存储了UTC瞬间和偏移量,你只需应用该偏移量就可以回到原始用户的当地时间。您还没有说过如何将值转换为文本,但它应该只是简单地删除 - 只需格式化值,您应该获得原始的本地时间。
如果您决定使用Noda Time,则只需使用OffsetDateTime
代替DateTimeOffset
。
答案 1 :(得分:11)
标准方法是,如果特定时刻很重要,则始终将任何时间数据存储为UTC。那个时间不会受到时区变化和文化的影响。
显示时区时间的最常用方法是将时间存储为UTC,并在显示值时转换为当前用户的文化/时区组合。此方法仅需要在存储中提交单个日期时间。
请注意,对于Web情况(如ASP.Net),您可能需要首先确定用户的文化/时区并将其发送到服务器(因为此信息在GET请求中不是必需的)或者在浏览器。
根据“显示相同的历史时间”,您可能需要存储其他信息,如当前文化和/或当前偏移量。如果你需要完全像原始用户看到的那样显示时间,你也可以保存字符串表示(因为格式/翻译可以稍后更改,值看起来会有所不同,也很不寻常)。
注意:文化和时区没有联系在一起,因此您需要决定在美国PST时区如何处理IN-IN文化等案例。
答案 2 :(得分:7)
我对你的问题的措辞感到有些困惑,但似乎你想确定你的用户的时区。
你有没有试过问他们?许多应用程序让用户在用户设置中选择他们的时区。
您可以从下拉列表或一对列表(国家/地区,然后是国家/地区内的时区)或a map-based time zone picker control中选择。
您可以take a guess并将其用作默认设置,除非您的用户对其进行更改。
如果沿着这条路走下去,您将需要能够使用IANA / Olson时区,这是Noda Time发挥作用的地方。您可以从DateTimeZoneProviders.Tzdb
访问它们。
如果您使用UTC,托管位置无关紧要。这是件好事。
另外,如果你正在使用Noda Time,那么你可能应该使用SystemClock.Instance.Now
而不是DateTime.UtcNow
。
此外 - 另一种解决方案是将UTC时间传递给浏览器并将其加载到JavaScript Date
对象中。浏览器可以将其转换为用户的本地时间。您还可以使用moment.js之类的库来简化这一过程。
关于将文化代码映射到时区的方法:
<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
时,这意味着“西班牙语,就像在墨西哥说的那样”。这并不意味着用户实际上是 in Mexico。它只是意味着用户说的是西班牙语方言,而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);
答案 3 :(得分:1)
我们最近处理的应用程序遇到了类似的问题。在开发过程中,每个人都处于同一时区,问题没有被发现。无论如何,有很多遗留代码本来很难改变,更不用说转换已经在数据库中的所有日期时间信息了。因此,不能选择更改为DateTimeOffset。但是我们设法通过在出路上从服务器时间转换为用户时间并在转换时从用户时间转换为服务器时间来实现一致性。对于作为边界的任何日期时间比较来执行此操作也很重要。因此,如果用户期望某些事情在午夜时间到期,那么我们会将该时间转换为服务器时间并在服务器时间内进行所有比较。这听起来像是很多工作,但是将整个应用程序和DB转换为使用DateTimeOffsets的工作要少得多。
听到一个看起来像时区问题的解决方案的线程。
答案 4 :(得分:0)
如果您想向用户显示一致的日期/时间历史记录,无论他们从哪个区域设置查看历史记录,那么:
Save
期间,不仅存储UTC“创建”日期/时间,还存储检测到的区域设置saved from locale
计算原始日期/时间并发出要显示的字符串(即,在进行双工时不要使用当前用户区域设置)如果您无法修改存储空间,那么也许您可以更改提交以发送“当前客户端时间”,按字面意思存储(不转换为UTC)然后按字面显示(不要转换)检测文化)
但正如我在你的问题评论中所说,我不确定我的要求是否正确。