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