chrono_intervals/
lib.rs

1//! # chrono-intervals: Grouped time intervals for Rust
2//!
3//! Create chrono time intervals as "per-day", "per-week" etc.
4//!
5//! ## Usage
6//!
7//! The most convenient way to get intervals is by creating an
8//! [`IntervalGenerator`].
9//!
10//! ```rust
11//! use chrono::{DateTime, TimeZone, Utc};
12//! use chrono_intervals::{IntervalGenerator};
13//!
14//! let begin = DateTime::parse_from_rfc3339("2022-06-25T08:23:45.000000Z").unwrap();
15//! let end = DateTime::parse_from_rfc3339("2022-06-27T09:31:12.000000Z").unwrap();
16//!
17//! let daily_intervals = IntervalGenerator::new().get_intervals(begin, end);
18//!
19//! assert_eq!(
20//!     daily_intervals,
21//!     vec![
22//!         (
23//!             Utc.ymd(2022, 6, 25).and_hms(0, 0, 0),
24//!             Utc.ymd(2022, 6, 25).and_hms_milli(23, 59, 59, 999),
25//!         ),
26//!         (
27//!             Utc.ymd(2022, 6, 26).and_hms(0, 0, 0),
28//!             Utc.ymd(2022, 6, 26).and_hms_milli(23, 59, 59, 999),
29//!         ),
30//!         (
31//!             Utc.ymd(2022, 6, 27).and_hms(0, 0, 0),
32//!             Utc.ymd(2022, 6, 27).and_hms_milli(23, 59, 59, 999),
33//!         ),
34//!     ]
35//! );
36//! ```
37//!
38//! The [`IntervalGenerator`] can be configured in many ways. Let's look at an
39//! example of retrieving monthly intervals but in the Pacific Daylight Time
40//! (PDT) timezone:
41//!
42//! ```rust
43//! use chrono::{DateTime, TimeZone, Utc};
44//! use chrono_intervals::{Grouping, IntervalGenerator};
45//!
46//! // We want to obtain monthly intervals for month in PDT instead of in UTC.
47//! let begin = DateTime::parse_from_rfc3339("2022-06-10T12:23:45.000000-07:00").unwrap();
48//! let end = DateTime::parse_from_rfc3339("2022-08-26T12:23:45.000000-07:00").unwrap();
49//!
50//! // PDT is 7h behind of UTC (towards the **west**), thus the
51//! // `offset_west_seconds` are 7*3600
52//! let pdt_offset_west_seconds = 7 * 3600;
53//!
54//! let monthly_intervals = IntervalGenerator::new()
55//!     .with_grouping(Grouping::PerMonth)
56//!     .with_offset_west_secs(pdt_offset_west_seconds)
57//!     .get_intervals(begin, end);
58//!
59//! // In UTC, we expect the intervals to start 7h after the month boundary.
60//! assert_eq!(
61//!     monthly_intervals,
62//!     vec![
63//!         (
64//!             Utc.ymd(2022, 6, 1).and_hms(7, 0, 0),
65//!             Utc.ymd(2022, 7, 1).and_hms_milli(6, 59, 59, 999),
66//!         ),
67//!         (
68//!             Utc.ymd(2022, 7, 1).and_hms(7, 0, 0),
69//!             Utc.ymd(2022, 8, 1).and_hms_milli(6, 59, 59, 999),
70//!         ),
71//!         (
72//!             Utc.ymd(2022, 8, 1).and_hms(7, 0, 0),
73//!             Utc.ymd(2022, 9, 1).and_hms_milli(6, 59, 59, 999),
74//!         ),
75//!     ]
76//! );
77//! ```
78//!
79//! ### Configuration options and defaults
80//!
81//! Here is an overview of configurable options and their defaults:
82//! - The interval grouping: You can choose any grouping represented in,
83//!   [`Grouping`], the default is [`Grouping::PerDay`].
84//! - The time span between the end of one interval and the beginning of the
85//!   next (precision): This defaults to 1ms but can be overwritten by passing
86//!   an arbitrary [`chrono::Duration`]. We do not check that the precision is
87//!   reasonable. You probably want to set it to the smallest duration that you
88//!   still consider, e.g. milliseconds or microseconds.
89//! - The offset in seconds towards the west of your local timezone: If you want
90//!   time intervals for e.g. Pacific Daylight Time (PDT) which is at GMT-7, you
91//!   have to pass 7*3600, so the time difference in seconds with a shift
92//!   towards the west as _positive_ values. Central European Time (CET) at
93//!   GMT+1 for example would need -3600 offset seconds towards the west.
94//! - Whether the first interval extends to before `begin` or not: By default,
95//!   the first interval will start on the boundary _before_ `begin`. You can
96//!   switch this off if you want only full intervals that are strickly _after_
97//!   `begin`.
98//! - Whether the last interval extends to _after_ `end` or not: By default, the
99//!   last interval will end at the boundary _after_ `end`. You can switch this
100//!   off if you want only full intervals that are strickly _before_ `end`.
101//!
102//! Let's look at an example with all configuration options used:
103//!
104//! ```rust
105//! use chrono::{DateTime, Duration, TimeZone, Utc};
106//! use chrono_intervals::{Grouping, IntervalGenerator};
107//!
108//! let begin = DateTime::parse_from_rfc3339("2022-10-02T08:23:45.000000Z").unwrap();
109//! let end = DateTime::parse_from_rfc3339("2022-10-18T08:23:45.000000Z").unwrap();
110//!
111//! let inter_gen = IntervalGenerator::new()
112//!     .with_grouping(Grouping::PerWeek)
113//!     .with_precision(Duration::microseconds(1))
114//!     .with_offset_west_secs(-3600)
115//!     .without_extended_begin()
116//!     .without_extended_end();
117//!
118//! let weekly_intervals = inter_gen.get_intervals(begin, end);
119//!
120//! assert_eq!(
121//!     weekly_intervals,
122//!     vec![
123//!         (
124//!             Utc.ymd(2022, 10, 2).and_hms(23, 0, 0),
125//!             Utc.ymd(2022, 10, 9).and_hms_micro(22, 59, 59, 999999),
126//!         ),
127//!         (
128//!             Utc.ymd(2022, 10, 9).and_hms(23, 0, 0),
129//!             Utc.ymd(2022, 10, 16).and_hms_micro(22, 59, 59, 999999),
130//!         ),
131//!     ]
132//! );
133//! ```
134//!
135//! ## Using functions instead of the generator
136//!
137//! The generator is the most convenient way. However you can also use two
138//! different functions to obtain intervals:
139//! - [get_extended_utc_intervals] returns grouped intervals which enclose the
140//!   `begin` and `end` and have a precision of 1ms. This is pretty close to
141//!   the default [`IntervalGenerator`] behavior, just that you have to
142//!   specify a [`Grouping`].
143//! - [get_utc_intervals_opts] returns grouped intervals and allows to specify
144//!   all options that the generator also accepts.
145//!
146//! ### Examples
147//!
148//! Get daily intervals between two times with default options:
149//! ```rust
150//! use chrono::{DateTime, TimeZone, Utc};
151//! use chrono_intervals::{Grouping, get_extended_utc_intervals};
152//!
153//! let begin = DateTime::parse_from_rfc3339("2022-06-25T08:23:45.000000Z").unwrap();
154//! let end = DateTime::parse_from_rfc3339("2022-06-27T09:31:12.000000Z").unwrap();
155//!
156//! let daily_intervals =
157//!     get_extended_utc_intervals(begin, end, &Grouping::PerDay, 0);
158//!
159//! assert_eq!(
160//!     daily_intervals,
161//!     vec![
162//!         (
163//!             Utc.ymd(2022, 6, 25).and_hms(0, 0, 0),
164//!             Utc.ymd(2022, 6, 25).and_hms_milli(23, 59, 59, 999),
165//!         ),
166//!         (
167//!             Utc.ymd(2022, 6, 26).and_hms(0, 0, 0),
168//!             Utc.ymd(2022, 6, 26).and_hms_milli(23, 59, 59, 999),
169//!         ),
170//!         (
171//!             Utc.ymd(2022, 6, 27).and_hms(0, 0, 0),
172//!             Utc.ymd(2022, 6, 27).and_hms_milli(23, 59, 59, 999),
173//!         ),
174//!     ]
175//! );
176//! ```
177//!
178//! Get monthly intervals with default options in the Pacific Daylight Time
179//! (PDT) timezone:
180//! ```rust
181//! use chrono::{DateTime, TimeZone, Utc};
182//! use chrono_intervals::{Grouping, get_extended_utc_intervals};
183//!
184//! // We want to obtain monthly intervals for months in PDT instead of in UTC.
185//! let begin = DateTime::parse_from_rfc3339("2022-06-10T12:23:45.000000-07:00").unwrap();
186//! let end = DateTime::parse_from_rfc3339("2022-08-26T12:23:45.000000-07:00").unwrap();
187//!
188//! // PDT is 7h behind of UTC (towards the **west**), thus the
189//! // `offset_west_seconds` are 7*3600
190//! let pdt_offset_west_seconds = 7 * 3600;
191//!
192//! let monthly_intervals =
193//!     get_extended_utc_intervals(begin, end, &Grouping::PerMonth, pdt_offset_west_seconds);
194//!
195//! // In UTC, we expect the intervals to start 7h after the day boundary.
196//! assert_eq!(
197//!     monthly_intervals,
198//!     vec![
199//!         (
200//!             Utc.ymd(2022, 6, 1).and_hms(7, 0, 0),
201//!             Utc.ymd(2022, 7, 1).and_hms_milli(6, 59, 59, 999),
202//!         ),
203//!         (
204//!             Utc.ymd(2022, 7, 1).and_hms(7, 0, 0),
205//!             Utc.ymd(2022, 8, 1).and_hms_milli(6, 59, 59, 999),
206//!         ),
207//!         (
208//!             Utc.ymd(2022, 8, 1).and_hms(7, 0, 0),
209//!             Utc.ymd(2022, 9, 1).and_hms_milli(6, 59, 59, 999),
210//!         ),
211//!     ]
212//! );
213//! ```
214//!
215//! Specify options for [`get_utc_intervals_opts`]:
216//! ```rust
217//! use chrono::{DateTime, Duration, TimeZone, Utc};
218//! use chrono_intervals::{Grouping, get_utc_intervals_opts};
219//!
220//! let begin = DateTime::parse_from_rfc3339("2022-06-15T08:23:45.000000Z").unwrap();
221//! let end = DateTime::parse_from_rfc3339("2022-06-30T09:31:12.000000Z").unwrap();
222//!
223//! let weekly_intervals =
224//!     get_utc_intervals_opts(
225//!         begin,
226//!         end,
227//!         &Grouping::PerWeek,
228//!         0,
229//!         Duration::microseconds(1),  // interval end is 1µs before the next
230//!         false,                      // start on the boundary after `start`
231//!         true,                       // end at the boundary after `end`
232//!     );
233//!
234//! assert_eq!(
235//!     weekly_intervals,
236//!     vec![
237//!         (
238//!             // First interval begins **after** `begin`
239//!             Utc.ymd(2022, 6, 20).and_hms(0, 0, 0),
240//!             Utc.ymd(2022, 6, 26).and_hms_micro(23, 59, 59, 999999),
241//!         ),
242//!         (
243//!             Utc.ymd(2022, 6, 27).and_hms(0, 0, 0),
244//!             // Last interval ends **after** `end`
245//!             Utc.ymd(2022, 7, 3).and_hms_micro(23, 59, 59, 999999),
246//!         ),
247//!     ]
248//! );
249//! ```
250//!
251mod generator;
252mod grouping;
253mod intervals;
254mod intervals_impl;
255
256use chrono::DateTime;
257pub use generator::IntervalGenerator;
258pub use grouping::Grouping;
259pub use intervals::{get_extended_utc_intervals, get_utc_intervals_opts};
260
261/// Error type of the crate.
262pub type Error = Box<dyn std::error::Error>;
263
264/// A tuple of `chrono::DateTime` objects forming a time interval.
265pub type TimeInterval<T> = (DateTime<T>, DateTime<T>);