Menu

C# DateTime 格式化:标准与自定义格式字符串、区域性、ISO 8601

如何在 C# 中把 DateTime 格式化为字符串:标准格式字符串(d、D、o、s、u、t)、yyyy-MM-dd HH:mm:ss 这样的自定义模式、完整的说明符表、区域性如何改变输出、用 "o" 实现 ISO 8601 往返,以及 mm 与 MM、hh 与 HH 的常见混淆。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

DateTime.ToString(format) 把日期转换成文本。格式可以是标准格式字符串(d 或 o 这样的单个字母,具体模式由区域性决定),也可以是由 yyyy、MM、HH 等说明符组成的自定义模式。同样的字符串也可以用在字符串内插 $"{date:yyyy-MM-dd}" 和 string.Format 中。

输出:

2026-10-09
2026-10-09 18:30:07
09/10/2026
Oct 9, 2026
Friday, October 9
6:30 PM
18:30:07.045
Shipped 2026-10-09 at 18:30

本页的示例都传入了 CultureInfo.InvariantCulture,所以在每台机器上输出都一样;不传的话,ToString 会使用当前区域性,月份名称、分隔符甚至日历都可能随之改变。

自定义格式说明符

自定义格式是一种模式,其中某些字母代表日期的各个部分。它们区分大小写:M 是月份,m 是分钟;H 是 24 小时制的小时,h 是 12 小时制的小时。

说明符含义2026-10-09 18:30:07.045 的示例
yyyy年份,4 位2026
yy年份,2 位26
MMMM月份全称October
MMM月份缩写Oct
MM月份,2 位10
M月份,1 或 2 位10
dddd星期全称Friday
ddd星期缩写Fri
dd日,2 位09
d日,1 或 2 位(在模式中)9
HH小时,00 至 2318
H小时,0 至 2318
hh小时,01 至 1206
h小时,1 至 126
mm分钟,2 位30
ss秒,2 位07
fff毫秒045
fffffffTick(七位小数)0450000
ttAM/PM 标识符PM
zzz相对 UTC 的偏移量(DateTimeOffset 或本地时间)+02:00
K时区信息:Z、偏移量或空Z(UTC 值)
/区域性的日期分隔符/ 或 . 或 -
:区域性的时间分隔符:
'text' 或 \c字面文本或字符'at'

其他字符都按原样复制,包括不是说明符的字母。这就是 "YYYY-DD" 会悄无声息地打印出 YYYY-DD 的原因:.NET 中没有大写的 Y 或 D 说明符。

单独一个字母会被当作标准格式,而不是自定义格式:ToString("d") 是短日期模式,而不是日期中的“日”。如果只想取日,写 ToString("%d"),或者使用 Day 属性。

输出:

10/09/2026
9
20261009_183007
Week of Oct 9
18h30
2026-10-09T18:30:07
YYYY-DD

标准格式字符串

标准格式是单个字母,实际模式由区域性定义。它们最适合显示给用户的文本,因为每个区域性都会自动得到自己的习惯写法。

输出:

d  10/09/2026
D  Friday, 09 October 2026
f  Friday, 09 October 2026 18:30
g  10/09/2026 18:30
G  10/09/2026 18:30:07
M  October 09
Y  2026 October
t  18:30
T  18:30:07
o  2026-10-09T18:30:07.0000000Z
s  2026-10-09T18:30:07
u  2026-10-09 18:30:07Z
r  Fri, 09 Oct 2026 18:30:07 GMT
en-US d: 10/9/2026, D: Friday, October 9, 2026
代码名称依赖区域性?
d / D短日期 / 长日期是
t / T短时间 / 长时间是
f / F长日期加短时间 / 长时间是
g / G短日期加短时间 / 长时间是
M月和日是
Y年和月是
o往返 ISO 8601,7 位小数,带时区否
s可排序 ISO 8601,无小数,无时区否
u通用可排序,以 Z 结尾否
rRFC 1123,用于 HTTP 头否

o、s、u 和 r 在每个区域性中都生成相同的文本,因此可以放心用于日志、文件名和数据交换。注意,对于 DateTime,u 和 r 不会把值转换为 UTC;它们按原样打印,再标上 UTC,所以如果值是本地时间,请先调用 ToUniversalTime()(或使用 DateTime.UtcNow)。(对于 DateTimeOffset,它们会进行转换。)

区域性会改变输出

标准格式、月份和星期名称,以及 / 和 : 分隔符,全都来自区域性。把区域性作为第二个参数传给 ToString:

输出:

en-US  10/9/2026
en-GB  09/10/2026
de-DE  09.10.2026
fr-FR  09/10/2026
ja-JP  2026/10/09
Freitag, 9. Oktober 2026
vendredi 9 octobre 2026
09.10.2026
09/10/2026

区域性数据并不是一成不变的。Linux 和 macOS 上的 .NET 从系统安装的 ICU 库读取这些数据,从 .NET 5 起,Windows 10 及更高版本上的 .NET 也是如此(.NET Framework 使用 Windows 自己的 NLS 数据),所以同一个模式在不同版本和平台之间可能略有差别:例如,较新的 ICU 版本在美国短时间格式的 “PM” 前面放的是窄不换行空格而不是普通空格,有些版本给出的西班牙语短日期不带前导零。这也是永远不要解析为显示而格式化的文本的又一个理由。

由此得出两条规则。给人看的文本,用对方的区域性来格式化(在服务器上,用请求中的区域性,而不是服务器的),并优先使用标准格式,这样德国读者看到 09.10.2026,美国读者看到 10/9/2026。给程序读的文本,用 CultureInfo.InvariantCulture 和 yyyy-MM-dd 或 o 这样没有歧义的模式。用当前区域性写出、再在另一台机器上解析的日期,是日和月被颠倒的经典来源。

ISO 8601 与 "o" 往返格式

往返格式 "o" 会写出每一个 tick 以及时区信息,因此解析这段文本能还原出完全相同的 DateTime,连 Kind 也一样:

输出:

2026-10-09T18:30:07.0450000Z
2026-10-09T18:30:07.0450000
2026-10-09T20:30:07.0000000+02:00
True
Utc
2026-10-09T18:30:07Z
2026-10-09 20:30 +02:00

后缀 Z 表示 UTC;Kind 为 Unspecified 的 DateTime 没有后缀;本地时间或 DateTimeOffset 带上它的偏移量(+02:00)。解析时使用 DateTimeStyles.RoundtripKind,Kind 才能保留下来。JSON 序列化器、数据库和 JavaScript 的 Date 都能读取这种格式。

格式化 TimeSpan

TimeSpan 有一套自己的、更小的自定义说明符:d(天)、hh、mm、ss 和 fff。与 DateTime 不同,每个字面字符,包括 : 和 .,都必须用反斜杠转义或加上引号:

输出:

1.03:25:09.1200000
1.03:25:09.1200000
03:25:09
1.03:25
25:09.120
27:25

TimeSpan 格式中的 hh 是小时分量(0 至 23),所以 27 小时的时长会打印为 03。超过一天的总时长,要像最后一行那样用 TotalHours 计算。不加反斜杠的 elapsed.ToString("hh:mm") 会抛出 FormatException。

常见错误

  • 用 mm 表示月份。 "yyyy-mm-dd" 会在中间打印分钟:2026-30-09。月份是 MM。
  • 用了 hh 却没有 tt。 "hh:mm" 是不带 AM 或 PM 的 12 小时制,所以 06:30 有歧义。24 小时制请用 HH。
  • YYYY 和 DD。 它们在 .NET 中不是说明符,会被原样打印。请用 yyyy 和 dd。
  • 在数据用的模式里写 /。 它是区域性的日期分隔符。给它加引号,或使用固定区域性。
  • 在日志或文件中使用默认的 ToString()。 格式取决于机器。请传入格式和区域性。
  • 本地时间被标成了 UTC。 "u"、"r" 和手写的 'Z' 都不会做转换;请先转换。
  • 解析用的模式和写入时的不一样。 把数据用的格式字符串放在一个常量里,ToString 和 ParseExact 都用它。

常见问题

C# 中如何把 DateTime 格式化为 yyyy-MM-dd?

date.ToString("yyyy-MM-dd") 得到 2026-10-09。在内插字符串中写 $"{date:yyyy-MM-dd}"。要加上 24 小时制的时间,再加 " HH:mm:ss"。如果文本是写给文件或其他程序的,把 CultureInfo.InvariantCulture 作为第二个参数传入,这样日历、月份名称和分隔符就永远不受机器影响。

C# 日期格式中 mm 和 MM 有什么区别?

MM 是月份(01 至 12),mm 是分钟(00 至 59)。格式区分大小写,所以 yyyy-mm-dd 会在月份的位置打印分钟,只看一个值时很容易漏掉这个 bug。同理,HH 是 24 小时制的小时,hh 是 12 小时制的小时。

C# 中如何把 DateTime 格式化为 ISO 8601?

使用往返格式 "o":DateTime.UtcNow.ToString("o") 得到 2026-10-09T18:30:07.0450000Z,带七位小数,UTC 时间以 Z 结尾,本地时间则带上偏移量。"s" 给出更短的可排序形式,不带小数和时区,"yyyy-MM-ddTHH:mm:ssZ" 是常见的自定义写法。

C# 日期格式中如何显示 AM 和 PM?

用 hh 或 h 表示 12 小时制,用 tt 表示 AM/PM 标识符:date.ToString("h:mm tt", CultureInfo.GetCultureInfo("en-US")) 得到 6:30 PM。标识符来自区域性,有些区域性根本没有它,所以需要 AM 和 PM 时,请显式传入区域性。

为什么我的日期格式显示的是点而不是斜杠?

在格式字符串中,/ 不是字面意义上的斜杠,而是区域性的日期分隔符,: 则是区域性的时间分隔符。在德语环境的机器上,dd/MM/yyyy 打印 09.10.2026。给这个字符加上引号即可强制输出斜杠:dd'/'MM'/'yyyy,或者用 CultureInfo.InvariantCulture 来格式化。

Coddy 编程语言插图

和 Coddy 一起学编程

开始