Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to create local date time with zero hour/minute/second #1022

Closed
DoumanAsh opened this issue Apr 18, 2023 · 7 comments
Closed

How to create local date time with zero hour/minute/second #1022

DoumanAsh opened this issue Apr 18, 2023 · 7 comments

Comments

@DoumanAsh
Copy link

DoumanAsh commented Apr 18, 2023

Due to tons of unecessary deprecations it became impossible to use this library for our use case which is:

  1. Get local date ('chrono::Local::today()`)
  2. Set hour/minute/second to 0:0:0 (and_hms(0, 0, 0))

There is no need to deprecate useful things unless you have alternative for their usage?

chrono::Local::today cannot be replaced with chrono::Local::now as it is.
It returns two completely different things

Something simple as creating midnight timestamp should be error-less, yet you force user unwrapping all these infallible operations because you cannot even provide const constructors

@pitdicker
Copy link
Collaborator

pitdicker commented Apr 18, 2023

Ai, I can understand your frustration. That was non-trivial.

use chrono::{Local, TimeZone};

fn main() {
    let today_date_naive = Local::now().date_naive();
    let midnight_naive = today_date_naive.and_hms_opt(0, 0, 0).unwrap();
    let midnight_local = Local.from_local_datetime(&midnight_naive).unwrap();
    println!("{:?}", midnight_local);
}

The deprecation of methods that are infallible as long as you pass values that are in range (like and_hms) is something that I as a user also don't really like, but that is another issue.

It is maybe interesting to know that if you wanted a time 1 minute after midnight today this program could have panicked in some locales, because it is not uncommon to have a DST transition at midnight. Than that time wouldn't exist.

I wonder if a set_time method on DateTime would be possible. Than maybe something like Local::now().set_time(time) could work.

@DoumanAsh
Copy link
Author

It is maybe interesting to know that if you wanted a time 1 minute after midnight today this program could have panicked in some locales, because it is not uncommon to have a DST transition at midnight. Than that time wouldn't exist.

I'm not sure how is that possible?
Just because DST exist, doesn't make 00:01 impossible time I think.
Every timezone exists within range of 0 to 24 hours etc

@pitdicker
Copy link
Collaborator

Just for fun, may I suggest you run the following code on a Unix system with the date and timezone set to 2022-03-25 Damascus, Syria (to pick an example)?

use chrono::{Local, TimeZone};

fn main() {
    let today_date_naive = Local::now().date_naive();
    let midnight_naive = today_date_naive.and_hms_opt(0, 0, 1).unwrap();
    let midnight_local = Local.from_local_datetime(&midnight_naive).unwrap();
    println!("{:?}", midnight_local);
}

That will fail with:

thread 'main' panicked at 'No such local time', /home/pitdicker/.cargo/registry/src/github.com-1ecc6299db9ec823/chrono-0.4.24/src/offset/mod.rs:186:34
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The local time jumps from 0:00:00 to 1:00:00, making the day 23 hours long.

On the other hand, one second after midnight can exist (it will just be at 1:00:01):

use chrono::{Duration, Local, TimeZone};

fn main() {
    let today_date_naive = Local::now().date_naive();
    let midnight_naive = today_date_naive.and_hms_opt(0, 0, 0).unwrap();
    let midnight_local = Local.from_local_datetime(&midnight_naive).unwrap();
    println!("{:?}", midnight_local + Duration::seconds(1));
}

Result:

2022-03-25T01:00:01+03:00

@DoumanAsh
Copy link
Author

DoumanAsh commented Apr 19, 2023

I don't understand why it should fail? and how second would matter.
Is 0:00:00 not valid time all of the sudden? (DST cannot make it invalid, it only offsets time)

The local time jumps from 0:00:00 to 1:00:00

That actually makes chrono unusable.
It means you cannot construct time properly using current date

@esheppa
Copy link
Collaborator

esheppa commented Apr 19, 2023

Thanks for the comments @DoumanAsh and @pitdicker. There has been quite a bit of past discussion on this as we are trying to find a solution where we offer easy construction of date literals (which are obviously correct from inspection), but also don't catch people out with unexpected panics.

With regards to construction of literals, there is a lot of discussion in #815. There is also a PR that implements this, but it requires more discouse on the API before being merged (and also some work to ensure there isn't a major performance regression)

One example of a possible API is:

let my_time = NaiveTime::<22, 05, 05>::new_hms();

@DoumanAsh with regards to the specific requirement of getting midnight on local time, the easiest way is probably:

let midnight_result = Local::now().date_naive().and_time(NaiveTime::MIN).and_local_timezone(Local);

The only fallible function here is and_local_timezone which must be fallible as specific timestamps often don't exist in a specific named timezone due to offset changes, even though they always exist at UTC or a fixed offset. If midnight is specifically required, the best option may be avoiding the call to and_local_timezone and simply keeping it as a NaiveDateTime.

That actually makes chrono unusable.
It means you cannot construct time properly using current date

I appreciate that some of these API changes - by virtue of aiming for correctness - can make some use cases a little more tricky. However I think in this case, you may be better served by using a fixed offset directly or even just a NaiveDateTime. With regards to local timestamps,the problem is that some local timestamps are potentially invalid, due to either not existing at all (easterly offset transition), or being ambiguous (westerly offset transition)

There is also some discussion on finding the nearest earlier or later valid local time at #716, and one method of this will likely be implemented for 0.5, however it can't be done in 0.4 as LocalResult looses information which we otherwise need.

@iwinux
Copy link

iwinux commented Aug 14, 2023

Got bitten by this when upgrading deps. I decide to pin chrono at 0.4.20 until things get better :(

@pitdicker
Copy link
Collaborator

@iwinux Don't you think it is better to just use the line Local::now().date_naive().and_time(NaiveTime::MIN).and_local_timezone(Local) as suggested above, and update to recent versions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants