Рассчитайте продолжительность между настоящим моментом и следующей полуночью с помощью Chrono.

Каким идиоматическим способом определить продолжительность между настоящим моментом и следующей полуночью?

У меня есть такая функция:

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??
}

Должно получиться Duration с 1 часом, так как следующая полночь будет суббота, 9 декабря 2017 г., 00:00:00 GMT-0300 (-03).


person cspinetta    schedule 08.12.2017    source источник


Ответы (3)


После просмотра документов я наконец нашел недостающую ссылку: Date::and_hms .

Так что на самом деле это очень просто:

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);
}

Идея проста:

  • увеличить DateTime на завтра,
  • извлеките часть Date, которая сохраняет часовой пояс,
  • восстанавливает новый DateTime, указывая "00:00:00" Time с and_hms.

В and_hms есть panic!, поэтому нужно быть осторожным, чтобы указать правильное время.

person Matthieu M.    schedule 08.12.2017
comment
let tomorrow_midnight = now + Duration::days(1); достаточно. Он даст завтрашнюю дату с 00:00:00 в качестве времени. - person azzamsa; 22.04.2021
comment
@azzamsa: документация и игровая площадка не согласуются с вашей оценкой, см. и последний пример docs.rs/chrono/0.4.19/chrono/naive/ - person Matthieu M.; 22.04.2021

Просто вычтите две даты: полночь и сейчас:

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())
}

По просьбе Матье вы можете написать что-то вроде:

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()
}

Но не уверен, что лучше.

person Boiethios    schedule 08.12.2017
comment
Есть ли способ установить час / минуту / секунду / наносекунды в одном вызове? Помогло бы совсем немного. - person Matthieu M.; 08.12.2017
comment
@MatthieuM. К сожалению, похоже, что это невозможно сделать с меньшим количеством шаблонов: docs .rs / chrono / 0.4.0 / chrono / trait.Timelike.html. - person Boiethios; 08.12.2017
comment
Может быть, было бы проще извлечь NaiveDate и перестроить из него NaiveDateTime, а затем построить новую дату из этого нового NaiveDateTime и часового пояса? - person Matthieu M.; 08.12.2017
comment
@MatthieuM. Даже это кажется трудным. На мой взгляд, в ящике отсутствуют некоторые функции: я написал то, что вы предлагаете, но код менее читабельный. ИМО, имея impl From<Date> for DateTime, было бы здорово, или даже DateTime::new(date: Date, time: Time). - person Boiethios; 08.12.2017
comment
Честно говоря, я в основном стремился уменьшить количество unwrap, так что в этом смысле это чистый выигрыш. - person Matthieu M.; 08.12.2017
comment
Ах! Наконец-то нашел способ получше. Нам не хватало Date::and_hms -> DateTime: это (1) сохраняет часовой пояс и (2) не требует всего танца конструктора. С ним намного аккуратнее. - person Matthieu M.; 08.12.2017
comment
@MatthieuM. Извини я не видел твой ответ - person Boiethios; 09.12.2017
comment
Я не совсем понимаю, о чем вы сожалеете, так что ... хм ... не беспокойтесь? - person Matthieu M.; 09.12.2017
comment
Я обновил свой ответ благодаря вашей подсказке, не видя, что вы добавили свой ответ. - person Boiethios; 09.12.2017
comment
О, не о чем сожалеть! Я бы точно не пришел к своему ответу без вашего и нашего обсуждения; Я на самом деле поддержал ваш ответ, потому что он был мне полезен :) - person Matthieu M.; 09.12.2017

Один из способов - вычислить секунды, оставшиеся до следующей полуночи, учитывая, что time::Tm учитывает летнее время и часовые пояса:

tm_utcoff: i32

Определяет часовой пояс, который использовался для вычисления этого значения времени с разбивкой, включая любые корректировки для перехода на летнее время. Это количество секунд к востоку от UTC. Например, для Тихоокеанского летнего времени США значение -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,
    )
}

Если вам нужна точность в наносекунды, вам нужно сделать еще несколько математических расчетов ...

person attdona    schedule 08.12.2017
comment
Возможно, я бы использовал псевдонимы для удобства чтения, например let seconds_in_a_day = 24 * 3600. - person Boiethios; 08.12.2017