timekit 0.2.0

A simple Rust library for working with timezones and displaying current time in multiple zones.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
#[cfg(test)]
mod tests {
    use super::*;
    use std::ops::{Add, Sub};
    use timekit::{
        adjust_second_with_timezone, calculate_date_since_epoch, compute_total_seconds,
        constants::*, days_in_month, is_leap_year, now, DateTime, TimeDelta, TimeZone,
    };

    // Test the is_leap_year function
    #[test]
    fn test_is_leap_year() {
        // Regular leap years
        assert_eq!(is_leap_year(2020), true);
        assert_eq!(is_leap_year(2024), true);

        // Century years not leap years unless divisible by 400
        assert_eq!(is_leap_year(1900), false);
        assert_eq!(is_leap_year(2000), true);

        // Common years
        assert_eq!(is_leap_year(2019), false);
        assert_eq!(is_leap_year(2021), false);
    }

    // Test the days_in_month function
    #[test]
    fn test_days_in_month() {
        // Months with 31 days
        assert_eq!(days_in_month(1, 2023), 31);
        assert_eq!(days_in_month(3, 2023), 31);
        assert_eq!(days_in_month(5, 2023), 31);
        assert_eq!(days_in_month(7, 2023), 31);
        assert_eq!(days_in_month(8, 2023), 31);
        assert_eq!(days_in_month(10, 2023), 31);
        assert_eq!(days_in_month(12, 2023), 31);

        // Months with 30 days
        assert_eq!(days_in_month(4, 2023), 30);
        assert_eq!(days_in_month(6, 2023), 30);
        assert_eq!(days_in_month(9, 2023), 30);
        assert_eq!(days_in_month(11, 2023), 30);

        // February in leap year
        assert_eq!(days_in_month(2, 2024), 29);

        // February in common year
        assert_eq!(days_in_month(2, 2023), 28);

        // Invalid month
        assert_eq!(days_in_month(13, 2023), 0);
        assert_eq!(days_in_month(0, 2023), 0);
    }

    // Test the DateTime::new function with valid inputs
    #[test]
    fn test_datetime_new_valid() {
        let datetime = DateTime::new(2023, 1, 1, 0, 0, 0, TimeZone::UTC);
        assert!(datetime.is_ok());

        let datetime = DateTime::new(2024, 2, 29, 23, 59, 59, TimeZone::UTC); // Leap year
        assert!(datetime.is_ok());

        let datetime = DateTime::new(2023, 12, 31, 23, 59, 59, TimeZone::UTC);
        assert!(datetime.is_ok());
    }

    // Test the DateTime::new function with invalid inputs
    #[test]
    fn test_datetime_new_invalid() {
        // Invalid month
        let datetime = DateTime::new(2023, 0, 1, 0, 0, 0, TimeZone::UTC);
        assert!(datetime.is_err());

        let datetime = DateTime::new(2023, 13, 1, 0, 0, 0, TimeZone::UTC);
        assert!(datetime.is_err());

        // Invalid day
        let datetime = DateTime::new(2023, 2, 29, 0, 0, 0, TimeZone::UTC); // Not a leap year
        assert!(datetime.is_err());

        // Invalid hour
        let datetime = DateTime::new(2023, 1, 1, 24, 0, 0, TimeZone::UTC);
        assert!(datetime.is_err());

        // Invalid minute
        let datetime = DateTime::new(2023, 1, 1, 0, 60, 0, TimeZone::UTC);
        assert!(datetime.is_err());

        // Invalid second
        let datetime = DateTime::new(2023, 1, 1, 0, 0, 60, TimeZone::UTC);
        assert!(datetime.is_err());
    }

    // Test the now function for UTC timezone
    #[test]
    fn test_now_utc() {
        let current_time = now(TimeZone::UTC).unwrap();
        assert!(current_time.year >= 1970);
        assert!(current_time.month >= 1 && current_time.month <= 12);
        assert!(current_time.day >= 1 && current_time.day <= 31);
        assert!(current_time.hour <= 23);
        assert!(current_time.minute <= 59);
        assert!(current_time.second <= 59);
    }

    // Test the now function for KST timezone
    #[test]
    fn test_now_kst() {
        let current_time = now(TimeZone::KST).unwrap();
        assert!(current_time.year >= 1970);
        assert!(current_time.month >= 1 && current_time.month <= 12);
        assert!(current_time.day >= 1 && current_time.day <= 31);
        assert!(current_time.hour <= 23);
        assert!(current_time.minute <= 59);
        assert!(current_time.second <= 59);
    }

    // Test strftime formatting
    #[test]
    fn test_strftime() {
        let datetime = DateTime::new(2023, 8, 3, 5, 20, 59, TimeZone::UTC).unwrap();
        let formatted = datetime.strftime("%Y-%m-%d %H:%M:%S");
        assert_eq!(formatted, "2023-08-03 05:20:59");
    }

    // Test to_unix_seconds and from_unix_seconds functions
    #[test]
    fn test_unix_seconds_conversion() {
        let datetime = DateTime::new(2023, 1, 1, 0, 0, 0, TimeZone::UTC).unwrap();
        let unix_seconds = datetime.to_unix_seconds();
        let converted_datetime = DateTime::from_unix_seconds(unix_seconds, TimeZone::UTC).unwrap();
        assert_eq!(datetime, converted_datetime);

        // Test with different timezone
        let datetime_kst = DateTime::new(2023, 1, 1, 9, 0, 0, TimeZone::KST).unwrap();
        let unix_seconds_kst = datetime_kst.to_unix_seconds();
        let converted_datetime_kst =
            DateTime::from_unix_seconds(unix_seconds_kst, TimeZone::KST).unwrap();
        assert_eq!(datetime_kst, converted_datetime_kst);

        // Ensure unix_seconds are the same for UTC and KST adjusted for timezone
        assert_eq!(unix_seconds, unix_seconds_kst);
    }

    // Test add_timedelta function
    #[test]
    fn test_add_timedelta() {
        let datetime = DateTime::new(2023, 12, 31, 23, 59, 59, TimeZone::UTC).unwrap();
        let delta = TimeDelta {
            days: 1,
            seconds: 1,
            ..Default::default()
        };
        let new_datetime = datetime.add_timedelta(delta).unwrap();
        let expected_datetime = DateTime::new(2024, 1, 2, 0, 0, 0, TimeZone::UTC).unwrap();
        assert_eq!(new_datetime, expected_datetime);
    }

    // Test sub_timedelta function
    #[test]
    fn test_sub_timedelta() {
        let datetime = DateTime::new(2024, 1, 1, 0, 0, 0, TimeZone::UTC).unwrap();
        let delta = TimeDelta {
            days: 1,
            ..Default::default()
        };
        let new_datetime = datetime.sub_timedelta(delta).unwrap();
        let expected_datetime = DateTime::new(2023, 12, 31, 0, 0, 0, TimeZone::UTC).unwrap();
        assert_eq!(new_datetime, expected_datetime);
    }

    // Test addition operator overloading
    #[test]
    fn test_add_operator() {
        let datetime = DateTime::new(2023, 1, 1, 0, 0, 0, TimeZone::UTC).unwrap();
        let delta = TimeDelta {
            days: 30,
            ..Default::default()
        };
        let new_datetime = datetime + delta;
        let expected_datetime = DateTime::new(2023, 1, 31, 0, 0, 0, TimeZone::UTC).unwrap();
        assert_eq!(new_datetime.unwrap(), expected_datetime);
    }

    // Test subtraction operator overloading
    #[test]
    fn test_sub_operator() {
        let datetime = DateTime::new(2023, 1, 31, 0, 0, 0, TimeZone::UTC).unwrap();
        let delta = TimeDelta {
            days: 30,
            ..Default::default()
        };
        let new_datetime = datetime - delta;
        let expected_datetime = DateTime::new(2023, 1, 1, 0, 0, 0, TimeZone::UTC).unwrap();
        assert_eq!(new_datetime.unwrap(), expected_datetime);
    }

    // Test edge case transitions (e.g., leap years, month/year boundaries)
    #[test]
    fn test_edge_case_transitions() {
        // Leap year transition
        let datetime = DateTime::new(2024, 2, 28, 23, 59, 59, TimeZone::UTC).unwrap();
        let delta = TimeDelta {
            seconds: 1,
            ..Default::default()
        };
        let new_datetime = datetime.add_timedelta(delta).unwrap();
        let expected_datetime = DateTime::new(2024, 2, 29, 0, 0, 0, TimeZone::UTC).unwrap();
        assert_eq!(new_datetime, expected_datetime);

        // Non-leap year transition
        let datetime = DateTime::new(2023, 2, 28, 23, 59, 59, TimeZone::UTC).unwrap();
        let new_datetime = datetime.add_timedelta(delta).unwrap();
        let expected_datetime = DateTime::new(2023, 3, 1, 0, 0, 0, TimeZone::UTC).unwrap();
        assert_eq!(new_datetime, expected_datetime);

        // Year boundary transition
        let datetime = DateTime::new(2023, 12, 31, 23, 59, 59, TimeZone::UTC).unwrap();
        let new_datetime = datetime.add_timedelta(delta).unwrap();
        let expected_datetime = DateTime::new(2024, 1, 1, 0, 0, 0, TimeZone::UTC).unwrap();
        assert_eq!(new_datetime, expected_datetime);
    }

    // Test timezone offset correctness
    #[test]
    fn test_timezone_offsets() {
        let datetime_utc = DateTime::new(2023, 8, 1, 12, 0, 0, TimeZone::UTC).unwrap();
        let datetime_kst = DateTime::new(2023, 8, 1, 21, 0, 0, TimeZone::KST).unwrap();

        // Both should represent the same point in time
        assert_eq!(
            datetime_utc.to_unix_seconds(),
            datetime_kst.to_unix_seconds()
        );

        let datetime_est = DateTime::new(2023, 8, 1, 7, 0, 0, TimeZone::EST).unwrap();
        assert_eq!(
            datetime_utc.to_unix_seconds(),
            datetime_est.to_unix_seconds()
        );
    }

    // Test TimeDelta display implementation
    #[test]
    fn test_timedelta_display() {
        let delta = TimeDelta {
            weeks: 1,
            days: 2,
            hours: 3,
            minutes: 4,
            seconds: 5,
        };
        let delta_str = format!("{}", delta);
        assert_eq!(delta_str, "1 week, 2 days, 3 hours, 4 minutes, 5 seconds");

        let delta = TimeDelta {
            weeks: 0,
            days: 0,
            hours: 0,
            minutes: 0,
            seconds: 0,
        };
        let delta_str = format!("{}", delta);
        assert_eq!(delta_str, "0 seconds");
    }

    // Test TimeDelta default implementation
    #[test]
    fn test_timedelta_default() {
        let delta = TimeDelta::default();
        assert_eq!(delta.weeks, 0);
        assert_eq!(delta.days, 0);
        assert_eq!(delta.hours, 0);
        assert_eq!(delta.minutes, 0);
        assert_eq!(delta.seconds, 0);
    }

    // Test error handling when subtracting more than current DateTime
    #[test]
    fn test_subtract_past_epoch() {
        let datetime = DateTime::new(1970, 1, 1, 0, 0, 0, TimeZone::UTC).unwrap();
        let delta = TimeDelta {
            days: 1,
            ..Default::default()
        };
        let result = datetime.sub_timedelta(delta);
        assert!(result.is_err());
        assert_eq!(
            result.unwrap_err(),
            "Resulting DateTime is before Unix epoch (1970-01-01 00:00:00 UTC)"
        );
    }

    // Test adding negative TimeDelta
    #[test]
    fn test_add_negative_timedelta() {
        let datetime = DateTime::new(2023, 1, 2, 0, 0, 0, TimeZone::UTC).unwrap();
        let delta = TimeDelta {
            days: -1,
            ..Default::default()
        };
        let new_datetime = datetime.add_timedelta(delta).unwrap();
        let expected_datetime = DateTime::new(2023, 1, 1, 0, 0, 0, TimeZone::UTC).unwrap();
        assert_eq!(new_datetime, expected_datetime);
    }

    // Test TimeZone offset_in_seconds correctness
    #[test]
    fn test_timezone_offset_in_seconds() {
        assert_eq!(TimeZone::UTC.offset_in_seconds(), 0);
        assert_eq!(TimeZone::KST.offset_in_seconds(), 9 * 3600);
        assert_eq!(TimeZone::EST.offset_in_seconds(), -5 * 3600);
        assert_eq!(TimeZone::IST.offset_in_seconds(), 5 * 3600 + 1800); // 5 hours 30 minutes
    }

    // Test compute_total_seconds utility function
    #[test]
    fn test_compute_total_seconds() {
        let total_seconds = compute_total_seconds(1, 1, 1, 1, 1);
        let expected_seconds = SECONDS_IN_WEEK as i64
            + SECONDS_IN_DAY as i64
            + SECONDS_IN_HOUR as i64
            + SECONDS_IN_MINUTE as i64
            + 1;
        assert_eq!(total_seconds, expected_seconds);
    }

    // Test adjust_second_with_timezone utility function
    #[test]
    fn test_adjust_second_with_timezone() {
        let total_seconds = 1000;
        let adjusted_seconds = adjust_second_with_timezone(total_seconds, TimeZone::KST);
        assert_eq!(adjusted_seconds, total_seconds + (9 * 3600) as u64);
    }

    // Test calculate_date_since_epoch utility function
    #[test]
    fn test_calculate_date_since_epoch() {
        let adjusted_seconds = 0;
        let datetime = calculate_date_since_epoch(adjusted_seconds, TimeZone::UTC).unwrap();
        let expected_datetime = DateTime::new(1970, 1, 1, 0, 0, 0, TimeZone::UTC).unwrap();
        assert_eq!(datetime, expected_datetime);
    }

    #[test]
    fn test_timezone_offsets_correctness() {
        assert_eq!(TimeZone::UTC.offset_in_seconds(), 0);
        assert_eq!(TimeZone::KST.offset_in_seconds(), 32400);
        assert_eq!(TimeZone::EST.offset_in_seconds(), -18000);
        assert_eq!(TimeZone::PST.offset_in_seconds(), -28800);
        assert_eq!(TimeZone::IST.offset_in_seconds(), 19800);
    }
    #[test]
    fn test_from_unix_seconds_debug() {
        let unix_seconds = 1690891200; // UTC 기준 2023-08-01 12:00:00
        let datetime_utc = DateTime::from_unix_seconds(unix_seconds, TimeZone::UTC).unwrap();
        let datetime_kst = DateTime::from_unix_seconds(unix_seconds, TimeZone::KST).unwrap();

        println!("UTC DateTime: {:?}", datetime_utc);
        println!("KST DateTime: {:?}", datetime_kst);

        assert_eq!(datetime_utc.to_unix_seconds(), unix_seconds);
        assert_eq!(datetime_kst.to_unix_seconds(), unix_seconds);
    }

    #[test]
    fn test_all_timezone_offsets() {
        let unix_seconds = 1690891200; // UTC 기준 2023-08-01 12:00:00

        let timezones = vec![
            TimeZone::UTC,
            TimeZone::KST,
            TimeZone::EST,
            TimeZone::PST,
            TimeZone::JST,
            TimeZone::IST,
            TimeZone::CET,
            TimeZone::AST,
            TimeZone::CST,
            TimeZone::MST,
            TimeZone::AKST,
            TimeZone::HST,
            TimeZone::BST,
            TimeZone::WET,
            TimeZone::EET,
            TimeZone::SAST,
            TimeZone::EAT,
            TimeZone::AEST,
            TimeZone::ACST,
            TimeZone::AWST,
            TimeZone::CSTAsia,
            TimeZone::SGT,
            TimeZone::HKT,
        ];

        for timezone in &timezones {
            let datetime = DateTime::from_unix_seconds(unix_seconds, *timezone).unwrap();
            let recalculated_unix = datetime.to_unix_seconds();

            println!(
                "Timezone: {:?}, DateTime: {:?}, Recalculated Unix: {}, Expected Unix: {}",
                timezone, datetime, recalculated_unix, unix_seconds
            );

            assert_eq!(
                recalculated_unix, unix_seconds,
                "Timezone {:?} failed",
                timezone
            );
        }
    }

    #[test]
    fn test_datetime_new_correctness_debug() {
        let datetime_utc = DateTime::new(2023, 8, 1, 12, 0, 0, TimeZone::UTC).unwrap();
        let datetime_kst = DateTime::new(2023, 8, 1, 21, 0, 0, TimeZone::KST).unwrap();
        let datetime_est = DateTime::new(2023, 8, 1, 7, 0, 0, TimeZone::EST).unwrap();

        println!("UTC: {:?}", datetime_utc);
        println!("KST: {:?}", datetime_kst);
        println!("EST: {:?}", datetime_est);

        println!(
            "UTC to Unix Seconds: {}, KST to Unix Seconds: {}, EST to Unix Seconds: {}",
            datetime_utc.to_unix_seconds(),
            datetime_kst.to_unix_seconds(),
            datetime_est.to_unix_seconds()
        );

        assert_eq!(datetime_utc.to_unix_seconds(), 1690891200);
        assert_eq!(datetime_kst.to_unix_seconds(), 1690891200);
        assert_eq!(datetime_est.to_unix_seconds(), 1690891200);
    }
}