1. 入门
如果你觉得时区就是“加减几个小时”这么简单,那你很快就会体会到全世界大多数程序员的痛苦。时区、夏令时/冬令时的切换、试图“把时间转到另一个地区”时突然冒出来的异常——这些都能带来很难查、也很难跟用户解释的bug。
最可怕的是:世界上根本没有“通用”的时间概念——比如,两个邻国对夏令时的态度可能完全不同,有时候甚至一年中途才决定要不要切换(真的有这种事!)。举个例子:2011年萨摩亚直接“跳过”了一整天,换到了国际日期变更线的另一边。
但C#和.NET给了我们强大的工具,至少能让我们不那么崩溃。今天我们要认识我们的救星——TimeZoneInfo类型,以及它的那些能帮你和你的应用搞定时区的功能。
你什么时候需要处理时区
- 当你的用户/服务器分布在世界不同地区
- 当你把日期和时间以UTC存进数据库,但要按用户本地时间显示
- 做日程、提醒、在线活动(“网络研讨会将在柏林时间19:00开始——你那边几点?”)
- 开发企业级/全球系统(比如国际业务的财务或CRM)
TimeZoneInfo类型概览
TimeZoneInfo是.NET里专门用来表示时区信息的类:它能告诉你和UTC的偏移、名字、是否支持夏令时切换等等。
跟老掉牙的TimeZone类只知道“本地”和“UTC”时区不同,TimeZoneInfo能让你访问系统里注册的所有时区(甚至还能自己手动造一个)。
2. 获取时区信息
本地和UTC时区
获取本地时区信息超级简单:
// 本地(也就是你程序现在运行的地方)
TimeZoneInfo local = TimeZoneInfo.Local;
// 永远是UTC
TimeZoneInfo utc = TimeZoneInfo.Utc;
Console.WriteLine(local.DisplayName); // 比如: (UTC+01:00) Berlin, Vienna
Console.WriteLine(utc.DisplayName); // (UTC) Coordinated Universal Time
所有可用时区
经常需要给用户展示所有可选时区的列表(比如注册服务时):
foreach (var tz in TimeZoneInfo.GetSystemTimeZones())
{
Console.WriteLine($"{tz.Id} | {tz.DisplayName}");
}
输出会像这样:
- Central European Standard Time | (UTC+01:00) Berlin, Vienna
- Pacific Standard Time | (UTC-08:00) Pacific Time (US & Canada)
- 等等。
可视化:TimeZoneInfo的主要属性
| 属性 | 说明 |
|---|---|
|
时区的系统标识符(查找时用) |
|
给用户看的描述(带时差和城市) |
|
“标准时间”的名字 |
|
夏令时的名字 |
|
和UTC的偏移(比如柏林是+01:00) |
|
True表示这个时区支持夏令时/冬令时切换 |
3. 时区之间的时间转换
这才是我们折腾这些的原因!你可能需要:
- 把时间从一个时区转到另一个(比如服务器存的是UTC,显示给用户要用他本地时区)
- 正确处理夏令时/冬令时切换
基本语法
DateTime utcNow = DateTime.UtcNow;
// 比如,把这个时间换算成中欧时区
TimeZoneInfo europeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time");
DateTime europeTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, europeZone);
Console.WriteLine($"UTC now: {utcNow}"); // 2024-06-20 10:30:00
Console.WriteLine($"Europe now: {europeTime}"); // 2024-06-20 11:30:00
注意:内部计算和存储时一定要用UTC,只有“输出”给用户时才转成对应时区!
怎么知道时区的标识符?
Windows下时区的Id是特定的(比如"Central European Standard Time"),Linux/Unix下一般用IANA/Olson标识符("Europe/Berlin","America/New_York")。
你可以用TimeZoneInfo.GetSystemTimeZones()拿到你系统上的所有标识符。
4. 任意时区之间的转换
假设你拿到伦敦时间(GMT/UTC+0),想把它转成东京时间(UTC+9):
DateTime londonTime = new DateTime(2024, 6, 20, 12, 0, 0, DateTimeKind.Unspecified);
TimeZoneInfo londonZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
TimeZoneInfo tokyoZone = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
// 第一步:把时间转成UTC(如果它是本地时间)
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(londonTime, londonZone);
// 第二步:把UTC转成东京时间
DateTime tokyoTime = TimeZoneInfo.ConvertTimeFromUtc(utc, tokyoZone);
Console.WriteLine($"London: {londonTime} | UTC: {utc} | Tokyo: {tokyoTime}");
处理夏令时和冬令时切换
TimeZoneInfo会自动考虑夏令时/冬令时切换——只要目标时区支持。
TimeZoneInfo eastern = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
DateTime dateWinter = new DateTime(2024, 1, 1, 12, 0, 0, DateTimeKind.Unspecified);
DateTime dateSummer = new DateTime(2024, 7, 1, 12, 0, 0, DateTimeKind.Unspecified);
Console.WriteLine( TimeZoneInfo.ConvertTimeToUtc(dateWinter, eastern)); // UTC-5偏移
Console.WriteLine( TimeZoneInfo.ConvertTimeToUtc(dateSummer, eastern)); // UTC-4偏移——夏令时!
就算美国(上面例子)哪天真不搞夏令时了(每年都有这种提案!),你操作系统的时区数据库还是会更新,你的代码也能继续正常跑。
5. 实用建议和常见坑
异常和隐藏的坑
- 在切换夏令时/冬令时的时刻(比如凌晨2:30),有些时间点可能“根本不存在”或者“重复出现”:
比如,往回拨表那天2:30会出现两次——但其实是两个不同的2:30。 - 别忘了:时区列表会随着操作系统更新而变化!比如有些国家会取消/新增夏令时切换。
GO TO FULL VERSION