如何优雅地处理时区

发布于 2024-12-07 07:36:02 字数 1636 浏览 0 评论 0 原文

我有一个网站托管在与使用该应用程序的用户不同的时区。除此之外,用户还可以拥有特定的时区。我想知道其他 SO 用户和应用程序如何处理这个问题?最明显的部分是在数据库内部,日期/时间以 UTC 存储。在服务器上时,所有日期/时间都应以 UTC 格式处理。但是,我发现我正在努力克服三个问题:

  1. 获取 UTC 中的当前时间(使用 DateTime.UtcNow 可以轻松解决)。

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

    这也会增加使用诸如 JsonResult 之类的东西的难度。您无法再轻松调用 Json(myEnumerable),它必须是 Json(myEnumerable.Select(transformAllDates))。也许 AutoMapper 可以在这种情况下提供帮助?

  3. 获取用户的输入(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 是否有意义?用户的时区存储在数据库中并且可以轻松检索。

I have a website that is hosted in a different timezone than the users using the application. In addition to this, users can have a specific timezone. I was wondering how other SO users and applications approach this? The most obvious part is that inside the DB, date/times are stored in UTC. When on the server, all date/times should be dealt with in UTC. However, I see three problems that I'm trying to overcome:

  1. Getting the current time in UTC (solved easily with DateTime.UtcNow).

  2. Pulling date/times from the database and displaying these to the user. There are potentially lots of calls to print dates on different views. I was thinking of some layer in between the view and the controllers that could solve this issue. Or having a custom extension method on DateTime (see below). The major down side is that at every location of using a datetime in a view, the extension method must be called!

    This would also add difficulty to using something like the JsonResult. You could no longer easily call Json(myEnumerable), it would have to be Json(myEnumerable.Select(transformAllDates)). Maybe AutoMapper could help in this situation?

  3. Getting input from the user (Local to UTC). For example, POSTing a form with a date would require converting the date to UTC before. The first thing that comes to mind is creating a custom ModelBinder.

Here's the extensions that I thought of using in the views:

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);
    }
}

I would think that dealing with timezones would be such a common thing considering a lot of applications are now cloud-based where the server's local time could be much different than the expected time zone.

Has this been elegantly solved before? Is there anything that I'm missing? Ideas and thoughts are much appreciated.

EDIT: To clear some confusion I thought add some more details. The issue right now isn't how to store UTC times in the db, it's more about the process of going from UTC->Local and Local->UTC. As @Max Zerbini points out, it's obviously smart to put the UTC->Local code in the view, but is using the DateTimeExtensions really the answer? When getting input from the user, does it make sense to accept dates as the user's local time (since that's what JS would be using) and then use a ModelBinder to transform to UTC? The user's timezone is stored in the DB and is easily retrieved.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

梦里南柯 2024-12-14 07:36:02

并不是说这是一个建议,而是更多地共享一种范例,但我所见过的在 Web 应用程序中处理时区信息的最激进的方式(这并非 ASP.NET MVC 独有的)是以下内容:

  • 服务器上的所有日期时间均为 UTC。
    正如您所说,这意味着使用 DateTime.UtcNow

  • 尝试尽可能少地信任客户端向服务器传递日期。例如,如果您需要“现在”,请不要在客户端上创建日期然后将其传递到服务器。在 GET 中创建一个日期并将其传递给 ViewModel,或者在 POST 中执行 DateTime.UtcNow

到目前为止,票价相当标准,但这就是事情变得“有趣”的地方。

  • 如果您必须接受来自客户端的日期,请使用 javascript 来确保您发布到服务器的数据采用 UTC。客户端知道它所在的时区,因此它可以以合理的精度将时间转换为 UTC。

  • 在渲染视图时,他们使用 HTML5 元素,他们永远不会直接在 ViewModel 中渲染日期时间。它被实现为 HtmlHelper 扩展,类似于 Html.Time(Model.when)。它将呈现

    然后他们将使用 JavaScript 将 UTC 时间转换为客户端本地时间。该脚本将查找所有 元素,并使用 date-format 数据属性来格式化日期并填充元素的内容。

这样,他们就不必跟踪、存储或管理客户的时区。服务器不关心客户端所在的时区,也不必进行任何时区转换。它只是输出 UTC 并让客户端将其转换为合理的值。这对于浏览器来说很容易,因为它知道它所在的时区。如果客户端更改了他/她的时区,Web 应用程序将自动更新自身。他们存储的唯一内容是用户区域设置的日期时间格式字符串。

我并不是说这是最好的方法,但这是一种我以前从未见过的不同方法。也许你会从中收集到一些有趣的想法。

Not that this is a recommendation, its more sharing of a paradigm, but the most agressive way I've seen of handling timezone information in a web app (which is not exclusive to ASP.NET MVC) was the following:

  • All date times on the server are UTC.
    That means using, like you said, DateTime.UtcNow.

  • Try to trust the client passing dates to the server as little as possible. For example, if you need "now", don't create a date on the client and then pass it to the server. Either create a date in your GET and pass it to the ViewModel or on POST do DateTime.UtcNow.

So far, pretty standard fare, but this is where things get 'interesting'.

  • If you have to accept a date from the client, then use javascript to make sure the data that you are posting to the server is in UTC. The client knows what timezone it is in, so it can with reasonable accuracy convert times into UTC.

  • When rendering views, they were using the HTML5 <time> element, they would never render datetimes directly in the ViewModel. It was implemented as as HtmlHelper extension, something like Html.Time(Model.when). It would render <time datetime='[utctime]' data-date-format='[datetimeformat]'></time>.

    Then they would use javascript to translate UTC time into the clients local time. The script would find all the <time> elements and use the date-format data property to format the date and populate the contents of the element.

This way they never had to keep track of, store, or manage a clients timezone. The server didn't care what timezone the client was in, nor had to do any timezone translations. It simply spit out UTC and let the client convert that into something that was reasonable. Which is easy from the browser, because it knows what timezone it is in. If the client changed his/her timezone, the web application would automatically update itself. The only thing that they stored were the datetime format string for the locale of the user.

I'm not saying it was the best approach, but it was a different one that I had not seen before. Maybe you'll glean some interesting ideas from it.

通知家属抬走 2024-12-14 07:36:02

经过多次反馈,这是我的最终解决方案,我认为它干净简单,并且涵盖了夏令时问题。

1 - 我们在模型级别处理转换。因此,在 Model 类中,我们编写:

    public class Quote
    {
        ...
        public DateTime DateCreated
        {
            get { return CRM.Global.ToLocalTime(_DateCreated); }
            set { _DateCreated = value.ToUniversalTime(); }
        }
        private DateTime _DateCreated { get; set; }
        ...
    }

2 - 在全局帮助程序中,我们创建自定义函数“ToLocalTime”:

    public static DateTime ToLocalTime(DateTime utcDate)
    {
        var localTimeZoneId = "China Standard Time";
        var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(localTimeZoneId);
        var localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localTimeZone);
        return localTime;
    }

3 - 我们可以进一步改进这一点,方法是将时区 ID 保存在每个用户配置文件中,以便我们可以从用户类中检索而不是使用常量“中国标准时间”:

public class Contact
{
    ...
    public string TimeZone { get; set; }
    ...
}

4 - 这里我们可以获取显示给用户的时区列表,以从下拉框中进行选择:

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;
    }
}

所以,现在中国上午 9:25,网站托管在美国,日期保存在 UTC在数据库中,这是最终结果:

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)

编辑

感谢Matt Johnson指出了薄弱环节原始解决方案,很抱歉删除原始帖子,但在获取正确的代码显示格式时遇到问题...结果编辑器在将“项目符号”与“前代码”混合时出现问题,所以我删除了项目符号,就可以了。

After several feedbacks, here is my final solution which I think is clean and simple and covers daylight saving issues.

1 - We handle the conversion at model level. So, in the Model class, we write:

    public class Quote
    {
        ...
        public DateTime DateCreated
        {
            get { return CRM.Global.ToLocalTime(_DateCreated); }
            set { _DateCreated = value.ToUniversalTime(); }
        }
        private DateTime _DateCreated { get; set; }
        ...
    }

2 - In a global helper we make our custom function "ToLocalTime":

    public static DateTime ToLocalTime(DateTime utcDate)
    {
        var localTimeZoneId = "China Standard Time";
        var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(localTimeZoneId);
        var localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localTimeZone);
        return localTime;
    }

3 - We can improve this further, by saving the timezone id in each User profile so we can retrieve from the user class instead of using constant "China Standard Time":

public class Contact
{
    ...
    public string TimeZone { get; set; }
    ...
}

4 - Here we can get the list of timezone to show to user to select from a dropdownbox:

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;
    }
}

So, now at 9:25 AM in China, Website hosted in USA, date saved in UTC at database, here is the final result:

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)

EDIT

Thanks to Matt Johnson for pointing out the weak parts of original solution, and sorry for deleting original post, but got issues getting right code display format... turned out the editor has problems mixing "bullets" with "pre code", so I removed the bulles and it was ok.

一紙繁鸢 2024-12-14 07:36:02

活动部分 >sf4answers,用户输入活动的地址以及开始日期和可选的结束日期。这些时间将转换为 datetimeoffset在 SQL Server 中,它考虑了与 UTC 的偏移量。

这与您面临的问题相同(尽管您采取了不同的方法,因为您正在使用 DateTime.UtcNow);您有一个位置,并且需要将时间从一个时区转换为另一个时区。

我做了两件对我有用的主要事情。首先,使用 DateTimeOffset 结构 , 总是。它考虑了与 UTC 的偏移,如果您可以从客户那里获得该信息,它会让您的生活变得更轻松一些。

其次,在执行翻译时,假设您知道客户端所在的位置/时区,则可以使用 公共信息时区数据库将时间从 UTC 转换为另一个时区(或者,如果您愿意,可以在两个时区之间进行三角测量)。 tz 数据库(有时称为 Olson 数据库)的伟大之处在于它可以记录历史上时区的变化;获取偏移量是您想要获取偏移量的日期的函数(只需查看能源政策2005 年法案 其中 更改了美国夏令时生效的日期)。

有了数据库,您就可以使用 ZoneInfo(tz 数据库/Olson 数据库).NET API。请注意,没有二进制发行版,您必须下载最新版本 并自行编译。

在撰写本文时,它当前解析最新数据分发中的所有文件(我实际上针对 ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz 文件,2011 年 9 月 25 日; 2017 年 3 月,您可以通过 https://iana.org/time-zones 或从ftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz)。

因此,在 sf4answers 上,获取地址后,将其地理编码为纬度/经度组合,然后发送到第三方 Web 服务以获取与 tz 数据库中的条目相对应的时区。从那里,开始时间和结束时间将转换为具有正确 UTC 偏移量的 DateTimeOffset 实例,然后存储在数据库中。

至于在SO和网站上处理它,这取决于受众和你想要展示的内容。如果您注意到,大多数社交网站(以及 SO,以及 sf4answers 上的事件部分)都会以相对时间显示事件,或者,如果使用绝对值,则通常为 UTC。

但是,如果您的受众期望当地时间,那么使用 DateTimeOffset 以及将时区转换为的扩展方法就可以了; SQL 数据类型 datetimeoffset 将转换为 .NET DateTimeOffset,然后您可以使用 GetUniversalTime 方法。从那里,您只需使用 ZoneInfo 类上的方法从 UTC 转换为本地时间(您需要做一些工作才能将其转换为 DateTimeOffset,但这很简单)。

转型去哪里?这是您必须在某处支付的成本,并且没有“最佳”方法。不过,我会选择视图,并将时区偏移作为呈现给视图的视图模型的一部分。这样,如果视图的要求发生变化,您不必更改视图模型来适应变化。您的 JsonResult 将仅包含一个带有 IEnumerable 偏移量。

在输入端,使用模型绑定器?我想说绝对没办法。您不能保证所有日期(现在或将来)都必须以这种方式转换,它应该是控制器的显式函数来执行此操作。同样,如果需求发生变化,您不必调整一个或多个 ModelBinder 实例来调整您的业务逻辑;它是业务逻辑,这意味着它应该位于控制器中。

In the events section on sf4answers, users enter an address for an event, as well as a start date and an optional end date. These times are translated into a datetimeoffset in SQL server that accounts for the offset from UTC.

This is the same problem you are facing (although you are taking a different approach to it, in that you are using DateTime.UtcNow); you have a location and you need to translate a time from one timezone to another.

There are two main things I did which worked for me. First, use DateTimeOffset structure, always. It accounts for offset from UTC and if you can get that information from your client, it makes your life a little easier.

Second, when performing the translations, assuming you know the location/time zone that the client is in, you can use the public info time zone database to translate a time from UTC to another time zone (or triangulate, if you will, between two time zones). The great thing about the tz database (sometimes referred to as the Olson database) is that it accounts for the changes in time zones throughout history; getting an offset is a function of the date that you want to get the offset on (just look at the Energy Policy Act of 2005 which changed the dates when daylight savings time goes into effect in the US).

With the database in hand, you can use the ZoneInfo (tz database / Olson database) .NET API. Note that there isn't a binary distribution, you'll have to download the latest version and compile it yourself.

At the time of this writing, it currently parses all of the files in the latest data distribution (I actually ran it against the ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz file on September 25, 2011; in March 2017, you'd get it via https://iana.org/time-zones or from ftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz).

So on sf4answers, after getting the address, it is geocoded into a latitude/longitude combination and then sent to a third-party web service to get a timezone which corresponds to an entry in the tz database. From there, the start and end times are converted into DateTimeOffset instances with the proper UTC offset and then stored in the database.

As for dealing with it on SO and websites, it depends on the audience and what you are trying to display. If you notice, most social websites (and SO, and the events section on sf4answers) display events in relative time, or, if an absolute value is used, it's usually UTC.

However, if your audience expects local times, then using DateTimeOffset along with an extension method that takes the time zone to convert to would be just fine; the SQL data type datetimeoffset would translate to the .NET DateTimeOffset which you can then get the universal time for using the GetUniversalTime method. From there, you simply use the methods on the ZoneInfo class to convert from UTC to local time (you'll have to do a little work to get it into a DateTimeOffset, but it's simple enough to do).

Where to do the transformation? That's a cost you are going to have to pay somewhere, and there's no "best" way. I'd opt for the view though, with the timezone offset as part of the view model presented to the view. That way, if the requirements for the view change, you don't have to change your view model to accommodate the change. Your JsonResult would simply contain a model with the IEnumerable<T> and the offset.

On the input side, using a model binder? I'd say absolutely no way. You can't guarantee that all the dates (now or in the future) will have to be transformed in this way, it should be an explicit function of your controller to perform this action. Again, if the requirements change, you don't have to tweak one or many ModelBinder instances to adjust your business logic; and it is business logic, which means it should be in the controller.

北渚 2024-12-14 07:36:02

这只是我的观点,我认为MVC应用程序应该很好地将数据表示问题与数据模型管理分开。数据库可以以本地服务器时间存储数据,但表示层有责任使用本地用户时区呈现日期时间。在我看来,这与不同国家的 I18N 和数字格式有同样的问题。
在您的情况下,您的应用程序应该检测用户的文化和时区,并更改显示不同文本、数字和日期表示形式的视图,但存储的数据可以具有相同的格式。

This is just my opinion, I think that MVC application should separate well data presentation problem from data model management. A database can store data in local server time but it's a duty of the presentation layer to render datetime using local user timezone. This seems to me the same problem as I18N and number format for different countries.
In your case, your application should detect the Culture and timezone of the user and change the View showing different text, number and datime presentation, but the stored data can have the same format.

柠北森屋 2024-12-14 07:36:02

对于输出,创建一个像这样的显示/编辑器模板。

@inherits System.Web.Mvc.WebViewPage<System.DateTime>
@Html.Label(Model.ToLocalTime().ToLongTimeString()))

如果您只想某些模型使用这些模板,则可以根据模型上的属性绑定它们。

请参阅此处此处有关创建自定义编辑器模板的更多详细信息。

或者,由于您希望它同时适用于输入和输出,我建议扩展一个控件,甚至创建您自己的控件。这样您就可以拦截输入和输出并根据需要转换文本/值。

此链接希望能将您推向正确的方向(如果您想去)沿着那条路。

不管怎样,如果你想要一个优雅的解决方案,这将是一项艰巨的工作。好的一面是,一旦完成,您就可以将其保存在代码库中以供将来使用!

For output, create an display/editor template like this

@inherits System.Web.Mvc.WebViewPage<System.DateTime>
@Html.Label(Model.ToLocalTime().ToLongTimeString()))

You can bind them based on attributes on your model if you want only certain models to use those templates.

See here and here for more details on creating custom editor templates.

Alternatively, since you want it to work for both input and output, I would suggest extending a control or even creating your own. That way you can intercept both the input and outputs and convert the text/value as needed.

This link will hopefully push you in the right direction if you want to go down that path.

Either way, if you want an elegant solution, its going to be a bit of work. On the bright side, once you have done it once you can keep it in your code library for future use!

萌化 2024-12-14 07:36:02

这可能是一个解决难题的大锤,但您可以在 UI 层和业务层之间注入一个层,该层将返回的对象图上的日期时间透明地转换为本地时间,并在输入日期时间参数上将日期时间转换为 UTC。

我想这可以使用 PostSharp 或一些控制反转容器来实现。

就我个人而言,我只是在用户界面中显式转换日期时间......

This is probably a sledgehammer to crack a nut but you could inject a layer between the UI and Business layers which transparently converts datetimes to the local time on returned object graphs, and to UTC on input datetime parameters.

I imagine this could be achieved using PostSharp or some inversion of control container.

Personally, I'd just go with explicitly converting your datetimes in the UI...

别在捏我脸啦 2024-12-14 07:36:02

我想将日期存储为 DateTimeOffset,以便我可以维护写入数据库的用户的时区偏移。但是,我只想在应用程序本身内部使用 DateTime。

因此,本地时区输入,本地时区输出。无论用户在谁/何地/何时查看数据,对于观察者来说都将是本地时间 - 并且更改存储为 UTC + 本地偏移量。

这是我实现这一目标的方法。

1.
首先,我需要获取 Web 客户端的本地时区偏移量并将该值存储在 Web 服务器上:

// 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");
        }
    });
}

该格式旨在与 SQL Server DateTimeOffset 类型的格式匹配。

SetTimeZone - 仅设置会话变量的值。当用户登录时,我将此值合并到用户配置文件缓存中。

2.
当用户向数据库提交更改时,我通过实用程序类过滤日期时间值:

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;
}

3。
当我从 SQL Server 读取日期时,我这样做:

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;
}

在视图中,我只是显示/编辑/验证日期时间。

我希望这可以帮助有类似需求的人。

I wanted to store dates as DateTimeOffset so that I could maintain the Time Zone Offset of the user that writes to the database. However, I wanted to only use DateTime inside of the application itself.

So, local time zone in, local time zone out. Regardless of who/where/when the user is looking at the data, it will be a local time to the observer - and changes are stored as UTC + local offset.

Here is how I achieved this.

1.
Firstly, I needed to get the web client's local time zone offset and store this value on the web server:

// 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");
        }
    });
}

The format is intended to match that of the SQL Server DateTimeOffset type.

SetTimeZone - just sets the value of Session variable. When the user logs on, I incorporate this value into the User profile cache.

2.
As a user submits a change to the database, I filter the DateTime value through a utility class:

cmdADO.Parameters.AddWithValue("@AwardDate", (object)Utility.ConvertLocal2UTC(theContract.AwardDate, theContract.TimeOffset) ?? DBNull.Value);

The Method:

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;
}

3.
When I and reading in the date from the SQL Server, I am doing this:

theContract.AwardDate = theRow.IsNull("AwardDate") ? new Nullable<DateTime>() : DateTimeOffset.Parse(Convert.ToString(theRow["AwardDate"])).DateTime;

In the controller, I modify the datetime to match the local time of the observer. (I am sure someone can do better with an extension or something):

theContract.AwardDate = Utilities.ConvertUTC2Local(theContract.AwardDate, CachedCurrentUser.TimeZoneOffset);

The method:

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;
}

In the View, I am just displaying/editing/validating a DateTime.

I hope this helps someone who has a similar need.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文