【发布时间】:2016-01-15 01:24:25
【问题描述】:
我们的应用程序是一个大型的 n 层 ASP.NET MVC 应用程序,它严重依赖于日期和(本地)时间。到目前为止,我们一直在为所有模型使用 DateTime,这很好用,因为多年来我们严格来说是一个国家网站,只处理一个时区。
现在情况发生了变化,我们正在向国际观众敞开大门。第一个想法是“哦,废话。我们需要重构整个解决方案!”
时区信息
我们打开 LinQPad 并开始绘制各种转换器以将常规 DateTime 对象转换为 DateTimeOffset 对象,基于基于用户的 TimeZone ID 值创建的 TimeZoneInfo 对象说用户的个人资料。
我们认为我们会将 models 中的所有 DateTime 属性更改为 DateTimeOffset 并完成它。毕竟,我们现在拥有了存储和显示用户本地日期和时间所需的所有信息。
sn-ps 的大部分代码都受到Rick Strahl's blog post 的启发。
NodaTime 和 DateTimeOffset
但后来我读到了Matt Johnson's excellent comment。他验证了我切换到 DateTimeOffset 的意图,声称:“DateTimeOffset 在 Web 应用程序中是必不可少的”。
关于野田时间,马特说:
说到 Noda Time,我不同意你必须更换整个系统的所有内容。当然,如果你这样做了,你犯错的机会就会少很多,但你当然可以在有意义的地方使用 Noda Time。我个人曾在需要使用 IANA 时区(例如“America/Los_Angeles”)进行时区转换的系统上工作,但在 DateTime 和 DateTimeOffset 类型中跟踪其他所有内容。实际上很常见的是 Noda Time 在应用程序逻辑中被广泛使用,但完全被排除在 DTO 和持久层之外。在某些技术中,比如实体框架,如果你愿意,你不能直接使用 Noda Time - 因为没有地方可以连接它。
这可能直接针对我们,因为我们现在正处于这种情况,包括我们选择使用 IANA 时区。
我们的计划是好是坏?
我们的主要目标是创建最简单的工作流程来处理不同时区的日期和时间。在我们的服务、存储库和控制器中尽可能避免时区计算。
简而言之,计划是从我们的前端接受本地日期和时间,尽快将它们转换为ZonedDateTime,并尽可能晚地将它们转换为DateTimeOffset,然后再将信息保存到数据库。
确定正确的ZonedDateTime 的关键因素是用户模型中的TimeZoneId 属性。
public class ApplicationUser : IdentityUser
{
[Required]
public string TimezoneId { get; set; }
}
本地日期时间到 NodaTime
为了防止出现大量重复代码,我们的计划是创建自定义 ModelBinders,将本地 DateTime 转换为 ZonedDateTime。
public class LocalDateTimeModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
// Get the posted local datetime
string dt = request.Form.Get("DateTime");
DateTime dateTime = DateTime.Parse(dt);
// Get the logged in User
IPrincipal p = controllerContext.HttpContext.User;
var user = p.ApplicationUser();
// Convert to ZonedDateTime
LocalDateTime localDateTime = LocalDateTime.FromDateTime(dateTime);
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
var usersTimezone = timeZoneProvider[user.TimezoneId];
var zonedDbDateTime = usersTimezone.AtLeniently(localDateTime);
return zonedDbDateTime;
}
}
我们可以用这些模型绑定器乱扔我们的控制器。
[HttpPost]
[Authorize]
public ActionResult SimpleDateTime([ModelBinder(typeof (LocalDateTimeModelBinder))] ZonedDateTime dateTime)
{
// Do stuff with the ZonedDateTime object
}
我们是不是想多了?
在数据库中存储 DateTimeOffset
我们将使用concept of Buddy properties。老实说,我不是这个的忠实粉丝,因为它造成的混乱。新开发者可能会因为我们有不止一种方法来保存创建日期这一事实而苦恼。
非常欢迎就如何改进这一点提出建议。我已阅读有关从 IntelliSense 隐藏属性以将真实属性设置为 private 的 cmets。
public class Item
{
public int Id { get; set; }
public string Title { get; set; }
// The "real" property
public DateTimeOffset DateCreated { get; private set; }
// Buddy property
[NotMapped]
public ZonedDateTime CreatedAt
{
get
{
// DateTimeOffset to NodaTime, based on User's TZ
return ToZonedDateTime(DateCreated);
}
// NodaTime to DateTimeOffset
set { DateCreated = value.ToDateTimeOffset(); }
}
public string OwnerId { get; set; }
[ForeignKey("OwnerId")]
public virtual ApplicationUser Owner { get; set; }
// Helper method
public ZonedDateTime ToZonedDateTime(DateTimeOffset dateTime, string tz = null)
{
if (string.IsNullOrEmpty(tz))
{
tz = Owner.TimezoneId;
}
IDateTimeZoneProvider timeZoneProvider = DateTimeZoneProviders.Tzdb;
var usersTimezoneId = tz;
var usersTimezone = timeZoneProvider[usersTimezoneId];
var zonedDate = ZonedDateTime.FromDateTimeOffset(dateTime);
return zonedDate.ToInstant().InZone(usersTimezone);
}
}
介于两者之间的一切
我们现在有了一个基于 Noda Time 的应用程序。 ZonedDateTime 对象可以更轻松地进行临时计算和时区驱动的查询。
这是一个正确的假设吗?
【问题讨论】:
-
现在正在消化您的帖子......很快就会回复...... :)
-
酷 :) 谢谢马特。这将是一个影响很大的重构,所以只是想在开始之前验证我们的计划。
-
重要问题 - 什么样的字符串输入期望作为参数接收到您的控制器?国际标准化组织?带偏移还是平原?世界标准时间?变化?请给出示例值。谢谢。
-
我们得到本地日期时间,没有 UTC 信息。
-
例如“2015-10-16 20:40:00”
标签: c# datetime timezone datetimeoffset nodatime