基本概念
日期
形如:2021-07-01,2021-7-5 记录一个日子。
时间
形如:14:26:01,2021-07-09 14:27:30 记录一个时间点。
本地时间
平时我们看到的时间,国内一般指北京时间,同一时刻,地球上的不同地方的人看到的时间是不一样的,各个地方都有自己的本地时间,他们都是不同的。
时区
时区指使用相同时间的某个地理区域。 相邻时区通常(但不总是)相差一小时。 世界上任一时区的时间都能以与协调世界时 (UTC) 之间的时差表示。
全球一共分为24个时区,伦敦所在的时区称为标准时区,其他时区按东/西偏移的小时区分,北京所在的时区是东八区。
因为光靠本地时间还无法唯一确定一个准确的时刻,所以我们还需要给本地时间加上一个时区。时区有好几种表示方式。
一种是以GMT
或者UTC
加时区偏移表示,例如:GMT+08:00或者UTC+08:00表示东八区。
因为时区的存在,东八区的2021年11月20日早上8:15,和西五区的2021年11月19日晚上19:15,他们的时刻是相同的。
时刻相同的意思就是,分别在两个时区的两个人,如果在这一刻通电话,他们各自报出自己手表上的时间,虽然本地时间是不同的,但是这两个时间表示的时刻是相同的。
夏令时
世界上许多时区都实施夏令时。 夏令时是指,在春季或初夏将时间提前一小时并在夏末或秋季将时间调回正常(或标准)时间,从而最大化地利用光照。 这种针对标准时间的来回更改就称为调整规则。
例如,每年 10 月 25 日从夏令时转换到标准时间,这一案例就是遵循的固定调整规则。 浮动调整规则更为常见,该规则会设定在特定月特定星期的某一天进行夏令时转换。 例如,在 3 月的第三个星期日从标准时间转换到夏令时,这一案例遵循的就是浮动调整规则。
歧义时间:一个时区内可映射到两个不同时间的时间。 在向后调整时钟时间时,例如从时区的夏令时调整到标准时间这段转换期间,便会出现不明确时间。 例如,如果此转换发生在特定日期的凌晨 2:00, 导致时间变为凌晨 1:00,则每次凌晨 1:00 与凌晨 1:59:99 之间的时间间隔既可理解为标准时间,也可以是夏令时时间。
无效时间:从标准时间转换到夏令时而导致产生的不存在的时间。 在向前调整时钟时间时,例如从时区的标准时间转换到夏令时这段转换期间,便会出现无效时间。 例如,如果此转换发生在特定日期的凌晨 2:00, 导致时间变为凌晨 3:00,则每次凌晨 2:00 与凌晨 2:59:99 之间的时间间隔无效。
Ticks:表示0001 年 1 月 1 日午夜 12:00:00 以来所经历的 100 纳秒数,即Ticks的属性为100纳秒(1Ticks = 0.0001毫秒)。
Unix时间戳:是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。
换算:1秒=1000毫秒,1毫秒=1000微秒,1微秒=1000纳秒,1毫秒=10000纳秒
DateTime
表示时间上的一刻,通常以日期和时间表示。包括一个Kind属性,该属性提供有关日期和时间所属时区的有限信息。
Kind:DateTimeKind.Local
(本地),DateTimeKind.Utc
(UTC),DateTimeKind.Unspecified
(未指定)
除非特定的 DateTime
值表示 UTC
,否则日期和时间值通常不明确的或其可移植性受限。
使用场景:
- 仅使用日期。
- 只使用时间。
- 使用抽象的日期和时间。
- 使用缺少时区信息的日期和时间。
- 只使用
UTC
日期和时间。 - 从外部(sql数据库)检索的日期和时间信息。
- 执行日期和时间算法,例如加减几个月。
除非特定的 DateTime 值表示 UTC,否则日期和时间值通常不明确的或其可移植性受限。
重要:
在保存或共享 DateTime 数据时,应使用 UTC 并将 DateTime 值的 Kind 属性设置为 DateTimeKind.Utc。
推荐使用DateTimeOffset来表示日期时间。
DateTimeOffset
表示日期和时间值,以及指示此值与 UTC
的偏移量。 因此,此值始终明确地标识单个时间点。DateTimeOffset
类型包括 DateTime
类型的所有功能以及时区感知功能。
使用场景:
- 唯一、明确地标识单个时间点。例如:记录事务时间,记录系统或程序
event
时间,记录创建修改时间。 - 执行常规日期和时间算法。
- 保留多个相关时间,只要这些时间存储为两个单独的值或结构的两个成员。例如:model中的两个时间属性(
StartDateTime
,EndDateTime
)
备注
DateTimeOffset
值的使用频率比DateTime
值的更高。 因此,请考虑DateTimeOffset
将其用作应用程序开发的默认日期和时间类型。
实例化DateTimeOffset
DateTime thisDate = new DateTime(2021, 7, 10, 10, 0, 0);
//偏移量8小时
DateTimeOffset thisTimeOffset = new DateTimeOffset(thisDate, new TimeSpan(8, 0, 0));//2021/7/10 10:00:00 +08:00
DateTime sourceDate = new DateTime(2008, 5, 1, 8, 30, 0);
DateTimeOffset targetTime;
//从具有零偏移量的 UTC 时间实例化 DateTimeOffset 值。
DateTime utcTime = DateTime.SpecifyKind(sourceDate, DateTimeKind.Utc);
targetTime = new DateTimeOffset(utcTime, TimeSpan.Zero);
Console.WriteLine(targetTime);
// Displays 5/1/2008 8:30:00 AM +00:00
// Because the Kind property is DateTimeKind.Utc
//农历日期初始化DateTimeOffset,即2021年大年初一的日期
DateTimeOffset dateTimeOffset=new DateTimeOffset(2021,1,1,0,0,0,0,new ChineseLunisolarCalendar(),TimeSpan.FromHours(8));
//display:2021/2/12 0:00:00
Console.WriteLine(dateTimeOffset.LocalDateTime);
//display:2021/2/12 0:00:00 +08:00
Console.WriteLine(dateTimeOffset);
TimeSpan
表示时间间隔
使用场景:
- 反映两个日期和时间值之间的时间间隔。 例如,两个
DateTime
值相减将返回TimeSpan
值。 - 测量运行时间。 例如,
Stopwatch.Elapsed
属性返回一个TimeSpan
值,该值反映自调用Stopwatch
开始测量运行时间的其中一个方法以来经过的时间间隔。 - 表示一个不引用日期的时间点,例如商店每天的开门时间
new TimeSpan(8, 0, 0);
TimeZoneInfo
TimeZoneInfo
类代表地球上的任何时区,并允许将一个时区中的任何日期和时间转换为另一个时区中的等效日期和时间。借助 TimeZoneInfo
类,即可处理日期和时间,以使任何日期和时间值均明确标识单个时间点。TimeZoneInfo
类提供两个预定义的时区对象,分别表示 UTC
时间和本地
时区。 它们分别可从 Utc
和 Local
属性获得。ConvertTime(DateTime, TimeZoneInfo)
将时间转换为特定时区的时间。ConvertTime(DateTimeOffset, TimeZoneInfo)
将时间转换为特定时区的时间。ConvertTime(DateTime, TimeZoneInfo, TimeZoneInfo)
将时间从一个时区转换到另一个时区。
// 创建一个标准东部时间EST和一个TimeZoneInfo对象
DateTime estTime = new DateTime(2021, 1, 1, 00, 00, 00);
string timeZoneName = "Eastern Standard Time";
try
{
TimeZoneInfo est = TimeZoneInfo.FindSystemTimeZoneById(timeZoneName);
// 转换 EST 到 本地时间
DateTime localTime = TimeZoneInfo.ConvertTime(estTime, est, TimeZoneInfo.Local);
//At 2021/1/1 0:00:00 (UTC-05:00) 东部时间(美国和加拿大), the local time is 2021/1/1 13:00:00 中国标准时间.
Console.WriteLine("At {0} {1}, the local time is {2} {3}.",
estTime,
est,
localTime,
//判断是否在夏令时区间
TimeZoneInfo.Local.IsDaylightSavingTime(localTime) ?
TimeZoneInfo.Local.DaylightName :
TimeZoneInfo.Local.StandardName);
//转换 EST to UTC
DateTime utcTime = TimeZoneInfo.ConvertTime(estTime, est, TimeZoneInfo.Utc);
//At 2021/1/1 0:00:00 (UTC-05:00) 东部时间(美国和加拿大), the time is 2021/1/1 5:00:00 Coordinated Universal Time.
Console.WriteLine("At {0} {1}, the time is {2} {3}.",
estTime,
est,
utcTime,
TimeZoneInfo.Utc.StandardName);
}
catch (TimeZoneNotFoundException)
{
Console.WriteLine("The {0} zone cannot be found in the registry.",
timeZoneName);
}
catch (InvalidTimeZoneException)
{
Console.WriteLine("The registry contains invalid data for the {0} zone.",
timeZoneName);
}
实例化TimeZoneInfo
TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
查找本地系统上定义的时区
ReadOnlyCollection<TimeZoneInfo> tzCollection = TimeZoneInfo.GetSystemTimeZones();
foreach (TimeZoneInfo timeZone in tzCollection)
{
Console.WriteLine(" {0}: {1}", timeZone.Id, timeZone.DisplayName);
}
// Dateline Standard Time: (UTC-12:00) 国际日期变更线西
// UTC-11: (UTC-11:00) 协调世界时-11
// Hawaiian Standard Time: (UTC-10:00) 夏威夷
// Aleutian Standard Time: (UTC-10:00) 阿留申群岛
// Marquesas Standard Time: (UTC-09:30) 马克萨斯群岛
// UTC-09: (UTC-09:00) 协调世界时-09
// Alaskan Standard Time: (UTC-09:00) 阿拉斯加
.........
时区时间算术运算
将时区中的时间转换为 UTC、执行算术运算,然后从 UTC 转换回时区中的时间来应用调整规则
const string tzName = "Central Standard Time";
DateTime generalTime = new DateTime(2008, 3, 9, 1, 30, 0);
TimeZoneInfo cst = TimeZoneInfo.FindSystemTimeZoneById(tzName);
TimeSpan twoAndAHalfHours = new TimeSpan(2, 30, 0);
// 实例化 DateTimeOffset 值以获得正确的 CST 偏移量
try
{
DateTimeOffset centralTime1 = new DateTimeOffset(generalTime,
cst.GetUtcOffset(generalTime));
// Add two and a half hours
DateTimeOffset utcTime = centralTime1.ToUniversalTime();
utcTime += twoAndAHalfHours;
DateTimeOffset centralTime2 = TimeZoneInfo.ConvertTime(utcTime, cst);
// Display result
Console.WriteLine("{0} + {1} hours = {2}", centralTime1,
twoAndAHalfHours.ToString(),
centralTime2);
}
catch (TimeZoneNotFoundException)
{
Console.WriteLine("Unable to retrieve Central Standard Time zone information.");
}
DateTime 与 DateTimeOffset 之间进行转换
以下代码将 UTC 时间转换为其等效的 DateTimeOffset 值。
DateTime utcTime1 = new DateTime(2008, 6, 19, 7, 0, 0);
utcTime1 = DateTime.SpecifyKind(utcTime1, DateTimeKind.Utc);
DateTimeOffset utcTime2 = utcTime1;
Console.WriteLine("Converted {0} {1} to a DateTimeOffset value of {2}",
utcTime1,
utcTime1.Kind,
utcTime2);
// This example displays the following output to the console:
// Converted 6/19/2008 7:00:00 AM Utc to a DateTimeOffset value of 6/19/2008 7:00:00 AM +00:00
下面的示例实例化 DateTimeOffset 反映中部标准时间的对象。
DateTime time1 = new DateTime(2008, 6, 19, 7, 0, 0); // Kind is DateTimeKind.Unspecified
try
{
DateTimeOffset time2 = new DateTimeOffset(time1,
TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time").GetUtcOffset(time1));
Console.WriteLine("Converted {0} {1} to a DateTime value of {2}",
time1,
time1.Kind,
time2);
}
// Handle exception if time zone is not defined in registry
catch (TimeZoneNotFoundException)
{
Console.WriteLine("Unable to identify target time zone for conversion.");
}
// This example displays the following output to the console:
// Converted 6/19/2008 7:00:00 AM Unspecified to a DateTime value of 6/19/2008 7:00:00 AM -05:00
DateTimeOffset
转DateTime
DateTimeOffset
的DateTime
属性通常用于执行 DateTimeOffset
DateTime
转换。它返回的 DateTime
值的 Kind
属性为 Unspecified
所以,若要在将转换为值时保留尽可能多的时区信息 DateTimeOffset
to DateTime
,可以使用 DateTimeOffset.UtcDateTime
和 DateTimeOffset.LocalDateTime
属性。
转换 UTC 时间
DateTimeOffset originalTime = new DateTimeOffset(2008, 6, 19, 7, 0, 0, new TimeSpan(5, 0, 0));
DateTime utcTime = originalTime.UtcDateTime;
Console.WriteLine("{0} converted to {1} {2}",
originalTime,
utcTime,
utcTime.Kind);
// The example displays the following output to the console:
// 6/19/2008 7:00:00 AM +05:00 converted to 6/19/2008 2:00:00 AM Utc
转换本地时间
DateTime sourceDate = new DateTime(2008, 6, 19, 7, 0, 0);
DateTimeOffset localTime1 = new DateTimeOffset(sourceDate,
TimeZoneInfo.Local.GetUtcOffset(sourceDate));
DateTime localTime2 = localTime1.LocalDateTime;
Console.WriteLine("{0} converted to {1} {2}",
localTime1,
localTime2,
localTime2.Kind);
// The example displays the following output to the console:
// 6/19/2008 7:00:00 AM -07:00 converted to 6/19/2008 7:00:00 AM Local
通用转换方法
该方法将 DateTimeOffset 值转换为 DateTime 值。 它基于其偏移量来确定 DateTimeOffset 值是 UTC 时间、本地时间还是其他时间,并相应地定义返回的日期和时间值的 Kind 属性。
static DateTime ConvertFromDateTimeOffset(DateTimeOffset dateTime)
{
if (dateTime.Offset.Equals(TimeSpan.Zero))
return dateTime.UtcDateTime;
else if (dateTime.Offset.Equals(TimeZoneInfo.Local.GetUtcOffset(dateTime.DateTime)))
return DateTime.SpecifyKind(dateTime.DateTime, DateTimeKind.Local);
else
return dateTime.DateTime;
}
在各时区之间转换时间
以下代码可将当前本地时间转换为 UTC,并将结果显示在控制台上
DateTime dateNow = DateTime.Now;
Console.WriteLine("The date and time are {0} UTC.",
TimeZoneInfo.ConvertTimeToUtc(dateNow));
下面的代码使用 TimeZoneInfo.ConvertTimeToUtc
方法将东部标准时间转换为 UTC。
DateTime easternTime = new DateTime(2007, 01, 02, 12, 16, 00);
string easternZoneId = "Eastern Standard Time";
try
{
TimeZoneInfo easternZone = TimeZoneInfo.FindSystemTimeZoneById(easternZoneId);
Console.WriteLine("The date and time are {0} UTC.",
TimeZoneInfo.ConvertTimeToUtc(easternTime, easternZone));
}
catch (TimeZoneNotFoundException)
{
Console.WriteLine("Unable to find the {0} zone in the registry.",
easternZoneId);
}
catch (InvalidTimeZoneException)
{
Console.WriteLine("Registry data on the {0} zone has been corrupted.",
easternZoneId);
}
以上方法参数都是 DateTime
DateTimeOffset
结构具有 ToUniversalTime
实例方法,该方法可将当前实例的日期和时间转换为 UTC
将 UTC 转换为指定的时区
若要将 UTC
转换为任何指定时区中的时间,请调用 ConvertTimeFromUtc
方法。 该方法采用以下两种参数:
- 要转换的
UTC
。 这必须是DateTime
其Kind
属性设置为Unspecified
或Utc
。 -
UTC
要转换的目标时区。
DateTime timeUtc = DateTime.UtcNow;
try
{
TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
DateTime cstTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone);
Console.WriteLine("The date and time are {0} {1}.",
cstTime,
cstZone.IsDaylightSavingTime(cstTime) ?
cstZone.DaylightName : cstZone.StandardName);
}
catch (TimeZoneNotFoundException)
{
Console.WriteLine("The registry does not define the Central Standard Time zone.");
}
catch (InvalidTimeZoneException)
{
Console.WriteLine("Registry data on the Central Standard Time zone has been corrupted.");
}
将 UTC 转换为本地时间
若要将 UTC
转换为本地时间,请调用 ToLocalTime
。
下面实例UTC时间转为本地东八区时间,+8小时。
DateTime thisDate = new DateTime(2021, 7, 21, 18, 57, 41, 0);
Console.WriteLine(thisDate);
//UTC时间2021-7-21 18:57:41
DateTimeOffset dto = new DateTimeOffset(thisDate, TimeSpan.Zero);
Console.WriteLine(TimeZoneInfo.Local);
//当前时区东八
DateTime destDate = dto.LocalDateTime;
Console.WriteLine(destDate);
// 2021/7/21 18:57:41
// (UTC+08:00) 北京,重庆,香港特别行政区,乌鲁木齐
// 2021/7/22 2:57:41
在各时区之间转换时间
ToOffset
方法
using System;
public class DateTimeOffsetConversion
{
private static DateTimeOffset sourceTime;
public static void Main()
{
DateTimeOffset targetTime;
sourceTime = new DateTimeOffset(2007, 9, 1, 9, 30, 0,
new TimeSpan(-5, 0, 0));
// Convert to same time (return sourceTime unchanged)
targetTime = sourceTime.ToOffset(new TimeSpan(-5, 0, 0));
ShowDateAndTimeInfo(targetTime);
// Convert to UTC (0 offset)
targetTime = sourceTime.ToOffset(TimeSpan.Zero);
ShowDateAndTimeInfo(targetTime);
// Convert to 8 hours behind UTC
targetTime = sourceTime.ToOffset(new TimeSpan(-8, 0, 0));
ShowDateAndTimeInfo(targetTime);
// Convert to 3 hours ahead of UTC
targetTime = sourceTime.ToOffset(new TimeSpan(3, 0, 0));
ShowDateAndTimeInfo(targetTime);
}
private static void ShowDateAndTimeInfo(DateTimeOffset newTime)
{
Console.WriteLine("{0} converts to {1}", sourceTime, newTime);
Console.WriteLine("{0} and {1} are equal: {2}",
sourceTime, newTime, sourceTime.Equals(newTime));
Console.WriteLine("{0} and {1} are identical: {2}",
sourceTime, newTime,
sourceTime.EqualsExact(newTime));
Console.WriteLine();
}
}
//
// The example displays the following output:
// 9/1/2007 9:30:00 AM -05:00 converts to 9/1/2007 9:30:00 AM -05:00
// 9/1/2007 9:30:00 AM -05:00 and 9/1/2007 9:30:00 AM -05:00 are equal: True
// 9/1/2007 9:30:00 AM -05:00 and 9/1/2007 9:30:00 AM -05:00 are identical: True
//
// 9/1/2007 9:30:00 AM -05:00 converts to 9/1/2007 2:30:00 PM +00:00
// 9/1/2007 9:30:00 AM -05:00 and 9/1/2007 2:30:00 PM +00:00 are equal: True
// 9/1/2007 9:30:00 AM -05:00 and 9/1/2007 2:30:00 PM +00:00 are identical: False
//
// 9/1/2007 9:30:00 AM -05:00 converts to 9/1/2007 6:30:00 AM -08:00
// 9/1/2007 9:30:00 AM -05:00 and 9/1/2007 6:30:00 AM -08:00 are equal: True
// 9/1/2007 9:30:00 AM -05:00 and 9/1/2007 6:30:00 AM -08:00 are identical: False
//
// 9/1/2007 9:30:00 AM -05:00 converts to 9/1/2007 5:30:00 PM +03:00
// 9/1/2007 9:30:00 AM -05:00 and 9/1/2007 5:30:00 PM +03:00 are equal: True
// 9/1/2007 9:30:00 AM -05:00 and 9/1/2007 5:30:00 PM +03:00 are identical: False
数据库中存储日期和时间
一般我们会在数据库存储一个long
类型(数据库类型BIGINT
)的时间戳,有了这个时间戳,也就是时刻信息,我们就可以在前端转换为不同时区
来显示这个时间。
public static void Main(string[] args)
{
DateTime thisDate= new DateTime(2021, 7, 21, 18, 57, 41, 0);
Console.WriteLine(thisDate);
long utcTimestamp= GetUtcTimestamp(thisDate);
Console.WriteLine(utcTimestamp);
DateTime destDate= GetLocalDateTime(utcTimestamp,TimeZoneInfo.Local.BaseUtcOffset);
Console.WriteLine(destDate);
}
public static long GetUtcTimestamp(DateTime thisDate)
{
//DateTime thisDate = new DateTime(2021, 7, 21, 18, 57, 41, 0);
DateTimeOffset dto = new DateTimeOffset(thisDate, TimeZoneInfo.Local.BaseUtcOffset);
//自 1970-01-01T00:00:00Z 以来经过的秒数。可以存储到数据库
long elapsedSeconds = dto.ToUnixTimeSeconds();//1626865061
return elapsedSeconds;
}
public static DateTime GetLocalDateTime(long elapsedSeconds,TimeSpan offset)
{
//根据elapsedSeconds,转换为UTC DateTimeOffset
var utcDto = DateTimeOffset.FromUnixTimeSeconds(elapsedSeconds);
var destDto= utcDto.ToOffset(offset);
return destDto.DateTime;
}
//2021/7/21 18:57:41
//1626865061
//2021/7/21 18:57:41