C# 如何优雅地处理时区
我有一个网站,它与使用该应用程序的用户所在的时区不同。除此之外,用户还可以有特定的时区。我想知道其他SO用户和应用程序是如何做到这一点的?最明显的是,在数据库中,日期/时间存储在UTC中。在服务器上时,所有日期/时间都应以UTC处理。然而,我看到了我正在努力克服的三个问题:C# 如何优雅地处理时区,c#,.net,asp.net-mvc,asp.net-mvc-3,timezone,C#,.net,Asp.net Mvc,Asp.net Mvc 3,Timezone,我有一个网站,它与使用该应用程序的用户所在的时区不同。除此之外,用户还可以有特定的时区。我想知道其他SO用户和应用程序是如何做到这一点的?最明显的是,在数据库中,日期/时间存储在UTC中。在服务器上时,所有日期/时间都应以UTC处理。然而,我看到了我正在努力克服的三个问题: 以UTC获取当前时间(使用DateTime.UtcNow轻松解决) 从数据库中提取日期/时间并将其显示给用户。在不同的视图上打印日期的调用可能很多。我想在视图和控制器之间的某个层可以解决这个问题。或者在DateTime上使
DateTime.UtcNow
轻松解决)
DateTime
上使用自定义扩展方法(见下文)。主要的缺点是,在视图中使用datetime的每个位置,都必须调用扩展方法
这也会给使用类似于JsonResult
的东西增加困难。您不能再轻松调用Json(myEnumerable)
,它必须是Json(myEnumerable.Select(transformAllDates))
。也许AutoMapper可以在这种情况下提供帮助
ModelBinder
public static class DateTimeExtensions
{
public static DateTime UtcToLocal(this DateTime source,
TimeZoneInfo localTimeZone)
{
return TimeZoneInfo.ConvertTimeFromUtc(source, localTimeZone);
}
public static DateTime LocalToUtc(this DateTime source,
TimeZoneInfo localTimeZone)
{
source = DateTime.SpecifyKind(source, DateTimeKind.Unspecified);
return TimeZoneInfo.ConvertTimeToUtc(source, localTimeZone);
}
}
考虑到现在很多应用程序都是基于云的,服务器的本地时间可能与预期的时区有很大的不同,我认为处理时区是一件很常见的事情
这个问题以前解决得很好吗?有什么我遗漏的吗?我们非常欣赏你的想法和想法
编辑:为了消除一些困惑,我想添加一些详细信息。现在的问题不是如何在数据库中存储UTC时间,而是从UTC->Local和Local->UTC的过程。正如@Max Zerbini指出的,将UTC->Local代码放在视图中显然是明智的,但是使用DateTimeExtensions
真的是答案吗?当从用户获取输入时,接受日期作为用户的本地时间(因为JS将使用该时间),然后使用ModelBinder
转换为UTC是否有意义?用户的时区存储在DB中,很容易检索。在on中,用户输入事件的地址,以及开始日期和可选结束日期。这些时间在SQL server中转换为一个值,用于计算UTC的偏移量
这与您面临的问题相同(尽管您使用的方法不同);您有一个位置,需要将时间从一个时区转换到另一个时区
我做了两件对我有用的事情。首先,使用,永远。它说明了UTC的偏移量,如果您可以从客户那里获得这些信息,那么您的生活会更轻松一些
其次,在执行转换时,假设您知道客户机所在的位置/时区,您可以使用将时间从UTC转换为另一个时区(如果愿意,可以在两个时区之间进行三角测量)。tz数据库(有时被称为)的伟大之处在于它记录了历史上时区的变化;获取偏移量是您希望获取偏移量的日期的函数(只需查看哪个日期)
有了数据库,您可以使用。请注意,没有二进制发行版,您必须下载并自己编译
在撰写本文时,它目前正在解析最新数据分发中的所有文件(我实际上在2011年9月25日针对该文件运行了它;2017年3月,您可以通过或从中获得它)
因此,在sf4answers上,在获得地址后,将其地理编码为纬度/经度组合,然后发送给第三方web服务,以获得与tz数据库中的条目相对应的时区。从那里,开始时间和结束时间被转换成具有适当UTC偏移量的DateTimeOffset
实例,然后存储在数据库中
至于在SO和网站上处理它,这取决于观众和你想要展示的内容。如果您注意到,大多数社交网站(以及SO和sf4answers上的“事件”部分)都以相对时间显示事件,或者,如果使用绝对值,则通常为UTC
但是,如果您的受众希望使用本地时间,那么使用DateTimeOffset
以及将时区转换为的扩展方法就可以了;SQL数据类型datetimeoffset
将转换为.NETdatetimeoffset
,然后您可以从中获取使用的通用时间。从这里开始,您只需使用ZoneInfo
类上的方法将UTC转换为本地时间(您需要做一些工作将其转换为DateTimeOffset
,但这非常简单)
在哪里进行转换?这是你必须在某处付出的代价,没有“最好”的办法。不过我还是选择视图,将时区偏移作为视图模型的一部分呈现给视图。这样,如果视图的需求发生更改,则不必更改视图模型以适应更改。您只需包含一个具有和偏移量的模型
在输入端,使用模型活页夹?我说绝对不行。您不能保证所有日期(现在或将来)都必须以这种方式转换,执行此操作应该是控制器的显式功能。同样,如果需求发生变化,您不必调整一个或多个实例来调整业务逻辑;而且
@inherits System.Web.Mvc.WebViewPage<System.DateTime>
@Html.Label(Model.ToLocalTime().ToLongTimeString()))
public class Quote
{
...
public DateTime DateCreated
{
get { return CRM.Global.ToLocalTime(_DateCreated); }
set { _DateCreated = value.ToUniversalTime(); }
}
private DateTime _DateCreated { get; set; }
...
}
public static DateTime ToLocalTime(DateTime utcDate)
{
var localTimeZoneId = "China Standard Time";
var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(localTimeZoneId);
var localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localTimeZone);
return localTime;
}
public class Contact
{
...
public string TimeZone { get; set; }
...
}
public class ListHelper
{
public IEnumerable<SelectListItem> GetTimeZoneList()
{
var list = from tz in TimeZoneInfo.GetSystemTimeZones()
select new SelectListItem { Value = tz.Id, Text = tz.DisplayName };
return list;
}
}
5/9/2013 6:25:58 PM (Server - in USA)
5/10/2013 1:25:58 AM (Database - Converted UTC)
5/10/2013 9:25:58 AM (Local - in China)
// Sets a session variable for local time offset from UTC
function SetTimeZone() {
var now = new Date();
var offset = now.getTimezoneOffset() / 60;
var sign = offset > 0 ? "-" : "+";
var offset = "0" + offset;
offset = sign + offset + ":00";
$.ajax({
type: "post",
url: prefixWithSitePathRoot("/Home/SetTimeZone"),
data: { OffSet: offset },
datatype: "json",
traditional: true,
success: function (data) {
var data = data;
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert("SetTimeZone failed");
}
});
}
cmdADO.Parameters.AddWithValue("@AwardDate", (object)Utility.ConvertLocal2UTC(theContract.AwardDate, theContract.TimeOffset) ?? DBNull.Value);
public static DateTimeOffset? ConvertLocal2UTC(DateTime? theDateTime, string TimeZoneOffset)
{
DateTimeOffset? DtOffset = null;
if (null != theDateTime)
{
TimeSpan AmountOfTime;
TimeSpan.TryParse(TimeZoneOffset, out AmountOfTime);
DateTime datetime = Convert.ToDateTime(theDateTime);
DateTime datetimeUTC = datetime.ToUniversalTime();
DtOffset = new DateTimeOffset(datetimeUTC.Ticks, AmountOfTime);
}
return DtOffset;
}
theContract.AwardDate = theRow.IsNull("AwardDate") ? new Nullable<DateTime>() : DateTimeOffset.Parse(Convert.ToString(theRow["AwardDate"])).DateTime;
theContract.AwardDate = Utilities.ConvertUTC2Local(theContract.AwardDate, CachedCurrentUser.TimeZoneOffset);
public static DateTime? ConvertUTC2Local(DateTime? theDateTime, string TimeZoneOffset)
{
if (null != theDateTime)
{
TimeSpan AmountOfTime;
TimeSpan.TryParse(TimeZoneOffset, out AmountOfTime);
DateTime datetime = Convert.ToDateTime(theDateTime);
datetime = datetime.Add(AmountOfTime);
theDateTime = new DateTime(datetime.Ticks, DateTimeKind.Utc);
}
return theDateTime;
}