前言
如果在你的项目中只使用Datetime 足以满足一切需求,那你可能不需要点亮关于时间的技能点;
一旦你需要参与到一些国际化项目或者与定时调度相关的项目,则有必要对.Net中的时间处理方式进行一些系统的了解。
背景知识
• 时区:由于世界各国家与地区经度不同,地方时也有所不同,因此会划分为不同的时区。正式的时区划分,其中包括24个时区,每一时区由一个英文字母表示。每隔经度15°划分一个时区。1884年在华盛顿召开的一次国际经度会议(又称国际子午线会议)上,规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为中时区(零时区)、东1-12区,西1-12区。每个时区横跨经度15度,时间正好是1小时。最后的东、西第12区各跨经度7.5度,以东、西经180度为界。
• 时区计算:计算的区时=已知区时-(已知区时的时区-要计算区时的时区),(注:东时区为正,西时区为负)。例如:
例1:已知东京(东九区)时间为5月1日12:00,求北京(东八区)的区时?
答:北京时间=5/1 12:00时 -(9-8)时=5/1 11:00 时。
例2:已知北京时间为5月1日12:00,求伦敦(中时区)的区时?
答:伦敦时间=5/1 12:00时-(8-0)时=5/1 4:00时。
例3:已知北京时间为5月1日12:00,求纽约(西五区)的区时。
答:纽约时间=5/1 12:00-(8-(-5))=4/30 23:00时。
例4:已知纽约时间(西五区)为5月1日12:00,求东京(东九区)的区时。
答:东京时间=5/1 12:00-(-5-9)=5/1 12+14 超过24时加一天 ,即5/2 2:00时。
判断新旧两天,要看两条线,一是人为日界线-180度国际日期变更线,二是自然分界线-当地时间为0点的地区经线。自西向东越过国际日期变更线,日期应减1天,比如你在国际日期变更线西侧,当地时间是20日的00:30,当你自西向东越过国际日期变更线后,你所在位置的当地时间是19日的00:30。如果是自东向西越过国际日期变更线,则应该加1天。
.Net 中的时间相关概念
TimeZone
TimeZone是一个Class,主要用来得到本地时区信息,并将时间转换到协调世界时(UTC)。注意:这个API已过时,关于时区的操作都使用TimeZoneInfo类。
参考:https://docs.microsoft.com/zh-cn/dotnet/api/system.timezone?view=netcore-3.0
TimeZoneInfo
TimeZoneInfo 是一个class,一个TimeZoneInfo对象可以表示任何时区,可用于将一个时区的时间转换为其他时区中对应的事件。并且TimeZoneInfo的实例时不可变的,一旦已实例化一个对象(不能通过new实例化),不能修改其值。
class Program
{
static void Main(string[] args)
{
TimeZoneInfo timeZoneInfo = TimeZoneInfo.Local;
// 属性 本机所在时区
Console.WriteLine("本地时区名称:{0}",timeZoneInfo.StandardName);
Console.WriteLine("是否支持夏令时规则:{0}", timeZoneInfo.SupportsDaylightSavingTime);
Console.WriteLine("与国际标准时(零时区)的时差:{0}", timeZoneInfo.BaseUtcOffset);
Console.WriteLine("夏令时(中国1992年后已不再实施):{0}", timeZoneInfo.DaylightName);
Console.WriteLine("本地时区显示全名:{0}", timeZoneInfo.DisplayName);
// 时间转换 从本地时区到目标时区
DateTime dateTime = DateTime.Now;
TimeZoneInfo est = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
TimeZoneInfo hwt = TimeZoneInfo.FindSystemTimeZoneById("Hawaiian Standard Time");
var estTime= TimeZoneInfo.ConvertTime(dateTime, est);
Console.WriteLine("本地时间:{0}", dateTime.ToString());
Console.WriteLine("美国东部时间:{0}", estTime.ToString());
/*
* 重载 表示把一个时间(来自本地的时间)从美国东部时间转换成夏威夷时间
* 注意执行此转换时第一个DateTime参数的DaTimeKind必须和源DatetimeKind一致,否则会抛出异常。
* 所以下面的转换不会成功。
* https://docs.microsoft.com/zh-cn/dotnet/api/system.timezoneinfo.converttime?view=netcore-3.0#System_TimeZoneInfo_ConvertTime_System_DateTime_System_TimeZoneInfo_
*/
var tartetTime =TimeZoneInfo.ConvertTime(dateTime, est, hwt);
Console.WriteLine("本地时间:{0}", tartetTime.ToString());
}
}
参考:https://docs.microsoft.com/zh-cn/dotnet/api/system.timezoneinfo?view=netcore-3.0
DateTime
DateTime是一个Struct,是最常用用于跟时间相关的操作的对象。主要关注的功能点:
• 使用构造函数初始化一个时间
// new DateTime()默认等于DateTime.MinValue 01/01/0001 00:00:00
// 可以直接静态方法得到系统时间 DateTime.Now/DateTime.UtcNow/DateTime.Today ……
// 构造函数创建时间的重载方法
public DateTime(long ticks); // ticks 是以100纳秒为单位的数值,初始化时以0001/01/01 00:00:00 为起点
public DateTime(long ticks, DateTimeKind kind);
public DateTime(int year, int month, int day);
public DateTime(int year, int month, int day, Calendar calendar);// Calendar表示系统使用的日历,System.Globalization有其实现
public DateTime(int year, int month, int day, int hour, int minute, int second);
public DateTime(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind);
public DateTime(int year, int month, int day, int hour, int minute, int second, Calendar calendar);
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond);
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind);
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar);
public DateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, DateTimeKind kind);
• 将一定格式的字符串转换成时间对象
// 可以使用Parse() TryParse()方法将字符串转换成DateTime。
// 支持的格式通过如下代码查看:
List<string> badFormats = new List<String>();
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.CurrentCulture;
foreach (var dateString in DateTime.Now.GetDateTimeFormats())
{
DateTime parsedDate;
if (DateTime.TryParse(dateString, out parsedDate))
Console.WriteLine($"{dateString,-37} {DateTime.Parse(dateString),-19}");
else
badFormats.Add(dateString);
}
if (badFormats.Count > 0)
{
Console.WriteLine("
Strings that could not be parsed: ");
foreach (var badFormat in badFormats)
Console.WriteLine($" {badFormat}");
}
Console.ReadKey();
2019/6/28 2019/6/28 0:00:00
2019-6-28 2019/6/28 0:00:00
2019.6.28 2019/6/28 0:00:00
2019/06/28 2019/6/28 0:00:00
2019-06-28 2019/6/28 0:00:00
2019.06.28 2019/6/28 0:00:00
19/6/28 2019/6/28 0:00:00
19-6-28 2019/6/28 0:00:00
19.6.28 2019/6/28 0:00:00
19/06/28 2019/6/28 0:00:00
2019年6月28日 2019/6/28 0:00:00
2019年6月28日, 星期五 2019/6/28 0:00:00
星期五, 2019年6月28日 2019/6/28 0:00:00
2019年6月28日 2019/6/28 0:00:00
2019年6月28日, 星期五 2019/6/28 0:00:00
2019年6月28日 14:35 2019/6/28 14:35:00
2019年6月28日 14:35 2019/6/28 14:35:00
2019年6月28日 下午 2:35 2019/6/28 14:35:00
2019年6月28日 下午 02:35 2019/6/28 14:35:00
2019年6月28日, 星期五 14:35 2019/6/28 14:35:00
2019年6月28日, 星期五 14:35 2019/6/28 14:35:00
2019年6月28日, 星期五 下午 2:35 2019/6/28 14:35:00
2019年6月28日, 星期五 下午 02:35 2019/6/28 14:35:00
星期五, 2019年6月28日 14:35 2019/6/28 14:35:00
星期五, 2019年6月28日 14:35 2019/6/28 14:35:00
星期五, 2019年6月28日 下午 2:35 2019/6/28 14:35:00
星期五, 2019年6月28日 下午 02:35 2019/6/28 14:35:00
2019年6月28日 14:35 2019/6/28 14:35:00
2019年6月28日 14:35 2019/6/28 14:35:00
2019年6月28日 下午 2:35 2019/6/28 14:35:00
2019年6月28日 下午 02:35 2019/6/28 14:35:00
2019年6月28日, 星期五 14:35 2019/6/28 14:35:00
2019年6月28日, 星期五 14:35 2019/6/28 14:35:00
2019年6月28日, 星期五 下午 2:35 2019/6/28 14:35:00
2019年6月28日, 星期五 下午 02:35 2019/6/28 14:35:00
2019年6月28日 14:35:13 2019/6/28 14:35:13
2019年6月28日 14:35:13 2019/6/28 14:35:13
2019年6月28日 下午 2:35:13 2019/6/28 14:35:13
2019年6月28日 下午 02:35:13 2019/6/28 14:35:13
2019年6月28日, 星期五 14:35:13 2019/6/28 14:35:13
2019年6月28日, 星期五 14:35:13 2019/6/28 14:35:13
2019年6月28日, 星期五 下午 2:35:13 2019/6/28 14:35:13
2019年6月28日, 星期五 下午 02:35:13 2019/6/28 14:35:13
星期五, 2019年6月28日 14:35:13 2019/6/28 14:35:13
星期五, 2019年6月28日 14:35:13 2019/6/28 14:35:13
星期五, 2019年6月28日 下午 2:35:13 2019/6/28 14:35:13
星期五, 2019年6月28日 下午 02:35:13 2019/6/28 14:35:13
2019年6月28日 14:35:13 2019/6/28 14:35:13
2019年6月28日 14:35:13 2019/6/28 14:35:13
2019年6月28日 下午 2:35:13 2019/6/28 14:35:13
2019年6月28日 下午 02:35:13 2019/6/28 14:35:13
2019年6月28日, 星期五 14:35:13 2019/6/28 14:35:13
2019年6月28日, 星期五 14:35:13 2019/6/28 14:35:13
2019年6月28日, 星期五 下午 2:35:13 2019/6/28 14:35:13
2019年6月28日, 星期五 下午 02:35:13 2019/6/28 14:35:13
2019/6/28 14:35 2019/6/28 14:35:00
2019/6/28 14:35 2019/6/28 14:35:00
2019/6/28 下午 2:35 2019/6/28 14:35:00
2019/6/28 下午 02:35 2019/6/28 14:35:00
2019-6-28 14:35 2019/6/28 14:35:00
2019-6-28 14:35 2019/6/28 14:35:00
2019-6-28 下午 2:35 2019/6/28 14:35:00
2019-6-28 下午 02:35 2019/6/28 14:35:00
2019.6.28 14:35 2019/6/28 14:35:00
2019.6.28 14:35 2019/6/28 14:35:00
2019.6.28 下午 2:35 2019/6/28 14:35:00
2019.6.28 下午 02:35 2019/6/28 14:35:00
2019/06/28 14:35 2019/6/28 14:35:00
2019/06/28 14:35 2019/6/28 14:35:00
2019/06/28 下午 2:35 2019/6/28 14:35:00
2019/06/28 下午 02:35 2019/6/28 14:35:00
2019-06-28 14:35 2019/6/28 14:35:00
2019-06-28 14:35 2019/6/28 14:35:00
2019-06-28 下午 2:35 2019/6/28 14:35:00
2019-06-28 下午 02:35 2019/6/28 14:35:00
2019.06.28 14:35 2019/6/28 14:35:00
2019.06.28 14:35 2019/6/28 14:35:00
2019.06.28 下午 2:35 2019/6/28 14:35:00
2019.06.28 下午 02:35 2019/6/28 14:35:00
19/6/28 14:35 2019/6/28 14:35:00
19/6/28 14:35 2019/6/28 14:35:00
19/6/28 下午 2:35 2019/6/28 14:35:00
19/6/28 下午 02:35 2019/6/28 14:35:00
19-6-28 14:35 2019/6/28 14:35:00
19-6-28 14:35 2019/6/28 14:35:00
19-6-28 下午 2:35 2019/6/28 14:35:00
19-6-28 下午 02:35 2019/6/28 14:35:00
19.6.28 14:35 2019/6/28 14:35:00
19.6.28 14:35 2019/6/28 14:35:00
19/06/28 14:35 2019/6/28 14:35:00
19/06/28 14:35 2019/6/28 14:35:00
19/06/28 下午 2:35 2019/6/28 14:35:00
19/06/28 下午 02:35 2019/6/28 14:35:00
2019/6/28 14:35:13 2019/6/28 14:35:13
2019/6/28 14:35:13 2019/6/28 14:35:13
2019/6/28 下午 2:35:13 2019/6/28 14:35:13
2019/6/28 下午 02:35:13 2019/6/28 14:35:13
2019-6-28 14:35:13 2019/6/28 14:35:13
2019-6-28 14:35:13 2019/6/28 14:35:13
2019-6-28 下午 2:35:13 2019/6/28 14:35:13
2019-6-28 下午 02:35:13 2019/6/28 14:35:13
2019.6.28 14:35:13 2019/6/28 14:35:13
2019.6.28 14:35:13 2019/6/28 14:35:13
2019.6.28 下午 2:35:13 2019/6/28 14:35:13
2019.6.28 下午 02:35:13 2019/6/28 14:35:13
2019/06/28 14:35:13 2019/6/28 14:35:13
2019/06/28 14:35:13 2019/6/28 14:35:13
2019/06/28 下午 2:35:13 2019/6/28 14:35:13
2019/06/28 下午 02:35:13 2019/6/28 14:35:13
2019-06-28 14:35:13 2019/6/28 14:35:13
2019-06-28 14:35:13 2019/6/28 14:35:13
2019-06-28 下午 2:35:13 2019/6/28 14:35:13
2019-06-28 下午 02:35:13 2019/6/28 14:35:13
2019.06.28 14:35:13 2019/6/28 14:35:13
2019.06.28 14:35:13 2019/6/28 14:35:13
2019.06.28 下午 2:35:13 2019/6/28 14:35:13
2019.06.28 下午 02:35:13 2019/6/28 14:35:13
19/6/28 14:35:13 2019/6/28 14:35:13
19/6/28 14:35:13 2019/6/28 14:35:13
19/6/28 下午 2:35:13 2019/6/28 14:35:13
19/6/28 下午 02:35:13 2019/6/28 14:35:13
19-6-28 14:35:13 2019/6/28 14:35:13
19-6-28 14:35:13 2019/6/28 14:35:13
19-6-28 下午 2:35:13 2019/6/28 14:35:13
19-6-28 下午 02:35:13 2019/6/28 14:35:13
19.6.28 14:35:13 2019/6/28 14:35:13
19.6.28 14:35:13 2019/6/28 14:35:13
19/06/28 14:35:13 2019/6/28 14:35:13
19/06/28 14:35:13 2019/6/28 14:35:13
19/06/28 下午 2:35:13 2019/6/28 14:35:13
19/06/28 下午 02:35:13 2019/6/28 14:35:13
6月28日 2019/6/28 0:00:00
6月28日 2019/6/28 0:00:00
2019-06-28T14:35:13.8497646+08:00 2019/6/28 14:35:13
2019-06-28T14:35:13.8497646+08:00 2019/6/28 14:35:13
Fri, 28 Jun 2019 14:35:13 GMT 2019/6/28 22:35:13
Fri, 28 Jun 2019 14:35:13 GMT 2019/6/28 22:35:13
2019-06-28T14:35:13 2019/6/28 14:35:13
14:35 2019/6/28 14:35:00
14:35 2019/6/28 14:35:00
下午 2:35 2019/6/28 14:35:00
下午 02:35 2019/6/28 14:35:00
14:35:13 2019/6/28 14:35:13
14:35:13 2019/6/28 14:35:13
下午 2:35:13 2019/6/28 14:35:13
下午 02:35:13 2019/6/28 14:35:13
2019-06-28 14:35:13Z 2019/6/28 22:35:13
2019年6月28日 6:35:13 2019/6/28 6:35:13
2019年6月28日 06:35:13 2019/6/28 6:35:13
2019年6月28日 上午 6:35:13 2019/6/28 6:35:13
2019年6月28日 上午 06:35:13 2019/6/28 6:35:13
2019年6月28日, 星期五 6:35:13 2019/6/28 6:35:13
2019年6月28日, 星期五 06:35:13 2019/6/28 6:35:13
2019年6月28日, 星期五 上午 6:35:13 2019/6/28 6:35:13
2019年6月28日, 星期五 上午 06:35:13 2019/6/28 6:35:13
星期五, 2019年6月28日 6:35:13 2019/6/28 6:35:13
星期五, 2019年6月28日 06:35:13 2019/6/28 6:35:13
星期五, 2019年6月28日 上午 6:35:13 2019/6/28 6:35:13
星期五, 2019年6月28日 上午 06:35:13 2019/6/28 6:35:13
2019年6月28日 6:35:13 2019/6/28 6:35:13
2019年6月28日 06:35:13 2019/6/28 6:35:13
2019年6月28日 上午 6:35:13 2019/6/28 6:35:13
2019年6月28日 上午 06:35:13 2019/6/28 6:35:13
2019年6月28日, 星期五 6:35:13 2019/6/28 6:35:13
2019年6月28日, 星期五 06:35:13 2019/6/28 6:35:13
2019年6月28日, 星期五 上午 6:35:13 2019/6/28 6:35:13
2019年6月28日, 星期五 上午 06:35:13 2019/6/28 6:35:13
2019年6月 2019/6/1 0:00:00
2019年6月 2019/6/1 0:00:00
2019.6 2019/6/1 0:00:00
2019年6月 2019/6/1 0:00:00
2019年6月 2019/6/1 0:00:00
2019.6 2019/6/1 0:00:00
Strings that could not be parsed:
19.6.28 下午 2:35
19.6.28 下午 02:35
19.6.28 下午 2:35:13
19.6.28 下午 02:35:13
2019年六月
2019年六月
• 计算时间之间的差值
DateTime 是一个可以计算的结构体,可以对其年月日时分秒进行数学计算,一般结合系统日历输出。如果计算时间差值一般使用TimeSpan对象。
DateTimeKind
DateTimeKind是一个枚举,是DateTime的一个属性,表示得到时间是Utc时间还是本地时间,或者是Unspecified。在做时间计算时需要保持一致。
var dt = DateTime.Now;
Console.WriteLine("时间:{0},DateTimeKind:{1}", dt, dt.Kind);
var dt2 = DateTime.UtcNow;
Console.WriteLine("时间:{0},DateTimeKind:{1}", dt, dt2.Kind);
Console.ReadKey();
时间:2019/6/28 14:47:58,DateTimeKind:Local
时间:2019/6/28 14:47:58,DateTimeKind:Utc
TimeSpan
TimeSpan是一个结构体,表示一个时间间隔。如果需要进行比较精确的计算可以通过ticks(100纳秒为单位)来计算。
// 构造方式 显然由于年和月不是一个固定的时间刻度,所以不能按年和月构造时间刻度。
public TimeSpan(long ticks);
public TimeSpan(int hours, int minutes, int seconds);
public TimeSpan(int days, int hours, int minutes, int seconds);
public TimeSpan(int days, int hours, int minutes, int seconds, int milliseconds);
DateTimeOffset
DateTimeOffset是一个结构体,包含了Datetime值和Offset值,用于定义当前的时间和相对于零时区的偏移量。
它提供了以下几个方面的功能:
• 日期和时间的计算
• 类型转换 和DateTime类型互换
• 比较:两个DateTimeOffset值的比较会通过转换成UTC时间进行。
var dt = DateTime.Now;
var dtoffset = DateTimeOffset.Now;
Console.WriteLine("当前系统时间:{0},DateTimeOffset:{1}",dt,dtoffset);
//当前系统时间:2019/6/28 15:12:45,DateTimeOffset:2019/6/28 15:12:45 +08:00
var dateOffset1 = DateTimeOffset.Now;
var dateOffset2 = DateTimeOffset.UtcNow;
var difference = dateOffset1 - dateOffset2;
Console.WriteLine("{0} - {1} = {2}",
dateOffset1, dateOffset2, difference);
//2019/6/28 15:19:26 +08:00 - 2019/6/28 7:19:26 +00:00 = -00:00:00.0068201
//构造方式:
public DateTimeOffset(DateTime dateTime);
public DateTimeOffset(long ticks, TimeSpan offset);
public DateTimeOffset(DateTime dateTime, TimeSpan offset);
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, TimeSpan offset);
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, TimeSpan offset);
public DateTimeOffset(int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, TimeSpan offset);
在SQL Server2008 以上版本提供了datetimeoffset(7)的字段类型,可以进行一系列排序或比较操作。
参考:https://docs.microsoft.com/zh-cn/dotnet/api/system.datetimeoffset?view=netframework-4.8
选择DateTime还是DateTimeOffset?
DateTime 结构适用于执行以下操作的应用程序:
• 仅使用日期。
• 只使用时间。
• 使用抽象的日期和时间。
• 使用缺少时区信息的日期和时间。
• 只使用 UTC 日期和时间。
• 从.NET 中,外部的源,如 SQL 数据库中检索日期和时间信息。 通常,这些源按与 DateTime 结构兼容的简单格式存储日期和时间信息。
• 执行日期和时间算法,但不关注常规结果。 例如,在向特定日期和时间添加六个月的加法运算中,是否将结果调整为夏令时通常并不重要。
DateTimeOffset 结构表示日期和时间值,以及指示此值与 UTC 的差异程度的偏移量。 因此,此值始终明确地标识单个时间点。它适合于应用程序执行以下操作:
• 唯一、明确地标识单个时间点。 DateTimeOffset 类型可用于明确定义“现在”的含义、记录事务时间、记录系统或应用程序事件时间以及记录创建和修改时间。
• 执行常规日期和时间算法。
• 保留多个相关时间,只要这些时间存储为两个单独的值或结构中的两个成员。
DateTimeOffset 的使用范围比DateTime更加广泛,应该优先考虑使用。