1use crate::{ATTOS_PER_SEC, Dt, SEC_PER_DAYI64, Scale, Weekday, YmdHms, leap_seconds::leap_sec};
2
3impl Dt {
4 pub(crate) const DAYS_IN_GREGORIAN_MONTHS: [u8; 12] =
5 [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
6
7 pub(crate) const DAYS_IN_GREGORIAN_MONTHS_LEAP_YR: [u8; 12] =
8 [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
9
10 pub const fn unix_sec_to_ymd(unix_sec: i64) -> (i64, u8, u8) {
13 let days = unix_sec.div_euclid(86400);
14
15 let z = days + 719468;
17
18 let era = if z >= 0 {
19 z / 146097
20 } else {
21 (z - 146096) / 146097
22 };
23 let doe = z - era * 146097; let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; let y = yoe + era * 400;
26 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let m = if mp < 10 { mp + 3 } else { mp - 9 }; let yr = y + if m <= 2 { 1 } else { 0 };
32
33 (yr, m as u8, d as u8)
34 }
35
36 pub const fn to_ymd(&self) -> YmdHms {
81 let tai = self.to_tai();
82 let from_unix_epoch = self.to_scale_and_diff(Dt::UNIX_EPOCH, false);
83
84 let unix_sec = from_unix_epoch.to_sec64();
85 let frac = from_unix_epoch.to_sec_ufrac();
86 let (yr, mo, day) = Self::unix_sec_to_ymd(unix_sec);
87
88 let seconds_since_midnight = unix_sec.rem_euclid(SEC_PER_DAYI64);
89 let hr = (seconds_since_midnight / 3600) as u8;
90 let min = ((seconds_since_midnight % 3600) / 60) as u8;
91 let mut sec = (seconds_since_midnight % 60) as u8;
92 let is_leap = match tai.leap_sec(false) {
93 Some(i) => i.is_leap_sec,
94 None => false,
95 };
96 if self.target.uses_leap_seconds() && is_leap {
97 sec += 1;
98 }
99
100 YmdHms {
101 yr,
102 mo,
103 day,
104 hr,
105 min,
106 sec,
107 attos: frac,
108 scale: self.target,
109 }
110 }
111
112 pub const fn ymd_to_unix_sec(yr: i64, mo: u8, day: u8, hr: u8, min: u8, sec: u8) -> i64 {
119 let jd = Self::ymd_to_jd(yr, mo, day);
120 let days_since_1970 = jd.saturating_sub(2440588);
122 let time_of_day = (hr as i64) * 3600 + (min as i64) * 60 + (sec as i64);
123 days_since_1970
124 .saturating_mul(SEC_PER_DAYI64)
125 .saturating_add(time_of_day)
126 }
127
128 pub const fn jd_to_ymd(jd: i64) -> (i64, u8, u8) {
135 let j = jd as i128;
136
137 #[inline]
138 const fn floor_div_pos(a: i128, b: i128) -> i128 {
139 if a >= 0 { a / b } else { (a - (b - 1)) / b }
140 }
141
142 let a = j + 32044;
143 let b = floor_div_pos(4 * a + 3, 146097);
144 let c = a - floor_div_pos(b * 146097, 4);
145 let d = floor_div_pos(4 * c + 3, 1461);
146 let e = c - floor_div_pos(1461 * d, 4);
147 let m = floor_div_pos(5 * e + 2, 153);
148 let day = (e - floor_div_pos(153 * m + 2, 5) + 1) as u8;
149 let mo = (m + 3 - 12 * floor_div_pos(m, 10)) as u8;
150 let yr = b * 100 + d - 4800 + floor_div_pos(m, 10);
151
152 (Dt::i128_to_i64(yr), mo, day)
153 }
154
155 #[inline]
177 pub const fn ymd_to_jd(yr: i64, mo: u8, day: u8) -> i64 {
178 let y = yr as i128;
179 let m = mo as i16;
180 let d = day as i16;
181
182 let a = (14 - m) / 12;
183 let y = y + 4800 - a as i128;
184 let m = m + 12 * a - 3;
185
186 let y4 = y >> 2; let y100 = if y >= 0 { y / 100 } else { (y - 99) / 100 };
190
191 let y400 = y100 >> 2; let day_mo = d + (153 * m + 2) / 5;
194 let yr_part = 365 * y + y4 - y100 + y400 - 32045;
195
196 Dt::i128_to_i64(day_mo as i128 + yr_part)
197 }
198
199 pub const fn from_ymd(
215 yr: i64,
216 mo: u8,
217 day: u8,
218 hr: u8,
219 min: u8,
220 sec: u8,
221 attos: u64,
222 scale: Scale,
223 ) -> Dt {
224 let (mo, day, hr, min, sec) = Dt::clamp_mdhms(yr, mo, day, hr, min, sec);
225 let attos = Dt::clamp_u64(attos, 0, ATTOS_PER_SEC - 1);
226
227 let sec_is_60 = sec == 60;
228 let s_for_unix = if sec_is_60 { 59 } else { sec };
229
230 let unix_sec = Dt::ymd_to_unix_sec(yr, mo, day, hr, min, s_for_unix);
231 let unix_attos = Dt::sec_to_attos(unix_sec as i128) + (attos as i128);
232
233 if sec_is_60 && scale.uses_leap_seconds() {
234 let t =
235 Dt::from_diff_and_scale(Dt::new(unix_attos, scale, scale), Dt::UNIX_EPOCH, false);
236 let is_leap = match leap_sec(t.add_sec(1).to_sec64(), false) {
237 Some(i) => i.is_leap_sec,
238 None => false,
239 };
240 if is_leap { t.add_sec(1) } else { t }
241 } else {
242 Dt::from_diff_and_scale(Dt::new(unix_attos, scale, scale), Dt::UNIX_EPOCH, false)
243 }
244 }
245
246 #[inline]
248 pub const fn ydoy_to_jd(yr: i64, day_of_yr: u16) -> i64 {
249 let jd_jan1 = Self::ymd_to_jd(yr, 1, 1);
250 jd_jan1.saturating_add(day_of_yr as i64 - 1)
251 }
252
253 #[inline]
255 pub const fn jd_to_wkday(jd: i64) -> u8 {
256 let rem = ((jd as i128) + 1) % 7;
257 let positive = if rem < 0 { rem + 7 } else { rem };
258 positive as u8
259 }
260
261 pub const fn iso_wk_to_jd(iso_yr: i64, iso_wk: u8, wkday: Weekday) -> i64 {
263 let jan4_jd = Self::ymd_to_jd(iso_yr, 1, 4);
264 let wd_jan4 = Self::jd_to_wkday(jan4_jd);
265
266 let days_to_monday = {
267 let tmp = (wd_jan4 as i64).saturating_add(6);
268 let rem = tmp % 7;
269 if rem < 0 { rem + 7 } else { rem }
270 };
271
272 let monday_wk1 = jan4_jd.saturating_sub(days_to_monday);
273 let monday_requested =
274 monday_wk1.saturating_add(((iso_wk as i64).saturating_sub(1)).saturating_mul(7));
275
276 monday_requested.saturating_add((wkday.wkday_mon_0_based()) as i64)
277 }
278
279 pub const fn wk_sun_to_jd(yr: i64, wk: u8, wkday: Weekday) -> i64 {
281 let jan1_jd = Self::ymd_to_jd(yr, 1, 1);
282 let wd_jan1 = Self::jd_to_wkday(jan1_jd);
283
284 let days_to_first_sunday = ((7u8 - wd_jan1) % 7u8) as i64;
285 let first_sunday_jd = jan1_jd.saturating_add(days_to_first_sunday);
286
287 let sunday_of_wk =
288 first_sunday_jd.saturating_add(((wk as i64).saturating_sub(1)).saturating_mul(7));
289
290 sunday_of_wk.saturating_add(wkday.wkday_sun_0_based() as i64)
291 }
292
293 pub const fn wk_mon_to_jd(yr: i64, wk: u8, wkday: Weekday) -> i64 {
295 let jan1_jd = Self::ymd_to_jd(yr, 1, 1);
296 let wd_jan1 = Self::jd_to_wkday(jan1_jd);
297
298 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
299 let first_monday_jd = jan1_jd.saturating_add(days_to_first_monday);
300
301 let monday_of_wk =
302 first_monday_jd.saturating_add(((wk as i64).saturating_sub(1)).saturating_mul(7));
303
304 monday_of_wk.saturating_add((wkday.wkday_mon_0_based()) as i64)
305 }
306
307 #[inline(always)]
309 pub const fn is_leap_yr(yr: i64) -> bool {
310 (yr & 3 == 0) && ((yr & 15 == 0) || (yr % 25 != 0))
311 }
312
313 #[inline]
315 pub const fn is_valid_ymd(yr: i64, mo: u8, day: u8) -> bool {
316 if mo < 1 || mo > 12 || day < 1 {
317 return false;
318 }
319 let days = Self::DAYS_IN_GREGORIAN_MONTHS[(mo - 1) as usize];
321 if mo == 2 && Self::is_leap_yr(yr) {
322 day <= days + 1 } else {
324 day <= days
325 }
326 }
327
328 pub const fn has_iso_wk_53(yr: i64) -> bool {
330 let jan1_jd = Self::ymd_to_jd(yr, 1, 1);
331 let wd_jan1 = Self::jd_to_wkday(jan1_jd);
332 wd_jan1 == 4 || (Self::is_leap_yr(yr) && wd_jan1 == 3)
333 }
334
335 pub const fn day_of_yr(&self, ymd: Option<(i64, u8, u8)>) -> u16 {
340 let (yr, mo, day) = if let Some(ymd) = ymd {
341 ymd
342 } else {
343 let g = self.to_ymd();
344 (g.yr, g.mo, g.day)
345 };
346 Self::_day_of_yr(yr, mo, day)
347 }
348
349 pub(crate) const fn _day_of_yr(yr: i64, mo: u8, day: u8) -> u16 {
350 let jd = Self::ymd_to_jd(yr, mo, day);
351 let jd_jan1 = Self::ymd_to_jd(yr, 1, 1);
352
353 let doy = jd.saturating_sub(jd_jan1).saturating_add(1);
354 doy as u16
355 }
356
357 pub const fn wk_sun(&self, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
367 let (yr, _, _) = if let Some(ymd) = ymd {
368 ymd
369 } else {
370 let g = self.to_ymd();
371 (g.yr, g.mo, g.day)
372 };
373 let doy = if let Some(doy) = doy {
374 doy
375 } else {
376 self.day_of_yr(ymd)
377 };
378 Self::_wk_sun(yr, doy)
379 }
380
381 pub(crate) const fn _wk_sun(yr: i64, doy: u16) -> u8 {
382 let jan1_jd = Self::ymd_to_jd(yr, 1, 1);
383 let wd_jan1 = Self::jd_to_wkday(jan1_jd);
384 let days_to_first_sunday = (7u8 - wd_jan1) % 7u8;
385 let first_sunday_doy = days_to_first_sunday as u16 + 1;
386 if doy < first_sunday_doy {
387 0
388 } else {
389 let days_since_first_sunday = doy.saturating_sub(first_sunday_doy);
390 ((days_since_first_sunday / 7) + 1) as u8
391 }
392 }
393
394 pub const fn wk_mon(&self, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
403 let (yr, _, _) = if let Some(ymd) = ymd {
404 ymd
405 } else {
406 let g = self.to_ymd();
407 (g.yr, g.mo, g.day)
408 };
409 let doy = if let Some(doy) = doy {
410 doy
411 } else {
412 self.day_of_yr(ymd)
413 };
414 Self::_wk_mon(yr, doy)
415 }
416
417 pub(crate) const fn _wk_mon(yr: i64, doy: u16) -> u8 {
418 let jan1_jd = Self::ymd_to_jd(yr, 1, 1);
419 let wd_jan1 = Self::jd_to_wkday(jan1_jd);
420 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
421 let first_monday_doy = days_to_first_monday as u16 + 1;
422 if doy < first_monday_doy {
423 0
424 } else {
425 let days_since_first_monday = doy.saturating_sub(first_monday_doy);
426 ((days_since_first_monday / 7) + 1) as u8
427 }
428 }
429
430 pub const fn to_iso_wk_date(&self, ymd: Option<(i64, u8, u8)>) -> (i64, u8, Weekday) {
445 let (yr, mo, day) = if let Some(ymd) = ymd {
446 ymd
447 } else {
448 let g = self.to_ymd();
449 (g.yr, g.mo, g.day)
450 };
451 Self::_to_iso_wk_date(yr, mo, day)
452 }
453
454 pub(crate) const fn _to_iso_wk_date(yr: i64, mo: u8, day: u8) -> (i64, u8, Weekday) {
455 let jd = Self::ymd_to_jd(yr, mo, day);
456 let wd = Self::jd_to_wkday(jd);
457 let wd_iso = if wd == 0 { 7 } else { wd };
458
459 let jan4_jd = Self::ymd_to_jd(yr, 1, 4);
460 let wd_jan4 = Self::jd_to_wkday(jan4_jd);
461 let days_to_monday = {
462 let tmp = (wd_jan4 as i64) + 6;
463 let rem = tmp % 7;
464 if rem < 0 { rem + 7 } else { rem }
465 };
466
467 let monday_wk1 = jan4_jd - days_to_monday;
468
469 let days_since = jd - monday_wk1;
470
471 let wk = if days_since < 0 {
472 0u8
473 } else {
474 ((days_since / 7) + 1) as u8
475 };
476
477 let iso_yr = if wk == 0 {
478 yr - 1
479 } else if wk >= 53 && !Self::has_iso_wk_53(yr) {
480 yr + 1
481 } else {
482 yr
483 };
484
485 let iso_wk = if wk == 0 {
486 if Self::has_iso_wk_53(yr - 1) { 53 } else { 52 }
487 } else if (wk == 53 && !Self::has_iso_wk_53(yr)) || wk > 53 {
488 1
489 } else {
490 wk
491 };
492 let wkday_enum = match Weekday::from_monday_1_based(wd_iso) {
493 Some(w) => w,
494 None => Weekday::Monday,
495 };
496
497 (iso_yr, iso_wk, wkday_enum)
498 }
499
500 #[inline]
502 pub const fn days_in_month(yr: i64, mo: u8) -> u8 {
503 match mo {
504 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
505 4 | 6 | 9 | 11 => 30,
506 2 => {
507 if Self::is_leap_yr(yr) {
508 29
509 } else {
510 28
511 }
512 }
513 _ => 0,
514 }
515 }
516
517 pub const fn clamp_mdhms(
523 yr: i64,
524 mo: u8,
525 day: u8,
526 hr: u8,
527 min: u8,
528 sec: u8,
529 ) -> (u8, u8, u8, u8, u8) {
530 let mo = Self::clamp_u8(mo, 1, 12);
531 let max_day = Self::days_in_month(yr, mo);
532 let day = Self::clamp_u8(day, 1, max_day);
533 let h = Self::clamp_u8(hr, 0, 23);
534 let m = Self::clamp_u8(min, 0, 59);
535 let s = Self::clamp_u8(sec, 0, 60);
536
537 (mo, day, h, m, s)
538 }
539}