如何创建具有计时时区的通用 Rust 结构?
How do I create a generic Rust struct with a chrono time zone?
免责声明:我是 Rust 的新手(以前的经验是 Python、TypeScript 和 Go,按此顺序),我完全有可能遗漏了一些非常明显的东西。
我正在尝试构建一个 Rust 时钟接口。我的基本目标是我有一个报告实际时间的小时钟结构,以及一个将报告伪造版本以供测试的存根版本。请注意,这些是历史测试而不是单元测试:我的目标是重放历史数据。我想部分问题也可能是我对chrono
的理解不够透彻。它显然是一个很棒的库,但我在 chrono
和 chrono_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>;
}
我的最终目标是让其他结构在特定时区具有 dyn Clock
。该时钟可能是系统时钟(具有适当的时区转换)或者它可能是某种存根。
这是我对系统时钟 shim 的尝试,但一切都大错特错:
/// 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)
}
}
关键问题是Utc::now().with_timezone(&Tz)
。编译器需要 value,而不是 type。很公平,除了 chrono
和 chrono_tz
似乎没有时区值。我一直在寻找合适的东西放在这里,但似乎没有什么是正确的答案。
问题是时区 type 不足以实现指定的 now()
。大多数时区都没有实现为单独的类型,Utc
实际上在这方面很特殊(Local
也是如此)。正常时区实现为更通用时区类型的 值 ,例如 FixedOffset
或 chrono_tz::Tz
。这些类型在 运行 时间存储时区偏移量,因此有效的 FixedOffset
s 包括 FixedOffset::east(1)
(CET)、FixedOffset::west(5)
(EST),甚至 FixedOffset::east(0)
(格林威治标准时间、协调世界时)。这就是为什么 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())
);
}
请特别注意第二个和最后一个示例,其中时区是在 运行 时间选择的,并且显然没有被时区类型捕获。
如果您发现在 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
和Local
,时区字段将不占用space,而SystemClock
将是零大小的,就像在您的原始设计中一样。对于在 运行 时间选择偏移量的时区,SystemClock
将在结构中存储该信息。
最后,随着 const 泛型的出现,可以想象 FixedOffset
的一种变体,它在编译时将偏移量存储为 const 泛型。这些类型由 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());
}
如果生产时选择哪个选项不是很明显,我推荐第二个,带 playground 的那个 link。
免责声明:我是 Rust 的新手(以前的经验是 Python、TypeScript 和 Go,按此顺序),我完全有可能遗漏了一些非常明显的东西。
我正在尝试构建一个 Rust 时钟接口。我的基本目标是我有一个报告实际时间的小时钟结构,以及一个将报告伪造版本以供测试的存根版本。请注意,这些是历史测试而不是单元测试:我的目标是重放历史数据。我想部分问题也可能是我对chrono
的理解不够透彻。它显然是一个很棒的库,但我在 chrono
和 chrono_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>;
}
我的最终目标是让其他结构在特定时区具有 dyn Clock
。该时钟可能是系统时钟(具有适当的时区转换)或者它可能是某种存根。
这是我对系统时钟 shim 的尝试,但一切都大错特错:
/// 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)
}
}
关键问题是Utc::now().with_timezone(&Tz)
。编译器需要 value,而不是 type。很公平,除了 chrono
和 chrono_tz
似乎没有时区值。我一直在寻找合适的东西放在这里,但似乎没有什么是正确的答案。
问题是时区 type 不足以实现指定的 now()
。大多数时区都没有实现为单独的类型,Utc
实际上在这方面很特殊(Local
也是如此)。正常时区实现为更通用时区类型的 值 ,例如 FixedOffset
或 chrono_tz::Tz
。这些类型在 运行 时间存储时区偏移量,因此有效的 FixedOffset
s 包括 FixedOffset::east(1)
(CET)、FixedOffset::west(5)
(EST),甚至 FixedOffset::east(0)
(格林威治标准时间、协调世界时)。这就是为什么 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())
);
}
请特别注意第二个和最后一个示例,其中时区是在 运行 时间选择的,并且显然没有被时区类型捕获。
如果您发现在 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
和Local
,时区字段将不占用space,而SystemClock
将是零大小的,就像在您的原始设计中一样。对于在 运行 时间选择偏移量的时区,SystemClock
将在结构中存储该信息。
最后,随着 const 泛型的出现,可以想象 FixedOffset
的一种变体,它在编译时将偏移量存储为 const 泛型。这些类型由 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());
}
如果生产时选择哪个选项不是很明显,我推荐第二个,带 playground 的那个 link。