Coverage Report

Created: 2025-08-13 21:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/a220/proj/radnelac/src/calendar/symmetry.rs
Line
Count
Source
1
// This Source Code Form is subject to the terms of the Mozilla Public
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5
use crate::calendar::gregorian::Gregorian;
6
use crate::calendar::prelude::CommonDate;
7
use crate::calendar::prelude::CommonWeekOfYear;
8
use crate::calendar::prelude::GuaranteedMonth;
9
use crate::calendar::prelude::HasLeapYears;
10
use crate::calendar::prelude::Quarter;
11
use crate::calendar::prelude::ToFromCommonDate;
12
use crate::calendar::AllowYearZero;
13
use crate::calendar::CalendarMoment;
14
use crate::calendar::OrdinalDate;
15
use crate::calendar::ToFromOrdinalDate;
16
use crate::common::error::CalendarError;
17
use crate::day_count::BoundedDayCount;
18
use crate::day_count::CalculatedBounds;
19
use crate::day_count::Epoch;
20
use crate::day_count::Fixed;
21
use crate::day_count::FromFixed;
22
use crate::day_count::ToFixed;
23
use std::num::NonZero;
24
25
use crate::common::math::TermNum;
26
27
#[allow(unused_imports)] //FromPrimitive is needed for derive
28
use num_traits::FromPrimitive;
29
30
#[allow(non_snake_case)]
31
struct SymmetryParams {
32
    C: i64,
33
    L: i64,
34
    K: i64,
35
}
36
37
const NORTHWARD_EQUINOX_PARAMS: SymmetryParams = SymmetryParams {
38
    C: 293,
39
    L: 52,
40
    K: 146,
41
};
42
43
const NORTH_SOLSTICE_PARAMS: SymmetryParams = SymmetryParams {
44
    C: 389,
45
    L: 69,
46
    K: 194,
47
};
48
49
/// Represents a month of the Symmetry calendars
50
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)]
51
pub enum SymmetryMonth {
52
    January = 1,
53
    February,
54
    March,
55
    April,
56
    May,
57
    June,
58
    July,
59
    August,
60
    September,
61
    October,
62
    November,
63
    December,
64
    /// Only appears in leap years
65
    Irvember,
66
}
67
68
/// Represents a date in one of the Symmetry calendars
69
///
70
/// ## Introduction
71
///
72
/// The Symmetry calendars are a collection of calendar systems developed by Dr. Irvin L. Bromberg.
73
/// Bromberg proposed 2 leap year rules and 2 month length rules, which can be combined to form
74
/// 4 variants of the Symmetry calendar.
75
///
76
/// ## Variants
77
///
78
///
79
/// | `T`       | `U`       | Alias                   | Leap cycle | Quarter length    |
80
/// |-----------|-----------|-------------------------|------------|-------------------|
81
/// | [`true`]  | [`true`]  | [`Symmetry454`]         | 293 year   | 4 + 5 + 4 weeks   |
82
/// | [`false`] | [`true`]  | [`Symmetry010`]         | 293 year   | 30 + 31 + 30 days |
83
/// | [`true`]  | [`false`] | [`Symmetry454Solstice`] | 389 year   | 4 + 5 + 4 weeks   |
84
/// | [`false`] | [`false`] | [`Symmetry010Solstice`] | 389 year   | 30 + 31 + 30 days |
85
///
86
/// The combinations are summarized in the table above. Columns `T` and `U` are the type parameters.
87
/// Column Alias refers to the type aliases provided for convenience.
88
///
89
/// The placement of leap years  is symmetric within a cycle - the length of the cycle is in
90
/// column Leap Cycle. The 293 year leap rule approximates the northward equinox, while the 389
91
/// year rule approximates the north solstice.
92
///
93
/// Column Quarter Length refers to how days are distributed within a common year.
94
/// Symmetry calendars have years split into 4 quarters. Each quarter is composed of
95
/// 3 months. In Symmetry454 calendars, the months have lengths of 4, 5, and 4 weeks respectively.
96
/// In Symmetry010 calendars, the months have lenghts of 30, 31, and 30 days respectively.
97
///
98
/// ## Irvember
99
///
100
/// The Symmetry calendars have leap weeks instead of leap days. The extra week added in a leap
101
/// year is a standalone thirteenth month called [Irvember](SymmetryMonth::Irvember).
102
/// Dr. Bromberg suggested an alternative scheme where the extra week is added to
103
/// December instead of being standalone - however this alternative scheme is **not** implemented.
104
///
105
/// ## Bromberg's Warning
106
///
107
/// The calculations used in this library mirror Dr. Bromberg's reference documents closely
108
/// while still being idiomatic Rust. From *Basic Symmetry454 and Symmetry010 Calendar
109
/// Arithmetic* by Bromberg:
110
///
111
/// > Symmetry454 and Symmetry010 calendar arithmetic is very simple, but there is a tendency
112
/// > for those who are programming their first implementation of these calendars to immediately
113
/// > cut corners that may suffice for a limited range of dates, or to skip thorough validation
114
/// > of their implementation.
115
/// >
116
/// > Please don’t deviate from the arithmetic outlined herein. Please “stick to the script”.
117
/// > Don’t try to invent your own arithmetic using novel expressions. There is no reason to do
118
/// > so, because this arithmetic is in the public domain, royalty free. The algorithm steps
119
/// > documented herein were carefully designed for efficiency, simplicity, and clarity of
120
/// > program code, and were thoroughly validated. Cutting corners will most likely result in
121
/// > harder-to-read programs that are more difficult to maintain and troubleshoot. In all
122
/// > probability a novel expression intended to “simplify” the arithmetic documented herein
123
/// > will actually prove to function erroneously under specific circumstances. It is just not
124
/// > worth wasting the time on the trouble that will make for you.
125
///
126
/// ## Further reading
127
/// + Dr. Irvin L. Bromberg
128
///   + [*Basic Symmetry454 and Symmetry010 Calendar Arithmetic*](https://kalendis.free.nf/Symmetry454-Arithmetic.pdf)
129
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
130
pub struct Symmetry<const T: bool, const U: bool>(CommonDate);
131
132
/// Symmetry454 calendar with 293 year leap rule
133
///
134
/// See [Symmetry] for more details.
135
pub type Symmetry454 = Symmetry<true, true>;
136
/// Symmetry010 calendar with 293 year leap rule
137
///
138
/// See [Symmetry] for more details.
139
pub type Symmetry010 = Symmetry<false, true>;
140
/// Symmetry454 calendar with 389 year leap rule
141
///
142
/// See [Symmetry] for more details.
143
pub type Symmetry454Solstice = Symmetry<true, false>;
144
/// Symmetry010 calendar with 389 year leap rule
145
///
146
/// See [Symmetry] for more details.
147
pub type Symmetry010Solstice = Symmetry<false, false>;
148
149
impl<const T: bool, const U: bool> Symmetry<T, U> {
150
77.1k
    fn params() -> SymmetryParams {
151
77.1k
        if U {
152
38.5k
            NORTHWARD_EQUINOX_PARAMS
153
        } else {
154
38.5k
            NORTH_SOLSTICE_PARAMS
155
        }
156
77.1k
    }
157
158
    /// Returns (T, U)
159
    ///
160
    /// T is true for Symmetry454 calendars and false for Symmetry010 calendars.
161
    /// U is true for the 292 year leap cycle and false for the 389 year leap cycle.
162
    /// See [Symmetry] for more details.
163
0
    pub fn mode(self) -> (bool, bool) {
164
0
        (T, U)
165
0
    }
166
167
    /// Returns the fixed day number of a Symmetry year
168
73.5k
    pub fn new_year_day_unchecked(sym_year: i32, sym_epoch: i64) -> i64 {
169
        //LISTING SymNewYearDay (*Basic Symmetry454 and Symmetry010 Calendar Arithmetic* by Dr. Irvin L. Bromberg)
170
73.5k
        let p = Self::params();
171
        #[allow(non_snake_case)]
172
73.5k
        let E = (sym_year - 1) as i64;
173
73.5k
        sym_epoch + (364 * E) + (7 * ((p.L * E) + p.K).div_euclid(p.C))
174
73.5k
    }
175
176
34.8k
    fn days_before_month_454(sym_month: i64) -> u16 {
177
        //LISTING DaysBeforeMonth (*Basic Symmetry454 and Symmetry010 Calendar Arithmetic* by Dr. Irvin L. Bromberg)
178
34.8k
        ((28 * (sym_month - 1)) + 7 * sym_month.div_euclid(3)) as u16
179
34.8k
    }
180
181
34.3k
    fn days_before_month_010(sym_month: i64) -> u16 {
182
        //LISTING DaysBeforeMonth (*Basic Symmetry454 and Symmetry010 Calendar Arithmetic* by Dr. Irvin L. Bromberg)
183
34.3k
        ((30 * (sym_month - 1)) + sym_month.div_euclid(3)) as u16
184
34.3k
    }
185
186
69.2k
    fn days_before_month(sym_month: u8) -> u16 {
187
69.2k
        if T {
188
34.8k
            Self::days_before_month_454(sym_month as i64)
189
        } else {
190
34.3k
            Self::days_before_month_010(sym_month as i64)
191
        }
192
69.2k
    }
193
194
25.1k
    fn day_of_year(sym_month: u8, sym_day: u8) -> u16 {
195
        //LISTING DayOfYear (*Basic Symmetry454 and Symmetry010 Calendar Arithmetic* by Dr. Irvin L. Bromberg)
196
25.1k
        Self::days_before_month(sym_month) + (sym_day as u16)
197
25.1k
    }
198
199
45.1k
    fn year_from_fixed(fixed: i64, epoch: i64) -> (i32, i64) {
200
        //LISTING FixedToSymYear (*Basic Symmetry454 and Symmetry010 Calendar Arithmetic* by Dr. Irvin L. Bromberg)
201
        // Tempting to cut "corners here" to avoid floating point.
202
        // But the notice at the top of the file reminds us to "stick to the script"
203
45.1k
        let fixed_date = fixed as f64;
204
45.1k
        let sym_epoch = epoch as f64;
205
45.1k
        let cycle_mean_year = if U {
206
22.5k
            365.0 + (71.0 / 293.0)
207
        } else {
208
22.5k
            365.0 + (94.0 / 389.0)
209
        };
210
45.1k
        let sym_year = ((fixed_date - sym_epoch) / cycle_mean_year).ceil() as i32;
211
45.1k
        let start_of_year = Self::new_year_day_unchecked(sym_year, epoch);
212
45.1k
        if start_of_year < fixed {
213
45.0k
            if (fixed - start_of_year) >= 364 {
214
164
                let start_of_next_year = Self::new_year_day_unchecked(sym_year + 1, epoch);
215
164
                if fixed >= start_of_next_year {
216
78
                    (sym_year + 1, start_of_next_year)
217
                } else {
218
86
                    (sym_year, start_of_year)
219
                }
220
            } else {
221
44.8k
                (sym_year, start_of_year)
222
            }
223
84
        } else if start_of_year > fixed {
224
54
            (
225
54
                sym_year - 1,
226
54
                Self::new_year_day_unchecked(sym_year - 1, epoch),
227
54
            )
228
        } else {
229
30
            (sym_year, start_of_year)
230
        }
231
45.1k
    }
232
233
    /// This function is not described by Dr. Bromberg and is not
234
    /// used in conversion to and from other timekeeping systems.
235
    /// Instead it is used for checking if a [CommonDate] is valid.
236
66.0k
    pub fn days_in_month(month: SymmetryMonth) -> u8 {
237
66.0k
        if month == SymmetryMonth::Irvember {
238
1.22k
            7
239
64.7k
        } else if T {
240
33.4k
            (28 + (7 * ((month as u8).modulus(3).div_euclid(2)))) as u8
241
        } else {
242
31.3k
            (30 + (month as u8).modulus(3).div_euclid(2)) as u8
243
        }
244
66.0k
    }
245
}
246
247
impl<const T: bool, const U: bool> AllowYearZero for Symmetry<T, U> {}
248
249
impl<const T: bool, const U: bool> ToFromOrdinalDate for Symmetry<T, U> {
250
5.12k
    fn valid_ordinal(ord: OrdinalDate) -> Result<(), CalendarError> {
251
        // Not described by Dr. Bromberg
252
5.12k
        let new_year_0 = Self::new_year_day_unchecked(ord.year, Self::epoch().get_day_i());
253
5.12k
        let new_year_1 = Self::new_year_day_unchecked(ord.year + 1, Self::epoch().get_day_i());
254
5.12k
        let diff = new_year_1 - new_year_0;
255
5.12k
        if (ord.day_of_year as i64) < 1 || 
(ord.day_of_year as i64) > diff4.09k
{
256
2.90k
            Err(CalendarError::InvalidDayOfYear)
257
        } else {
258
2.21k
            Ok(())
259
        }
260
5.12k
    }
261
262
45.1k
    fn ordinal_from_fixed(fixed_date: Fixed) -> OrdinalDate {
263
        //LISTING FixedToSym (*Basic Symmetry454 and Symmetry010 Calendar Arithmetic* by Dr. Irvin L. Bromberg)
264
        //Only the SymYear and DayOfYear terms.
265
45.1k
        let date = fixed_date.get_day_i();
266
45.1k
        let (sym_year, start_of_year) = Self::year_from_fixed(date, Self::epoch().get_day_i());
267
45.1k
        let day_of_year = (date - start_of_year + 1) as u16;
268
45.1k
        OrdinalDate {
269
45.1k
            year: sym_year,
270
45.1k
            day_of_year: day_of_year,
271
45.1k
        }
272
45.1k
    }
273
274
7.16k
    fn to_ordinal(self) -> OrdinalDate {
275
7.16k
        OrdinalDate {
276
7.16k
            year: self.0.year,
277
7.16k
            day_of_year: Self::day_of_year(self.0.month, self.0.day),
278
7.16k
        }
279
7.16k
    }
280
281
44.0k
    fn from_ordinal_unchecked(ord: OrdinalDate) -> Self {
282
        //LISTING FixedToSym (*Basic Symmetry454 and Symmetry010 Calendar Arithmetic* by Dr. Irvin L. Bromberg)
283
        // Originally second part of from_fixed
284
44.0k
        let (sym_year, day_of_year) = (ord.year, ord.day_of_year);
285
        // Thank Ferris for div_ceil
286
44.0k
        let week_of_year = day_of_year.div_ceil(7);
287
44.0k
        debug_assert!(week_of_year > 0 && week_of_year < 54);
288
44.0k
        let quarter = (4 * week_of_year).div_ceil(53);
289
44.0k
        debug_assert!(quarter > 0 && quarter < 5);
290
44.0k
        let day_of_quarter = day_of_year - (91 * (quarter - 1));
291
44.0k
        let week_of_quarter = day_of_quarter.div_ceil(7);
292
44.0k
        let month_of_quarter = if T {
293
22.0k
            (2 * week_of_quarter).div_ceil(9)
294
        } else {
295
22.0k
            (2 * day_of_quarter).div_ceil(61)
296
        };
297
44.0k
        let sym_month = (3 * (quarter - 1) + month_of_quarter) as u8;
298
        // Skipping optionals
299
44.0k
        let sym_day = (day_of_year - Self::days_before_month(sym_month)) as u8;
300
44.0k
        Self(CommonDate::new(sym_year, sym_month, sym_day))
301
44.0k
    }
302
}
303
304
impl<const T: bool, const U: bool> HasLeapYears for Symmetry<T, U> {
305
3.59k
    fn is_leap(sym_year: i32) -> bool {
306
        //LISTING isSymLeapYear (*Basic Symmetry454 and Symmetry010 Calendar Arithmetic* by Dr. Irvin L. Bromberg)
307
3.59k
        let p = Self::params();
308
3.59k
        let sym_year = sym_year as i64;
309
3.59k
        ((p.L * sym_year) + p.K).modulus(p.C) < p.L
310
3.59k
    }
311
}
312
313
impl<const T: bool, const U: bool> CalculatedBounds for Symmetry<T, U> {}
314
315
impl<const T: bool, const U: bool> Epoch for Symmetry<T, U> {
316
73.3k
    fn epoch() -> Fixed {
317
73.3k
        Gregorian::epoch()
318
73.3k
    }
319
}
320
321
impl<const T: bool, const U: bool> FromFixed for Symmetry<T, U> {
322
43.0k
    fn from_fixed(fixed_date: Fixed) -> Symmetry<T, U> {
323
        //LISTING FixedToSym (*Basic Symmetry454 and Symmetry010 Calendar Arithmetic* by Dr. Irvin L. Bromberg)
324
        // Compared to Dr. Bromberg's original, this function is split in two
325
43.0k
        let ord = Self::ordinal_from_fixed(fixed_date);
326
43.0k
        Self::from_ordinal_unchecked(ord)
327
43.0k
    }
328
}
329
330
impl<const T: bool, const U: bool> ToFixed for Symmetry<T, U> {
331
17.9k
    fn to_fixed(self) -> Fixed {
332
        //LISTING SymToFixed (*Basic Symmetry454 and Symmetry010 Calendar Arithmetic* by Dr. Irvin L. Bromberg)
333
17.9k
        let new_year_day = Self::new_year_day_unchecked(self.0.year, Self::epoch().get_day_i());
334
17.9k
        let day_of_year = Self::day_of_year(self.0.month, self.0.day) as i64;
335
17.9k
        Fixed::cast_new(new_year_day + day_of_year - 1)
336
17.9k
    }
337
}
338
339
impl<const T: bool, const U: bool> ToFromCommonDate<SymmetryMonth> for Symmetry<T, U> {
340
59.4k
    fn to_common_date(self) -> CommonDate {
341
59.4k
        self.0
342
59.4k
    }
343
344
30.8k
    fn from_common_date_unchecked(date: CommonDate) -> Self {
345
30.8k
        debug_assert!(Self::valid_ymd(date).is_ok());
346
30.8k
        Self(date)
347
30.8k
    }
348
349
67.7k
    fn valid_ymd(date: CommonDate) -> Result<(), CalendarError> {
350
67.7k
        let month_opt = SymmetryMonth::from_u8(date.month);
351
67.7k
        if month_opt.is_none() {
352
3.07k
            Err(CalendarError::InvalidMonth)
353
64.7k
        } else if date.day < 1 {
354
1.02k
            Err(CalendarError::InvalidDay)
355
63.6k
        } else if date.day > Self::days_in_month(month_opt.unwrap()) {
356
1.02k
            Err(CalendarError::InvalidDay)
357
        } else {
358
62.6k
            Ok(())
359
        }
360
67.7k
    }
361
362
1.02k
    fn year_end_date(year: i32) -> CommonDate {
363
1.02k
        let m = if Self::is_leap(year) {
364
182
            SymmetryMonth::Irvember
365
        } else {
366
846
            SymmetryMonth::December
367
        };
368
1.02k
        CommonDate::new(year, m as u8, Self::days_in_month(m))
369
1.02k
    }
370
}
371
372
impl<const T: bool, const U: bool> Quarter for Symmetry<T, U> {
373
10.2k
    fn quarter(self) -> NonZero<u8> {
374
10.2k
        match self.month() {
375
6
            SymmetryMonth::Irvember => NonZero::new(4 as u8).unwrap(),
376
10.2k
            m => NonZero::new((((m as u8) - 1) / 3) + 1).expect("(m-1)/3 > -1"),
377
        }
378
10.2k
    }
379
}
380
381
impl<const T: bool, const U: bool> GuaranteedMonth<SymmetryMonth> for Symmetry<T, U> {}
382
impl<const T: bool, const U: bool> CommonWeekOfYear<SymmetryMonth> for Symmetry<T, U> {}
383
384
/// Represents a date *and time* in the Symmetry454 Calendar
385
pub type Symmetry454Moment = CalendarMoment<Symmetry454>;
386
387
/// Represents a date *and time* in the Symmetry010 Calendar
388
pub type Symmetry010Moment = CalendarMoment<Symmetry010>;
389
390
/// Represents a date *and time* in the Symmetry454 Calendar (solstice-approximating)
391
pub type Symmetry454SolsticeMoment = CalendarMoment<Symmetry454Solstice>;
392
393
/// Represents a date *and time* in the Symmetry010 Calendar (solstice-approximating)
394
pub type Symmetry010SolsticeMoment = CalendarMoment<Symmetry010Solstice>;
395
396
#[cfg(test)]
397
mod tests {
398
    use super::*;
399
    use crate::day_count::RataDie;
400
    use crate::day_count::FIXED_MAX;
401
    use crate::day_cycle::Weekday;
402
    use proptest::proptest;
403
    const MAX_YEARS: i32 = (FIXED_MAX / 365.25) as i32;
404
405
    #[test]
406
1
    fn is_leap_example() {
407
1
        assert!(Symmetry454::is_leap(2009));
408
1
        assert!(Symmetry454::is_leap(2015));
409
1
        assert!(Symmetry010::is_leap(2009));
410
1
        assert!(Symmetry010::is_leap(2015));
411
412
1
        assert!(!Symmetry454Solstice::is_leap(2009));
413
1
        assert!(!Symmetry010Solstice::is_leap(2009));
414
1
        assert!(Symmetry454Solstice::is_leap(2010));
415
1
        assert!(Symmetry010Solstice::is_leap(2016));
416
1
    }
417
418
    #[test]
419
1
    fn new_year_day_example() {
420
1
        assert_eq!(Symmetry454::new_year_day_unchecked(2010, 1), 733776);
421
1
        assert_eq!(Symmetry010::new_year_day_unchecked(2010, 1), 733776);
422
1
        assert_eq!(Symmetry454Solstice::new_year_day_unchecked(2010, 1), 733769);
423
1
        assert_eq!(Symmetry010Solstice::new_year_day_unchecked(2010, 1), 733769);
424
1
    }
425
426
    #[test]
427
1
    fn days_before_month() {
428
1
        assert_eq!(Symmetry454::days_before_month(1), 0);
429
1
        assert_eq!(Symmetry454::days_before_month(13), 364);
430
1
        assert_eq!(Symmetry454::days_before_month(6), 154);
431
1
        assert_eq!(Symmetry010::days_before_month(1), 0);
432
1
        assert_eq!(Symmetry010::days_before_month(13), 364);
433
1
        assert_eq!(Symmetry010::days_before_month(6), 152);
434
435
1
        assert_eq!(Symmetry454Solstice::days_before_month(1), 0);
436
1
        assert_eq!(Symmetry454Solstice::days_before_month(13), 364);
437
1
        assert_eq!(Symmetry454Solstice::days_before_month(6), 154);
438
1
        assert_eq!(Symmetry010Solstice::days_before_month(1), 0);
439
1
        assert_eq!(Symmetry010Solstice::days_before_month(13), 364);
440
1
        assert_eq!(Symmetry010Solstice::days_before_month(6), 152);
441
1
    }
442
443
    #[test]
444
1
    fn day_of_year() {
445
1
        assert_eq!(Symmetry454::day_of_year(6, 17), 171);
446
1
        assert_eq!(Symmetry010::day_of_year(6, 17), 169);
447
1
        assert_eq!(Symmetry454Solstice::day_of_year(6, 17), 171);
448
1
        assert_eq!(Symmetry010Solstice::day_of_year(6, 17), 169);
449
1
    }
450
451
    #[test]
452
1
    fn to_fixed() {
453
1
        assert_eq!(
454
1
            Symmetry454::try_from_common_date(CommonDate::new(2009, 4, 5))
455
1
                .unwrap()
456
1
                .to_fixed()
457
1
                .get_day_i(),
458
            733500
459
        );
460
1
        assert_eq!(
461
1
            Symmetry010::try_from_common_date(CommonDate::new(2009, 4, 5))
462
1
                .unwrap()
463
1
                .to_fixed()
464
1
                .get_day_i(),
465
            733500
466
        );
467
1
        assert_eq!(
468
1
            Symmetry454Solstice::try_from_common_date(CommonDate::new(2009, 4, 5))
469
1
                .unwrap()
470
1
                .to_fixed()
471
1
                .get_day_i(),
472
            733500
473
        );
474
1
        assert_eq!(
475
1
            Symmetry010Solstice::try_from_common_date(CommonDate::new(2009, 4, 5))
476
1
                .unwrap()
477
1
                .to_fixed()
478
1
                .get_day_i(),
479
            733500
480
        );
481
1
    }
482
483
    #[test]
484
1
    fn year_from_fixed() {
485
1
        assert_eq!(Symmetry454::year_from_fixed(733649, 1), (2009, 733405));
486
1
        assert_eq!(Symmetry010::year_from_fixed(733649, 1), (2009, 733405));
487
1
        assert_eq!(Symmetry454::year_from_fixed(733406, 1), (2009, 733405));
488
1
        assert_eq!(Symmetry010::year_from_fixed(733406, 1), (2009, 733405));
489
1
        assert_eq!(Symmetry454::year_from_fixed(733774, 1).0, 2009);
490
1
        assert_eq!(Symmetry010::year_from_fixed(733774, 1).0, 2009);
491
1
    }
492
493
    #[test]
494
1
    fn example_data() {
495
1
        let d_list = [
496
1
            (
497
1
                -44444 as i32,
498
1
                CommonDate::new(-121, 4, 27),
499
1
                CommonDate::new(-121, 4, 27),
500
1
                CommonDate::new(-121, 4, 27),
501
1
                CommonDate::new(-121, 4, 27),
502
1
            ),
503
1
            (
504
1
                -33333 as i32,
505
1
                CommonDate::new(-91, 9, 22),
506
1
                CommonDate::new(-91, 9, 24),
507
1
                CommonDate::new(-91, 9, 22),
508
1
                CommonDate::new(-91, 9, 24),
509
1
            ),
510
1
            (
511
1
                44444 as i32,
512
1
                CommonDate::new(122, 9, 8),
513
1
                CommonDate::new(122, 9, 10),
514
1
                CommonDate::new(122, 9, 8),
515
1
                CommonDate::new(122, 9, 10),
516
1
            ),
517
1
            (
518
1
                648491 as i32,
519
1
                CommonDate::new(1776, 7, 4),
520
1
                CommonDate::new(1776, 7, 4),
521
1
                CommonDate::new(1776, 7, 4),
522
1
                CommonDate::new(1776, 7, 4),
523
1
            ),
524
1
            (
525
1
                681724 as i32,
526
1
                CommonDate::new(1867, 7, 1),
527
1
                CommonDate::new(1867, 7, 1),
528
1
                CommonDate::new(1867, 7, 1),
529
1
                CommonDate::new(1867, 7, 1),
530
1
            ),
531
1
            (
532
1
                711058 as i32,
533
1
                CommonDate::new(1947, 10, 26),
534
1
                CommonDate::new(1947, 10, 26),
535
1
                CommonDate::new(1947, 10, 26),
536
1
                CommonDate::new(1947, 10, 26),
537
1
            ),
538
1
            (
539
1
                728515 as i32,
540
1
                CommonDate::new(1995, 8, 11),
541
1
                CommonDate::new(1995, 8, 9),
542
1
                CommonDate::new(1995, 8, 11),
543
1
                CommonDate::new(1995, 8, 9),
544
1
            ),
545
1
            (
546
1
                730179 as i32,
547
1
                CommonDate::new(2000, 2, 30),
548
1
                CommonDate::new(2000, 2, 28),
549
1
                CommonDate::new(2000, 2, 30),
550
1
                CommonDate::new(2000, 2, 28),
551
1
            ),
552
1
            (
553
1
                731703 as i32,
554
1
                CommonDate::new(2004, 5, 7),
555
1
                CommonDate::new(2004, 5, 5),
556
1
                CommonDate::new(2004, 5, 7),
557
1
                CommonDate::new(2004, 5, 5),
558
1
            ),
559
1
            (
560
1
                731946 as i32,
561
1
                CommonDate::new(2004, 13, 5),
562
1
                CommonDate::new(2004, 13, 5),
563
1
                CommonDate::new(2005, 1, 5),
564
1
                CommonDate::new(2005, 1, 5),
565
1
            ),
566
1
            (
567
1
                737475 as i32,
568
1
                CommonDate::new(2020, 2, 25),
569
1
                CommonDate::new(2020, 2, 23),
570
1
                CommonDate::new(2020, 2, 25),
571
1
                CommonDate::new(2020, 2, 23),
572
1
            ),
573
1
            (
574
1
                811236 as i32,
575
1
                CommonDate::new(2222, 2, 6),
576
1
                CommonDate::new(2222, 2, 4),
577
1
                CommonDate::new(2222, 2, 6),
578
1
                CommonDate::new(2222, 2, 4),
579
1
            ),
580
1
            (
581
1
                1217048 as i32,
582
1
                CommonDate::new(3333, 2, 35),
583
1
                CommonDate::new(3333, 3, 2),
584
1
                CommonDate::new(3333, 2, 35),
585
1
                CommonDate::new(3333, 3, 2),
586
1
            ),
587
1
        ];
588
14
        for 
item13
in d_list {
589
13
            let rd = RataDie::new(item.0 as f64);
590
13
            let s454q = Symmetry454::try_from_common_date(item.1).unwrap();
591
13
            let s010q = Symmetry010::try_from_common_date(item.2).unwrap();
592
13
            let s454s = Symmetry454Solstice::try_from_common_date(item.3).unwrap();
593
13
            let s010s = Symmetry010Solstice::try_from_common_date(item.4).unwrap();
594
13
            assert_eq!(rd.to_fixed(), s454q.to_fixed());
595
13
            assert_eq!(rd.to_fixed(), s010q.to_fixed());
596
13
            assert_eq!(rd.to_fixed(), s454s.to_fixed());
597
13
            assert_eq!(rd.to_fixed(), s010s.to_fixed());
598
        }
599
1
    }
600
601
    proptest! {
602
        #[test]
603
        fn month_start_on_monday_454(year in -MAX_YEARS..MAX_YEARS, month in 1..12) {
604
            let c = CommonDate::new(year as i32, month as u8, 1);
605
            let d = Symmetry454::try_from_common_date(c).unwrap();
606
            assert_eq!(d.convert::<Weekday>(), Weekday::Monday);
607
            let d = Symmetry454Solstice::try_from_common_date(c).unwrap();
608
            assert_eq!(d.convert::<Weekday>(), Weekday::Monday);
609
        }
610
611
        #[test]
612
        fn month_end_on_sunday_454(year in -MAX_YEARS..MAX_YEARS, month in 1..12) {
613
            let m = SymmetryMonth::from_i32(month).unwrap();
614
            let c = CommonDate::new(year as i32, month as u8, Symmetry454::days_in_month(m));
615
            let d = Symmetry454::try_from_common_date(c).unwrap();
616
            assert_eq!(d.convert::<Weekday>(), Weekday::Sunday);
617
            let d = Symmetry454Solstice::try_from_common_date(c).unwrap();
618
            assert_eq!(d.convert::<Weekday>(), Weekday::Sunday);
619
        }
620
621
        #[test]
622
        fn no_friday_13_454(year in -MAX_YEARS..MAX_YEARS, month in 1..12) {
623
            let c = CommonDate::new(year as i32, month as u8, 13);
624
            let d = Symmetry454::try_from_common_date(c).unwrap();
625
            assert_ne!(d.convert::<Weekday>(), Weekday::Friday);
626
            let d = Symmetry454Solstice::try_from_common_date(c).unwrap();
627
            assert_ne!(d.convert::<Weekday>(), Weekday::Friday);
628
        }
629
630
        #[test]
631
        fn year_start_on_monday_010(year in -MAX_YEARS..MAX_YEARS) {
632
            let c = CommonDate::new(year as i32, 1, 1);
633
            let d = Symmetry010::try_from_common_date(c).unwrap();
634
            assert_eq!(d.convert::<Weekday>(), Weekday::Monday);
635
            let d = Symmetry010Solstice::try_from_common_date(c).unwrap();
636
            assert_eq!(d.convert::<Weekday>(), Weekday::Monday);
637
        }
638
639
        #[test]
640
        fn no_friday_13_010(year in -MAX_YEARS..MAX_YEARS, month in 1..12) {
641
            let c = CommonDate::new(year as i32, month as u8, 13);
642
            let d = Symmetry010::try_from_common_date(c).unwrap();
643
            assert_ne!(d.convert::<Weekday>(), Weekday::Friday);
644
            let d = Symmetry010Solstice::try_from_common_date(c).unwrap();
645
            assert_ne!(d.convert::<Weekday>(), Weekday::Friday);
646
        }
647
    }
648
}