c#:时区、DateTime和DateTimeOffset

参考:
https://blog.csdn.net/u010476739/article/details/118339679
https://www.cnblogs.com/wuxiaoqian726/archive/2011/03/19/1988931.html

先说下结论:
如果系统不考虑全球化的话,那么我们不用考虑时区的问题,因为我们可以认为中国境内的计算机全部用的是北京时间。

时区介绍

地球自转一圈是360度,共24小时,所以1小时15度,即:15度就是1个时区。
国际定义了本初子午线(0度经线)的位置在英国伦敦格林尼治天文台,自然世界时(UTC)也是指的格林尼治天文台(GMT)那里的时间。
认为北京处在东经120度线上,也就是东八区上,所以北京相比UTC时间有8个小时的时差。
由于地球自西向东转,所以北京比格林尼治最先接触阳光,自然就认为北京的时间比格林尼治快8个小时。
比如说:
2020-01-01 00:00:00 +00:00: 表示格林尼治此时正值午夜凌晨。
但用北京时间表示为 2020-01-01 00:08:00 +08:00,即:北京此时天已经亮了,人们正在准备上班。

为什么会有北京时间,全世界都用一个UTC时间不就行嘛?
世界那么大,各地都有自己的习惯,如果突然统一起来,真的不习惯。
比如说,按照UTC时间,我们应该午夜凌晨正在上班的路上,这听起来就很疯狂。。。

如果需要国际化,该如何使用时间

前提: http请求头中虽然有关于语言(中文简体、英文?)的信息,但是没有携带客户端时区的信息。
既然如此,我们就要在服务端统一一种时区,这样方便我们开发调试,因为是国人开发的应用,所以就用北京时间即可。
剩下的就是客户端了,因为服务端无法预料客户端的时区,所以为了方便统一开发,我们可以统一返回携带时区信息的北京时间,客户端接收后自行转换即可。
常用的携带时区的时间有下面两种格式:

2020-01-02T01:08:07Z
2020-01-02 09:08:07 +08:00

针对这两种格式的转换示例如下:

//北京时间(东八区): 2020-01-02 09:08:07.123
var baseTime = new DateTimeOffset(2020, 01, 02, 09, 08, 07, 123, TimeSpan.FromHours(8));

//常规输出: 不考虑时区
var str = baseTime.ToString("yyyy-MM-dd hh:mm:ss");//2020-01-02 09:08:07
str = baseTime.ToString("yyyy-MM-dd hh:mm:ss.fff");//2020-01-02 09:08:07.123

//考虑时区,形式1
str = baseTime.ToUniversalTime().ToString("yyyy-MM-ddThh:mm:ssZ");//2020-01-02T01:08:07Z
str = baseTime.ToUniversalTime().ToString("yyyy-MM-ddThh:mm:ss.fffZ");//2020-01-02T01:08:07.123Z

//考虑时区,形式2
str = baseTime.ToString("yyyy-MM-dd HH:mm:ss zzz");//2020-01-02 09:08:07 +08:00
str = baseTime.ToString("yyyy-MM-dd HH:mm:ss.fff zzz");//2020-01-02 09:08:07.123 +08:00

//如果当前计算机设置的时区是北京时间(东八区)则输出为true,否则输出false
var parse1 = DateTimeOffset.Parse("2020-01-02 09:08:07.123");
Console.WriteLine($"parse1==baseTime: {parse1 == baseTime}");

//输出true
var parse2 = DateTimeOffset.Parse("2020-01-02T01:08:07.123Z");
Console.WriteLine($"parse2==baseTime: {parse2 == baseTime}");

//输出true
var parse3 = DateTimeOffset.Parse("2020-01-02 09:08:07.123 +08:00");
Console.WriteLine($"parse3==baseTime: {parse3 == baseTime}");

那么c#中的DataTime和DataTimeOffset有什么差别?
相同点: 它们两个都存储了年、月、日、时、分、秒、毫秒信息。
区别点:DateTimeOffset中还存储了时区信息;而DateTime中未存储时区信息(虽然有个DateTimeKind,但它十个枚举,只有Unspecified、Utc、Local三个值且默认为Unspecified),它总是认为自己存储的时间的时区是当前计算机设置的时区;

以sqlserver为例看数据库中如何存储时间

sqlserver中的关于时间的类型如下:

date:3字节,表示范围:0001-01-01 到 9999-12-31
datetime:8字节,表示范围: 1753-01-01 00:00:00 到 9999-12-31 23:59:59
datetime2:6-8个字节,表示范围:0001-01-01 00:00:00 到 9999-12-31 23:59:59.9999999
smalldatetime:4个字节,表示范围:1900-01-01 00:00:00 到 2079-06-06 23:59:59
time:5个字节,表示范围: 00:00:00.0000000 到 23:59:59.9999999
datetimeoffset:10个字节,表示范围:0001-01-01 00:00:00 到 23:59:59.9999999,可表示时区

通过上面列举,我们可以发现,除非我们使用datetimeoffset,否则我们是无法保存时区信息的。
其实,对于服务器来说,数据库中存不存储时区并不是那么重要,只要保证web服务器、数据库服务器设置的时区一致即可!!!
推荐sqlserver中使用:datetime2。
另外:一般我们常设计列的类型为datetime,而不是datetime2。
其实这有个隐患:当程序中使用DateTime的默认值,即:0000-01-01时,我们无法插入到数据库中,报错如下:

“从 datetime2 数据类型到 datetime 数据类型的转换产生一个超出范围的值”

浏览器端如何显示时间

上面中建议后端返回的时间携带时区信息,也就是下面两种格式:

2020-01-02T01:08:07Z
2020-01-02 09:08:07 +08:00

那么前端应该怎么显示呢?
幸运的是,js脚本可以直接解析上面两种类型的时间,看下面代码:

new Date(Date.parse("2020-01-02 09:08:07 +08:00")).toDateString()
"Thu Jan 02 2020"
new Date(Date.parse("2020-01-02 09:08:07 +08:00")).toTimeString()
"09:08:07 GMT+0800 (中国标准时间)"

new Date(Date.parse("2020-01-02T01:08:07Z")).toLocaleDateString()
"2020/1/2"
new Date(Date.parse("2020-01-02T01:08:07Z")).toLocaleTimeString()
"上午9:08:07"

关于夏令时

夏令时是个奇怪且别扭的东西,中国曾经实行过6年(1986年到1991年),之后便废除了。
那么什么是夏令时的呢?
因为夏季天亮的早、黑的晚,而人们还是以冬季的时间点作息,导致起的晚浪费了阳光,睡的晚浪费了蜡烛,所以就人为的在夏季到来时的某天夜里2点偷偷的将时间调快1小时,然后在夏季结束时的某天夜里2点偷偷的将时间调慢1小时。
上面的一听就很别扭,还有人为改时间的!!! 调整作息时间不就行了吗!
事实上,我们也仅在1986-1991年执行了6年而已,之后便废除了,现在我们都是夏季和冬季到来时调整作息时间的。
假如,我们使用夏令时会有什么影响呢?

夏季到来时,比如:1986年5月4日,在夜里凌晨2点你的表突然跳到了3点,这样你就少睡了一小时。。。
冬季到来时,比如:1986年9月14日,在夜里凌晨2点你的表突然跳到1点,这样你就多睡了一小时。。。
如果你恰好在这个时间段设置了定时任务,那么,你会发现,你的定时任务可能触发了两次,也可能没触发。。。
虽然我国废除了夏令时,但不代表其他国家也废除了,比如说美国目前就实行夏令时。
但,夏令时在全球已经明显不受欢迎了,
俄罗斯: 实行了很长时间,最终2014年通过杜马法案废除了。
西班牙:还在偷偷改时间。
美国:不仅偷偷改时间,而且各个州的时区不一样,所以重要会议一般都会标注时区。
全球有110多个国家在使用夏令时。
欧盟正在废除夏令时的路上。

另外,想查看哪些地区支持夏令时,可以在电脑的时区设置中发现:

TimeZoneInfo

TimeZoneInfo是一个关于时区的静态类,可以使得我们方便的操作与时区相关的信息
对于一个开放于全球的网站或服务,在时间上的显示是一个问题,因为各个国家都会有所谓的时差,好在 .Net提供了 TimeZoneInfo 类来解决这个问题。
TimeZoneInfo 类的方法可用于将一个时区的时间转换为其他任意时区的相应时间

静态成员

  • CreateCustomTimeZone()使用应用程序提供的数据创建自定义时区。 CreateCustomTimeZone 方法创建在本地系统注册表中未定义的时区。然后可使用 ToSerializedString 属性将时区对象的信息保存为字符串,该字符串可以以应用程序可访问的某种格式存储。使用 FromSerializedString 方法可将序列化字符串转换回 TimeZoneInfo 对象
  • FindSystemTimeZoneById()根据时区的标识符实例化在系统注册表中定义的时区。
  • FromSerializedString()反序列化一个字符串值,以重新创建先前已序列化的 TimeZoneInfo 对象。
  • GetSystemTimeZones()返回 TimeZoneInfo 对象的可枚举的 ReadOnlyCollection<(Of <(T>)>),它表示本地系统上可用的所有时区。
  • Local 属性:实例化表示本地时区的 TimeZoneInfo 对象。
  • Utc 属性:实例化表示 UTC 时区的 TimeZoneInfo 对象。

案例

TimeZoneInfo 的使用很简单,一开始利用 FindSystemTimeZoneById 找到对应的 TimeZoneInfo 对象,就可以利用该对象来转换时间。下列程序是示范如何将 UTC Time 转为东京的当地间时。

TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
Console.WriteLine(string.Format("UTC Time:{0}", time.ToString()));
Console.WriteLine(string.Format("Tokyo Time:{0}", TimeZoneInfo.ConvertTime(time, TimeZoneInfo.Utc, timeZoneInfo)));

或许有人会问,该如何知道各个地区所对应的 id,其实利用 TimeZoneInfo.GetSystemtimeZones 就可以了。

var list =  TimeZoneInfo.GetSystemTimeZones();
            foreach (var timeZoneInfo in list)
            {
                Console.WriteLine(timeZoneInfo.DisplayName);
                Console.WriteLine(timeZoneInfo.Id);
            }

时区类型

Windows时区,在Windows平台实现,例:"China Standard Time"
Olson(奥尔森)时区,在Linux,Mac,IOS,Andorid,JavaScript,Java,PHP和许多其他平台上广泛实现,例:"Asia/Shanghai"
两种类型可以互转,需要用Windows时区与国际时区的映射表来确认当前国际时区id
通过olson时区获取TimeZoneInfo:

/// <summary>
/// Converts an Olson time zone ID to a Windows time zone ID.
/// </summary>
/// <param name="olsonTimeZoneId">An Olson time zone ID. See http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html. </param>
/// <returns>
/// The TimeZoneInfo corresponding to the Olson time zone ID, 
/// or null if you passed in an invalid Olson time zone ID.
/// </returns>
/// <remarks>
/// See http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/zone_tzid.html
/// </remarks>
public static TimeZoneInfo OlsonTimeZoneToTimeZoneInfo(string olsonTimeZoneId)
{
    var olsonWindowsTimes = new Dictionary<string, string>()
    {
        { "Africa/Bangui", "W. Central Africa Standard Time" },
        { "Africa/Cairo", "Egypt Standard Time" },
        { "Africa/Casablanca", "Morocco Standard Time" },
        { "Africa/Harare", "South Africa Standard Time" },
        { "Africa/Johannesburg", "South Africa Standard Time" },
        { "Africa/Lagos", "W. Central Africa Standard Time" },
        { "Africa/Monrovia", "Greenwich Standard Time" },
        { "Africa/Nairobi", "E. Africa Standard Time" },
        { "Africa/Windhoek", "Namibia Standard Time" },
        { "America/Anchorage", "Alaskan Standard Time" },
        { "America/Argentina/San_Juan", "Argentina Standard Time" },
        { "America/Asuncion", "Paraguay Standard Time" },
        { "America/Bahia", "Bahia Standard Time" },
        { "America/Bogota", "SA Pacific Standard Time" },
        { "America/Buenos_Aires", "Argentina Standard Time" },
        { "America/Caracas", "Venezuela Standard Time" },
        { "America/Cayenne", "SA Eastern Standard Time" },
        { "America/Chicago", "Central Standard Time" },
        { "America/Chihuahua", "Mountain Standard Time (Mexico)" },
        { "America/Cuiaba", "Central Brazilian Standard Time" },
        { "America/Denver", "Mountain Standard Time" },
        { "America/Fortaleza", "SA Eastern Standard Time" },
        { "America/Godthab", "Greenland Standard Time" },
        { "America/Guatemala", "Central America Standard Time" },
        { "America/Halifax", "Atlantic Standard Time" },
        { "America/Indianapolis", "US Eastern Standard Time" },
        { "America/Indiana/Indianapolis", "US Eastern Standard Time" },
        { "America/La_Paz", "SA Western Standard Time" },
        { "America/Los_Angeles", "Pacific Standard Time" },
        { "America/Mexico_City", "Mexico Standard Time" },
        { "America/Montevideo", "Montevideo Standard Time" },
        { "America/New_York", "Eastern Standard Time" },
        { "America/Noronha", "UTC-02" },
        { "America/Phoenix", "US Mountain Standard Time" },
        { "America/Regina", "Canada Central Standard Time" },
        { "America/Santa_Isabel", "Pacific Standard Time (Mexico)" },
        { "America/Santiago", "Pacific SA Standard Time" },
        { "America/Sao_Paulo", "E. South America Standard Time" },
        { "America/St_Johns", "Newfoundland Standard Time" },
        { "America/Tijuana", "Pacific Standard Time" },
        { "Antarctica/McMurdo", "New Zealand Standard Time" },
        { "Atlantic/South_Georgia", "UTC-02" },
        { "Asia/Almaty", "Central Asia Standard Time" },
        { "Asia/Amman", "Jordan Standard Time" },
        { "Asia/Baghdad", "Arabic Standard Time" },
        { "Asia/Baku", "Azerbaijan Standard Time" },
        { "Asia/Bangkok", "SE Asia Standard Time" },
        { "Asia/Beirut", "Middle East Standard Time" },
        { "Asia/Calcutta", "India Standard Time" },
        { "Asia/Colombo", "Sri Lanka Standard Time" },
        { "Asia/Damascus", "Syria Standard Time" },
        { "Asia/Dhaka", "Bangladesh Standard Time" },
        { "Asia/Dubai", "Arabian Standard Time" },
        { "Asia/Irkutsk", "North Asia East Standard Time" },
        { "Asia/Jerusalem", "Israel Standard Time" },
        { "Asia/Kabul", "Afghanistan Standard Time" },
        { "Asia/Kamchatka", "Kamchatka Standard Time" },
        { "Asia/Karachi", "Pakistan Standard Time" },
        { "Asia/Katmandu", "Nepal Standard Time" },
        { "Asia/Kolkata", "India Standard Time" },
        { "Asia/Krasnoyarsk", "North Asia Standard Time" },
        { "Asia/Kuala_Lumpur", "Singapore Standard Time" },
        { "Asia/Kuwait", "Arab Standard Time" },
        { "Asia/Magadan", "Magadan Standard Time" },
        { "Asia/Muscat", "Arabian Standard Time" },
        { "Asia/Novosibirsk", "N. Central Asia Standard Time" },
        { "Asia/Oral", "West Asia Standard Time" },
        { "Asia/Rangoon", "Myanmar Standard Time" },
        { "Asia/Riyadh", "Arab Standard Time" },
        { "Asia/Seoul", "Korea Standard Time" },
        { "Asia/Shanghai", "China Standard Time" },
        { "Asia/Singapore", "Singapore Standard Time" },
        { "Asia/Taipei", "Taipei Standard Time" },
        { "Asia/Tashkent", "West Asia Standard Time" },
        { "Asia/Tbilisi", "Georgian Standard Time" },
        { "Asia/Tehran", "Iran Standard Time" },
        { "Asia/Tokyo", "Tokyo Standard Time" },
        { "Asia/Ulaanbaatar", "Ulaanbaatar Standard Time" },
        { "Asia/Vladivostok", "Vladivostok Standard Time" },
        { "Asia/Yakutsk", "Yakutsk Standard Time" },
        { "Asia/Yekaterinburg", "Ekaterinburg Standard Time" },
        { "Asia/Yerevan", "Armenian Standard Time" },
        { "Atlantic/Azores", "Azores Standard Time" },
        { "Atlantic/Cape_Verde", "Cape Verde Standard Time" },
        { "Atlantic/Reykjavik", "Greenwich Standard Time" },
        { "Australia/Adelaide", "Cen. Australia Standard Time" },
        { "Australia/Brisbane", "E. Australia Standard Time" },
        { "Australia/Darwin", "AUS Central Standard Time" },
        { "Australia/Hobart", "Tasmania Standard Time" },
        { "Australia/Perth", "W. Australia Standard Time" },
        { "Australia/Sydney", "AUS Eastern Standard Time" },
        { "Etc/GMT", "UTC" },
        { "Etc/GMT+11", "UTC-11" },
        { "Etc/GMT+12", "Dateline Standard Time" },
        { "Etc/GMT+2", "UTC-02" },
        { "Etc/GMT-12", "UTC+12" },
        { "Europe/Amsterdam", "W. Europe Standard Time" },
        { "Europe/Athens", "GTB Standard Time" },
        { "Europe/Belgrade", "Central Europe Standard Time" },
        { "Europe/Berlin", "W. Europe Standard Time" },
        { "Europe/Brussels", "Romance Standard Time" },
        { "Europe/Budapest", "Central Europe Standard Time" },
        { "Europe/Dublin", "GMT Standard Time" },
        { "Europe/Helsinki", "FLE Standard Time" },
        { "Europe/Istanbul", "GTB Standard Time" },
        { "Europe/Kiev", "FLE Standard Time" },
        { "Europe/London", "GMT Standard Time" },
        { "Europe/Minsk", "E. Europe Standard Time" },
        { "Europe/Moscow", "Russian Standard Time" },
        { "Europe/Paris", "Romance Standard Time" },
        { "Europe/Sarajevo", "Central European Standard Time" },
        { "Europe/Warsaw", "Central European Standard Time" },
        { "Indian/Mauritius", "Mauritius Standard Time" },
        { "Pacific/Apia", "Samoa Standard Time" },
        { "Pacific/Auckland", "New Zealand Standard Time" },
        { "Pacific/Fiji", "Fiji Standard Time" },
        { "Pacific/Guadalcanal", "Central Pacific Standard Time" },
        { "Pacific/Guam", "West Pacific Standard Time" },
        { "Pacific/Honolulu", "Hawaiian Standard Time" },
        { "Pacific/Pago_Pago", "UTC-11" },
        { "Pacific/Port_Moresby", "West Pacific Standard Time" },
        { "Pacific/Tongatapu", "Tonga Standard Time" }
    };

    var windowsTimeZoneId = default(string);
    var windowsTimeZone = default(TimeZoneInfo);
    if (olsonWindowsTimes.TryGetValue(olsonTimeZoneId, out windowsTimeZoneId))
    {
        try { windowsTimeZone = TimeZoneInfo.FindSystemTimeZoneById(windowsTimeZoneId); }
        catch (TimeZoneNotFoundException) { }
        catch (InvalidTimeZoneException) { }
    }
    return windowsTimeZone;
}

夏令时:

夏令时是指某个时区在春季将时间向前移动(通常为一小时),然后在秋季向后移动的时间段。
并非每个时区都会转换一小时。例如, Australia/Lord_Howe 时区仅偏移30分钟

posted @ 2020-12-16 19:32  .Neterr  阅读(4108)  评论(0编辑  收藏  举报