Calculate the duration between now and the next midnight using Chrono

5.1k Views Asked by At

What is the idiomatic way to get the duration between now and the next midnight?

I have a function like this:

extern crate chrono;

use chrono::prelude::*;
use time;

fn duration_until_next_midnight() -> time::Duration {
    let now = Local::now(); // Fri Dec 08 2017 23:00:00 GMT-0300 (-03)
    // ... how to continue??
}

It should make a Duration with 1 hour, since the next midnight is Sat Dec 09 2017 00:00:00 GMT-0300 (-03)

4

There are 4 best solutions below

2
On BEST ANSWER

After scouring the docs, I finally found the missing link: Date::and_hms.

So, actually, it's as easy as:

fn main() {
    let now = Local::now();

    let tomorrow_midnight = (now + Duration::days(1)).date().and_hms(0, 0, 0);

    let duration = tomorrow_midnight.signed_duration_since(now).to_std().unwrap();

    println!("Duration between {:?} and {:?}: {:?}", now, tomorrow_midnight, duration);
}

The idea is simple:

  • increase the DateTime to tomorrow,
  • extract the Date part, which keeps the timezone,
  • reconstructs a new DateTime by specifying a "00:00:00" Time with and_hms.

There's a panic! in and_hms, so one has to be careful to specify a correct time.

1
On

One way is to calculate the seconds missing to next midnight, keeping in mind that time::Tm accounts for both Daylight Saving Time and time zones:

tm_utcoff: i32

Identifies the time zone that was used to compute this broken-down time value, including any adjustment for Daylight Saving Time. This is the number of seconds east of UTC. For example, for U.S. Pacific Daylight Time, the value is -7*60*60 = -25200.

extern crate time;
use std::time::Duration;

fn duration_until_next_midnight() -> Duration {
    let tnow = time::now();

    Duration::new(
        (86400 - tnow.to_timespec().sec % 86400 - 
        i64::from(tnow.tm_utcoff)) as u64,
        0,
    )
}

If you want nanoseconds precision you have to do some more maths ...

10
On

Just subtract the two dates: midnight and now:

extern crate chrono;
use chrono::prelude::*;
use std::time;

fn duration_until_next_midnight() -> time::Duration {
    let now = Local::now();
    // change to next day and reset hour to 00:00
    let midnight = (now + chrono::Duration::days(1))
        .with_hour(0).unwrap()
        .with_minute(0).unwrap()
        .with_second(0).unwrap()
        .with_nanosecond(0).unwrap();

    println!("Duration between {:?} and {:?}:", now, midnight);
    midnight.signed_duration_since(now).to_std().unwrap()
}

fn main() {
    println!("{:?}", duration_until_next_midnight())
}

As requested by Matthieu, you can write something like:

fn duration_until_next_midnight() -> Duration {
    let now = Local::now();
    // get the NaiveDate of tomorrow
    let midnight_naivedate = (now + chrono::Duration::days(1)).naive_utc().date();
    // create a NaiveDateTime from it
    let midnight_naivedatetime = NaiveDateTime::new(midnight_naivedate, NaiveTime::from_hms(0, 0, 0));
    // get the local DateTime of midnight
    let midnight: DateTime<Local> = DateTime::from_utc(midnight_naivedatetime, *now.offset());

    println!("Duration between {:?} and {:?}:", now, midnight);
    midnight.signed_duration_since(now).to_std().unwrap()
}

But I am not sure if it is better.

0
On

For the next person who reaches here. Here is a version that uses the newer API as the most of the ones used in the top answer and now depricated.

let now = chrono::Local::now();

let tomorrow_midnight = (now + chrono::Duration::days(1))
    .date_naive()
    .and_hms_opt(0, 0, 0)
    .unwrap();

let duration = tomorrow_midnight
    .signed_duration_since(now.naive_local())
    .to_std()
    .unwrap();

println!(
    "Duration between {:?} and {:?}: {:?}",
    now, tomorrow_midnight, duration
);