chrono-intervals 0.3.0

Create chrono time intervals as per-day, per-week etc.
Documentation
# [chrono-intervals][docsrs]: Grouped time intervals for Rust

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

[docsrs]: https://docs.rs/chrono-intervals

## Usage

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

```rust
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:

```rust
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:

```rust
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:

```rust
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:

```rust
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`:

```rust
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),
        ),
    ]
);
```