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/cotsworth.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::GuaranteedMonth;
8
use crate::calendar::prelude::HasLeapYears;
9
use crate::calendar::prelude::Perennial;
10
use crate::calendar::prelude::Quarter;
11
use crate::calendar::prelude::ToFromCommonDate;
12
use crate::calendar::prelude::ToFromOrdinalDate;
13
use crate::calendar::AllowYearZero;
14
use crate::calendar::CalendarMoment;
15
use crate::calendar::HasIntercalaryDays;
16
use crate::calendar::OrdinalDate;
17
use crate::common::error::CalendarError;
18
use crate::common::math::TermNum;
19
use crate::day_count::BoundedDayCount;
20
use crate::day_count::CalculatedBounds;
21
use crate::day_count::Epoch;
22
use crate::day_count::Fixed;
23
use crate::day_count::FromFixed;
24
use crate::day_count::ToFixed;
25
use crate::day_cycle::Weekday;
26
#[allow(unused_imports)] //FromPrimitive is needed for derive
27
use num_traits::FromPrimitive;
28
use std::num::NonZero;
29
30
/// Represents a month in the Cotsworth Calendar
31
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)]
32
pub enum CotsworthMonth {
33
    January = 1,
34
    February,
35
    March,
36
    April,
37
    May,
38
    June,
39
    Sol,
40
    July,
41
    August,
42
    September,
43
    October,
44
    November,
45
    December,
46
}
47
48
/// Represents a complementary day of the Cotsworth Calendar
49
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, FromPrimitive, ToPrimitive)]
50
pub enum CotsworthComplementaryDay {
51
    /// The day that ends every year of the Cotsworth Calendar.
52
    /// This is also represented as December 29. It is not part of any week.
53
    YearDay = 1,
54
    /// The extra day added in leap years of the Cotsworth Calendar.
55
    /// This is also represented as June 29. It is not part of any week.
56
    LeapDay,
57
}
58
59
/// Represents a date in the Cotsworth calendar
60
/// (ie. International Fixed Calendar, Eastman plan, Yearal)
61
///
62
/// This calendar was originally designed by Moses B Cotsworth.
63
/// George Eastman instituted its use within the Eastman Kodak Company from 1928 to 1989.
64
///
65
/// ## Further reading
66
/// + [Wikipedia](https://en.wikipedia.org/wiki/Cotsworth_calendar)
67
/// + ["Nation's Business" May 1926](https://www.freexenon.com/wp-content/uploads/2018/07/The-Importance-of-Calendar-Reform-to-the-Business-World-George-Eastman.pdf)
68
///   + "The Importance of Calendar Reform to the Business World"
69
///   + by George Eastman, President, Eastman Kodak Company
70
71
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
72
pub struct Cotsworth(CommonDate);
73
74
impl AllowYearZero for Cotsworth {}
75
76
impl ToFromOrdinalDate for Cotsworth {
77
1.28k
    fn valid_ordinal(ord: OrdinalDate) -> Result<(), CalendarError> {
78
1.28k
        Gregorian::valid_ordinal(ord)
79
1.28k
    }
80
81
8.97k
    fn ordinal_from_fixed(fixed_date: Fixed) -> OrdinalDate {
82
8.97k
        Gregorian::ordinal_from_fixed(fixed_date)
83
8.97k
    }
84
85
9.02k
    fn to_ordinal(self) -> OrdinalDate {
86
9.02k
        let approx_m = ((self.0.month as i64) - 1) * 28;
87
9.02k
        let offset_m = if self.0.month > 6 && 
Cotsworth::is_leap4.91k
(
self.0.year4.91k
) {
88
1.19k
            approx_m + 1
89
        } else {
90
7.82k
            approx_m
91
        };
92
9.02k
        let doy = (offset_m as u16) + (self.0.day as u16);
93
9.02k
        OrdinalDate {
94
9.02k
            year: self.0.year,
95
9.02k
            day_of_year: doy,
96
9.02k
        }
97
9.02k
    }
98
99
8.71k
    fn from_ordinal_unchecked(ord: OrdinalDate) -> Self {
100
        const LEAP_DAY_ORD: u16 = (6 * 28) + 1;
101
8.71k
        let result = match (ord.day_of_year, Cotsworth::is_leap(ord.year)) {
102
6
            (366, true) => CommonDate::new(ord.year, 13, 29),
103
20
            (365, false) => CommonDate::new(ord.year, 13, 29),
104
11
            (LEAP_DAY_ORD, true) => CommonDate::new(ord.year, 6, 29),
105
8.67k
            (doy, is_leap) => {
106
8.67k
                let correction = if doy < LEAP_DAY_ORD || 
!is_leap4.51k
{
07.69k
} else {
1979
};
107
8.67k
                let month = ((((doy - correction) - 1) as i64).div_euclid(28) + 1) as u8;
108
8.67k
                let day = ((doy - correction) as i64).adjusted_remainder(28) as u8;
109
8.67k
                CommonDate::new(ord.year, month, day)
110
            }
111
        };
112
8.71k
        Cotsworth(result)
113
8.71k
    }
114
}
115
116
impl HasIntercalaryDays<CotsworthComplementaryDay> for Cotsworth {
117
2.56k
    fn complementary(self) -> Option<CotsworthComplementaryDay> {
118
2.56k
        if self.0.day == 29 && 
self.0.month == (CotsworthMonth::December as u8)6
{
119
4
            Some(CotsworthComplementaryDay::YearDay)
120
2.55k
        } else if self.0.day == 29 && 
self.0.month == (CotsworthMonth::June as u8)2
{
121
2
            Some(CotsworthComplementaryDay::LeapDay)
122
        } else {
123
2.55k
            None
124
        }
125
2.56k
    }
126
127
0
    fn complementary_count(p_year: i32) -> u8 {
128
0
        if Cotsworth::is_leap(p_year) {
129
0
            2
130
        } else {
131
0
            1
132
        }
133
0
    }
134
}
135
136
impl Perennial<CotsworthMonth, Weekday> for Cotsworth {
137
2.30k
    fn weekday(self) -> Option<Weekday> {
138
2.30k
        if self.complementary().is_some() {
139
6
            None
140
        } else {
141
2.29k
            Weekday::from_i64(((self.0.day as i64) - 1).modulus(7))
142
        }
143
2.30k
    }
144
145
2.04k
    fn days_per_week() -> u8 {
146
2.04k
        7
147
2.04k
    }
148
149
2.04k
    fn weeks_per_month() -> u8 {
150
2.04k
        4
151
2.04k
    }
152
}
153
154
impl HasLeapYears for Cotsworth {
155
14.7k
    fn is_leap(c_year: i32) -> bool {
156
14.7k
        Gregorian::is_leap(c_year)
157
14.7k
    }
158
}
159
160
impl CalculatedBounds for Cotsworth {}
161
162
impl Epoch for Cotsworth {
163
0
    fn epoch() -> Fixed {
164
0
        Gregorian::epoch()
165
0
    }
166
}
167
168
impl FromFixed for Cotsworth {
169
8.45k
    fn from_fixed(fixed_date: Fixed) -> Cotsworth {
170
8.45k
        let ord = Self::ordinal_from_fixed(fixed_date);
171
8.45k
        Self::from_ordinal_unchecked(ord)
172
8.45k
    }
173
}
174
175
impl ToFixed for Cotsworth {
176
7.22k
    fn to_fixed(self) -> Fixed {
177
7.22k
        let offset_y = Gregorian::try_year_start(self.0.year)
178
7.22k
            .expect("Year known to be valid")
179
7.22k
            .to_fixed()
180
7.22k
            .get_day_i()
181
7.22k
            - 1;
182
7.22k
        let ord = self.to_ordinal();
183
7.22k
        Fixed::cast_new(offset_y + (ord.day_of_year as i64))
184
7.22k
    }
185
}
186
187
impl ToFromCommonDate<CotsworthMonth> for Cotsworth {
188
11.4k
    fn to_common_date(self) -> CommonDate {
189
11.4k
        self.0
190
11.4k
    }
191
192
11.4k
    fn from_common_date_unchecked(date: CommonDate) -> Self {
193
11.4k
        debug_assert!(Self::valid_ymd(date).is_ok());
194
11.4k
        Self(date)
195
11.4k
    }
196
197
24.4k
    fn valid_ymd(date: CommonDate) -> Result<(), CalendarError> {
198
24.4k
        if date.month < 1 || 
date.month > 1324.1k
{
199
768
            Err(CalendarError::InvalidMonth)
200
23.6k
        } else if date.day < 1 || 
date.day > 2923.4k
{
201
512
            Err(CalendarError::InvalidDay)
202
23.1k
        } else if date.day == 29 {
203
2.40k
            if date.month == 13 || (
Cotsworth::is_leap360
(
date.year360
) &&
date.month == 6360
) {
204
2.40k
                Ok(())
205
            } else {
206
0
                Err(CalendarError::InvalidDay)
207
            }
208
        } else {
209
20.7k
            Ok(())
210
        }
211
24.4k
    }
212
213
256
    fn year_end_date(year: i32) -> CommonDate {
214
256
        CommonDate::new(year, CotsworthMonth::December as u8, 29)
215
256
    }
216
}
217
218
impl Quarter for Cotsworth {
219
2.81k
    fn quarter(self) -> NonZero<u8> {
220
2.81k
        let m = self.to_common_date().month;
221
2.81k
        if m == 13 {
222
292
            NonZero::new(4 as u8).expect("4 != 0")
223
        } else {
224
2.52k
            NonZero::new(((m - 1) / 3) + 1).expect("(m-1)/3 > -1")
225
        }
226
2.81k
    }
227
}
228
229
impl GuaranteedMonth<CotsworthMonth> for Cotsworth {}
230
231
/// Represents a date *and time* in the Cotsworth Calendar
232
pub type CotsworthMoment = CalendarMoment<Cotsworth>;