C# 如何优雅地处理时区

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上使

我有一个网站,它与使用该应用程序的用户所在的时区不同。除此之外,用户还可以有特定的时区。我想知道其他SO用户和应用程序是如何做到这一点的?最明显的是,在数据库中,日期/时间存储在UTC中。在服务器上时,所有日期/时间都应以UTC处理。然而,我看到了我正在努力克服的三个问题:

  • 以UTC获取当前时间(使用
    DateTime.UtcNow
    轻松解决)

  • 从数据库中提取日期/时间并将其显示给用户。在不同的视图上打印日期的调用可能很多。我想在视图和控制器之间的某个层可以解决这个问题。或者在
    DateTime
    上使用自定义扩展方法(见下文)。主要的缺点是,在视图中使用datetime的每个位置,都必须调用扩展方法

    这也会给使用类似于
    JsonResult
    的东西增加困难。您不能再轻松调用
    Json(myEnumerable)
    ,它必须是
    Json(myEnumerable.Select(transformAllDates))
    。也许AutoMapper可以在这种情况下提供帮助

  • 从用户处获取输入(从本地到UTC)。例如,发布带有日期的表单需要在发布之前将日期转换为UTC。首先想到的是创建一个定制的
    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
    将转换为.NET
    datetimeoffset
    ,然后您可以从中获取使用的通用时间。从这里开始,您只需使用
    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;
    }