如何创建具有计时时区的通用 Rust 结构?

Luk*_*ger 4 rust rust-chrono

免责声明:我是 Rust 的新手(以前的经验是 Python、TypeScript 和 Go,按顺序),我完全有可能遗漏了一些非常明显的东西。

我正在尝试构建一个 Rust 时钟接口。我在这里的基本目标是我有一个报告实际时间的小时钟结构,以及一个报告伪造版本以供测试的存根版本。请注意,这些是历史测试而不是单元测试:我的目标是重放历史数据。我认为部分问题也可能是我理解chrono得不够好。这显然是一个伟大的图书馆,但我有在主场迎战类型实例关系的麻烦chronochrono_tz

无论如何,这就是我所拥有的:

use chrono::{DateTime, TimeZone, Utc};

/// A trait representing the internal clock for timekeeping requirements.
/// Note that for some testing environments, clocks may be stubs.
pub trait Clock<Tz: TimeZone> {
  fn now() -> DateTime<Tz>;
}
Run Code Online (Sandbox Code Playgroud)

我的最终目标是让其他结构dyn Clock在特定时区有一个。该时钟可能是系统时钟(具有适当的时区转换),也可能是某种存根。

这是我对系统时钟垫片的尝试,一切都发生了可怕的错误:

/// A clock that reliably reports system time in the requested time zone.
struct SystemClock<Tz: TimeZone> {
  time_zone: std::marker::PhantomData<*const Tz>,
}
impl<Tz: TimeZone> Clock<Tz> for SystemClock<Tz> {
  /// Return the current time.
  fn now() -> DateTime<Tz> {
    Utc::now().with_timezone(&Tz)
  }
}
Run Code Online (Sandbox Code Playgroud)

关键问题是Utc::now().with_timezone(&Tz)。编译器需要一个,而不是一个类型。很公平,除了chrono并且chrono_tz似乎没有时区值。我一直在寻找合适的东西放在这里,但似乎没有什么是正确的答案。

use*_*342 6

问题是时区类型不足以now()按规定实施。大多数时区不是作为单独的类型实现的,Utc在这方面实际上是特殊的(原样Local)。普通的时区实现价值更一般的时区类型,如FixedOffsetchrono_tz::Tz。这些类型在运行时存储时区偏移量,因此有效的FixedOffsets 包括FixedOffset::east(1)(CET)、FixedOffset::west(5)(EST) 甚至FixedOffset::east(0)(GMT、UTC)。这就是为什么DateTime::with_timezone()需要一个具体的时区值,而不仅仅是它的类型。

解决此问题的最简单方法是修改now()以接受时区值:

pub trait Clock<Tz: TimeZone> {
    fn now(tz: Tz) -> DateTime<Tz>;
}

struct SystemClock<Tz: TimeZone> {
    time_zone: std::marker::PhantomData<*const Tz>,
}

impl<Tz: TimeZone> Clock<Tz> for SystemClock<Tz> {
    fn now(tz: Tz) -> DateTime<Tz> {
        Utc::now().with_timezone(&tz)
    }
}
Run Code Online (Sandbox Code Playgroud)

用法如下所示:

fn main() {
    // now in Utc
    println!("{:?}", SystemClock::now(Utc));
    // now in GMT+1
    println!("{:?}", SystemClock::now(FixedOffset::east(1)));
    // now in Copenhagen time
    println!(
        "{:?}",
        SystemClock::now("Europe/Copenhagen".parse::<chrono_tz::Tz>().unwrap())
    );
}
Run Code Online (Sandbox Code Playgroud)

请特别注意第二个和最后一个示例,其中时区是在运行时选择的,显然不是由时区类型捕获的。

如果您发现在 trait 方法中指定时区值是多余的now(),您可以授予方法访问权限self并将时区值保留在字段中SystemClock(这也可以很好地消除PhantomData):

pub trait Clock<Tz: TimeZone> {
    fn now(&self) -> DateTime<Tz>;
}

struct SystemClock<Tz: TimeZone> {
    time_zone: Tz,
}

impl SystemClock<Utc> {
    fn new_utc() -> SystemClock<Utc> {
        SystemClock { time_zone: Utc }
    }
}

impl<Tz: TimeZone> SystemClock<Tz> {
    fn new_with_time_zone(tz: Tz) -> SystemClock<Tz> {
        SystemClock { time_zone: tz }
    }
}

impl<Tz: TimeZone> Clock<Tz> for SystemClock<Tz> {
    fn now(&self) -> DateTime<Tz> {
        Utc::now().with_timezone(&self.time_zone)
    }
}

fn main() {
    println!("{:?}", SystemClock::new_utc().now());
    println!("{:?}", SystemClock::new_with_time_zone(FixedOffset::east(1)).now());
    // ...
}
Run Code Online (Sandbox Code Playgroud)

操场

对于在编译时已知偏移量的时区,例如Utcand Local,时区字段将不占用空间,并且SystemClock大小为零,就像在您的原始设计中一样。对于在运行时选择偏移量的时区,SystemClock将在结构中存储该信息。

最后,随着 const 泛型的出现,人们可以想象一种FixedOffset在编译时将偏移量存储为 const 泛型的变体。这些类型由chrono-simpletzcrate提供,您可以使用它来创建Clock您最初想要的那种特征。由于其类型在编译时完全指定Default,因此它们实现,因此您可以使用 轻松获取时区值Tz::default()。结果(遗憾的是再次需要PhantomData)可能如下所示:

use std::marker::PhantomData;

use chrono::{DateTime, TimeZone, Utc};
use chrono_simpletz::{UtcZst, known_timezones::UtcP1};

type UtcP0 = UtcZst<0, 0>;  // chrono_simpletz doesn't provide this

pub trait Clock<Tz: TimeZone + Default> {
    fn now() -> DateTime<Tz>;
}

struct SystemClock<Tz: TimeZone> {
    time_zone: PhantomData<fn() -> Tz>,
}

impl<Tz: TimeZone + Default> Clock<Tz> for SystemClock<Tz> {
    fn now() -> DateTime<Tz> {
        Utc::now().with_timezone(&Tz::default())
    }
}

fn main() {
    println!("{:?}", SystemClock::<UtcP0>::now());
    println!("{:?}", SystemClock::<UtcP1>::now());
}
Run Code Online (Sandbox Code Playgroud)

如果不清楚选择哪个选项进行生产,我推荐第二个选项,即带有 Playground 链接的选项。

  • 不是 OP,而是非常明确的答案,谢谢。每当我需要使用它们时,我都会花很长时间来了解日期时间,所以这会被加入书签。 (2认同)