Hi Rustaceans of Stack Overflow, I am continuing my new project in Rust and I am still having trouble around the clock trait that I have been trying to build. For reference, I went with the second solution (the one recommended to me).
My challenge now is that I am finding that the need to specify a generic time zone is "bleeding" out into other structs. Essentially, if any struct needs to contain a clock (or any other struct that contains a clock), a generic is required on that struct also, even though these structs have little to do with time or time zones.
// Assert for purposes of argument that this struct is not really about time
// or time zones and it is confusing why one is required.
pub struct <Tz: TimeZone> UnrelatedThing {
// Other stuff...
clock: &dyn Clock<Tz>,
}
I ultimately want to get to a point where I can store a time zone as a property of something and be able to have a method that returns DateTime
objects in that time zone:
struct TimeRelatedThing {
tz: dyn TimeZone,
}
impl TimeRelatedThing {
pub fn now(&self) -> DateTime<??> {
Utc::now().with_timezone(&self.tz)
}
}
The piece I can not get to work correctly is the DateTime<??>
part, because the time zone is not necessarily known at compile time. What is the right way to approach this? It seems like I can do Box<dyn impl Datelike + Timelike>
but that seems to my naive understanding like high overhead? It certainly seems like a lot of work for people who want to write code against my function.
I should mention that I am new to Rust, and it is entirely possible I am misunderstanding something elementary, but I have looked at the documentation to try to understand better and have not found what I was looking for.
UPDATE
After more iterating, I have a state where I always ask for the timezone as an argument:
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 {
/// Return the current datetime.
fn now<Tz: TimeZone>(&self, tz: Tz) -> DateTime<Tz>;
}
/// A clock that reliably reports system time in the requested time zone.
pub struct SystemClock {}
impl SystemClock {
/// Return a new system clock in the given time zone.
pub fn new() -> SystemClock {
SystemClock {}
}
}
impl Clock for SystemClock {
/// Return the current datetime.
fn now<Tz: TimeZone>(&self, tz: Tz) -> DateTime<Tz> {
Utc::now().with_timezone(&tz)
}
}
/// A stub clock that reports "stub time", useful for replaying historical
/// data and other purposes.
pub struct StubClock {
/// The current "now" time, in UTC.
cursor: DateTime<Utc>,
}
impl StubClock {
/// Return a new stub clock in the given time zone, and with the given
/// starting time.
pub fn new<Tz: TimeZone>(start: DateTime<Tz>) -> StubClock {
StubClock { cursor: start.with_timezone(&Utc) }
}
/// Set the cursor to the current time.
pub fn set_cursor<Tz: TimeZone>(&mut self, cursor: DateTime<Tz>) -> &Self {
self.cursor = cursor.with_timezone(&Utc);
return self;
}
}
impl Clock for StubClock {
/// Return the current datetime.
fn now<Tz: TimeZone>(&self, tz: Tz) -> DateTime<Tz> {
self.cursor.clone().with_timezone(&tz)
}
}
This still seems slightly less good than having a time zone as the property of my clock, since I expect a clock to consistently operate in a single time zone, although there are indisputably advantages.
This creates a new problem, though, which is that now Clock
is not an "object-safe" trait, because TimeZone
seems to have Sized
as a supertrait. :-(