Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/tests/leap_second_demo.rs

12.4 KiB, 4 runs

created by r1870400018:8652, 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_datime::{
5 time::{
6 CalClock,
7 CalClockZone,
8 LeapSecondTable,
9 LeapSecondConfig,
10 LeapSecondEntry,
11 LeapSecondStatistics,
12 },
13 clock::ClockTime,
14};
15
16use oxedyne_fe2o3_core::prelude::*;
17
18#[test]
19fn test_leap_second_table_creation() -> Outcome<()> {
20 // Test standard leap second table
21 let table = LeapSecondTable::standard();
22 assert!(table.is_enabled());
23 assert!(table.leap_second_count() > 0);
24
25 // Test disabled table
26 let disabled = LeapSecondTable::disabled();
27 assert!(!disabled.is_enabled());
28 assert_eq!(disabled.leap_second_count(), 0);
29
30 Ok(())
31}
32
33#[test]
34fn test_tai_utc_offset_calculation() -> Outcome<()> {
35 let table = LeapSecondTable::standard();
36
37 // Before first leap second (1972-07-01)
38 assert_eq!(table.tai_utc_offset_at(0), 10);
39
40 // First leap second: 1972-07-01 00:00:00 UTC = 78796800 seconds since epoch
41 assert_eq!(table.tai_utc_offset_at(78796800), 10);
42 assert_eq!(table.tai_utc_offset_at(78796801), 10);
43
44 // Most recent leap second: 2017-01-01 00:00:00 UTC = 1483228800 seconds
45 assert_eq!(table.tai_utc_offset_at(1483228800), 36);
46
47 // Current time (should still be 36 as no leap seconds since 2017)
48 assert_eq!(table.tai_utc_offset_at(1600000000), 36); // 2020
49
50 Ok(())
51}
52
53#[test]
54fn test_utc_tai_conversion() -> Outcome<()> {
55 let table = LeapSecondTable::standard();
56
57 // Test conversion for 2020-01-01 00:00:00 UTC
58 let utc_timestamp = 1577836800; // 2020-01-01 00:00:00 UTC
59 let tai_timestamp = table.utc_to_tai(utc_timestamp);
60 assert_eq!(tai_timestamp, utc_timestamp + 37); // Should be 37 seconds ahead
61
62 // Round trip conversion
63 let converted_utc = res!(table.tai_to_utc(tai_timestamp));
64 assert_eq!(converted_utc, utc_timestamp);
65
66 Ok(())
67}
68
69#[test]
70fn test_leap_second_detection() -> Outcome<()> {
71 let table = LeapSecondTable::standard();
72
73 // Test known leap second: 2017-01-01 00:00:00 UTC
74 assert!(table.is_leap_second(1483228800));
75
76 // Test non-leap second
77 assert!(!table.is_leap_second(1483228801));
78
79 // Test leap second validation for specific dates
80 assert!(table.validate_leap_second(2017, 1, 1, 23, 59)); // Valid leap second
81 assert!(!table.validate_leap_second(2017, 1, 2, 23, 59)); // Not a leap second date
82 assert!(!table.validate_leap_second(2017, 1, 1, 12, 0)); // Wrong time
83
84 Ok(())
85}
86
87#[test]
88fn test_leap_second_configuration() -> Outcome<()> {
89 // Test default configuration
90 let config = LeapSecondConfig::default();
91 assert!(config.enabled);
92 assert!(config.allow_leap_second_parsing);
93 assert!(config.validate_leap_seconds);
94
95 // Test disabled configuration
96 let disabled = LeapSecondConfig::disabled();
97 assert!(!disabled.enabled);
98 assert!(!disabled.allow_leap_second_parsing);
99 assert!(!disabled.validate_leap_seconds);
100
101 // Test permissive configuration
102 let permissive = LeapSecondConfig::permissive();
103 assert!(permissive.enabled);
104 assert!(permissive.allow_leap_second_parsing);
105 assert!(!permissive.validate_leap_seconds);
106
107 Ok(())
108}
109
110#[test]
111fn test_clock_time_with_leap_seconds() -> Outcome<()> {
112 let zone = CalClockZone::utc();
113 let config = LeapSecondConfig::default();
114
115 // Test creating normal time
116 let normal_time = res!(ClockTime::new_with_leap_seconds(23, 59, 59, 0, zone.clone(), &config));
117 assert!(!normal_time.is_leap_second());
118
119 // Test creating leap second time with validation disabled
120 let permissive_config = LeapSecondConfig::permissive();
121 let leap_time = res!(ClockTime::new_with_leap_seconds(23, 59, 60, 0, zone.clone(), &permissive_config));
122 assert!(leap_time.is_leap_second());
123 assert!(leap_time.is_potential_leap_second());
124
125 // Test that invalid leap second time fails with strict validation
126 let strict_config = LeapSecondConfig::default();
127 let result = ClockTime::new_with_leap_seconds(12, 30, 60, 0, zone.clone(), &strict_config);
128 assert!(result.is_err()); // Should fail because 12:30:60 is not valid leap second time
129
130 Ok(())
131}
132
133#[test]
134fn test_calclock_with_leap_seconds() -> Outcome<()> {
135 let zone = CalClockZone::utc();
136 let permissive_config = LeapSecondConfig::permissive();
137
138 // Test creating CalClock with leap second (validation disabled)
139 let leap_calclock = res!(CalClock::new_with_leap_seconds(
140 2017, 1, 1, 23, 59, 60, 0, zone.clone(), &permissive_config
141 ));
142
143 assert!(leap_calclock.is_leap_second());
144 assert!(leap_calclock.is_potential_leap_second());
145 assert_eq!(leap_calclock.year(), 2017);
146 assert_eq!(leap_calclock.month(), 1);
147 assert_eq!(leap_calclock.day(), 1);
148 assert_eq!(leap_calclock.hour(), 23);
149 assert_eq!(leap_calclock.minute(), 59);
150 assert_eq!(leap_calclock.second(), 60);
151
152 // Test leap second validation
153 let strict_config = LeapSecondConfig::default();
154 assert!(leap_calclock.validate_leap_second(&permissive_config));
155 // Note: strict validation would require the actual leap second table to contain this date
156
157 Ok(())
158}
159
160#[test]
161fn test_leap_second_normalization() -> Outcome<()> {
162 let zone = CalClockZone::utc();
163 let config = LeapSecondConfig::permissive();
164
165 // Create a leap second CalClock
166 let leap_calclock = res!(CalClock::new_with_leap_seconds(
167 2017, 1, 1, 23, 59, 60, 500_000_000, zone.clone(), &config
168 ));
169
170 // Normalize the leap second
171 let (normalized, day_advanced) = res!(leap_calclock.normalize_leap_second());
172
173 assert!(day_advanced);
174 assert_eq!(normalized.year(), 2017);
175 assert_eq!(normalized.month(), 1);
176 assert_eq!(normalized.day(), 2); // Should advance to next day
177 assert_eq!(normalized.hour(), 0);
178 assert_eq!(normalized.minute(), 0);
179 assert_eq!(normalized.second(), 0);
180 assert_eq!(normalized.nanosecond(), 500_000_000); // Nanoseconds preserved
181
182 // Test normalizing non-leap second (should be unchanged)
183 let normal_calclock = res!(CalClock::new(2017, 1, 1, 12, 30, 45, 0, zone));
184 let (normalized_normal, advanced) = res!(normal_calclock.normalize_leap_second());
185 assert!(!advanced);
186 assert_eq!(normalized_normal, normal_calclock);
187
188 Ok(())
189}
190
191#[test]
192fn test_tai_utc_conversions() -> Outcome<()> {
193 let zone = CalClockZone::utc();
194 let config = LeapSecondConfig::default();
195
196 // Create a CalClock for testing
197 let calclock = res!(CalClock::new(2020, 1, 1, 12, 0, 0, 0, zone.clone()));
198
199 // Convert to TAI timestamp
200 let tai_timestamp = res!(calclock.to_tai_timestamp(&config));
201
202 // Convert back from TAI timestamp
203 let converted_back = res!(CalClock::from_tai_timestamp(tai_timestamp, zone, &config));
204
205 // Should be approximately equal (might differ slightly due to timezone calculations)
206 assert_eq!(converted_back.year(), calclock.year());
207 assert_eq!(converted_back.month(), calclock.month());
208 assert_eq!(converted_back.day(), calclock.day());
209 assert_eq!(converted_back.hour(), calclock.hour());
210 assert_eq!(converted_back.minute(), calclock.minute());
211 assert_eq!(converted_back.second(), calclock.second());
212
213 Ok(())
214}
215
216#[test]
217fn test_leap_second_statistics() -> Outcome<()> {
218 let table = LeapSecondTable::standard();
219 let stats = table.statistics();
220
221 assert!(stats.total_leap_seconds > 0);
222 assert!(stats.enabled);
223 assert!(stats.first_leap_second.is_some());
224 assert!(stats.latest_leap_second.is_some());
225 assert_eq!(stats.current_tai_utc_offset, 36); // As of 2017
226
227 // First leap second should be 1972-07-01
228 assert_eq!(stats.first_leap_second.unwrap(), 78796800);
229
230 // Latest leap second should be 2017-01-01
231 assert_eq!(stats.latest_leap_second.unwrap(), 1483228800);
232
233 Ok(())
234}
235
236#[test]
237fn test_leap_second_boundary_cases() -> Outcome<()> {
238 let table = LeapSecondTable::standard();
239
240 // Test edge cases around leap second boundaries
241 let leap_second_time = 1483228800; // 2017-01-01 00:00:00 UTC (leap second)
242
243 // Test times around the leap second
244 assert_eq!(table.tai_utc_offset_at(leap_second_time - 1), 35); // Before leap second
245 assert_eq!(table.tai_utc_offset_at(leap_second_time), 36); // At leap second
246 assert_eq!(table.tai_utc_offset_at(leap_second_time + 1), 36); // After leap second
247
248 // Test TAI conversion around leap second
249 let tai_before = table.utc_to_tai(leap_second_time - 1);
250 let tai_at = table.utc_to_tai(leap_second_time);
251 let tai_after = table.utc_to_tai(leap_second_time + 1);
252
253 // TAI should be continuous despite leap second
254 assert_eq!(tai_at - tai_before, 2); // Gap due to leap second
255 assert_eq!(tai_after - tai_at, 1); // Normal progression
256
257 Ok(())
258}
259
260#[test]
261fn test_custom_leap_second_table() -> Outcome<()> {
262 // Create custom leap second table
263 let mut custom_table = LeapSecondTable::new();
264 custom_table.add_entry(946684800, 32, "2000-01-01: Custom leap second"); // Y2K
265 custom_table.add_entry(978307200, 33, "2001-01-01: Another custom leap second");
266
267 assert_eq!(custom_table.leap_second_count(), 2);
268 assert_eq!(custom_table.tai_utc_offset_at(946684800), 32);
269 assert_eq!(custom_table.tai_utc_offset_at(978307200), 33);
270 assert_eq!(custom_table.tai_utc_offset_at(1000000000), 33); // After last leap second
271
272 // Test custom configuration with custom table
273 let custom_config = LeapSecondConfig {
274 enabled: true,
275 allow_leap_second_parsing: true,
276 validate_leap_seconds: false,
277 custom_table: Some(custom_table),
278 };
279
280 let table = custom_config.get_table();
281 assert_eq!(table.leap_second_count(), 2);
282
283 Ok(())
284}
285
286#[test]
287fn test_disabled_leap_second_handling() -> Outcome<()> {
288 let disabled_config = LeapSecondConfig::disabled();
289 let zone = CalClockZone::utc();
290
291 // Should not allow leap second parsing when disabled
292 let result = ClockTime::new_with_leap_seconds(23, 59, 60, 0, zone.clone(), &disabled_config);
293 assert!(result.is_err());
294
295 // TAI-UTC conversion should return 0 offset when disabled
296 let table = disabled_config.get_table();
297 assert_eq!(table.tai_utc_offset_at(1600000000), 0);
298 assert_eq!(table.utc_to_tai(1600000000), 1600000000); // No offset
299
300 Ok(())
301}
302
303pub fn test_leap_second_support(filter: &str) -> Outcome<()> {
304 println!("=== Leap Second Support Demo ===");
305
306 res!(test_it(filter, &["leap_second_table", "all", "leap", "table"], || {
307 test_leap_second_table_creation()
308 }));
309
310 res!(test_it(filter, &["tai_utc_offset", "all", "leap", "offset"], || {
311 test_tai_utc_offset_calculation()
312 }));
313
314 res!(test_it(filter, &["utc_tai_conversion", "all", "leap", "conversion"], || {
315 test_utc_tai_conversion()
316 }));
317
318 res!(test_it(filter, &["leap_second_detection", "all", "leap", "detection"], || {
319 test_leap_second_detection()
320 }));
321
322 res!(test_it(filter, &["leap_second_config", "all", "leap", "config"], || {
323 test_leap_second_configuration()
324 }));
325
326 res!(test_it(filter, &["clock_time_leap", "all", "leap", "time"], || {
327 test_clock_time_with_leap_seconds()
328 }));
329
330 res!(test_it(filter, &["calclock_leap", "all", "leap", "calclock"], || {
331 test_calclock_with_leap_seconds()
332 }));
333
334 res!(test_it(filter, &["leap_normalization", "all", "leap", "normalize"], || {
335 test_leap_second_normalization()
336 }));
337
338 res!(test_it(filter, &["tai_conversions", "all", "leap", "tai"], || {
339 test_tai_utc_conversions()
340 }));
341
342 res!(test_it(filter, &["leap_statistics", "all", "leap", "stats"], || {
343 test_leap_second_statistics()
344 }));
345
346 res!(test_it(filter, &["leap_boundaries", "all", "leap", "boundary"], || {
347 test_leap_second_boundary_cases()
348 }));
349
350 res!(test_it(filter, &["custom_leap_table", "all", "leap", "custom"], || {
351 test_custom_leap_second_table()
352 }));
353
354 res!(test_it(filter, &["disabled_leap", "all", "leap", "disabled"], || {
355 test_disabled_leap_second_handling()
356 }));
357
358 println!("✓ All leap second tests passed!");
359 Ok(())
360}
361
362fn test_it<F>(filter: &str, keywords: &[&str], test_fn: F) -> Outcome<()>
363where
364 F: FnOnce() -> Outcome<()>,
365{
366 if keywords.iter().any(|&kw| filter.contains(kw)) {
367 test_fn()
368 } else {
369 Ok(())
370 }
371}