#date-time #time #interval #date

chrono-intervals

Create chrono time intervals as per-day, per-week etc

3 releases (breaking)

0.3.0 Nov 5, 2022
0.2.0 Nov 1, 2022
0.1.0 Oct 31, 2022

#288 in Date and time

Download history 222/week @ 2024-07-24 65/week @ 2024-07-31 77/week @ 2024-08-07 58/week @ 2024-08-14 45/week @ 2024-08-21 66/week @ 2024-08-28 63/week @ 2024-09-04 95/week @ 2024-09-11 94/week @ 2024-09-18 114/week @ 2024-09-25 58/week @ 2024-10-02 71/week @ 2024-10-09 83/week @ 2024-10-16 95/week @ 2024-10-23 127/week @ 2024-10-30 80/week @ 2024-11-06

398 downloads per month

MIT/Apache

30KB
286 lines

chrono-intervals: Grouped time intervals for Rust

Create chrono time intervals as "per-day", "per-week" etc.

Usage

The most convenient way to get intervals is by creating an IntervalGenerator.

use chrono::{DateTime, TimeZone, Utc};
use chrono_intervals::{IntervalGenerator};

let begin = DateTime::parse_from_rfc3339("2022-06-25T08:23:45.000000Z").unwrap();
let end = DateTime::parse_from_rfc3339("2022-06-27T09:31:12.000000Z").unwrap();

let daily_intervals = IntervalGenerator::new().get_intervals(begin, end);

assert_eq!(
    daily_intervals,
    vec![
        (
            Utc.ymd(2022, 6, 25).and_hms(0, 0, 0),
            Utc.ymd(2022, 6, 25).and_hms_milli(23, 59, 59, 999),
        ),
        (
            Utc.ymd(2022, 6, 26).and_hms(0, 0, 0),
            Utc.ymd(2022, 6, 26).and_hms_milli(23, 59, 59, 999),
        ),
        (
            Utc.ymd(2022, 6, 27).and_hms(0, 0, 0),
            Utc.ymd(2022, 6, 27).and_hms_milli(23, 59, 59, 999),
        ),
    ]
);

The IntervalGenerator can be configured in many ways. Let's look at an example of retrieving monthly intervals but in the Pacific Daylight Time (PDT) timezone:

use chrono::{DateTime, TimeZone, Utc};
use chrono_intervals::{Grouping, IntervalGenerator};

// We want to obtain monthly intervals for month in PDT instead of in UTC.
let begin = DateTime::parse_from_rfc3339("2022-06-10T12:23:45.000000-07:00").unwrap();
let end = DateTime::parse_from_rfc3339("2022-08-26T12:23:45.000000-07:00").unwrap();

// PDT is 7h behind of UTC (towards the **west**), thus the
// `offset_west_seconds` are 7*3600
let pdt_offset_west_seconds = 7 * 3600;

let monthly_intervals = IntervalGenerator::new()
    .with_grouping(Grouping::PerMonth)
    .with_offset_west_secs(pdt_offset_west_seconds)
    .get_intervals(begin, end);

// In UTC, we expect the intervals to start 7h after the month boundary.
assert_eq!(
    monthly_intervals,
    vec![
        (
            Utc.ymd(2022, 6, 1).and_hms(7, 0, 0),
            Utc.ymd(2022, 7, 1).and_hms_milli(6, 59, 59, 999),
        ),
        (
            Utc.ymd(2022, 7, 1).and_hms(7, 0, 0),
            Utc.ymd(2022, 8, 1).and_hms_milli(6, 59, 59, 999),
        ),
        (
            Utc.ymd(2022, 8, 1).and_hms(7, 0, 0),
            Utc.ymd(2022, 9, 1).and_hms_milli(6, 59, 59, 999),
        ),
    ]
);

Configuration options and defaults

Here is an overview of configurable options and their defaults:

  • The interval grouping: You can choose any grouping represented in, Grouping, the default is Grouping::PerDay.
  • The time span between the end of one interval and the beginning of the next (precision): This defaults to 1ms but can be overwritten by passing an arbitrary chrono::Duration. We do not check that the precision is reasonable. You probably want to set it to the smallest duration that you still consider, e.g. milliseconds or microseconds.
  • The offset in seconds towards the west of your local timezone: If you want time intervals for e.g. Pacific Daylight Time (PDT) which is at GMT-7, you have to pass 7*3600, so the time difference in seconds with a shift towards the west as positive values. Central European Time (CET) at GMT+1 for example would need -3600 offset seconds towards the west.
  • Whether the first interval extends to before begin or not: By default, the first interval will start on the boundary before begin. You can switch this off if you want only full intervals that are strickly after begin.
  • Whether the last interval extends to after end or not: By default, the last interval will end at the boundary after end. You can switch this off if you want only full intervals that are strickly before end.

Let's look at an example with all configuration options used:

use chrono::{DateTime, Duration, TimeZone, Utc};
use chrono_intervals::{Grouping, IntervalGenerator};

let begin = DateTime::parse_from_rfc3339("2022-10-02T08:23:45.000000Z").unwrap();
let end = DateTime::parse_from_rfc3339("2022-10-18T08:23:45.000000Z").unwrap();

let inter_gen = IntervalGenerator::new()
    .with_grouping(Grouping::PerWeek)
    .with_precision(Duration::microseconds(1))
    .with_offset_west_secs(-3600)
    .without_extended_begin()
    .without_extended_end();

let weekly_intervals = inter_gen.get_intervals(begin, end);

assert_eq!(
    weekly_intervals,
    vec![
        (
            Utc.ymd(2022, 10, 2).and_hms(23, 0, 0),
            Utc.ymd(2022, 10, 9).and_hms_micro(22, 59, 59, 999999),
        ),
        (
            Utc.ymd(2022, 10, 9).and_hms(23, 0, 0),
            Utc.ymd(2022, 10, 16).and_hms_micro(22, 59, 59, 999999),
        ),
    ]
);

Using functions instead of the generator

The generator is the most convenient way. However you can also use two different functions to obtain intervals:

  • get_extended_utc_intervals returns grouped intervals which enclose the begin and end and have a precision of 1ms. This is pretty close to the default IntervalGenerator behavior, just that you have to specify a Grouping.
  • get_utc_intervals_opts returns grouped intervals and allows to specify all options that the generator also accepts.

Examples

Get daily intervals between two times with default options:

use chrono::{DateTime, TimeZone, Utc};
use chrono_intervals::{Grouping, get_extended_utc_intervals};

let begin = DateTime::parse_from_rfc3339("2022-06-25T08:23:45.000000Z").unwrap();
let end = DateTime::parse_from_rfc3339("2022-06-27T09:31:12.000000Z").unwrap();

let daily_intervals =
    get_extended_utc_intervals(begin, end, &Grouping::PerDay, 0);

assert_eq!(
    daily_intervals,
    vec![
        (
            Utc.ymd(2022, 6, 25).and_hms(0, 0, 0),
            Utc.ymd(2022, 6, 25).and_hms_milli(23, 59, 59, 999),
        ),
        (
            Utc.ymd(2022, 6, 26).and_hms(0, 0, 0),
            Utc.ymd(2022, 6, 26).and_hms_milli(23, 59, 59, 999),
        ),
        (
            Utc.ymd(2022, 6, 27).and_hms(0, 0, 0),
            Utc.ymd(2022, 6, 27).and_hms_milli(23, 59, 59, 999),
        ),
    ]
);

Get monthly intervals with default options in the Pacific Daylight Time (PDT) timezone:

use chrono::{DateTime, TimeZone, Utc};
use chrono_intervals::{Grouping, get_extended_utc_intervals};

// We want to obtain monthly intervals for months in PDT instead of in UTC.
let begin = DateTime::parse_from_rfc3339("2022-06-10T12:23:45.000000-07:00").unwrap();
let end = DateTime::parse_from_rfc3339("2022-08-26T12:23:45.000000-07:00").unwrap();

// PDT is 7h behind of UTC (towards the **west**), thus the
// `offset_west_seconds` are 7*3600
let pdt_offset_west_seconds = 7 * 3600;

let monthly_intervals =
    get_extended_utc_intervals(begin, end, &Grouping::PerMonth, pdt_offset_west_seconds);

// In UTC, we expect the intervals to start 7h after the day boundary.
assert_eq!(
    monthly_intervals,
    vec![
        (
            Utc.ymd(2022, 6, 1).and_hms(7, 0, 0),
            Utc.ymd(2022, 7, 1).and_hms_milli(6, 59, 59, 999),
        ),
        (
            Utc.ymd(2022, 7, 1).and_hms(7, 0, 0),
            Utc.ymd(2022, 8, 1).and_hms_milli(6, 59, 59, 999),
        ),
        (
            Utc.ymd(2022, 8, 1).and_hms(7, 0, 0),
            Utc.ymd(2022, 9, 1).and_hms_milli(6, 59, 59, 999),
        ),
    ]
);

Specify options for get_utc_intervals_opts:

use chrono::{DateTime, Duration, TimeZone, Utc};
use chrono_intervals::{Grouping, get_utc_intervals_opts};

let begin = DateTime::parse_from_rfc3339("2022-06-15T08:23:45.000000Z").unwrap();
let end = DateTime::parse_from_rfc3339("2022-06-30T09:31:12.000000Z").unwrap();

let weekly_intervals =
    get_utc_intervals_opts(
        begin,
        end,
        &Grouping::PerWeek,
        0,
        Duration::microseconds(1),  // interval end is 1µs before the next
        false,                      // start on the boundary after `start`
        true,                       // end at the boundary after `end`
    );

assert_eq!(
    weekly_intervals,
    vec![
        (
            // First interval begins **after** `begin`
            Utc.ymd(2022, 6, 20).and_hms(0, 0, 0),
            Utc.ymd(2022, 6, 26).and_hms_micro(23, 59, 59, 999999),
        ),
        (
            Utc.ymd(2022, 6, 27).and_hms(0, 0, 0),
            // Last interval ends **after** `end`
            Utc.ymd(2022, 7, 3).and_hms_micro(23, 59, 59, 999999),
        ),
    ]
);

Dependencies

~1MB
~18K SLoC