我们的应用程序旨在处理来自不同地理位置的用户。
We are unable to detect what is the current end user local time and
time zone operate on it. They select different culture like sv-se,
en-us, ta-In even they access from Europe/London timezone..
我们在美国的托管服务器中托管它,应用程序用户来自Norway/Denmark/Sweden/UK/USA/India
问题是我们使用DateTime.Now来存储记录创建/更新日期等。
由于服务器在美国运行,所有用户数据都保存为美国时间:(
在SO中进行研究后,我们决定将所有历史日期存储在DB中DateTime.UtcNow
问题:
在29 Dec 2013, 3:15 P.M Swedish time上创建了一条记录。
1 2 3 4 5 6 7 8 9 10 11
| 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");
} |
我们希望向在India / Sweden / USA登录的用户显示相同的历史记录时间。
截至目前,我们正在使用当前文化用户登录并从配置文件中选择时区并使用TimeZoneInfo类进行转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| </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 );
} |
查看代码
1
| @Model.CreatedTime.ToString("dd-MMM-yyyy - HH':'mm") |
注意:用户可以在登录前更改文化/语言。它是一个基于本地化的应用程序,运行在美国服
我见过NODATIME,但我无法理解它如何帮助托管在不同位置的多文化Web应用程序。
题
如何为登录INDIA / USA / Anywhere`的用户显示相同的记录创建日期29 Dec 2013, 3:15 P.M?
截至目前,我在ConvertUTCBasedOnCulture中的逻辑基于用户登录文化。这应该与文化无关,因为用户可以使用来自印度/美国的任何文化登录
数据库列
CreatedTime:SMALLDATETIME
更新:擅自解决方案:
DATABASE COLUMN TYPE: DATETIMEOFFSET
UI
最后,我在每个请求中使用以下Momento.js代码发送当前用户的本地时间
1 2 3 4 5 6 7 8 9 10
| $.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) {
}
}
}); |
应用
1 2 3 4 5 6 7 8 9 10 11 12
| public static DateTimeOffset GetCurrentUserLocalTime()
{
try
{
return
DateTimeOffset.Parse(HttpContext.Current.Request.Headers["BrowserLocalTime"]);
}
catch
{
return DateTimeOffset.Now;
}
} |
然后打电话给
1
| model.AddedDateTime = WebAppHelper.GetCurrentUserLocalTime(); |
在视图中
1
| @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)因此来自USA / INDIA用户的其他观看者可以真正理解此记录是在距离INDIA / USA当前时间的6小时内创建的
几乎我认为我实现了一切,除了显示格式和显示格式计算。我怎样才能做到这一点?
-
如果要存储日期/时间(包括时区信息),是否应该存储DateTimeOffset值?
-
@hvd,我没找到你。你的意思是希望我将DateTime.UtcNow更改为DateTimeOffset.UtcNow?如何解决我的不同最终用户选择不同的文化问题?
-
@hvd,SQL服务器列是SMALLDATETIME
-
我可能误解了你,我认为通过"我们想要显示相同的历史时间"你的意思是你想要存储时间应该显示为IST的事实。
-
"问题是我们使用DateTime.Now存储创建的记录/更新日期等。" - 感觉就像是要解决的根本问题,其后大部分都应该自行解决。除此之外,你真正不清楚你在问什么。
-
你的问题很混乱,特别是图表。您可以谈论许多区域设置,约会和日期/时间以及记录创建日期/时间,但不清楚您在每个日期时间字段的区域设置中的要求是什么;此外,您的QUESTION仅询问记录创建时间。
-
@JonSkeet,是的,我得到了它,我在Save()中将其更改为DateTime.UtcNow而不是DateTime.Now。服务器在美国运行,并将最终用户当地时间"2013年12月29日,3:15 P.M`存储到UTC时间29 Dec 2013, 6:15 A.M。最初用户看到29 Dec 2013, 6:15 A.M的历史记录,然后我们开始使用ConvertUTCBasedOnCuture()中显示的基于文化的计算代码转换回29 Dec 2013, 3:15 P.M。它工作正常。现在问题是一个管理员用户,来自印度,他的文化ta-IN或en-US观看了不同的时间。他应该能够看到2013年12月29日,下午3:15
-
@Billa:如果你需要能够回到同一个本地时间,你需要存储一个DateTimeOffset而不是DateTime - 你需要将数据库字段设为DateTimeOffset字段。
-
@JonSkeet,如果我将它作为DateTimeOffset存储在数据库中,我如何显示/转换为印度用户才能看到29 Dec 2013, 3:15 P.M?请问有样品吗?对不起,我完全糊涂了如何采用这种新方法DateTimeOffset :(
-
@Billa:我已经添加了一个答案,但由于存在许多潜在的方面,此问题目前有点混乱。基本上你应该存储一个DateTimeOffset,你需要确定客户端而不是服务器的偏移量。
-
> @Billa - 我们无法检测当前最终用户当地时间和时区对其进行操作的情况。他们选择不同的文化,如sv-se,en-us,ta-In,即使他们从欧洲/伦敦时区进入。" - 如果你给他们一个选择,为什么你不能发现他们选择了什么?
-
@Billa:这是什么状态?你暂时没有留下任何评论,所以我们不知道你现在是否对这些答案感到满意......
-
@JonSkeet,我正在研究这个问题。与我们的团队讨论,更新托管服务器并在真实场景中进行测试需要一些时间。然而,我们是否感到困惑,我们是否采用您的/ Matt Johnson方法。我们所有人都只有1年的IT开发经验,并努力实施这两种方法。但是,我们需要快速做出这个赏金要约的决定。我应该标记你或马特。
-
@Billa:基本上找到用户的实际时区比在特定日期/时间找到UTC偏移更难。但是,它提供了有关其他日期和时间的更多信息。我们无法确定您是否需要其他信息,或者您是否只需要记录的每个值的偏移量。
-
@JonSkeet,我们正在更改基于DateTime偏移的代码,如Comment.AddedDateTime = DateTimeOffset.Now;和SQL侧列,参数类型为DateTimeOffset并显示@Model.AddedDateTime.ToString("dd-MMM-yyyy - HH':'mm")。我们将在服务器中更新此信息并通知您。非常感谢你帮助我们
-
@Billa:如果您在服务器端使用DateTimeOffset.Now,那么将使用服务器的时区来计算偏移量 - 您不希望这样。您想要客户的偏移量。
-
@JonSkeet,我如何获得客户日期时间? :(多数民众赞成我的问题,我无法从请求标题中获取。我是否需要使用Javascript发送存储在隐藏字段中的当前用户时间并将其发送到服务器?如果我发送,我认为我不需要更改DateTimeOffset 。
-
@Billa:正如我在答案中所说,你需要使用Javascript。存储DateTimeOffset的原因是为了让您知道绝对时间和本地时间 - 只是存储本地时间意味着您永远无法比较两次,例如。
-
@JonSkeet,我找到了你。但我需要考虑向服务器发送一个Javascript new Date()并将其保存到等效的DateTimeOffset。我相信我应该使用Moment.js并将有效的'CurrentBrowserOffsetTime'作为字符串发送并使用DateTimeOffset.Parse(currentJSOffsettime)。听起来不错吗?
-
@Billa:我没有使用Moment.js,但听起来好像没问题。
-
@JonSkeet,我试过并且最接近目标。我更新了我的问题UPDATE:ATTEMPTED SOLUTOIN。我想现在我可以在UI中显示我想要的内容。但努力进行计算和CST / IST显示......
-
@hvd,谢谢。你给了我一个正确的线索,最初我错过了,但是jon双向飞碟纠正了我:)
-
@Billa:你已经改变了要求。在您之前只说您需要显示原始当地时间("下午3:15")。现在你说你也需要显示原始时区。那是另一回事。对于一个不断变化的问题,Stack Overflow并不能很好地工作。请注意,"2小时前"部分不需要从任何时区计算 - 无论我们在世界的哪个地方,两个小时前都是两小时前。
-
@JonSkeet,在我们与客户讨论后,我不得不改变显示格式。我到达的当前解决方案完全符合最初的要求,当我向堆叠持有人展示时,他们提出了新的格式:(仍在努力...我的帮助,我得到了原始问题的答案真的很好。谢谢!!!
-
@Billa:说实话,开始一个新问题本来会更好 - 当然,在对它进行研究之后。看起来你需要去计划B:检测(有潜在的用户干预)用户的时区,并将其与日期/时间值一起存储。
-
另外:我相信还有其他关于在Javascript中检测时区的问题......你需要决定是使用.NET框架时区还是TZDB时区(使用Noda Time)。
-
@JonSkeet,我会使用相同的方法从DateTimeOffset存储它吗?我认为它包括DATETIME和时区偏移。我仍然在混淆为什么我应该使用DATETime和Timezone专栏
-
@Billa:一个偏移量不会告诉你时区。例如,UTC + 1的偏移可以是英国夏令时或中欧标准时间。你仍然可以存储一个DateTimeOffset - 这样可以很容易地回到当地时间 - 或者你可以存储瞬间(即UTC的DateTime)和时区。
-
@JonSkeet,我认为Matt Johnsons回答谈论它......我需要做的只是save AddedDateTime in DateTime.UtcNow and a TimeZone string (set in header using javascript) into database。显示我需要使用存储的UTC时间和时区作为日期,并从请求显示hours ago, etc检测到其他用户的时区。如果我是对的,让我开始研究这个问题!我继续更新:)
-
@Billa:不,你不需要在"小时前"使用任何用户的时区 - 只需从当前的UTC时间中获取存储的UTC时间。您使用用户的时区显示"3:15 CST"(或其他)。
-
@JonSkeet,我想我找到了你。 <5233>。使用像这样的DATETIME概念真的很难,我从来没有想到它会很棘手。是开始使用NODATIME的好时机
-
@Billa:值得早点阅读nodatime.org/unstable/userguide/concepts.html(以及用户指南的其他部分)。
听起来你需要存储DateTimeOffset而不是DateTime。您可以将本地DateTime存储到创建值的用户,但这意味着您无法执行任何订购操作等。您不能只使用DateTime.UtcNow,因为它不会存储任何指示本地的内容创建记录时用户的日期/时间。
或者,您可以及时存储用户的时区 - 这很难实现,但会提供更多信息,因为您可以说"一小时后用户的当地时间是多少?"
托管服务器应该是无关紧要的 - 你永远不应该使用服务器的时区。但是,您需要知道用户的相应UTC偏移(或时区)。这不能仅仅根据文化来完成 - 您需要在用户的机器上使用Javascript来确定您感兴趣时的UTC偏移(不一定是"现在")。
一旦你弄清楚如何存储值,检索它很简单 - 如果你已经存储了UTC瞬间和偏移量,你只需应用该偏移量,你就会回到原始用户的本地时间。您还没有说过如何将值转换为文本,但它应该只是简单地删除 - 只需格式化值,您应该获得原始的本地时间。
如果您决定使用Noda Time,则只需使用OffsetDateTime而不是DateTimeOffset。
-
我更新了我的问题。如果不清楚请告诉我,所以我尽量添加以便于理解:)
-
@Billa:我认为更新的问题中的任何内容都不会影响我的答案......它仍然有效。您应该存储DateTimeOffset,然后很容易检索当地时间。棘手的一点是制作DateTimeOffset来存储 - 这不能通过文化来完成;你需要使用Javascript。
-
在我进入的项目中,我们有相同的要求,而且之前的架构师从未尝试在项目开始时解决这个问题。因此,解决方案在创建的每个用户中进行,提供了选择时区的选项(来自我们仅预先选择该国家的第一个"valids"时区的国家/地区)。我们可以获取OffsetDateTime信息,并防止所有视图中的更改计算数据,我们转到数据层(自定义公司API)并在每个返回日期/时间列的查询中添加了一个新参数...
-
..并且在查询中我们添加了在SQL Server端进行计算的逻辑。因此,对数据库中的用户时区"更正"了每个日期时间值。当我们进行插入或更新时,我们也做了同样的事情,但做了倒置操作(从时区偏移到UTC)
如果特定时刻很重要,标准方法是始终将任何时间数据存储为UTC。那段时间不会受到时区变化和文化的影响。
显示时区的最常见方法是将时间存储为UTC,并在显示值时转换为当前用户的区域/时区组合。此方法仅需要在存储中提交单个日期时间。
请注意,对于Web情况(如ASP.Net),您可能需要首先确定用户的文化/时区并将其发送到服务器(因为此信息在GET请求中不是必需的)或在浏览器中进行时间格式化。
根据"显示相同的历史时间",您可能需要存储其他信息,如当前文化和/或当前偏移。如果你需要完全像原始用户看到的那样显示时间,你也可以保存字符串表示(因为格式/翻译可以稍后改变,值看起来会有所不同,也很不寻常)。
注意:文化和时区没有联系在一起,因此您需要决定如何处理美国PST时区的IN-IN文化等案例。
我对你的问题的措辞感到有些困惑,但似乎你想确定你的用户的时区。
如果沿着这条路走下去,您将需要能够使用IANA / Olson时区,这是Noda Time发挥作用的地方。您可以从DateTimeZoneProviders.Tzdb访问它们。
如果您使用UTC,则托管位置无关紧要。这是好事。
此外,如果您正在使用Noda Time,那么您可能应该使用SystemClock.Instance.Now而不是DateTime.UtcNow。
另见这里和这里。
此外 - 另一种解决方案是将UTC时间传递给浏览器并将其加载到JavaScript Date对象中。浏览器可以将其转换为用户的本地时间。您还可以使用像moment.js这样的库来简化这一过程。
更新
关于将文化代码映射到时区的方法:
由于以下几个原因,这是行不通的:
-
许多人在他们的计算机上使用不同于他们身体所在区域的文化环境。例如,我可能是一位住在德国的美国英语人士,我的文化代码可能仍然是en-US,而不是de-DE。
-
包含国家的文化代码用于区分语言的方言。当你看到es-MX时,这意味着"西班牙语,就像在墨西哥说的那样"。这并不意味着用户实际上在墨西哥。它只是意味着用户说西班牙语的方言,而es-ES意味着"西班牙语,在西班牙语中"。
-
即使文化代码的国家部分可靠,也有许多国家有多个时区!例如,您将在en-US的映射列表中添加什么?你不能只假设我们都在东部标准时间。
现在,我已经解释了为什么你现在的方法不起作用,我强烈建议你接受我原来的建议。非常简单:
确定用户的时区,最好是通过询问他们,或许可以通过我上面链接的其中一个实用程序获得一些帮助。
您正在存储UTC,因此只需转换为该时区进行显示即可。
使用Microsoft时区
1 2
| TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
DateTime localDatetime = TimeZoneInfo.ConvertTimeFromUtc(yourUTCDateTime, tz); |
使用IANA时区和Noda时间
1 2 3
| DateTimeZone tz = DateTimeZoneProviders.Tzdb["Europe/Stockholm"];
Instant theInstant = Instant.FromDateTimeUtc(yourUTCDateTime);
LocalDateTime localDateTime = theInstant.InZone(tz); |
-
非常感谢。我将通过它..基本上最终用户将从Europe/Stockholm访问,应用程序将托管在USA中。 Administrator from INDIA将审查瑞典用户对时间(历史)的约会。在这里,瑞典人将选择sv-se文化,印度人将选择In-In文化,for localization view
-
使用可视图片更新了一个问题,以便于理解
-
这是一幅很好的照片,但我仍然不明白你的问题是什么。文化只与格式有关,而与时区无关。他们根本没有关系。
-
现在我添加了一些编码和更多解释。如果有帮助,请告诉我。谢谢!
-
Jon建议使用DateTimeOffset。他的实现怎么样?,仍然需要大量的sql表结构更改:(
-
DateTimeOffset有其优点。我用它所有的时间。我不确定它是否对你有帮助,因为我仍然不完全明白你想要完成的事情。您可以在此处查看差异
-
如何将浏览器日期发送到c#in以便我可以在moment.js的帮助下使用DateTimeOffset.Parse()进行转换?计划尝试Jon建议的DateTimeOffset
-
我更新了我的问题。我想我几乎除了显示计算外几乎达到了。谢谢你的时刻js
-
添加到答案中的选项,TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tz)将返回DateTimeOffset而不是DateTime。
我们最近处理的应用程序遇到了类似的问题。在开发过程中,每个人都处于同一时区,问题没有被发现。无论如何,有很多遗留代码本来很难改变,更不用说转换已经在数据库中的所有日期时间信息了。因此,不能选择更改为DateTimeOffset。但是我们设法通过在出路上从服务器时间转换为用户时间并在转换时从用户时间转换为服务器时间来实现一致性。对于作为边界的任何日期时间比较来执行此操作也很重要。因此,如果用户期望某些事情在午夜时间到期,那么我们会将该时间转换为服务器时间并在服务器时间内进行所有比较。这听起来像是很多工作,但是将整个应用程序和DB转换为使用DateTimeOffsets的工作要少得多。
听到的是一个看起来像时区问题有一些很好的解决方案的线程。
确定用户的时区
-
你在什么基础上进行转换?
-
对我们来说,它更容易,因为我们可以假设一个时区来转换。服务器在澳大利亚东部标准时间和美国东部时间的所有用户。然而,正如许多人指出的那样,弄清楚用户所处的时区是一个不同的问题。关键是要始终如一地存储。
-
要弄清楚客户在哪个时区要么从用户那里获得,要么根据他们的位置做出假设。文化绝对是一个红色的haring。我在泰国呆了6年,整个时间我的电脑都设置为en-AU。
-
你如何检测他们的位置和时区?
-
那么你可以使用JS询问浏览器。或者,您可以在IP数据库中查找客户端IP。无论哪种方式,你都不会百分之百正确,所以让用户选择它也不会受伤。告诉用户他们被假定在哪个时区也不会受到伤害。
如果要向用户显示一致的日期/时间历史记录,无论他们查看历史记录的区域设置如何,则:
在Save期间,不仅存储UTC"创建"日期/时间,还存储检测到的区域设置
使用存储的saved from locale来计算原始日期/时间并发出要显示的字符串(即,在进行双工时不要使用当前用户区域设置)
如果您无法修改存储,那么也许您可以更改提交以发送"当前客户端时间",按字面意思存储(不转换为UTC)然后按字面显示(不要转换为检测到的文化)
但正如我在你的问题评论中所说,我不确定我的要求是否合适。