【Chrono库】Unix-like 系统时区处理实现(src/offset/local/unix.rs)

这段代码是 Chrono 时间库在 Unix-like 系统上的时区处理实现,通过 IANA 时区数据库来处理本地时间和 UTC 时间的转换。

核心架构

线程本地缓存

rust 复制代码
thread_local! {
    static TZ_INFO: RefCell<Option<Cache>> = Default::default();
}
  • 使用线程本地存储缓存时区信息
  • 避免重复解析时区数据
  • 支持多线程环境

统一的接口函数

rust 复制代码
pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
    offset(utc, false)
}

pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
    offset(local, true)
}

核心数据结构

Cache 结构体

rust 复制代码
struct Cache {
    zone: TimeZone,        // 当前时区信息
    source: Source,        // 时区数据来源
    last_checked: SystemTime, // 最后检查时间
}

Source 枚举

标识时区信息的来源:

  • LocalTime { mtime: SystemTime }: 来自 /etc/localtime 文件
  • Environment { hash: u64 }: 来自 TZ 环境变量

关键特性

1. 智能缓存机制

rust 复制代码
// 1秒内无条件重用缓存
Ok(d) if d.as_secs() < 1 => (),
// 超过1秒检查时区是否变化
Ok(_) | Err(_) => {
    // 检查时区源是否变化
    let out_of_date = match (&self.source, &new_source) {
        // 环境变量和文件来源切换
        (Source::Environment { .. }, Source::LocalTime { .. }) | ... => true,
        // 文件修改时间变化
        (Source::LocalTime { mtime: old_mtime }, Source::LocalTime { mtime })
            if old_mtime != mtime => true,
        // 环境变量内容变化
        (Source::Environment { hash: old_hash }, Source::Environment { hash })
            if old_hash != hash => true,
        // 缓存可重用
        _ => false,
    };
}

2. 时区发现策略

rust 复制代码
fn current_zone(var: Option<&str>) -> TimeZone {
    TimeZone::local(var).ok()
        .or_else(fallback_timezone)
        .unwrap_or_else(TimeZone::utc)  // 最终回退到 UTC
}

时区发现优先级:

  1. TZ 环境变量指定的时区
  2. 系统 /etc/localtime 文件
  3. IANA 时区数据库自动检测
  4. 回退到 UTC

3. 平台特定的时区数据库路径

rust 复制代码
#[cfg(target_os = "aix")]
const TZDB_LOCATION: &str = "/usr/share/lib/zoneinfo";

#[cfg(not(any(target_os = "android", target_os = "aix", target_env = "ohos")))]
const TZDB_LOCATION: &str = "/usr/share/zoneinfo";

核心算法

UTC 到本地时间转换

rust 复制代码
if !local {
    let offset = self.zone
        .find_local_time_type(d.and_utc().timestamp())
        .expect("unable to select local time type")
        .offset();
    
    FixedOffset::east_opt(offset)
}

本地时间到 UTC 转换

rust 复制代码
self.zone
    .find_local_time_type_from_local(d)
    .expect("unable to select local time type")
    .and_then(|o| FixedOffset::east_opt(o.offset()))

设计优势

1. 性能优化

  • 1秒缓存窗口: 平衡性能与时区变化响应速度
  • 哈希比较: 环境变量变化时快速检测
  • 文件修改时间: 检测系统时区配置变化

2. 健壮性

  • 多层回退机制: 确保总能获得有效的时区信息
  • 错误处理: 在时区解析失败时优雅降级到 UTC
  • 平台适配: 支持多种 Unix-like 系统

3. 标准兼容

  • 遵循 IANA 时区数据库标准
  • 支持 TZ 环境变量覆盖
  • 兼容系统 /etc/localtime 配置

这个实现提供了在 Unix-like 系统上高效、准确的时区处理能力,通过智能缓存和多重回退机制确保在各种环境下都能可靠工作。

复制代码
## 附源码

```rust
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::{cell::RefCell, collections::hash_map, env, fs, hash::Hasher, time::SystemTime};

use super::tz_info::TimeZone;
use super::{FixedOffset, NaiveDateTime};
use crate::MappedLocalTime;

pub(super) fn offset_from_utc_datetime(utc: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
    offset(utc, false)
}

pub(super) fn offset_from_local_datetime(local: &NaiveDateTime) -> MappedLocalTime<FixedOffset> {
    offset(local, true)
}

fn offset(d: &NaiveDateTime, local: bool) -> MappedLocalTime<FixedOffset> {
    TZ_INFO.with(|maybe_cache| {
        maybe_cache.borrow_mut().get_or_insert_with(Cache::default).offset(*d, local)
    })
}

// we have to store the `Cache` in an option as it can't
// be initialized in a static context.
thread_local! {
    static TZ_INFO: RefCell<Option<Cache>> = Default::default();
}

enum Source {
    LocalTime { mtime: SystemTime },
    Environment { hash: u64 },
}

impl Source {
    fn new(env_tz: Option<&str>) -> Source {
        match env_tz {
            Some(tz) => {
                let mut hasher = hash_map::DefaultHasher::new();
                hasher.write(tz.as_bytes());
                let hash = hasher.finish();
                Source::Environment { hash }
            }
            None => match fs::symlink_metadata("/etc/localtime") {
                Ok(data) => Source::LocalTime {
                    // we have to pick a sensible default when the mtime fails
                    // by picking SystemTime::now() we raise the probability of
                    // the cache being invalidated if/when the mtime starts working
                    mtime: data.modified().unwrap_or_else(|_| SystemTime::now()),
                },
                Err(_) => {
                    // as above, now() should be a better default than some constant
                    // TODO: see if we can improve caching in the case where the fallback is a valid timezone
                    Source::LocalTime { mtime: SystemTime::now() }
                }
            },
        }
    }
}

struct Cache {
    zone: TimeZone,
    source: Source,
    last_checked: SystemTime,
}

#[cfg(target_os = "aix")]
const TZDB_LOCATION: &str = "/usr/share/lib/zoneinfo";

#[cfg(not(any(target_os = "android", target_os = "aix", target_env = "ohos")))]
const TZDB_LOCATION: &str = "/usr/share/zoneinfo";

fn fallback_timezone() -> Option<TimeZone> {
    let tz_name = iana_time_zone::get_timezone().ok()?;
    #[cfg(not(any(target_os = "android", target_env = "ohos")))]
    let bytes = fs::read(format!("{TZDB_LOCATION}/{tz_name}")).ok()?;
    #[cfg(any(target_os = "android", target_env = "ohos"))]
    let bytes = crate::offset::local::tz_data::for_zone(&tz_name).ok()??;
    TimeZone::from_tz_data(&bytes).ok()
}

impl Default for Cache {
    fn default() -> Cache {
        // default to UTC if no local timezone can be found
        let env_tz = env::var("TZ").ok();
        let env_ref = env_tz.as_deref();
        Cache {
            last_checked: SystemTime::now(),
            source: Source::new(env_ref),
            zone: current_zone(env_ref),
        }
    }
}

fn current_zone(var: Option<&str>) -> TimeZone {
    TimeZone::local(var).ok().or_else(fallback_timezone).unwrap_or_else(TimeZone::utc)
}

impl Cache {
    fn offset(&mut self, d: NaiveDateTime, local: bool) -> MappedLocalTime<FixedOffset> {
        let now = SystemTime::now();

        match now.duration_since(self.last_checked) {
            // If the cache has been around for less than a second then we reuse it
            // unconditionally. This is a reasonable tradeoff because the timezone
            // generally won't be changing _that_ often, but if the time zone does
            // change, it will reflect sufficiently quickly from an application
            // user's perspective.
            Ok(d) if d.as_secs() < 1 => (),
            Ok(_) | Err(_) => {
                let env_tz = env::var("TZ").ok();
                let env_ref = env_tz.as_deref();
                let new_source = Source::new(env_ref);

                let out_of_date = match (&self.source, &new_source) {
                    // change from env to file or file to env, must recreate the zone
                    (Source::Environment { .. }, Source::LocalTime { .. })
                    | (Source::LocalTime { .. }, Source::Environment { .. }) => true,
                    // stay as file, but mtime has changed
                    (Source::LocalTime { mtime: old_mtime }, Source::LocalTime { mtime })
                        if old_mtime != mtime =>
                    {
                        true
                    }
                    // stay as env, but hash of variable has changed
                    (Source::Environment { hash: old_hash }, Source::Environment { hash })
                        if old_hash != hash =>
                    {
                        true
                    }
                    // cache can be reused
                    _ => false,
                };

                if out_of_date {
                    self.zone = current_zone(env_ref);
                }

                self.last_checked = now;
                self.source = new_source;
            }
        }

        if !local {
            let offset = self
                .zone
                .find_local_time_type(d.and_utc().timestamp())
                .expect("unable to select local time type")
                .offset();

            return match FixedOffset::east_opt(offset) {
                Some(offset) => MappedLocalTime::Single(offset),
                None => MappedLocalTime::None,
            };
        }

        // we pass through the year as the year of a local point in time must either be valid in that locale, or
        // the entire time was skipped in which case we will return MappedLocalTime::None anyway.
        self.zone
            .find_local_time_type_from_local(d)
            .expect("unable to select local time type")
            .and_then(|o| FixedOffset::east_opt(o.offset()))
    }
}

```
相关推荐
小灰灰搞电子15 小时前
Rust可以取代C++么?
开发语言·c++·rust
百锦再16 小时前
京东云鼎入驻方案解读——通往协同的“高架桥”与“快速路”
android·java·python·rust·django·restful·京东云
异步思考者20 小时前
Rust实战:一个内存消息队列的 Trait 驱动开发
rust
受之以蒙21 小时前
智能目标检测:用 Rust + dora-rs + yolo 构建“机器之眼”
人工智能·笔记·rust
熬了夜的程序员21 小时前
【Rust学习之路】第 0 章:理解 Rust 的核心哲学
开发语言·学习·rust
EniacCheng21 小时前
【RUST】学习笔记-环境搭建
笔记·学习·rust
禅思院21 小时前
在win10上配置 Rust以及修改默认位置问题
开发语言·前端·后端·rust·cargo·mingw64·cargo安装位置
shandianchengzi1 天前
【记录】Rust|Rust开发相关的7个VSCode插件的介绍和推荐指数(2025年)
开发语言·vscode·rust
JPX-NO1 天前
Rust + Rocket + Diesel构建的RESTful API示例(CRUD)
开发语言·rust·restful
林太白1 天前
Rust01-认识安装
开发语言·后端·rust