务必在应用启动时预热 TimeZoneInfo.FindSystemTimeZoneById,避免高并发下因首次解析时区数据库和锁竞争导致 5–20ms/次延迟;ID 大小写敏感,IANA 时区如 "Etc/GMT+8" 需用 CreateCustomTimeZone 构造并预缓存;慎用 TimeZoneInfo.Local,容器中需显式设 TZ 环境变量。
TimeZoneInfo.FindSystemTimeZoneById 内部不是纯内存查找,它在首次调用时会触发系统时区数据库的解析(Windows 上读注册表或 ICU 数据,Linux/macOS 依赖 /usr/share/zoneinfo 文件结构),后续调用虽有缓存,但该缓存受内部 ConcurrentDictionary 保护,且存在锁竞争和键规范化开销。实测在 10K+ QPS 场景下,未预热时平均耗时可飙升至 5–20ms/次。
TimeZoneInfo.FindSystemTimeZoneById("UTC") 或常用 ID(如 "China Standard Time")完成初始化FindSystemTimeZoneById —— 改为从预加载字典中取值:private static readonly ConcurrentDictionary_tzCache = new(); static MyService() { foreach (var id in new[] { "UTC", "China Standard Time", "Pacific Standard Time" }) { _tzCache[id] = TimeZoneInfo.FindSystemTimeZoneById(id); } }
"utc" 会抛 TimeZoneNotFoundException
能用 FindSystemTimeZoneById 直接获取像 "Etc/GMT+8" 这类 IANA 时区名在 Windows 上默认不可用,FindSystemTimeZoneById 会直接抛异常;.NET 6+ 虽支持 IANA 数据(需启用 AppContext.SetSwitch("System.Globalization.UseNls", false)),但该开关是进程级的,且切换后会影响所有 DateTimeFormatInfo 行为,不建议 runtime 动态开启。
TimeZoneInfo.CreateCustomTimeZone 构造固定偏移时区:var gmtPlus8 = TimeZoneInfo.CreateCustomTimeZone("GMT+8", TimeSpan.FromHours(8), "GMT+8", "GMT+8");ConcurrentDictionary 或 static readonly 字段CreateCustomTimeZone 不支持夏令时,如需 DST,请改用第三方库(如 NodaTime)TimeZoneInfo.ConvertTime 本身是线程安全的,但它的性能取决于两个因素:目标时区是否已“热”、转换逻辑是否涉及复杂规则(如历史 DST 变更)。对非本地时区做频繁转换(例如日志时间戳转用户本地时间),若未复用 TimeZoneInfo 实例,会反复触发内部时区规则解析。
var tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
var local = TimeZoneInfo.ConvertTime(utcTime, tz, TimeZoneInfo.Local); // 每次都 new tz?不,但 FindSystemTimeZoneById 被反复调用就糟了TimeZoneInfo 实例作为字段或参数传入,确保复用"Asia/Shanghai"),直接缓存该实例,别每次都查DateTimeOffset 替代 —— 若只需偏移量而无需时区名称或 DST 规则,DateTimeOffset 零分配、无查找开销TimeZoneInfo.Local 在 Linux/macOS 上依赖 TZ 环境变量或 /etc/localtime 符号链接,在容器中极易为空或指向错误时区(比如 Alpine 镜像默认没设 TZ)。此时 Local 可能回退为 UTC,且首次访问会触发同步锁,造成毛刺。
ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
TimeZoneInfo.Local 总是可用 —— 加一层 fallback:private static readonly TimeZoneInfo _appDefaultTz =
TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
public static TimeZoneInfo GetEffectiveTimeZone(TimeZoneInfo? userTz = null) =>
userTz ?? TimeZoneInfo.Local ?? _appDefaultTz;TimeZoneInfo.Local 是只读属性,但其内部缓存可能被 ClearCachedData() 清空(极少用,慎调)TZ。这些点一旦在线上高频路径触发,表现就是 CPU 突增 + 请求延迟抖动,排查时容易误判为 GC 或锁竞争。