DateTime.Now 和文化/时区特定

Posted

技术标签:

【中文标题】DateTime.Now 和文化/时区特定【英文标题】:DateTime.Now and Culture/Timezone specific 【发布时间】:2014-01-13 14:14:42 【问题描述】:

我们的应用程序旨在处理来自不同地理位置的用户。

我们无法检测到当前最终用户的当地时间和 时区操作就可以了。他们选择不同的文化,比如 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 中的逻辑是基于用户登录的文化。这应该与文化无关,因为用户可以使用来自印度/美国的任何文化登录

数据库列

创建时间: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 小时创建的

除了显示格式和计算之外,我认为我几乎完成了一切。我怎样才能做到这一点?

【问题讨论】:

如果您想存储包括时区信息在内的日期/时间,您不应该存储DateTimeOffset 值吗? @hvd,我没听懂你。你的意思是想让我将DateTime.UtcNow 更改为DateTimeOffset.UtcNow? 它将如何解决我的不同最终用户选择不同文化的问题? @hvd,SQL server 列是SMALLDATETIME 我可能误解了你,我认为“我们想要显示相同的历史时间”是指你想要存储时间应该显示为 IST 的事实。 "问题是我们使用 DateTime.Now 来存储记录创建/更新日期等。" - 这感觉像是要解决的根本问题,之后其余的大部分都应该自行解决。真的不清楚你在问什么。 【参考方案1】:

听起来您需要存储DateTimeOffset 而不是DateTime。您可以只将本地DateTime 存储给创建值的用户,但这意味着您不能执行任何排序操作等。您不能只使用DateTime.UtcNow,因为那样不会t 存储任何内容以指示创建记录时用户的本地日期/时间。

或者,您可以将一个瞬间与用户的时区一起存储 - 这更难实现,但会为您提供更多信息,因为这样您就可以说“用户的当地时间是一小时是多少以后呢?”

服务器的托管应该是无关紧要的——你永远不应该使用服务器的时区。但是,您需要知道用户的适当 UTC 偏移量(或时区)。这不能仅基于文化来完成 - 您需要在用户机器上使用 javascript 来确定您感兴趣的时间(不一定是“现在”)的 UTC 偏移量。

一旦您确定了如何存储该值,检索它就很简单 - 如果您已经存储了 UTC 时刻和一个偏移量,您只需应用该偏移量,您就会返回到原始用户的本地时间.您还没有说明如何将值转换为文本,但它应该只是简单地退出 - 只需格式化值,您就应该得到原始的本地时间。

如果您决定使用 Noda Time,您只需使用 OffsetDateTime 而不是 DateTimeOffset

【讨论】:

我更新了我的问题。有不清楚的地方请告诉我,所以为了便于理解我尽量补充:) @Billa:我认为更新后的问题中的任何内容都不会影响我的答案......它仍然存在。您应该存储DateTimeOffset,然后很容易检索本地时间。棘手的一点是要计算出DateTimeOffset 来存储——这不能通过文化来完成;您需要使用 Javascript。 在我中间做的一个项目中,我们有相同的需求,而之前的架构师在项目开始时从未尝试过解决这个问题。因此,在创建的每个用户中都提供了解决方案,提供了选择时区的选项(从国家我们只预先选择了该国家/地区的第一个“有效”时区)。有了这些信息,我们可以获得 OffsetDateTime,并防止更改所有视图以计算数据,我们进入数据层(自定义公司 API)并在每个返回日期/时间列的查询中添加一个新参数...... .. 在查询中,我们添加了在 SQL Server 端进行计算的逻辑。因此,每个日期时间值都针对数据库中的用户时区进行了“更正”。当我们进行插入或更新时,我们也做了同样的事情,但是做了反转操作(从时区偏移到 UTC)【参考方案2】:

如果特定时刻很重要,标准方法是始终将任何时间数据存储为 UTC。该时间不受时区变化和文化的影响。

使用时区显示时间的最常见方法是将时间存储为 UTC,并在显示值时转换为当前用户的文化/时区组合。这种方法只需要在存储中归档单个日期时间。

请注意,对于 Web 案例(如 ASP.Net),您可能需要先确定用户的文化/时区并将其发送到服务器(因为 GET 请求不需要此信息)或在浏览器。

根据“显示相同历史时间”的不同,您可能需要存储其他信息,例如当前文化和/或当前偏移量。如果您需要准确地显示原始用户所看到的时间,您还可以保存字符串表示形式(因为格式/翻译可能会在以后发生变化并且值看起来会有所不同,这也是不寻常的)。

注意:文化和时区没有联系在一起,因此您需要决定如何处理美国 PST 时区的 IN-IN 文化等情况。

【讨论】:

【参考方案3】:

我对您问题的措辞有点困惑,但您似乎想确定用户的时区。

您是否尝试过询问他们?许多应用程序让用户在用户设置中选择他们的时区。

您可以从一个下拉列表或一对列表(国家,然后是国内的时区)或a map-based time zone picker control 中进行选择。

您可以 take a guess 并将其用作默认值,除非您的用户更改它。

如果您沿着这条路线走,您将需要能够使用 IANA/Olson 时区,这正是 Noda Time 发挥作用的地方。您可以通过DateTimeZoneProviders.Tzdb 访问它们。

如果您使用 UTC,则托管位置无关紧要。这是一件好事。

另外,如果您使用的是 Noda Time,那么您可能应该使用 SystemClock.Instance.Now 而不是 DateTime.UtcNow

另请参阅 here 和 here。

另外 - 另一种解决方案是将 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 时,它的意思是“西班牙语,就像在墨西哥所说的那样”。这并不意味着用户实际上是墨西哥。这只是表示用户说的是西班牙语方言,而es-ES 的意思是“西班牙语,就像在西班牙所说的那样”。

即使文化代码中的国家/地区部分是可靠的,也有 许多 个国家有多个时区!例如,您会在en-US 的映射列表中添加什么?你不能只是假设我们都在东部标准时间。

现在,我已经解释了为什么您当前的方法行不通,我强烈建议您采纳我最初的建议。很简单:

    确定用户的时区,最好通过询问他们,也许在我上面链接的实用程序之一的帮助下。

    您正在存储 UTC,因此只需转换为该时区即可显示。

    使用 Microsoft 时区
    TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
    DateTime localDatetime = TimeZoneInfo.ConvertTimeFromUtc(yourUTCDateTime, tz);
    
    使用 IANA 时区和野田时间
    DateTimeZone tz = DateTimeZoneProviders.Tzdb["Europe/Stockholm"];
    Instant theInstant = Instant.FromDateTimeUtc(yourUTCDateTime);
    LocalDateTime localDateTime = theInstant.InZone(tz);
    

【讨论】:

非常感谢。我会通过它.. 基本上最终用户将从Europe/Stockholm 访问,应用程序将托管在USAAdministrator from INDIA 将查看瑞典用户的预约次数和时间(历史)。这里瑞典人会选择sv-se文化,印度人会选择In-In文化,for localization view 更新了一个问题,用视觉图片方便理解 这张图不错,但我还是不明白你的问题是什么。文化只与格式有关,与时区无关。它们根本不相关。 现在我添加了一些带有更多解释的编码。如果有帮助,请告诉我。谢谢! DateTimeOffset 有它的优势。我用它所有的时间。我不确定它是否对您有帮助,因为我仍然不完全理解您要完成的工作。您可以查看差异here【参考方案4】:

在我最近开发的一个应用程序中,我们遇到了类似的问题。在开发过程中,每个人都在同一个时区,并且没有注意到这个问题。无论如何,有很多遗留代码很难更改,更不用说转换数据库中已经存在的所有日期时间信息了。因此,更改为 DateTimeOffset 不是一种选择。但是我们设法通过在输出时从服务器时间转换为用户时间并在输入时从用户时间转换为服务器时间来使这一切保持一致。对任何作为边界的日期时间比较执行此操作也很重要。因此,如果用户希望某些东西在他们的时间午夜过期,那么我们会将该时间转换为服务器时间并在服务器时间中进行所有比较。这听起来像是很多工作,但比将整个应用程序和数据库转换为使用 DateTimeOffsets 的工作要少得多。

Hear 是一个看起来对时区问题有一些很好的解决方案的线程。

Determine a User's Timezone

【讨论】:

您的转化依据是什么? 对我们来说这更容易一些,因为我们可以假设一个时区来转换。服务器在澳大利亚东部标准时间,用户都在美国东部时间。然而,正如许多人指出的那样,弄清楚用户所在的时区是一个不同的问题。关键是始终一致地存储所有时间。 要弄清楚客户所在的时区,要么需要从用户那里获取,要么根据他们的位置做出假设。文化绝对是红色的野兔。我在泰国呆了 6 年,我的电脑一直设置为 en-AU。 你如何检测他们的位置和时区? 那你可以用JS问浏览器。或者您可以在 IP 数据库中查找客户端 IP。无论哪种方式,您都不会 100% 正确地做到这一点,因此让用户选择它也不会受到伤害。告诉用户他们被假定在哪个时区也不会受到伤害。【参考方案5】:

如果您想向用户显示一致的日期/时间历史记录,无论他们从哪个区域查看历史记录,那么:

    Save 期间,不仅存储UTC“创建”日期/时间,还存储检测到的语言环境 使用存储的saved from locale 计算原始日期/时间并发出要显示的字符串(即,在显示时不要使用当前用户区域设置)

如果您没有修改存储的能力,那么也许您可以更改提交以发送“当前客户端时间”,按字面存储(不要转换为 UTC),然后按字面显示(不要转换到检测到的文化)

但正如我在您的问题下的评论中所说,我不确定您的要求是否正确。

【讨论】:

以上是关于DateTime.Now 和文化/时区特定的主要内容,如果未能解决你的问题,请参考以下文章

python中的时区有几分钟的变化

如何表示各个时区的时间DateTime.ToString

在 Python 中获取随机时区感知日期时间

在 linux 机器中出现 astimezone 错误

更新文化短时间模式格式未进入 DateTime.now

DateTime.Now 和DateTime.UtcNow的区别