29

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

我们无法检测到当前最终用户的本地时间和时区对它进行的操作是什么。他们选择不同的文化,例如 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,但我不明白它如何帮助托管在不同位置的多文化 Web 应用程序。

问题

如何29 Dec 2013, 3:15 P.M为登录 INDIA/USA/Anywhere 的用户显示相同的记录创建日期?

到目前为止,我的逻辑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 小时创建的

几乎我想我什么都做到了,除了显示格式和计算。我怎样才能做到这一点?

4

5 回答 5

14

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

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

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

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

如果您决定使用 Noda Time,则只需OffsetDateTime使用DateTimeOffset.

于 2013-12-30T16:55:40.310 回答
11

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

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

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

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

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

于 2013-12-25T19:54:43.393 回答
9

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

  • 你试过问他们吗?许多应用程序让用户在用户设置中选择他们的时区。

  • 您可以从一个下拉列表或一对列表(国家,然后是国家内的时区)或基于地图的时区选择器控件中进行选择。

  • 您可以猜测并将其用作默认值,除非您的用户更改它。

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

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

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

另请参阅此处此处

此外 - 另一种解决方案是将 UTC 时间传递给浏览器并将其加载到 JavaScriptDate对象中。浏览器可以将其转换为用户的本地时间。您还可以使用像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?你不能只是假设我们都在东部标准时间。

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

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

  2. 您正在存储 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);
    
于 2013-12-28T17:27:07.227 回答
1

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

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

确定用户的时区

于 2014-01-04T10:02:08.940 回答
0

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

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

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

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

于 2013-12-30T16:39:27.550 回答