1use crate::{
2 ATTOS_PER_SEC, Dt, GregorianTime, SEC_PER_DAYI64, Scale, TSpan, Weekday, YmdHms,
3 leap_seconds::get_leap_seconds,
4};
5
6impl Dt {
7 #[inline]
10 pub const fn unix_sec_to_gregorian_ymd(unix_sec: i64) -> (i64, u8, u8) {
11 let days_since_1970 = unix_sec.div_euclid(SEC_PER_DAYI64);
12 let jdn = days_since_1970.saturating_add(2440588);
14 Self::jdn_to_ymd(jdn)
15 }
16
17 pub const fn to_gregorian_time(&self) -> GregorianTime {
18 let ymdhms = self.to_ymdhms();
20 let unix_attosec = self.to_epoch(Dt::UNIX_EPOCH, Scale::UTC).to_attos();
21
22 let (iso_yr, iso_wk, iso_wkday) =
23 self.to_iso_week_date(Some((ymdhms.yr, ymdhms.mo, ymdhms.day)));
24 let day_of_yr = self.day_of_year(Some((ymdhms.yr, ymdhms.mo, ymdhms.day)));
25 let jdn = Self::ymd_to_jdn(ymdhms.yr, ymdhms.mo, ymdhms.day);
26 let wkday = Self::jdn_to_weekday(jdn);
27 let wk_of_yr_sun = self.wk_sun(Some((ymdhms.yr, ymdhms.mo, ymdhms.day)), Some(day_of_yr));
28 let wk_of_yr_mon = self.wk_mon(Some((ymdhms.yr, ymdhms.mo, ymdhms.day)), Some(day_of_yr));
29
30 GregorianTime {
31 unix_attosec,
32 yr: ymdhms.yr,
33 mo: ymdhms.mo,
34 day: ymdhms.day,
35 hr: ymdhms.hr,
36 min: ymdhms.min,
37 sec: ymdhms.sec,
38 attos: ymdhms.attos,
39 iso_yr,
40 iso_wk,
41 iso_wkday,
42 day_of_yr,
43 wkday,
44 wk_of_yr_sun,
45 wk_of_yr_mon,
46 offset_sec: None,
47 tz: None,
48 tz_abbrev: None,
49 }
50 }
51
52 #[inline]
59 pub const fn to_ymdhms(&self) -> YmdHms {
60 let canon = self.to_epoch(Dt::UNIX_EPOCH, Scale::UTC);
63
64 let unix_sec = canon.sec;
65 let attos = canon.attos;
66
67 let is_leap_second = get_leap_seconds(&self, false).is_leap_second;
68
69 let unix_sec_for_date = if is_leap_second {
72 unix_sec - 1
73 } else {
74 unix_sec
75 };
76
77 let (yr, mo, day) = Self::unix_sec_to_gregorian_ymd(unix_sec_for_date);
78
79 let (hr, min, sec) = if is_leap_second {
81 (23, 59, 60)
82 } else {
83 let seconds_since_midnight = unix_sec.rem_euclid(SEC_PER_DAYI64);
84 let hr = (seconds_since_midnight / 3600) as u8;
85 let min = ((seconds_since_midnight % 3600) / 60) as u8;
86 let sec = (seconds_since_midnight % 60) as u8;
87 (hr, min, sec)
88 };
89
90 YmdHms {
91 yr,
92 mo,
93 day,
94 hr,
95 min,
96 sec,
97 attos,
98 }
99 }
100
101 pub const fn ymdhms_to_unix_sec(
106 year: i64,
107 month: u8,
108 day: u8,
109 hour: u8,
110 minute: u8,
111 second: u8,
112 ) -> i64 {
113 let jdn = Self::ymd_to_jdn(year, month, day);
114 let days_since_1970 = jdn.saturating_sub(2440588);
116 let time_of_day = (hour as i64) * 3600 + (minute as i64) * 60 + (second as i64);
117 days_since_1970
118 .saturating_mul(SEC_PER_DAYI64)
119 .saturating_add(time_of_day)
120 }
121
122 pub const fn jdn_to_ymd(jdn: i64) -> (i64, u8, u8) {
130 let j = jdn as i128;
132
133 const fn floor_div(a: i128, b: i128) -> i128 {
135 let q = a / b;
136 let r = a % b;
137 if (r > 0 && b < 0) || (r < 0 && b > 0) {
138 q - 1
139 } else {
140 q
141 }
142 }
143
144 let a = j + 32044;
145 let b = floor_div(4 * a + 3, 146097);
146 let c = a - floor_div(b * 146097, 4);
147 let d = floor_div(4 * c + 3, 1461);
148 let e = c - floor_div(1461 * d, 4);
149 let m = floor_div(5 * e + 2, 153);
150 let day = (e - floor_div(153 * m + 2, 5) + 1) as u8;
151 let month = (m + 3 - 12 * floor_div(m, 10)) as u8;
152 let year = b * 100 + d - 4800 + floor_div(m, 10);
153
154 debug_assert!(day >= 1 && day <= 31);
155 debug_assert!(month >= 1 && month <= 12);
156
157 (year as i64, month, day)
158 }
159
160 pub const fn ymd_to_jdn(year: i64, month: u8, day: u8) -> i64 {
165 let a = (14 - month as i64) / 12;
166 let y = year.saturating_add(4800).saturating_sub(a);
167 let m = month as i64 + 12 * a - 3;
168
169 const fn floor_div(a: i64, b: i64) -> i64 {
170 let q = a / b;
171 let r = a % b;
172 if (r > 0 && b < 0) || (r < 0 && b > 0) {
173 q - 1
174 } else {
175 q
176 }
177 }
178
179 let y4 = floor_div(y, 4);
180 let y100 = floor_div(y, 100);
181 let y400 = floor_div(y, 400);
182
183 let result = (day as i64)
184 .saturating_add((153i64 * m + 2) / 5)
185 .saturating_add(365i64 * y)
186 .saturating_add(y4)
187 .saturating_sub(y100)
188 .saturating_add(y400)
189 .saturating_sub(32045);
190
191 result
192 }
193
194 #[inline]
196 pub const fn is_leap_year(year: i64) -> bool {
197 year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
198 }
199
200 #[inline]
212 pub const fn from_ymdhms(
213 yr: i64,
214 mo: u8,
215 day: u8,
216 hr: u8,
217 min: u8,
218 sec: u8,
219 attos: u64,
220 ) -> Self {
221 Dt::from_ymdhms_on(yr, mo, day, hr, min, sec, attos, Scale::UTC)
222 }
223
224 pub const fn from_ymdhms_on(
225 yr: i64,
226 mo: u8,
227 day: u8,
228 hr: u8,
229 min: u8,
230 sec: u8,
231 attos: u64,
232 scale: Scale,
233 ) -> Self {
234 let mo = if mo > 12 { 12 } else { mo };
235 let day = if day > 31 { 31 } else { day };
236 let h = if hr > 23 { 23 } else { hr };
237 let m = if min > 59 { 59 } else { min };
238 let s = if sec > 60 { 60 } else { sec };
239
240 let extra_sec = (attos / ATTOS_PER_SEC) as i64;
241 let final_attos = attos % ATTOS_PER_SEC;
242
243 let is_exact_leap_second = s == 60 && extra_sec == 0;
252 let s_for_unix = if is_exact_leap_second { 59 } else { s };
253
254 let civil_unix_sec = Self::ymdhms_to_unix_sec(yr, mo, day, h, m, s_for_unix) + extra_sec;
255
256 let tp = Self::from_epoch(
257 TSpan::new(civil_unix_sec, final_attos),
258 Dt::UNIX_EPOCH,
259 scale,
260 );
261 if is_exact_leap_second {
262 tp.add(TSpan::from_sec(1))
263 } else {
264 tp
265 }
266 }
267
268 #[inline]
274 pub const fn from_ymd(yr: i64, mo: u8, day: u8) -> Self {
275 let unix_sec = Self::ymdhms_to_unix_sec(yr, mo, day, 0, 0, 0);
276 Self::from_epoch(TSpan::new(unix_sec, 0), Dt::UNIX_EPOCH, Scale::UTC)
277 }
278
279 #[inline]
280 pub const fn from_ymd_on(yr: i64, mo: u8, day: u8, scale: Scale) -> Self {
281 let unix_sec = Self::ymdhms_to_unix_sec(yr, mo, day, 0, 0, 0);
282 Self::from_epoch(TSpan::new(unix_sec, 0), Dt::UNIX_EPOCH, scale)
283 }
284
285 #[inline]
287 pub const fn ydoy_to_jdn(year: i64, day_of_year: u16) -> i64 {
288 let jdn_jan1 = Self::ymd_to_jdn(year, 1, 1);
289 jdn_jan1.saturating_add(day_of_year as i64 - 1)
290 }
291
292 #[inline]
294 pub const fn jdn_to_weekday(jdn: i64) -> u8 {
295 let rem = ((jdn as i128) + 1) % 7;
296 let positive = if rem < 0 { rem + 7 } else { rem };
297 positive as u8
298 }
299
300 pub const fn ymd_to_jdn_from_iso_week(iso_year: i64, iso_week: u8, weekday: Weekday) -> i64 {
302 let jan4_jdn = Self::ymd_to_jdn(iso_year, 1, 4);
303 let wd_jan4 = Self::jdn_to_weekday(jan4_jdn);
304
305 let days_to_monday = {
306 let tmp = (wd_jan4 as i64).saturating_add(6);
307 let rem = tmp % 7;
308 if rem < 0 { rem + 7 } else { rem }
309 };
310
311 let monday_week1 = jan4_jdn.saturating_sub(days_to_monday);
312
313 let monday_requested =
314 monday_week1.saturating_add(((iso_week as i64).saturating_sub(1)).saturating_mul(7));
315
316 let wd_offset = match weekday {
317 Weekday::Monday => 0,
318 Weekday::Tuesday => 1,
319 Weekday::Wednesday => 2,
320 Weekday::Thursday => 3,
321 Weekday::Friday => 4,
322 Weekday::Saturday => 5,
323 Weekday::Sunday => 6,
324 };
325
326 monday_requested.saturating_add(wd_offset as i64)
327 }
328
329 pub const fn ymd_to_jdn_from_week_sun(year: i64, week: u8, weekday: Weekday) -> i64 {
331 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
332 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
333
334 let days_to_first_sunday = ((7u8 - wd_jan1) % 7u8) as i64;
335 let first_sunday_jdn = jan1_jdn.saturating_add(days_to_first_sunday);
336
337 let sunday_of_week =
338 first_sunday_jdn.saturating_add(((week as i64).saturating_sub(1)).saturating_mul(7));
339
340 let wd_offset = match weekday {
341 Weekday::Sunday => 0,
342 Weekday::Monday => 1,
343 Weekday::Tuesday => 2,
344 Weekday::Wednesday => 3,
345 Weekday::Thursday => 4,
346 Weekday::Friday => 5,
347 Weekday::Saturday => 6,
348 };
349
350 sunday_of_week.saturating_add(wd_offset as i64)
351 }
352
353 pub const fn ymd_to_jdn_from_week_mon(year: i64, week: u8, weekday: Weekday) -> i64 {
355 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
356 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
357
358 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
359 let first_monday_jdn = jan1_jdn.saturating_add(days_to_first_monday);
360
361 let monday_of_week =
362 first_monday_jdn.saturating_add(((week as i64).saturating_sub(1)).saturating_mul(7));
363
364 let wd_offset = match weekday {
365 Weekday::Monday => 0,
366 Weekday::Tuesday => 1,
367 Weekday::Wednesday => 2,
368 Weekday::Thursday => 3,
369 Weekday::Friday => 4,
370 Weekday::Saturday => 5,
371 Weekday::Sunday => 6,
372 };
373
374 monday_of_week.saturating_add(wd_offset as i64)
375 }
376
377 pub const fn is_valid_ymd(year: i64, month: u8, day: u8) -> bool {
379 if month < 1 || month > 12 || day < 1 {
380 return false;
381 }
382 let days = match month {
383 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31u8,
384 4 | 6 | 9 | 11 => 30u8,
385 2 => {
386 if Self::is_leap_year(year) {
387 29
388 } else {
389 28
390 }
391 }
392 _ => return false,
393 };
394 day <= days
395 }
396
397 pub const fn has_iso_week_53(year: i64) -> bool {
399 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
400 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
401 wd_jan1 == 4 || (Self::is_leap_year(year) && wd_jan1 == 3)
402 }
403
404 pub const fn day_of_year(&self, ymd: Option<(i64, u8, u8)>) -> u16 {
409 let (year, month, day) = if let Some(ymd) = ymd {
410 ymd
411 } else {
412 let g = self.to_ymdhms();
413 (g.yr, g.mo, g.day)
414 };
415 let jdn = Self::ymd_to_jdn(year, month, day);
416 let jdn_jan1 = Self::ymd_to_jdn(year, 1, 1);
417
418 let doy = jdn.saturating_sub(jdn_jan1).saturating_add(1);
419 doy as u16
420 }
421
422 pub const fn wk_sun(&self, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
432 let (year, _, _) = if let Some(ymd) = ymd {
433 ymd
434 } else {
435 let g = self.to_ymdhms();
436 (g.yr, g.mo, g.day)
437 };
438 let doy = if let Some(doy) = doy {
439 doy
440 } else {
441 self.day_of_year(ymd)
442 };
443 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
444 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
445 let days_to_first_sunday = (7u8 - wd_jan1) % 7u8;
446 let first_sunday_doy = days_to_first_sunday as u16 + 1;
447 if doy < first_sunday_doy {
448 0
449 } else {
450 let days_since_first_sunday = doy.saturating_sub(first_sunday_doy);
451 ((days_since_first_sunday / 7) + 1) as u8
452 }
453 }
454
455 pub const fn wk_mon(&self, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
464 let (year, _, _) = if let Some(ymd) = ymd {
465 ymd
466 } else {
467 let g = self.to_ymdhms();
468 (g.yr, g.mo, g.day)
469 };
470 let doy = if let Some(doy) = doy {
471 doy
472 } else {
473 self.day_of_year(ymd)
474 };
475 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
476 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
477 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
478 let first_monday_doy = days_to_first_monday as u16 + 1;
479 if doy < first_monday_doy {
480 0
481 } else {
482 let days_since_first_monday = doy.saturating_sub(first_monday_doy);
483 ((days_since_first_monday / 7) + 1) as u8
484 }
485 }
486
487 pub const fn to_iso_week_date(&self, ymd: Option<(i64, u8, u8)>) -> (i64, u8, Weekday) {
502 let (year, month, day) = if let Some(ymd) = ymd {
503 ymd
504 } else {
505 let g = self.to_ymdhms();
506 (g.yr, g.mo, g.day)
507 };
508 let jdn = Self::ymd_to_jdn(year, month, day);
509 let wd = Self::jdn_to_weekday(jdn);
510 let wd_iso = if wd == 0 { 7 } else { wd };
511
512 let jan4_jdn = Self::ymd_to_jdn(year, 1, 4);
513 let wd_jan4 = Self::jdn_to_weekday(jan4_jdn);
514 let days_to_monday = {
515 let tmp = (wd_jan4 as i64) + 6;
516 let rem = tmp % 7;
517 if rem < 0 { rem + 7 } else { rem }
518 };
519
520 let monday_week1 = jan4_jdn - days_to_monday;
521
522 let days_since = jdn - monday_week1;
523
524 let week = if days_since < 0 {
525 0u8
526 } else {
527 ((days_since / 7) + 1) as u8
528 };
529
530 let iso_year = if week == 0 {
531 year - 1
532 } else if (week == 53 || week > 53) && !Self::has_iso_week_53(year) {
533 year + 1
534 } else {
535 year
536 };
537
538 let iso_week = if week == 0 {
539 if Self::has_iso_week_53(year - 1) {
540 53
541 } else {
542 52
543 }
544 } else if week == 53 && !Self::has_iso_week_53(year) {
545 1
546 } else if week > 53 {
547 1
548 } else {
549 week
550 };
551
552 let weekday_enum = match wd_iso {
553 1 => Weekday::Monday,
554 2 => Weekday::Tuesday,
555 3 => Weekday::Wednesday,
556 4 => Weekday::Thursday,
557 5 => Weekday::Friday,
558 6 => Weekday::Saturday,
559 _ => Weekday::Sunday,
560 };
561
562 (iso_year, iso_week, weekday_enum)
563 }
564}