Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/src/clock/fields.rs

5.4 KiB, 20 runs

created by r1870400018:6315, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
2//! Anthropic Claude
3
4use oxedyne_fe2o3_core::prelude::*;
5
6/// A mutable working space for time arithmetic. Fields may hold out-of-range
7/// values until normalise() carries the excess upwards, with whole days landing
8/// in day_carry.
9#[derive(Clone, Debug, Default)]
10pub struct ClockFields {
11 pub hour: i64,
12 pub minute: i64,
13 pub second: i64,
14 pub nanosecond: i64,
15 pub day_carry: i32,
16}
17
18impl ClockFields {
19 pub fn new() -> Self {
20 Self::default()
21 }
22
23 pub fn from_time(hour: u8, minute: u8, second: u8, nanosecond: u32) -> Self {
24 Self {
25 hour: hour as i64,
26 minute: minute as i64,
27 second: second as i64,
28 nanosecond: nanosecond as i64,
29 day_carry: 0,
30 }
31 }
32
33 /// Carries overflow upwards until every field is back in range.
34 pub fn normalize(&mut self) -> bool {
35 let mut changed = false;
36
37 // Normalise nanoseconds to seconds.
38 if self.nanosecond >= 1_000_000_000 {
39 let carry = self.nanosecond / 1_000_000_000;
40 self.second += carry;
41 self.nanosecond %= 1_000_000_000;
42 changed = true;
43 } else if self.nanosecond < 0 {
44 let borrow = (-self.nanosecond + 999_999_999) / 1_000_000_000;
45 self.second -= borrow;
46 self.nanosecond += borrow * 1_000_000_000;
47 changed = true;
48 }
49
50 // Normalise seconds to minutes.
51 if self.second >= 60 {
52 let carry = self.second / 60;
53 self.minute += carry;
54 self.second %= 60;
55 changed = true;
56 } else if self.second <= -60 {
57 let borrow = (-self.second + 59) / 60;
58 self.minute -= borrow;
59 self.second += borrow * 60;
60 changed = true;
61 }
62
63 // Normalise minutes to hours.
64 if self.minute >= 60 {
65 let carry = self.minute / 60;
66 self.hour += carry;
67 self.minute %= 60;
68 changed = true;
69 } else if self.minute <= -60 {
70 let borrow = (-self.minute + 59) / 60;
71 self.hour -= borrow;
72 self.minute += borrow * 60;
73 changed = true;
74 }
75
76 // Normalise hours to days.
77 if self.hour >= 24 {
78 let carry = self.hour / 24;
79 self.day_carry += carry as i32;
80 self.hour %= 24;
81 changed = true;
82 } else if self.hour <= -24 {
83 let borrow = (-self.hour + 23) / 24;
84 self.day_carry -= borrow as i32;
85 self.hour += borrow * 24;
86 changed = true;
87 }
88
89 changed
90 }
91
92 /// Component-wise, and left unnormalised: call normalise afterwards.
93 pub fn add(&mut self, other: &ClockFields) {
94 self.hour += other.hour;
95 self.minute += other.minute;
96 self.second += other.second;
97 self.nanosecond += other.nanosecond;
98 self.day_carry += other.day_carry;
99 }
100
101 pub fn subtract(&mut self, other: &ClockFields) {
102 self.hour -= other.hour;
103 self.minute -= other.minute;
104 self.second -= other.second;
105 self.nanosecond -= other.nanosecond;
106 self.day_carry -= other.day_carry;
107 }
108
109 /// day_carry is left alone, being derived rather than held.
110 pub fn multiply(&mut self, factor: i64) {
111 self.hour *= factor;
112 self.minute *= factor;
113 self.second *= factor;
114 self.nanosecond *= factor;
115 // day_carry is not multiplied as it's derived from normalization
116 }
117
118 /// None when a field is still out of range after normalisation.
119 pub fn to_time_components(&mut self) -> Option<(u8, u8, u8, u32, i32)> {
120 self.normalize();
121
122 if self.hour < 0 || self.hour > 24 ||
123 self.minute < 0 || self.minute >= 60 ||
124 self.second < 0 || self.second >= 60 ||
125 self.nanosecond < 0 || self.nanosecond >= 1_000_000_000 {
126 return None;
127 }
128
129 Some((
130 self.hour as u8,
131 self.minute as u8,
132 self.second as u8,
133 self.nanosecond as u32,
134 self.day_carry,
135 ))
136 }
137
138 pub fn is_valid_time(&mut self) -> bool {
139 self.to_time_components().is_some()
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn test_normalize_basic() {
149 let mut fields = ClockFields::from_time(12, 30, 45, 500_000_000);
150 assert!(!fields.normalize()); // no changes needed
151 assert_eq!(fields.hour, 12);
152 assert_eq!(fields.minute, 30);
153 assert_eq!(fields.second, 45);
154 assert_eq!(fields.nanosecond, 500_000_000);
155 }
156
157 #[test]
158 fn test_normalize_overflow() {
159 let mut fields = ClockFields {
160 hour: 0,
161 minute: 0,
162 second: 0,
163 nanosecond: 2_000_000_000, // 2 seconds
164 day_carry: 0,
165 };
166 assert!(fields.normalize());
167 assert_eq!(fields.second, 2);
168 assert_eq!(fields.nanosecond, 0);
169 }
170
171 #[test]
172 fn test_normalize_hour_overflow() {
173 let mut fields = ClockFields {
174 hour: 25,
175 minute: 0,
176 second: 0,
177 nanosecond: 0,
178 day_carry: 0,
179 };
180 assert!(fields.normalize());
181 assert_eq!(fields.hour, 1);
182 assert_eq!(fields.day_carry, 1);
183 }
184
185 #[test]
186 fn test_normalize_underflow() {
187 let mut fields = ClockFields {
188 hour: 0,
189 minute: 0,
190 second: 0,
191 nanosecond: -500_000_000,
192 day_carry: 0,
193 };
194 assert!(fields.normalize());
195 assert_eq!(fields.second, -1);
196 assert_eq!(fields.nanosecond, 500_000_000);
197 }
198
199 #[test]
200 fn test_arithmetic() {
201 let mut fields1 = ClockFields::from_time(12, 30, 45, 500_000_000);
202 let fields2 = ClockFields::from_time(1, 15, 30, 250_000_000);
203
204 fields1.add(&fields2);
205 assert!(fields1.normalize());
206 assert_eq!(fields1.hour, 13);
207 assert_eq!(fields1.minute, 46);
208 assert_eq!(fields1.second, 15);
209 assert_eq!(fields1.nanosecond, 750_000_000);
210 }
211
212 #[test]
213 fn test_to_time_components() {
214 let mut fields = ClockFields::from_time(23, 59, 59, 999_999_999);
215 let result = fields.to_time_components();
216 assert!(result.is_some());
217 let (h, m, s, n, d) = result.unwrap();
218 assert_eq!(h, 23);
219 assert_eq!(m, 59);
220 assert_eq!(s, 59);
221 assert_eq!(n, 999_999_999);
222 assert_eq!(d, 0);
223 }
224}