Cra*_*aig 3 c# sql-server asp.net timezone
有人问过,但我正在努力掌握如何在网络应用中处理时区的概念.我有一个跟踪项目进度的系统.我的SQL Server数据库中有一个ProjectStartDate DATE.(还有一些字段和表格,但让我们关注一个).
服务器位于美国的某个地方.我住在澳大利亚.
致电SELECT GETDATE()返回"2013-08-11 14:40:50.630"我的系统时钟显示"2013-08-12 07:40"
在我的数据库中,我在所有表上都有'CreateDateTime'列.当我存储它时,在我的c#代码中,我使用CreateDate = DateTime.UtcNow
我使用它,因为我听说使用UTC更好.
但是,当向用户显示日历控件并为项目选择开始日期时,我会存储用户选择的内容.没有转换......正如我所说,StartDate是数据库中的DATE类型.
问题是,如果一个项目今天开始 - 我的前端说当前项目是No Started,因为服务器仍然在昨天.
我认为日期应该存储,因为我存储它们.但也许我需要以某种方式获取用户时区,并在UI级别应用它?
我看到的问题是:
我使用EntityFramework和Linq来获取大部分时间的数据.我需要一种从SQL意义和.Net意义上插入和检索数据的策略.
我添加了代码以让用户根据此选择他们的时区:
public List<TimeZoneDto> GetTimeZones()
{
var zones = TimeZoneInfo.GetSystemTimeZones();
var result = zones.Select(tz => new TimeZoneDto
{
Id = tz.Id,
Description = tz.DisplayName
}).ToList();
return result;
}
Run Code Online (Sandbox Code Playgroud)
然后,这就是他们的个人资料.
所有日期都以UTC格式存储,如下面的答案所示.
我仍然很困惑如何处理他们往返数据库和客户端的日期.以下是我存储记录的示例:
public int SaveNonAvailibility(PersonNonAvailibilityDto n)
{
person_non_availibility o;
if (n.Id == 0)
{
o = new person_non_availibility
{
PersonId = n.PersonId,
Reason = n.Reason,
StartDate = n.StartDate,
EndDate = n.EndDate,
NonAvailibilityTypeId = n.NonAvailibilityTypeId,
CreateUser = _userId,
CreateDate = DateTime.UtcNow
};
_context.person_non_availibility.Add(o);
}
else
{
o = (from c in _context.person_non_availibility where c.Id == n.Id select c).FirstOrDefault();
o.StartDate = n.StartDate;
o.EndDate = n.EndDate;
o.Reason = n.Reason;
o.NonAvailibilityTypeId = n.NonAvailibilityTypeId;
o.LastUpdateDate = DateTime.UtcNow;
o.LastUpdateUser = _userId;
o.Deleted = n.Deleted ? DateTime.UtcNow : (DateTime?)null;
}
_context.SaveChanges();
return o.Id;
}
Run Code Online (Sandbox Code Playgroud)
当一个人无法工作时,这种方法基本上可以节省.请注意我存储LastUpdateDate的方式.此外,"开始"和"结束"日期.这些日期更多是"商业"日期.
在选择,然后日期检查,我有问题.在这个例子中,我根据NOW获得了一个人的收费率.
public decimal CurrentRate
{
get
{
if (ResourceCosts != null)
{
var i = ResourceCosts.FirstOrDefault(t => DateTime.UtcNow <= (t.EndDate.HasValue ? t.EndDate.Value : DateTime.UtcNow) && DateTime.UtcNow >= t.StartDate);
if (i != null) return i.Cost;
return 0;
}
return 0;
}
}
Run Code Online (Sandbox Code Playgroud)
所以,我想在这里做的是,根据当前的日期,我想看看他的费率(因为他的费用从1月1日到1月15日我们的费率可能是100美元,然后从16日到110美元因此,我会查找今天适用的费率(如果有的话).这不适用于时区,也许我需要根据'DateTime.UTCNow'进行日期操作?
注意,我现在根据上面的代码知道用户时区,我存储他的时区.我可以在这里以某种方式使用它?也许,当用户登录时,从他的个人资料中获取日期信息(Zimezone信息),然后拥有一个全局共享函数,该函数返回用户日期时间,基于从UTC日期添加或删除小时...并使用该日期时间我在哪里做DateTime.UTCNow?
希望有人能指导我.
您可能会发现没有一种"正确"的方法来处理所有这些问题.您在问题中描述的几个不同问题有多种方法.我将尝试澄清几点.
首先,不要试图考虑服务器上的本地时间.您的代码和数据不必根据部署位置进行更改.您说您的服务器位于美国,但有多个时区需要考虑,许多服务器的时区设置为UTC,无论其物理位置如何.
您应该避免GETDATE()或SYSDATETIME()在SQL Server中.如果您需要SQL中的当前时间戳,请使用GETUTCDATE()或SYSUTCDATETIME().如果由于某种原因服务器的时区对您很重要,请SYSDATETIMEOFFSET()改用.
同样,避免DateTime.Now在任何服务器端代码中使用.Net.使用DateTime.UtcNow或DateTimeOffset.UtcNow 用于UTC时间戳,或者DateTimeOffset.Now如果由于某种原因服务器的时区对您很重要,请使用.
您可以在我的博文中了解更多相关信息:The Case Against DateTime.Now
接下来,我们来谈谈您正在使用的数据类型.dateSQL Server中的类型只存储日期.而已.没时间,没有偏移,也没有时区.一个例子是2013-08-11.当你真正意味着整个日历日期时,你应该使用它.世界上没有"今天"的统一背景.相反,每个人都有自己的时区意义.此外,并非每个日历日都是24小时.一天可以是23,23.5,24,24.5或25小时,具体取决于在特定时区应用夏令时的时间,以及您是否正在评估DST过渡的日期.
在.Net中 - 没有Date类型,因此将SQL date转换为a DateTime,时间设置为midnight(00:00:00),并将类型设置为Unspecified.但是不要欺骗自己 - 时间不是突然午夜,我们只是填写零时间.这可能会导致很多错误和混乱.(如果你想避免这种情况,你可以试试Noda Time,它有一个LocalDate用于此目的的类型.)
您真正需要思考的问题是,在您的问题中没有定义,这是:
什么确切做了项目启动的时刻?
现在你只是说2013-08-11,这并不是指具体的时刻.你的意思是在特定时区的那一天的开始?或者你的意思是那天的开始根据用户的时区?那些可能不是一回事.除非你知道你在谈论什么时刻,否则你无法与任何人的"现在"(utc,本地或其他)进行比较.
如果项目在世界范围内的确切时刻开始,那么最简单的方法是存储包含UTC精确时间的datetime(或datetime2)类型.所以你可能会说一个项目从2013-08-10T14:00:00Z8月11日午夜到澳大利亚悉尼正好开始.在.Net中,您将使用set为的DateTime类型..KindUtc
另一种可以表示这种情况的方法是存储一个datetimeoffset值为2013-08-11T00:00:00+10:00- 的时间与时间相同的类型,但使用偏移量为您提供预转换的值.(悉尼时间为UTC + 10).您可以使用该DateTimeOffset类型在.Net中使用它.
但是如果项目根据用户的不同时间开始,那么它并不是一个确切的时刻.这更像是一个"浮动"的开始.如果来自世界各地的用户被分配到同一个项目,那么一些用户可能会在其他用户之前启动.如果这是你的意图,那么你可以只使用date类型,如果所有项目都在午夜开始,或者如果项目可能在不同时间开始,你可以使用datetime或(datetime2)类型.在.Net代码中,您将使用set为的DateTime类型..KindUnspecified
关于获取用户的时区,你能做的最好的事情就是问问他们.尽管存在常见的误解 - 您无法从浏览器中获取它.您可以从浏览器中了解到它们当前的偏移量.(阅读时区标签wiki的"TimeZone!= Offset"部分).
在询问用户的时区时,如果您决定使用Windows时区,则可以从该TimeZoneInfo.GetSystemTimeZones方法生成下拉列表.这.Id是您存储在数据库中的密钥,并向.DisplayName用户显示.稍后您可以使用该TimeZoneInfo.FindSystemTimeZoneById方法获取TimeZoneInfo可用于转换的对象.
如果您想更精确,可以使用IANA时区而不是Windows时区.为此,我建议使用基于地图的时区选择器控件,例如此控件.您还可以使用jsTimeZoneDetect来猜测控件的默认值.在服务器上,您将使用Noda Time来执行时区转换.
有一种方法不需要时区转换.基本上,你用UTC 做所有事情.这包括以UTC格式将时间传输到浏览器.然后,您可以使用JavaScript以UTC格式获取用户的当前时间并与之进行比较.
Date如果您愿意,可以使用JavaScript 类的各种函数来执行此操作.但是你可能会发现使用诸如moment.js之类的库更容易.
虽然这种方法对许多事情都是可行的,但安全性并不是其中之一.您的用户可以轻松更改计算机的时钟以解决此问题.
另一种方法是将服务器端与UTC进行比较.如果您在数据库中拥有确切的UTC开始时间,那么您只需签DateTime.UtcNow入.Net代码并使用它来决定要做什么.您不需要用户的时区进行此比较,但如果您想要向他们展示当地时间的含义,您将需要它.
我希望这可以解决困惑并且不会让情况变得更糟!:)如果您有其他疑虑,请编辑您的问题或在评论中提问.
UPDATE
在回答您更新的问题时,我建议您尝试以下方法:
var timeZoneId = "Eastern Standard Time"; // from your user's selection
var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
var nowInTimeZone = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, timeZone);
var todayInTimeZone = nowInTimeZone.Date;
var i = ResourceCosts.FirstOrDefault(t => t.StartDate <= todayInTimeZone &&
(!t.EndDate.HasValue || t.EndDate >= todayInTimeZone));
Run Code Online (Sandbox Code Playgroud)
当然,这意味着在您StartDate和EndDate字段中,您不是将这些存储为UTC - 而是存储与用户相关的"业务日期".这些仅在应用时区时与特定时刻对齐,因此相同的UTC时间戳可能会落在不同的日期,具体取决于用户的时区.
此外,您正在使用完全包含范围,这通常适用于这些日历日期范围.但请确保您意识到可能存在重叠.所以,如果你有2013-01-01 - 2013-02-01和2013-02-01 - 2013-03-01,再有就是有一天,2013-02-01那就是在这两个范围.
解决这个问题的一个常见方法是使用半开间隔[start,end).换句话说,start <= now && end > now.但是,当使用完整的日期和时间而不仅仅是日期时,这种情况更为常见.您可能不需要这样做,但至少应该针对您的特定情况考虑它.
| 归档时间: |
|
| 查看次数: |
3104 次 |
| 最近记录: |