chrono icon indicating copy to clipboard operation
chrono copied to clipboard

Ergonomics for naive::Days and Months

Open nathan opened this issue 2 years ago • 6 comments

naive::Days and Months are extremely unergonomic at the moment; it's impossible to recover the number of days/months once one has been constructed, and the constructors Days::new and Months::new are rather verbose.

Would it be possible for chrono to provide From and Into impls for u64/u32 (respectively) and/or make each integer pub so users can construct and destructure them?

nathan avatar Jul 28 '23 23:07 nathan

What's your use case for this? It seems unergonomic because you're using them in a way that we didn't foresee when designing them.

Also how is Days::new() more verbose than Days::from()? I suppose you want to use .into() instead?

djc avatar Jul 29 '23 13:07 djc

I suppose you want to use .into() instead?

Indeed, or the constructor Days().

What's your use case for this?

E.g., if I want an iterator that yields the same day/time on consecutive months, I currently write:

struct It {
    base: DateTime<Utc>,
    here: u32,
}
impl It {
    fn new(base: DateTime<Utc>) -> Self {
        Self { base, here: 0 }
    }
}
impl Iterator for It {
    type Item = DateTime<Utc>;
    fn next(&mut self) -> Option<Self::Item> {
        let m = self.here;
        self.here += 1;
        self.base.checked_add_months(Months::new(m))
    }
}

fn main() {
    let dt = Utc.with_ymd_and_hms(2023, 07, 31, 12, 34, 56).single().unwrap();
    dbg!(It::new(dt).take(5).collect::<Vec<_>>());
}

Whereas the following would be easier and clearer (less unit confusion):

// ...
    here: Months,
// ...
        Self { base, here: Months(0) }
// ...
        let m = self.here;
        self.here += Months(1); // or self.here.0 += 1, vel sim.
        self.base.checked_add_months(m)

I also can't write a helper that accepts Months and does anything but pass it on unmodified:

fn consecutive(dt: DateTime<Utc>, months: Months) -> Vec<DateTime<Utc>> {
    It::new(dt).take(months/* uh oh... */).collect()
}

When I initially encountered Days and Months I assumed they were newtypes meant to help manage units, but you can't actually use them like that. If the only purpose of these types is to force you to write Months::new() in every call to very-explicitly-named methods like checked_add_months, I don't see why they exist at all (the Add/Sub impls, I suppose?).

nathan avatar Jul 30 '23 00:07 nathan

Yes, they mostly exist for the Add/Sub impls. I don't want to expose the inner field publicly so you won't be able to write Days(1) directly.

djc avatar Aug 01 '23 07:08 djc

I don't want to

Ok…

nathan avatar Aug 02 '23 02:08 nathan

@nathan I don't really follow about the constructors, but regarding From, this PR adds Months::as_u32(), which should allow you to determine the value of months. Using that method you can then easily implement any From conversions you need 🙂

danwilliams avatar Jan 10 '24 03:01 danwilliams

Also I think all of this will be superseded by the CalendarDuration type as proposed in #1290.

djc avatar Jan 10 '24 08:01 djc