C#编程魔法书
上QQ阅读APP看书,第一时间看更新

2.5 时间和日期

一般操作系统是通过数字来处理时间的,这个数字在有的系统上是整数,在有的系统上是浮点数。除此之外,数字0代表的时间也有不同定义,再加上时区的差异,加大了时间处理的复杂度。在编程中,我们可能会遇到的时间如表2-7所示。

表2-7 编程常见的时间格式

070-01

代码清单2-26演示了在.NET时间与各种时间格式转换的方法。其中Windows系统为.NET的DateTime类型提供了原生函数转换,但与UNIX时间戳的转换需要做一些额外处理,第8行的To/FromUnixTimestamp两个函数演示了两者互换的方法。

代码清单2-26 .NET时间与各种时间格式的转换方法

// 源码位置:第2章\DateTimeDemo.cs
// 编译命令:csc DateTimeDemo.cs
01 static void Main()
02 {
03     var date = new DateTime(2019, 1, 20, 17, 18, 20);
04     Console.WriteLine($"ticks: {date.Ticks}, oadate: {date.ToOADate()}," +
05         $" unix: {ToUnixTimestamp(date)}, file: {date.ToFileTime()}");
06 }
07
08 static double ToUnixTimestamp(DateTime value)
09 {
10     value = value.ToUniversalTime();
11     return value.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
12 }
13
14 public static DateTime FromUnixTimestamp(double value)
15 {
16     var date = new DateTime(1970, 1, 1);
17     var utc = date.AddSeconds(value);
18     return utc.ToLocalTime();
19 }

有兴趣的读者可以在Excel里查看OLE自动化时间转换,如图2-20所示。读者可以访问网址https://www.epochconverter.com/核对UNIX时间戳。

071-01

图2-20 在Excel里查看OLE自动化时间转换

由于时区的存在,同一时刻各地的时间是不一样的。除此之外,很多国家有夏令时(Daylight Saving)。设计夏令时的目的是尽可能地多利用白天的时间。具体操作是在春夏的某一天将时间往前拨1小时,而在秋天的某一个时刻又把时间拨回来,这样原来冬天的9点上班时间到了夏天的时候,虽然电子表系统都显示9点,但实际上是8点。对于夏令时的调整,有的国家是在固定的某一天调整,而更多的国家是规定某月的第几周的星期几开始调整,如规定三月的第三个周日开始调整。TimeZoneInfo类型就是用来处理时间调整细节的。为了消除时区和夏令时的差异给跨区域使用时间带来的混乱,人们定义了UTC(Universal Coordinated Time,世界协调时间)。在跨时区传输时间时,建议将时间转换成UTC时间传输,在另外一台机器上接收到之后再转换成本地时间。

TimeZoneInfo类型的静态方法GetSystemTimeZones可以获取.NET支持的所有时区信息,如代码清单2-27第2~3行的循环。使用时区名称初始化TimeZoneInfo实例,如第6行和第7行分别初始化北京时间和太平洋时间,后者是微软总部西雅图所在的时区。第9~10行将本地时间转换成UTC时间,再分别用北京时间和太平洋时间解析。可以看到,太平洋时间(2019/1/24 8:00:00)比北京时间(2019/1/25 0:00:00)晚16个小时。而第16行的时间是夏天的时间,转换后可以看到太平洋时间是2019/6/24 9:00:00,这是因为美国采用夏令时制度,所以时间有1个小时的调整。这里也可以看到各个国家对时间的处理是不一样的。中国一般只用一个时区——北京时间,美国习惯上分时区,这意味着在设计手机移动应用时,当用户坐飞机从北京到新疆伊犁,移动应用不需要调整时间,但用户从美国西雅图飞到纽约的话,移动应用就应该根据用户位置动态调整时间了。

代码清单2-27 时区TimeZoneInfo使用示例

// 源码位置:第2章\DateTimeDemo.cs
// 编译命令:csc DateTimeDemo.cs

01 // 获取所有的时区信息
02 foreach (var z in TimeZoneInfo.GetSystemTimeZones())
03     Console.WriteLine($"{z.Id}: {z.DisplayName}");
04
05 // 北京时间
06 TimeZoneInfo bjtz = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
07 // 微软总部西雅图时间
08 TimeZoneInfo mstz=TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
09 // var date = DateTime.UtcNow;
10 date = new DateTime(2019, 1, 25).ToUniversalTime();
11 var bjtime = TimeZoneInfo.ConvertTimeFromUtc(date, bjtz);
12 Console.WriteLine($"北京时间:{bjtime}");
13 var mstime = TimeZoneInfo.ConvertTimeFromUtc(date, mstz);
14 Console.WriteLine($"微软时间:{mstime}");
15
16 date = new DateTime(2019, 6, 25).ToUniversalTime();
17 bjtime = TimeZoneInfo.ConvertTimeFromUtc(date, bjtz);
18 Console.WriteLine($"北京时间:{bjtime}");
19 mstime = TimeZoneInfo.ConvertTimeFromUtc(date, mstz);
20 Console.WriteLine($"微软时间:{mstime}");

除了时区差异,世界各地(主要是亚洲)使用的日历也是有差异的,如中国分农历和公历,公历也就是世界上常用的格力高历。在.NET中,GregorianCalendar类是公历,而农历是ChineseLunisolarCalendar。代码清单2-28演示了农历的用法,如2019年春节是2月5日,公历的月份是2月,但是农历获取的月份则是1月。第19~21行演示了根据时间获取干支纪年的方法。

代码清单2-28 日历的用法

01 // 天干
02 enum CelestialStem
03 {
04     甲 = 1, 乙, 丙, 丁, 戊,
05     己, 庚, 辛, 壬, 癸
06 }
07
08 // 地支
09 enum TerrestrialBranch
10 {
11     子 = 1, 丑, 寅, 卯, 辰, 巳,
12     午, 未, 申, 酉, 戌, 亥
13 }
14
15 date = new DateTime(2019, 2, 5);
16 var 公历 = new GregorianCalendar();
17 var 农历 = new ChineseLunisolarCalendar();
18
19 var 干支 = 农历.GetSexagenaryYear(date);
20 var 天干 = (CelestialStem)农历.GetCelestialStem(干支);
21 var 地支 = (TerrestrialBranch)农历.GetTerrestrialBranch(干支);
22 Console.WriteLine(
23     $"公历:{公历.GetMonth(date)},农历:{农历.GetMonth(date)}" +
24     $",干支:{天干}{地支}");