Coverage Report

Created: 2025-07-11 07:01

/src/chrono/src/naive/mod.rs
Line
Count
Source (jump to first uncovered line)
1
//! Date and time types unconcerned with timezones.
2
//!
3
//! They are primarily building blocks for other types
4
//! (e.g. [`TimeZone`](../offset/trait.TimeZone.html)),
5
//! but can be also used for the simpler date and time handling.
6
7
use core::hash::{Hash, Hasher};
8
use core::ops::RangeInclusive;
9
10
use crate::Weekday;
11
use crate::expect;
12
13
pub(crate) mod date;
14
pub(crate) mod datetime;
15
mod internals;
16
pub(crate) mod isoweek;
17
pub(crate) mod time;
18
19
#[allow(deprecated)]
20
pub use self::date::{MAX_DATE, MIN_DATE};
21
pub use self::date::{NaiveDate, NaiveDateDaysIterator, NaiveDateWeeksIterator};
22
#[allow(deprecated)]
23
pub use self::datetime::{MAX_DATETIME, MIN_DATETIME, NaiveDateTime};
24
pub use self::isoweek::IsoWeek;
25
pub use self::time::NaiveTime;
26
27
#[cfg(feature = "__internal_bench")]
28
#[doc(hidden)]
29
pub use self::internals::YearFlags as __BenchYearFlags;
30
31
/// A week represented by a [`NaiveDate`] and a [`Weekday`] which is the first
32
/// day of the week.
33
#[derive(Clone, Copy, Debug, Eq)]
34
pub struct NaiveWeek {
35
    date: NaiveDate,
36
    start: Weekday,
37
}
38
39
impl NaiveWeek {
40
    /// Create a new `NaiveWeek`
41
0
    pub(crate) const fn new(date: NaiveDate, start: Weekday) -> Self {
42
0
        Self { date, start }
43
0
    }
44
45
    /// Returns a date representing the first day of the week.
46
    ///
47
    /// # Panics
48
    ///
49
    /// Panics if the first day of the week happens to fall just out of range of `NaiveDate`
50
    /// (more than ca. 262,000 years away from common era).
51
    ///
52
    /// # Examples
53
    ///
54
    /// ```
55
    /// use chrono::{NaiveDate, Weekday};
56
    ///
57
    /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
58
    /// let week = date.week(Weekday::Mon);
59
    /// assert!(week.first_day() <= date);
60
    /// ```
61
    #[inline]
62
    #[must_use]
63
0
    pub const fn first_day(&self) -> NaiveDate {
64
0
        expect(self.checked_first_day(), "first weekday out of range for `NaiveDate`")
65
0
    }
66
67
    /// Returns a date representing the first day of the week or
68
    /// `None` if the date is out of `NaiveDate`'s range
69
    /// (more than ca. 262,000 years away from common era).
70
    ///
71
    /// # Examples
72
    ///
73
    /// ```
74
    /// use chrono::{NaiveDate, Weekday};
75
    ///
76
    /// let date = NaiveDate::MIN;
77
    /// let week = date.week(Weekday::Mon);
78
    /// if let Some(first_day) = week.checked_first_day() {
79
    ///     assert!(first_day == date);
80
    /// } else {
81
    ///     // error handling code
82
    ///     return;
83
    /// };
84
    /// ```
85
    #[inline]
86
    #[must_use]
87
0
    pub const fn checked_first_day(&self) -> Option<NaiveDate> {
88
0
        let start = self.start.num_days_from_monday() as i32;
89
0
        let ref_day = self.date.weekday().num_days_from_monday() as i32;
90
        // Calculate the number of days to subtract from `self.date`.
91
        // Do not construct an intermediate date beyond `self.date`, because that may be out of
92
        // range if `date` is close to `NaiveDate::MAX`.
93
0
        let days = start - ref_day - if start > ref_day { 7 } else { 0 };
94
0
        self.date.add_days(days)
95
0
    }
96
97
    /// Returns a date representing the last day of the week.
98
    ///
99
    /// # Panics
100
    ///
101
    /// Panics if the last day of the week happens to fall just out of range of `NaiveDate`
102
    /// (more than ca. 262,000 years away from common era).
103
    ///
104
    /// # Examples
105
    ///
106
    /// ```
107
    /// use chrono::{NaiveDate, Weekday};
108
    ///
109
    /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
110
    /// let week = date.week(Weekday::Mon);
111
    /// assert!(week.last_day() >= date);
112
    /// ```
113
    #[inline]
114
    #[must_use]
115
0
    pub const fn last_day(&self) -> NaiveDate {
116
0
        expect(self.checked_last_day(), "last weekday out of range for `NaiveDate`")
117
0
    }
118
119
    /// Returns a date representing the last day of the week or
120
    /// `None` if the date is out of `NaiveDate`'s range
121
    /// (more than ca. 262,000 years away from common era).
122
    ///
123
    /// # Examples
124
    ///
125
    /// ```
126
    /// use chrono::{NaiveDate, Weekday};
127
    ///
128
    /// let date = NaiveDate::MAX;
129
    /// let week = date.week(Weekday::Mon);
130
    /// if let Some(last_day) = week.checked_last_day() {
131
    ///     assert!(last_day == date);
132
    /// } else {
133
    ///     // error handling code
134
    ///     return;
135
    /// };
136
    /// ```
137
    #[inline]
138
    #[must_use]
139
0
    pub const fn checked_last_day(&self) -> Option<NaiveDate> {
140
0
        let end = self.start.pred().num_days_from_monday() as i32;
141
0
        let ref_day = self.date.weekday().num_days_from_monday() as i32;
142
        // Calculate the number of days to add to `self.date`.
143
        // Do not construct an intermediate date before `self.date` (like with `first_day()`),
144
        // because that may be out of range if `date` is close to `NaiveDate::MIN`.
145
0
        let days = end - ref_day + if end < ref_day { 7 } else { 0 };
146
0
        self.date.add_days(days)
147
0
    }
148
149
    /// Returns a [`RangeInclusive<T>`] representing the whole week bounded by
150
    /// [first_day](NaiveWeek::first_day) and [last_day](NaiveWeek::last_day) functions.
151
    ///
152
    /// # Panics
153
    ///
154
    /// Panics if the either the first or last day of the week happens to fall just out of range of
155
    /// `NaiveDate` (more than ca. 262,000 years away from common era).
156
    ///
157
    /// # Examples
158
    ///
159
    /// ```
160
    /// use chrono::{NaiveDate, Weekday};
161
    ///
162
    /// let date = NaiveDate::from_ymd_opt(2022, 4, 18).unwrap();
163
    /// let week = date.week(Weekday::Mon);
164
    /// let days = week.days();
165
    /// assert!(days.contains(&date));
166
    /// ```
167
    #[inline]
168
    #[must_use]
169
0
    pub const fn days(&self) -> RangeInclusive<NaiveDate> {
170
0
        // `expect` doesn't work because `RangeInclusive` is not `Copy`
171
0
        match self.checked_days() {
172
0
            Some(val) => val,
173
0
            None => panic!("{}", "first or last weekday is out of range for `NaiveDate`"),
174
        }
175
0
    }
176
177
    /// Returns an [`Option<RangeInclusive<T>>`] representing the whole week bounded by
178
    /// [checked_first_day](NaiveWeek::checked_first_day) and
179
    /// [checked_last_day](NaiveWeek::checked_last_day) functions.
180
    ///
181
    /// Returns `None` if either of the boundaries are out of `NaiveDate`'s range
182
    /// (more than ca. 262,000 years away from common era).
183
    ///
184
    ///
185
    /// # Examples
186
    ///
187
    /// ```
188
    /// use chrono::{NaiveDate, Weekday};
189
    ///
190
    /// let date = NaiveDate::MAX;
191
    /// let week = date.week(Weekday::Mon);
192
    /// let _days = match week.checked_days() {
193
    ///     Some(d) => d,
194
    ///     None => {
195
    ///         // error handling code
196
    ///         return;
197
    ///     }
198
    /// };
199
    /// ```
200
    #[inline]
201
    #[must_use]
202
0
    pub const fn checked_days(&self) -> Option<RangeInclusive<NaiveDate>> {
203
0
        match (self.checked_first_day(), self.checked_last_day()) {
204
0
            (Some(first), Some(last)) => Some(first..=last),
205
0
            (_, _) => None,
206
        }
207
0
    }
208
}
209
210
impl PartialEq for NaiveWeek {
211
0
    fn eq(&self, other: &Self) -> bool {
212
0
        self.first_day() == other.first_day()
213
0
    }
214
}
215
216
impl Hash for NaiveWeek {
217
0
    fn hash<H: Hasher>(&self, state: &mut H) {
218
0
        self.first_day().hash(state);
219
0
    }
220
}
221
222
/// A duration in calendar days.
223
///
224
/// This is useful because when using `TimeDelta` it is possible that adding `TimeDelta::days(1)`
225
/// doesn't increment the day value as expected due to it being a fixed number of seconds. This
226
/// difference applies only when dealing with `DateTime<TimeZone>` data types and in other cases
227
/// `TimeDelta::days(n)` and `Days::new(n)` are equivalent.
228
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
229
pub struct Days(pub(crate) u64);
230
231
impl Days {
232
    /// Construct a new `Days` from a number of days
233
0
    pub const fn new(num: u64) -> Self {
234
0
        Self(num)
235
0
    }
236
}
237
238
/// Serialization/Deserialization of `NaiveDateTime` in alternate formats
239
///
240
/// The various modules in here are intended to be used with serde's [`with` annotation] to
241
/// serialize as something other than the default ISO 8601 format.
242
///
243
/// [`with` annotation]: https://serde.rs/field-attrs.html#with
244
#[cfg(feature = "serde")]
245
pub mod serde {
246
    pub use super::datetime::serde::*;
247
}
248
249
#[cfg(test)]
250
mod test {
251
    use crate::{NaiveDate, NaiveWeek, Weekday};
252
    use std::hash::{DefaultHasher, Hash, Hasher};
253
    #[test]
254
    fn test_naiveweek() {
255
        let date = NaiveDate::from_ymd_opt(2022, 5, 18).unwrap();
256
        let asserts = [
257
            (Weekday::Mon, "Mon 2022-05-16", "Sun 2022-05-22"),
258
            (Weekday::Tue, "Tue 2022-05-17", "Mon 2022-05-23"),
259
            (Weekday::Wed, "Wed 2022-05-18", "Tue 2022-05-24"),
260
            (Weekday::Thu, "Thu 2022-05-12", "Wed 2022-05-18"),
261
            (Weekday::Fri, "Fri 2022-05-13", "Thu 2022-05-19"),
262
            (Weekday::Sat, "Sat 2022-05-14", "Fri 2022-05-20"),
263
            (Weekday::Sun, "Sun 2022-05-15", "Sat 2022-05-21"),
264
        ];
265
        for (start, first_day, last_day) in asserts {
266
            let week = date.week(start);
267
            let days = week.days();
268
            assert_eq!(Ok(week.first_day()), NaiveDate::parse_from_str(first_day, "%a %Y-%m-%d"));
269
            assert_eq!(Ok(week.last_day()), NaiveDate::parse_from_str(last_day, "%a %Y-%m-%d"));
270
            assert!(days.contains(&date));
271
        }
272
    }
273
274
    #[test]
275
    fn test_naiveweek_min_max() {
276
        let date_max = NaiveDate::MAX;
277
        assert!(date_max.week(Weekday::Mon).first_day() <= date_max);
278
        let date_min = NaiveDate::MIN;
279
        assert!(date_min.week(Weekday::Mon).last_day() >= date_min);
280
    }
281
282
    #[test]
283
    fn test_naiveweek_checked_no_panic() {
284
        let date_max = NaiveDate::MAX;
285
        if let Some(last) = date_max.week(Weekday::Mon).checked_last_day() {
286
            assert!(last == date_max);
287
        }
288
        let date_min = NaiveDate::MIN;
289
        if let Some(first) = date_min.week(Weekday::Mon).checked_first_day() {
290
            assert!(first == date_min);
291
        }
292
        let _ = date_min.week(Weekday::Mon).checked_days();
293
        let _ = date_max.week(Weekday::Mon).checked_days();
294
    }
295
296
    #[test]
297
    fn test_naiveweek_eq() {
298
        let a =
299
            NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Mon };
300
        let b =
301
            NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 4).unwrap(), start: Weekday::Mon };
302
        assert_eq!(a, b);
303
304
        let c =
305
            NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Sun };
306
        assert_ne!(a, c);
307
        assert_ne!(b, c);
308
    }
309
310
    #[test]
311
    fn test_naiveweek_hash() {
312
        let a =
313
            NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Mon };
314
        let b =
315
            NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 4).unwrap(), start: Weekday::Mon };
316
        let c =
317
            NaiveWeek { date: NaiveDate::from_ymd_opt(2025, 4, 3).unwrap(), start: Weekday::Sun };
318
319
        let mut hasher = DefaultHasher::default();
320
        a.hash(&mut hasher);
321
        let a_hash = hasher.finish();
322
323
        hasher = DefaultHasher::default();
324
        b.hash(&mut hasher);
325
        let b_hash = hasher.finish();
326
327
        hasher = DefaultHasher::default();
328
        c.hash(&mut hasher);
329
        let c_hash = hasher.finish();
330
331
        assert_eq!(a_hash, b_hash);
332
        assert_ne!(b_hash, c_hash);
333
        assert_ne!(a_hash, c_hash);
334
    }
335
}