问题是时区类型不足以now()
按规定实现。大多数时区不是作为单独的类型实现的,Utc
实际上在这方面是特殊的(就像是Local
)。正常时区被实现为更一般的时区类型的值,例如FixedOffset
或chrono_tz::Tz
。这些类型在运行时存储时区偏移量,因此有效FixedOffset
的 s 包括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)
}
}
用法如下所示:
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())
);
}
特别注意第二个和最后一个示例,其中时区是在运行时选择的,并且显然没有被时区类型捕获。
如果您发现在诸如 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());
// ...
}
操场
对于在编译时已知偏移量的时区,例如Utc
and Local
,时区字段将不占用空间,并且SystemClock
大小为零,就像在您的原始设计中一样。对于在运行时选择偏移量的时区,SystemClock
将该信息存储在结构中。
最后,随着 const 泛型的出现,人们可以想象一种FixedOffset
在编译时将偏移量存储为 const 泛型的变体。这种类型由 crate 提供,您可以使用它来创建您最初想要chrono-simpletz
的那种特征。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());
}
如果不清楚选择哪个选项进行生产,我推荐第二个,即带有操场链接的那个。