前言

心血来潮,这篇讲点基础的东西。

对日期和时区 timezone 不熟悉的读者,请先看这篇 Time Zone, Leap Year, Date Format, Epoch Time 时区, 闰年, 日期格式

.NET 中的日期与时间类型

DateTime

DateTime 是用来管理 date 和 time 的对象。它是一个比较早期的机制,目前已经逐渐被淘汰了。

取而代之的是 DateTimeOffset、DateOnly 和 TimeOnly。但我们考古一下也是不错的。

DateTime 最大的缺陷是它对 offset 的表达力很差。offset 就是 +08:00 这些(注:offset != timezone)

var datetime = new DateTime(1975, 1, 1, 8, 0, 0, DateTimeKind.Utc);

这是 1975 年 1 月 1 号 上午 8 点。UTC 时区,也就是英国 +00:00。由于是 +00:00 所以表达起来是 ok 的。

但是如果要表达不是 UTC 的话就...它就不太行了。

var datetime = new DateTime(2023, 1, 1, 8, 0, 0, DateTimeKind.Local);

这句表达的是 2023 年 1 月 1 号 上午 8 点。Local 的意思是依据当前 Server OS 选择的 timezone 来设置 offset。我的 Server 在 Malaysia offset 是 +08.00。

我们把它转成 UTC 显示看看

var datetime = new DateTime(2023, 1, 1, 8, 0, 0, DateTimeKind.Local);
var utcDatetime = datetime.ToUniversalTime();
Console.WriteLine(datetime.ToString("yyyy-MM-dd hh:mm:ss tt K")); // 2023-01-01 08:00:00 AM +08:00
Console.WriteLine(utcDatetime.ToString("yyyy-MM-dd hh:mm:ss tt K")); // 2023-01-01 12:00:00 AM Z

2023 年 1 月 1 号 凌晨 12 点钟,正确

虽然看上去还不错,但是它已经有 2 个大问题了。

1. 它无法任意的选择 timezone,因为它只让你选 Local,除非你去修改 Local timezone。

2. offset != timezone,在不同的时间上,它会有意想不到的结果。

我们把时间换成 1975 年,然后转成 UTC 来显示

var datetime = new DateTime(1975, 1, 1, 8, 0, 0, DateTimeKind.Local);
var utcDatetime = datetime.ToUniversalTime();
Console.WriteLine(datetime.ToString("yyyy-MM-dd hh:mm:ss tt K")); // 1975-01-01 08:00:00 AM +08:00
Console.WriteLine(utcDatetime.ToString("yyyy-MM-dd hh:mm:ss tt K")); // 1975-01-01 12:00:00 AM Z

结果是 1975 年 1 月 1 号 凌晨 12 点钟。如果不是马来西亚人,会以为这个答案是正确的,但其实它是错误的。

我们用 JavaScript 来验证。

console.log(new Date(2023, 0, 1, 8)); // 2023-01-01T00:00:00.000Z

console.log(new Date(1975, 0, 1, 8)); // 1975-01-01T00:30:00.000Z

注意看,正确答案是 1975 年 1 月 1 号 凌晨 12 点 30 分。这个 30 分钟是因为马来西亚曾经换过 timezone offset。以前是 +07:30 后来才改成 +08:00。

而 kind = Local 只是单纯的使用了当前 Server OS 此时此刻的 offset 而非 date time value 那个时候的 timezone offset,所以表达的时间就出错了。

所以结论是,如果你要表达精准时间,就用 UTC,如果不想用 UTC,那就不要用 DateTime,改用 DateTimeOffset。

DateTimeOffset

顾名思义,它就是多了一个 offset 设置的 DateTime。所以我们不在设置 kind = UTC 或 Local,我们直接告诉它具体的 offset 是多少。

var datetime = new DateTimeOffset(2023, 1, 1, 8, 0, 0, offset: TimeSpan.FromHours(8));
var utcDatetime = datetime.ToUniversalTime();
Console.WriteLine(datetime.ToString("yyyy-MM-dd hh:mm:ss tt K")); // 2023-01-01 08:00:00 AM +08:00
Console.WriteLine(utcDatetime.ToString("yyyy-MM-dd hh:mm:ss tt K")); // 2023-01-01 12:00:00 AM +00:00

我们可以放任何的 offset,而不像 DateTime kind = Local 那样,只能设置 Server OS 的 timezone offset。

虽然它已经比 DateTime 好多了,但是,它依据没有解决 offset != timezone 的问题哦。

1975 年依然是错误

var datetime = new DateTimeOffset(1975, 1, 1, 8, 0, 0, offset: TimeSpan.FromHours(8));
var utcDatetime = datetime.ToUniversalTime();
Console.WriteLine(datetime.ToString("yyyy-MM-dd hh:mm:ss tt K")); // 1975-01-01 08:00:00 AM +08:00
Console.WriteLine(utcDatetime.ToString("yyyy-MM-dd hh:mm:ss tt K")); // 1975-01-01 12:00:00 AM +00:00

但这个很好理解,因为我们设置的是 offset,而不是 timezone,依据 datetime 和 timezone 找出 offset 是我们的责任。

所以 1975 年,马来西亚的 timezone 我应该要设置的 offset 是 +07:30 才对。(注:ASP.NET Core 没有 build-in 的方法提我们做这个 timezone to offset 的转换,但是我们可以用 library NodaTime,下面我会提到)

DateOnly

DateTime 的问题是 offset,UTC 不需要 offset 所以可以用 DateTime,还有一种情况也是不需要 offset,那就是表达日期。

比如 1987 年 12 月 15 日是我的生辰,我就只表达日期,不表达具体时间和时区,这种情况 DateTime 也够用。

虽然如此,DateTime 还是不够好,因为我只是要表达日期,没有要表达时间丫。0 时 0 分 0 秒 表达的是 0 而不是 null。

于是为了更精确的表达,.NET 6.0 推出了 DateOnly 类型。它就是没有 Time 的 DateTime,仅此而已。

TimeOnly

有 DateOnly 自然就有 TimeOnly 咯。不管是 DateOnly 还是 TimeOnly,都只是缩小了原本 DateTime 的管理范围而已。

所以掌握了 DateTimeOffset 就掌握了 DateTime 就掌握了 DateOnly 和 TimeOnly。

NodaTime

NodaTime 不是 build-in 的类型,它是一个 library。上面提到最厉害的 DateTimeOffset 都无法处理 timezone,它只是处理 offset 而已。

当需要处理 timezone 时,需要引入这个 library。下面会给出例子。

常用方法

此时此刻

var utcNow = DateTimeOffset.UtcNow; // offset +00:00
var localNow = DateTimeOffset.Now; // offset +08:00 依据 Server OS 的 timezone offset

都是输出此时此刻,只是一个是用 UTC +00:00 表达,一个是依据 Server OS timezone offset 来表达。

今天

var today1 = DateTime.Today;
var today2 = DateTimeOffset.Now.Date;

2 个方法都可以,时间为 0 时 0 分 0 秒

Since 1970-01-01 (unix/epoch time)

相等于 JavaScript 的 .getTime()

var unitTimeMilliseconds = DateTimeOffset.Now.ToUnixTimeMilliseconds;
var unitTimeSeconds = DateTimeOffset.Now.ToUnixTimeSeconds;

秒或毫秒都可以。

DateTimeOffset to String

DateTimeOffset.Now.ToString("yyyy-MM-dd hh:mm:ss tt K"); // 2023-10-29 05:29:07 PM +08:00
DateTimeOffset.Now.ToString("yyyy-MM-dd"); // 2023-10-29
DateTimeOffset.Now.ToString("hh:mm:ss tt"); // 05:29:07 PM

代号可以参考这里这里

String to DateTimeOffset

var datetimeOffset = DateTimeOffset.ParseExact(
"2023-10-29 05:29:07 PM +08:00", "yyyy-MM-dd hh:mm:ss tt K", CultureInfo.InvariantCulture
); if (
DateTimeOffset.TryParseExact(
"2023-10-29 05:29:07 PM +08:00", "yyyy-MM-dd hh:mm:ss tt K", CultureInfo.InvariantCulture,
DateTimeStyles.None, out var datetime
)
)
{
// do something with datetime
}

DateTimeOffset to DateOnly and TimeOnly

var dateTimeOffset = new DateTimeOffset(1975, 1, 1, 8, 0, 0, TimeSpan.FromHours(8));
var date = DateOnly.FromDateTime(dateTimeOffset.Date); // 1975-01-01
var time = TimeOnly.FromDateTime(dateTimeOffset.DateTime); // 08:00

注意:它是不看 offset 的哦,直接 ignore。

Server OS Timezone Informations

foreach (TimeZoneInfo timeZoneInfo in TimeZoneInfo.GetSystemTimeZones())
{
Console.WriteLine($"{timeZoneInfo.Id} : {timeZoneInfo.DisplayName} : {timeZoneInfo.BaseUtcOffset.Hours}");
// Hawaiian Standard Time : (UTC-10:00) Hawaii : -10
// GMT Standard Time : (UTC+00:00) Dublin, Edinburgh, Lisbon, London : 0
// Singapore Standard Time : (UTC+08:00) Kuala Lumpur, Singapore : 8
}

通过 OS timezone 可以找到不同 timezone 的 offset,这个 offset 是最新的,像马来西亚曾经换过 timezone offset 从 +07:30 换成 +08:00,这里只会显示 +08:00。

NodaTime 常用方法

创建指定 timezone 的 DatetimeOffset

1. 创建 timezone

var timezone = DateTimeZoneProviders.Tzdb["Asia/Kuala_Lumpur"];

注: Noda timezone 和 .NET timezone 的 ID 虽然 ISO 一样但写法却不一样,参考 Docs – IANA (TZDB) time zone information

2. 定义日期时间

var datetime = new LocalDateTime(1975, 1, 1, 8, 0, 0);
// 或者 from .NET DateTime
// var datetime = LocalDateTime.FromDateTime(new DateTime(1975, 1, 1, 8, 0, 0));

我们用回上面 1975 年的例子。

3. set timezone to datetime

var datetimeWithZone = datetime.InZoneStrictly(timezone);

5. convert Noda DateTime to .NET DateTimeOffset

var datetimeoffset = datetimeWithZone.ToDateTimeOffset();
Console.WriteLine(datetimeoffset.ToString("yyyy-MM-dd hh:mm:ss tt K")); // 1975-01-01 08:00:00 AM +07:30
Console.WriteLine(datetimeoffset.UtcDateTime.ToString("yyyy-MM-dd hh:mm:ss tt K")); // 1975-01-01 12:30:00 AM Z

Noda 成功依据 datetime 和 timezone 返回了正确的 offset,1975 年 Malaysia 的 timezone 是 +07:30,现在才是 +08:00。

Change TimeZone

把马来西亚 1975 年 1 月 1 日 上午 8 点 convert to 夏威夷时区的时间。

var malaysiaTimeZone = DateTimeZoneProviders.Tzdb["Asia/Kuala_Lumpur"];

var malaysiaDateTime = new LocalDateTime(1975, 1, 1, 8, 0, 0);

var malaysiaDateTimeOffset = malaysiaDateTime.InZoneStrictly(malaysiaTimeZone).ToDateTimeOffset();

var hawaiiTimeZone = DateTimeZoneProviders.Tzdb["Pacific/Honolulu"];

Console.WriteLine(
Instant.FromDateTimeOffset(malaysiaDateTimeOffset).InZone(hawaiiTimeZone).ToDateTimeOffset().ToString("yyyy-MM-dd hh:mm:ss tt K")
);
// 1974-12-31 02:30:00 PM -10:00

关键是 NodaTime 有所以 timezone 在不同时期的 offset。

所以只要可以拿到指定 datetime 的 timezone offset,之后就只是 convert 来 convert 的问题而已。

List out all timezone ID

参考 Docs – IANA (TZDB) time zone information

foreach (var timezone in TzdbDateTimeZoneSource.Default.ZoneLocations!)
{
Console.WriteLine(timezone.ZoneId); // Pacific/Honolulu
Console.WriteLine(timezone.CountryName); // United States
Console.WriteLine(timezone.Comment); // Hawaii
}

ASP.NET Core – DateTime, DateTimeOffset, DateOnly, TimeOnly, TimeSpan, TimeZone, NodaTime 使用基础的更多相关文章

  1. ASP.NET Core on K8S深入学习(1)K8S基础知识与集群搭建

    在上一个小系列文章<ASP.NET Core on K8S学习初探>中,通过在Windows上通过Docker for Windows搭建了一个单节点的K8S环境,并初步尝试将ASP.NE ...

  2. Redis 入门与 ASP.NET Core 缓存

    目录 基础 Redis 库 连接 Redis 能用 redis 干啥 Redis 数据库存储 字符串 订阅发布 RedisValue ASP.NET Core 缓存与分布式缓存 内存中的缓存 ASP. ...

  3. ASP.NET Core中如何对不同类型的用户进行区别限流

    老板提出了一个新需求,从某某天起,免费用户每天只能查询100次,收费用户100W次. 这是一个限流问题,聪明的你也一定想到了如何去做:记录用户每一天的查询次数,然后根据当前用户的类型使用不同的数字做比 ...

  4. Windows docker k8s asp.net core

    在上一篇文章 Ubuntu 18 Kubernetes的Install and Deploy 我们在ubuntu在部署了k8s集群, 今天来看看windows下怎么搞. 主要点有: 1) window ...

  5. ASP.NET Core on K8S 入门学习系列文章目录

    一.关于这个系列 自从2018年底离开工作了3年的M公司加入X公司之后,开始了ASP.NET Core的实践,包括微服务架构与容器化等等.我们的实践是渐进的,当我们的微服务数量到了一定值时,发现运维工 ...

  6. 深入理解ASP.NET Core依赖注入

    概述        ASP.NET Core可以说是处处皆注入,本文从基础角度理解一下原生DI容器,及介绍下怎么使用并且如何替换官方提供的默认依赖注入容器. 什么是依赖注入        百度百科中对 ...

  7. ASP.NET Core 返回 Json DateTime 格式

    ASP.NET Core 返回 Json 格式的时候,如果返回数据中有DateTime类型,如何自定义其格式呢?配置如下: services.AddMvc().AddJsonOptions(opt = ...

  8. 解决asp.net core 日期格式 datetime Json返回 带T的问题

    原文:解决asp.net core 日期格式 datetime Json返回 带T的问题 记录一下: Startup中,将 services.AddMvc(); 改为: services.AddMvc ...

  9. ASP.Net Core中设置JSON中DateTime类型的格式化(解决时间返回T格式)

    最近项目有个新同事,每个API接口里返回的时间格式中都带T如:[2019-06-06T10:59:51.1860128+08:00],其实这个主要是ASP.Net Core自带时间格式列化时间格式设置 ...

  10. Asp.net Core 使用Redis存储Session

    前言 Asp.net Core 改变了之前的封闭,现在开源且开放,下面我们来用Redis存储Session来做一个简单的测试,或者叫做中间件(middleware). 对于Session来说褒贬不一, ...

随机推荐

  1. 如何做好一场NPS调研?

    我们在工作中经常遇到的一个词,那就是"产品NPS调研".当部分项目缺少专业的用研人员时,设计师.产品经理则经常会接受上级的要求,投身于NPS调研工作. 笔者也曾在2022年的某天突 ...

  2. 网易数帆开源贡献获业界肯定,轻舟API网关获OSCAR尖峰开源技术创新奖

    2020年10月16日,由中国信息通信研究院主办的"2020开源产业大会"在北京线下与线上同步召开,主办方在会上公布了"OSCAR尖峰开源奖项"各个奖项的评选结 ...

  3. 存储系列DAS,SAN,NAS常见网络架构

    随着主机.磁盘.网络等技术的发展,对于承载大量数据存储的服务器来说,服务器内置存储空间,或者说内置磁盘往往不足以满足存储需要.因此,在内置存储之外,服务器需要采用外置存储的方式扩展存储空间,今天在这里 ...

  4. NodeJS中Buffer与字符串相互转换时一个值得注意的问题

    什么问题 如果一个Buffer的 toString() 结果为乱码或含有乱码,那么用此字符串以 Buffer.from()方法构造出来的Buffer将与原来的Buffer不相同. 这一点其实很好理解, ...

  5. 关于elementUI的select组件回显问题

    最近接受了一个后台项目,需求是这样的,点击表单项,弹出的弹出层显示该表单项目的信息.但是回显的时候,关于弹出层中的级联显示有问题,如图: 回显结果为: 回显代码为: 弹框为: 我就不明白了,分明分公司 ...

  6. stream的优化:java封装的拆箱与装箱的弊端

    authors.stream() .map(author->author.getAge) .map(age->age+10)//Stream<Integer> .filter( ...

  7. 写写stream流的终结操作

    终结操作和中间操作的区别:中间操作返回的一直都是stream,所以可以一直使用,但是终结操作返回的不是stream,后面不能继续操作 foreach:对流中的所有元素进行遍历操作 count:获取当前 ...

  8. uniapp打包所需的ios证书和证书profile文件获取的图文教程

    使用uniapp进行云打包,可以打包android和ios两种app,但是uniapp官方并不能凭空产生这两种平台所需的打包证书. 那么这两种打包证书又是如何获取呢? android相对简单,使用jd ...

  9. Vue cil路由如何回到初始状态

    前景:我们在网页里进入路由的地址后,会发现地址栏中会加上我们的路由地址,这样我就知道当前在哪个位置.但是这样子我们如何手动刷新浏览器,想要浏览器回到根路径的话,是无法直接回去的,因为地址没有更改.再怎 ...

  10. Netty的源码分析和业务场景

    Netty 是一个高性能.异步事件驱动的网络应用框架,它基于 Java NIO 构建,广泛应用于互联网.大数据.游戏开发.通信行业等多个领域.以下是对 Netty 的源码分析.业务场景的详细介绍: 源 ...