Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_datime/tests/iana_integration.rs

15.0 KiB, 9 runs

created by r1870400018:6413, 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::{
5 prelude::*,
6 test::test_it,
7};
8use oxedyne_fe2o3_datime::{
9 time::{
10 CalClockZone,
11 TZifParser,
12 TZifData,
13 LocalTimeResult,
14 LocalTimeType,
15 LeapSecond,
16 },
17};
18
19pub fn test_iana_integration(filter: &str) -> Outcome<()> {
20
21 res!(test_it(filter, &["tzif_parser_creation", "all", "iana", "tzif"], || {
22 let parser = TZifParser::new();
23 // Parser should be created successfully
24 assert!(parser.timezone_data().is_none());
25 Ok(())
26 }));
27
28 res!(test_it(filter, &["tzif_minimal_header", "all", "iana", "tzif"], || {
29 // Create minimal valid TZif v1 file
30 let mut data = Vec::new();
31
32 // Header
33 data.extend_from_slice(b"TZif"); // Magic number
34 data.push(0); // Version 1 (0 byte)
35 data.extend_from_slice(&[0u8; 15]); // Reserved (15 bytes)
36
37 // Counts (all zero for minimal file)
38 data.extend_from_slice(&[0u8; 4]); // tzh_utcnt
39 data.extend_from_slice(&[0u8; 4]); // tzh_stdcnt
40 data.extend_from_slice(&[0u8; 4]); // tzh_leapcnt
41 data.extend_from_slice(&[0u8; 4]); // tzh_timecnt
42 data.extend_from_slice(&[0u8; 4]); // tzh_typecnt
43 data.extend_from_slice(&[0u8; 4]); // tzh_charcnt
44
45 let mut parser = TZifParser::new();
46 let result = parser.load_from_bytes(&data);
47 assert!(result.is_ok());
48
49 let tzif_data = parser.timezone_data().unwrap();
50 assert_eq!(tzif_data.version, 1);
51 assert!(tzif_data.transition_times.is_empty());
52 assert!(tzif_data.local_time_types.is_empty());
53
54 Ok(())
55 }));
56
57 res!(test_it(filter, &["tzif_v2_header", "all", "iana", "tzif"], || {
58 // Create minimal valid TZif v2 file
59 let mut data = Vec::new();
60
61 // Header
62 data.extend_from_slice(b"TZif"); // Magic number
63 data.push(b'2'); // Version 2
64 data.extend_from_slice(&[0u8; 15]); // Reserved
65
66 // Counts (all zero for minimal file)
67 data.extend_from_slice(&[0u8; 24]); // 6 * 4 bytes of counts
68
69 // Second header for v2 data
70 data.extend_from_slice(b"TZif"); // Magic number
71 data.push(b'2'); // Version 2
72 data.extend_from_slice(&[0u8; 15]); // Reserved
73 data.extend_from_slice(&[0u8; 24]); // 6 * 4 bytes of counts
74
75 // POSIX footer
76 data.push(b'\n'); // Newline
77 data.extend_from_slice(b"GMT0"); // POSIX TZ string
78 data.push(b'\n'); // Final newline
79
80 let mut parser = TZifParser::new();
81 let result = parser.load_from_bytes(&data);
82 assert!(result.is_ok());
83
84 let tzif_data = parser.timezone_data().unwrap();
85 assert_eq!(tzif_data.version, 2);
86 assert_eq!(tzif_data.posix_tz_string.as_ref().unwrap(), "GMT0");
87
88 Ok(())
89 }));
90
91 res!(test_it(filter, &["tzif_fixed_offset", "all", "iana", "tzif"], || {
92 // Create TZif file with single fixed offset (like UTC+5)
93 let mut data = Vec::new();
94
95 // Header
96 data.extend_from_slice(b"TZif"); // Magic
97 data.push(0); // Version 1
98 data.extend_from_slice(&[0u8; 15]); // Reserved
99
100 // Counts
101 data.extend_from_slice(&[0u8; 4]); // tzh_utcnt = 0
102 data.extend_from_slice(&[0u8; 4]); // tzh_stdcnt = 0
103 data.extend_from_slice(&[0u8; 4]); // tzh_leapcnt = 0
104 data.extend_from_slice(&[0u8; 4]); // tzh_timecnt = 0
105 data.extend_from_slice(&1u32.to_be_bytes()); // tzh_typecnt = 1
106 data.extend_from_slice(&4u32.to_be_bytes()); // tzh_charcnt = 4
107
108 // No transition times (tzh_timecnt = 0)
109 // No transition types (tzh_timecnt = 0)
110
111 // Local time types (1 entry)
112 data.extend_from_slice(&18000i32.to_be_bytes()); // UTC offset: +5 hours
113 data.push(0); // is_dst = false
114 data.push(0); // abbreviation index = 0
115
116 // Abbreviations
117 data.extend_from_slice(b"+05\0"); // "+05" + null terminator
118
119 let mut parser = TZifParser::new();
120 let result = parser.load_from_bytes(&data);
121 assert!(result.is_ok());
122
123 let tzif_data = parser.timezone_data().unwrap();
124 assert_eq!(tzif_data.local_time_types.len(), 1);
125 assert_eq!(tzif_data.local_time_types[0].utc_offset, 18000);
126 assert!(!tzif_data.local_time_types[0].is_dst);
127 assert_eq!(tzif_data.get_abbreviation(&tzif_data.local_time_types[0]).unwrap(), "+05");
128
129 Ok(())
130 }));
131
132 res!(test_it(filter, &["tzif_with_transitions", "all", "iana", "tzif"], || {
133 // Create TZif file with DST transitions (simplified)
134 let mut data = Vec::new();
135
136 // Header
137 data.extend_from_slice(b"TZif");
138 data.push(0); // Version 1
139 data.extend_from_slice(&[0u8; 15]); // Reserved
140
141 // Counts
142 data.extend_from_slice(&[0u8; 4]); // tzh_utcnt = 0
143 data.extend_from_slice(&[0u8; 4]); // tzh_stdcnt = 0
144 data.extend_from_slice(&[0u8; 4]); // tzh_leapcnt = 0
145 data.extend_from_slice(&2u32.to_be_bytes()); // tzh_timecnt = 2
146 data.extend_from_slice(&2u32.to_be_bytes()); // tzh_typecnt = 2
147 data.extend_from_slice(&8u32.to_be_bytes()); // tzh_charcnt = 8
148
149 // Transition times (2 entries, 32-bit)
150 data.extend_from_slice(&1609459200i32.to_be_bytes()); // 2021-01-01 00:00:00 UTC
151 data.extend_from_slice(&1625097600i32.to_be_bytes()); // 2021-07-01 00:00:00 UTC
152
153 // Transition types (2 entries)
154 data.push(0); // First transition uses type 0 (standard time)
155 data.push(1); // Second transition uses type 1 (daylight time)
156
157 // Local time types (2 entries)
158 // Type 0: Standard time (EST: -5 hours)
159 data.extend_from_slice(&(-18000i32).to_be_bytes());
160 data.push(0); // is_dst = false
161 data.push(0); // abbreviation index = 0
162
163 // Type 1: Daylight time (EDT: -4 hours)
164 data.extend_from_slice(&(-14400i32).to_be_bytes());
165 data.push(1); // is_dst = true
166 data.push(4); // abbreviation index = 4
167
168 // Abbreviations
169 data.extend_from_slice(b"EST\0EDT\0"); // "EST" + null + "EDT" + null
170
171 let mut parser = TZifParser::new();
172 let result = parser.load_from_bytes(&data);
173 assert!(result.is_ok());
174
175 let tzif_data = parser.timezone_data().unwrap();
176 assert_eq!(tzif_data.transition_times.len(), 2);
177 assert_eq!(tzif_data.transition_types.len(), 2);
178 assert_eq!(tzif_data.local_time_types.len(), 2);
179
180 // Check standard time type
181 assert_eq!(tzif_data.local_time_types[0].utc_offset, -18000);
182 assert!(!tzif_data.local_time_types[0].is_dst);
183 assert_eq!(tzif_data.get_abbreviation(&tzif_data.local_time_types[0]).unwrap(), "EST");
184
185 // Check daylight time type
186 assert_eq!(tzif_data.local_time_types[1].utc_offset, -14400);
187 assert!(tzif_data.local_time_types[1].is_dst);
188 assert_eq!(tzif_data.get_abbreviation(&tzif_data.local_time_types[1]).unwrap(), "EDT");
189
190 Ok(())
191 }));
192
193 res!(test_it(filter, &["tzif_leap_seconds", "all", "iana", "tzif"], || {
194 // Create TZif file with leap second data
195 let mut data = Vec::new();
196
197 // Header
198 data.extend_from_slice(b"TZif");
199 data.push(0); // Version 1
200 data.extend_from_slice(&[0u8; 15]); // Reserved
201
202 // Counts
203 data.extend_from_slice(&[0u8; 4]); // tzh_utcnt = 0
204 data.extend_from_slice(&[0u8; 4]); // tzh_stdcnt = 0
205 data.extend_from_slice(&1u32.to_be_bytes()); // tzh_leapcnt = 1
206 data.extend_from_slice(&[0u8; 4]); // tzh_timecnt = 0
207 data.extend_from_slice(&1u32.to_be_bytes()); // tzh_typecnt = 1
208 data.extend_from_slice(&4u32.to_be_bytes()); // tzh_charcnt = 4
209
210 // No transitions
211
212 // Local time type (UTC)
213 data.extend_from_slice(&0i32.to_be_bytes()); // UTC offset = 0
214 data.push(0); // is_dst = false
215 data.push(0); // abbreviation index = 0
216
217 // Abbreviations
218 data.extend_from_slice(b"UTC\0");
219
220 // Leap second (1 entry)
221 data.extend_from_slice(&78796800i32.to_be_bytes()); // 1972-07-01 (first leap second)
222 data.extend_from_slice(&1i32.to_be_bytes()); // correction = 1 second
223
224 let mut parser = TZifParser::new();
225 let result = parser.load_from_bytes(&data);
226 assert!(result.is_ok());
227
228 let tzif_data = parser.timezone_data().unwrap();
229 assert_eq!(tzif_data.leap_seconds.len(), 1);
230 assert_eq!(tzif_data.leap_seconds[0].transition_time, 78796800);
231 assert_eq!(tzif_data.leap_seconds[0].correction, 1);
232
233 Ok(())
234 }));
235
236 res!(test_it(filter, &["calclockzone_from_tzif", "all", "iana", "calclockzone"], || {
237 // Test CalClockZone creation from TZif data
238 let tzif_data = TZifData {
239 version: 2,
240 transition_times: vec![1609459200], // 2021-01-01
241 transition_types: vec![0],
242 local_time_types: vec![
243 LocalTimeType {
244 utc_offset: -18000, // EST: -5 hours
245 is_dst: false,
246 abbreviation_index: 0,
247 }
248 ],
249 abbreviations: "EST\0".to_string(),
250 leap_seconds: Vec::new(),
251 standard_wall_indicators: Vec::new(),
252 ut_local_indicators: Vec::new(),
253 posix_tz_string: Some("EST5".to_string()),
254 };
255
256 let zone = res!(CalClockZone::from_tzif_data("America/New_York", tzif_data));
257 assert_eq!(zone.id(), "America/New_York");
258
259 // Test timezone offset calculation using TZif data
260 let offset = res!(zone.offset_millis_at_time(1609459200000)); // 2021-01-01 00:00:00 UTC
261 assert_eq!(offset, -18000 * 1000); // -5 hours in milliseconds
262
263 Ok(())
264 }));
265
266 res!(test_it(filter, &["dst_transition_ambiguity", "all", "iana", "dst"], || {
267 // Test DST transition ambiguity handling
268 let tzif_data = TZifData {
269 version: 2,
270 transition_times: vec![
271 1615708800, // Spring forward: 2021-03-14 07:00:00 UTC (2 AM EST -> 3 AM EDT)
272 1636264800, // Fall back: 2021-11-07 06:00:00 UTC (2 AM EDT -> 1 AM EST)
273 ],
274 transition_types: vec![1, 0], // Spring to DST, Fall to standard
275 local_time_types: vec![
276 LocalTimeType {
277 utc_offset: -18000, // EST: -5 hours
278 is_dst: false,
279 abbreviation_index: 0,
280 },
281 LocalTimeType {
282 utc_offset: -14400, // EDT: -4 hours
283 is_dst: true,
284 abbreviation_index: 4,
285 }
286 ],
287 abbreviations: "EST\0EDT\0".to_string(),
288 leap_seconds: Vec::new(),
289 standard_wall_indicators: Vec::new(),
290 ut_local_indicators: Vec::new(),
291 posix_tz_string: Some("EST5EDT,M3.2.0,M11.1.0".to_string()),
292 };
293
294 let zone = res!(CalClockZone::from_tzif_data("America/New_York", tzif_data));
295
296 // Test UTC to local conversion
297 let utc_spring = 1615708800000; // Spring forward time
298 match zone.utc_to_local(utc_spring) {
299 LocalTimeResult::Single(local_time) => {
300 // Should convert normally
301 assert!(local_time != 0);
302 },
303 _ => {
304 // UTC to local should generally be unambiguous
305 }
306 }
307
308 // Test DST detection
309 let is_dst_summer = res!(zone.in_daylight_time(1625097600000)); // July 1, 2021
310 let is_dst_winter = res!(zone.in_daylight_time(1609459200000)); // January 1, 2021
311
312 // Summer should be DST, winter should not (depends on exact TZif data implementation)
313 // Note: This test depends on the TZif data interpretation logic
314
315 Ok(())
316 }));
317
318 res!(test_it(filter, &["tzif_error_handling", "all", "iana", "error"], || {
319 let mut parser = TZifParser::new();
320
321 // Test invalid magic number
322 let invalid_magic = b"TZIF1234567890123456789012345678901234567890";
323 assert!(parser.load_from_bytes(invalid_magic).is_err());
324
325 // Test truncated file
326 let truncated = b"TZif";
327 assert!(parser.load_from_bytes(truncated).is_err());
328
329 // Test invalid version
330 let mut invalid_version = Vec::new();
331 invalid_version.extend_from_slice(b"TZif");
332 invalid_version.push(b'X'); // Invalid version
333 invalid_version.extend_from_slice(&[0u8; 39]); // Rest of minimal header
334 assert!(parser.load_from_bytes(&invalid_version).is_err());
335
336 Ok(())
337 }));
338
339 res!(test_it(filter, &["historical_transitions", "all", "iana", "historical"], || {
340 // Test that TZif data supports historical timezone changes
341 let tzif_data = TZifData {
342 version: 2,
343 transition_times: vec![
344 -2147483648, // Very old date (1901)
345 0, // Unix epoch (1970)
346 1609459200, // Recent date (2021)
347 ],
348 transition_types: vec![0, 1, 0],
349 local_time_types: vec![
350 LocalTimeType {
351 utc_offset: -18000,
352 is_dst: false,
353 abbreviation_index: 0,
354 },
355 LocalTimeType {
356 utc_offset: -14400,
357 is_dst: true,
358 abbreviation_index: 4,
359 }
360 ],
361 abbreviations: "EST\0EDT\0".to_string(),
362 leap_seconds: Vec::new(),
363 standard_wall_indicators: Vec::new(),
364 ut_local_indicators: Vec::new(),
365 posix_tz_string: Some("EST5EDT,M3.2.0,M11.1.0".to_string()),
366 };
367
368 // Test that we can get offsets for historical dates
369 let historical_offset = res!(tzif_data.get_offset_at_utc(-2147483648));
370 assert_eq!(historical_offset, -18000);
371
372 let epoch_offset = res!(tzif_data.get_offset_at_utc(0));
373 assert_eq!(epoch_offset, -14400); // EDT during summer 1970
374
375 let recent_offset = res!(tzif_data.get_offset_at_utc(1609459200));
376 assert_eq!(recent_offset, -18000); // EST in January
377
378 Ok(())
379 }));
380
381 Ok(())
382}