chronotope / chrono

Date and time library for Rust
Other
3.33k stars 531 forks source link

Supporting normalising dates/times #1416

Open curiousdannii opened 9 months ago

curiousdannii commented 9 months ago

I'm porting code which makes use of the normalisation features of C's timegm)

The original values of the tm_wday and tm_yday components of the structure are ignored, and the original values of the other components are not restricted to their normal ranges, and will be normalized if needed. For example, October 40 is changed into November 9, a tm_hour of -1 means 1 hour before midnight, tm_mday of 0 means the day preceding the current month, and tm_mon of -2 means 2 months before January of tm_year. (A positive or zero value for tm_isdst causes mktime() to presume initially that summer time (for example, Daylight Saving Time) is or is not in effect for the specified time, respectively. A negative value for tm_isdst causes the mktime() function to attempt to divine whether summer time is in effect for the specified time. The tm_isdst and tm_gmtoff members are forced to zero by timegm(.))

I couldn't see anything in the current Chrono API which looks suitable for this, though it looks like I could manually do it by calling checked_add_months, checked_add_days, etc (though as negative values are possible I'd need to test and call the sub functions too.)

Is there a part of the API that I missed, or does anyone know of another Rust library that can handle this?

curiousdannii commented 9 months ago

Okay, here's what I ended up with. It's not the worst, but a helper function would still be appreciated:

    let mut normalised_date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap();
    let months = month - 1;
    if months > 0 {
        normalised_date = normalised_date.checked_add_months(chrono::Months::new(months as u32)).unwrap();
    }
    if months < 0 {
        normalised_date = normalised_date.checked_sub_months(chrono::Months::new(months as u32)).unwrap();
    }
    let mut normalised_date = NaiveDateTime::from(normalised_date);
    let duration = Duration::days(day as i64 - 1)
        + Duration::hours(hour as i64)
        + Duration::minutes(minute as i64)
        + Duration::seconds(second as i64)
        + Duration::nanoseconds(microsec as i64 * 1000);
    normalised_date = normalised_date.checked_add_signed(duration).unwrap();
djc commented 8 months ago

Other than trying to come up with an exact match for time_gm()'s semantics, what is your code trying to achieve? What is the use case? I'd like to avoid the XY problem here.

curiousdannii commented 8 months ago

I'm porting a library from C to Rust which basically wraps timegm. I have to normalise the dates or I don't pass the unit tests. Rejecting invalid dates like the _opt functions probably does make more sense, but the API isn't up to me.

That said, my code seems to be working, so if you want you can just close this feature request.

Using libc's wrapper of timegm would be another option (albeit it's an unsafe function.)

djc commented 8 months ago

It sounds like #1290 might address your use case?