1use crate::{ATTOS_PER_SEC, Dt, GregorianTime, SEC_PER_DAYI64, Scale, Weekday, YmdHms};
2
3impl Dt {
4 #[inline]
7 pub const fn unix_sec_to_ymd(unix_sec: i64) -> (i64, u8, u8) {
8 let days_since_1970 = unix_sec.div_euclid(SEC_PER_DAYI64);
9 let jdn = days_since_1970.saturating_add(2440588);
11 Self::jdn_to_ymd(jdn)
12 }
13
14 pub const fn to_gregorian_time(&self, current: Scale) -> GregorianTime {
15 let ymdhms = self.to_ymdhms(current);
16 let (iso_yr, iso_wk, iso_wkday) =
17 self.to_iso_wk_date(current, Some((ymdhms.yr, ymdhms.mo, ymdhms.day)));
18 let day_of_yr = self.day_of_yr(current, Some((ymdhms.yr, ymdhms.mo, ymdhms.day)));
19 let jdn = Self::ymd_to_jdn(ymdhms.yr, ymdhms.mo, ymdhms.day);
20 let wkday = Self::jdn_to_wkday(jdn);
21 let wk_of_yr_sun = self.wk_sun(
22 current,
23 Some((ymdhms.yr, ymdhms.mo, ymdhms.day)),
24 Some(day_of_yr),
25 );
26 let wk_of_yr_mon = self.wk_mon(
27 current,
28 Some((ymdhms.yr, ymdhms.mo, ymdhms.day)),
29 Some(day_of_yr),
30 );
31
32 GregorianTime {
33 unix_attosec: ymdhms.unix_attosec,
34 yr: ymdhms.yr,
35 mo: ymdhms.mo,
36 day: ymdhms.day,
37 hr: ymdhms.hr,
38 min: ymdhms.min,
39 sec: ymdhms.sec,
40 attos: ymdhms.attos,
41 iso_yr,
42 iso_wk,
43 iso_wkday,
44 day_of_yr,
45 wkday,
46 wk_of_yr_sun,
47 wk_of_yr_mon,
48 offset_sec: None,
49 tz: None,
50 tz_abbrev: None,
51 }
52 }
53
54 #[inline]
60 pub const fn to_ymdhms(&self, current: Scale) -> YmdHms {
61 self.to_ymdhms_on(current, Scale::UTC)
62 }
63
64 pub const fn to_ymdhms_on(&self, current: Scale, new: Scale) -> YmdHms {
123 let tai = if current.is_tai() {
125 *self
126 } else {
127 self.to(current, Scale::TAI)
128 };
129 let from_unix_epoch = tai.to_scale_and_then_diff(new, Dt::UNIX_EPOCH);
130
131 let (yr, mo, day) = Self::unix_sec_to_ymd(from_unix_epoch.sec);
132
133 let (hr, min, sec) = if new.uses_leap_seconds() && tai.leap_sec(false).is_leap_sec {
134 (23, 59, 60)
135 } else {
136 let seconds_since_midnight = from_unix_epoch.sec.rem_euclid(SEC_PER_DAYI64);
137 let hr = (seconds_since_midnight / 3600) as u8;
138 let min = ((seconds_since_midnight % 3600) / 60) as u8;
139 let sec = (seconds_since_midnight % 60) as u8;
140 (hr, min, sec)
141 };
142
143 YmdHms {
144 unix_attosec: from_unix_epoch.to_attos(),
145 yr,
146 mo,
147 day,
148 hr,
149 min,
150 sec,
151 attos: from_unix_epoch.attos,
152 }
153 }
154
155 pub const fn ymdhms_to_unix_sec(yr: i64, mo: u8, day: u8, hr: u8, min: u8, sec: u8) -> i64 {
160 let (mo, day, hr, min, sec) = Self::clamp_mdhms(mo, day, hr, min, sec);
161 let jdn = Self::ymd_to_jdn(yr, mo, day);
162 let days_since_1970 = jdn.saturating_sub(2440588);
164 let time_of_day = (hr as i64) * 3600 + (min as i64) * 60 + (sec as i64);
165 days_since_1970
166 .saturating_mul(SEC_PER_DAYI64)
167 .saturating_add(time_of_day)
168 }
169
170 pub const fn jdn_to_ymd(jdn: i64) -> (i64, u8, u8) {
177 let j = jdn as i128;
178
179 #[inline]
180 const fn floor_div_pos(a: i128, b: i128) -> i128 {
181 if a >= 0 { a / b } else { (a - (b - 1)) / b }
182 }
183
184 let a = j + 32044;
185 let b = floor_div_pos(4 * a + 3, 146097);
186 let c = a - floor_div_pos(b * 146097, 4);
187 let d = floor_div_pos(4 * c + 3, 1461);
188 let e = c - floor_div_pos(1461 * d, 4);
189 let m = floor_div_pos(5 * e + 2, 153);
190 let day = (e - floor_div_pos(153 * m + 2, 5) + 1) as u8;
191 let mo = (m + 3 - 12 * floor_div_pos(m, 10)) as u8;
192 let yr = b * 100 + d - 4800 + floor_div_pos(m, 10);
193
194 (Dt::clamp_i128_to_i64(yr), mo, day)
195 }
196
197 #[inline]
216 pub const fn ymd_to_jdn(yr: i64, mo: u8, day: u8) -> i64 {
217 let y = yr as i128;
218 let m = mo as i16;
219 let d = day as i16;
220
221 let a = (14 - m) / 12;
222 let y = y + 4800 - a as i128;
223 let m = m + 12 * a - 3;
224
225 let y4 = y >> 2; let y100 = if y >= 0 { y / 100 } else { (y - 99) / 100 };
229
230 let y400 = y100 >> 2; let day_mo = d + (153 * m + 2) / 5;
233 let yr_part = 365 * y + y4 - y100 + y400 - 32045;
234
235 Dt::clamp_i128_to_i64(day_mo as i128 + yr_part)
236 }
237
238 #[inline]
240 pub const fn is_leap_yr(yr: i64) -> bool {
241 yr % 4 == 0 && (yr % 100 != 0 || yr % 400 == 0)
242 }
243
244 pub const fn from_ymdhms_on(
263 yr: i64,
264 mo: u8,
265 day: u8,
266 hr: u8,
267 min: u8,
268 sec: u8,
269 attos: u64,
270 scale: Scale,
271 ) -> Self {
272 let (mo, day, hr, min, sec) = Self::clamp_mdhms(mo, day, hr, min, sec);
273 let carried_sec = (attos / ATTOS_PER_SEC) as i64;
274 let final_attos = attos % ATTOS_PER_SEC;
275
276 let is_exact_leap_second = sec == 60 && carried_sec == 0;
277 let s_for_unix = if is_exact_leap_second { 59 } else { sec };
278
279 let civil_unix_sec =
280 Self::ymdhms_to_unix_sec(yr, mo, day, hr, min, s_for_unix) + carried_sec;
281
282 let tp =
283 Self::from_diff_and_scale(Dt::new(civil_unix_sec, final_attos), Dt::UNIX_EPOCH, scale);
284 if is_exact_leap_second {
285 Dt::new(tp.sec.saturating_add(1), tp.attos)
286 } else {
287 tp
288 }
289 }
290
291 #[inline]
296 pub const fn from_ymd_on(yr: i64, mo: u8, day: u8, scale: Scale) -> Self {
297 Dt::from_ymdhms_on(yr, mo, day, 0, 0, 0, 0, scale)
298 }
299
300 #[inline]
304 pub const fn from_ymdhms(
305 yr: i64,
306 mo: u8,
307 day: u8,
308 hr: u8,
309 min: u8,
310 sec: u8,
311 attos: u64,
312 ) -> Self {
313 Dt::from_ymdhms_on(yr, mo, day, hr, min, sec, attos, Scale::UTC)
314 }
315
316 #[inline]
320 pub const fn from_ymd(yr: i64, mo: u8, day: u8) -> Self {
321 Dt::from_ymdhms_on(yr, mo, day, 0, 0, 0, 0, Scale::UTC)
322 }
323
324 #[inline]
326 pub const fn ydoy_to_jdn(yr: i64, day_of_yr: u16) -> i64 {
327 let jdn_jan1 = Self::ymd_to_jdn(yr, 1, 1);
328 jdn_jan1.saturating_add(day_of_yr as i64 - 1)
329 }
330
331 #[inline]
333 pub const fn jdn_to_wkday(jdn: i64) -> u8 {
334 let rem = ((jdn as i128) + 1) % 7;
335 let positive = if rem < 0 { rem + 7 } else { rem };
336 positive as u8
337 }
338
339 pub const fn ymd_to_jdn_from_iso_wk(iso_yr: i64, iso_wk: u8, wkday: Weekday) -> i64 {
341 let jan4_jdn = Self::ymd_to_jdn(iso_yr, 1, 4);
342 let wd_jan4 = Self::jdn_to_wkday(jan4_jdn);
343
344 let days_to_monday = {
345 let tmp = (wd_jan4 as i64).saturating_add(6);
346 let rem = tmp % 7;
347 if rem < 0 { rem + 7 } else { rem }
348 };
349
350 let monday_wk1 = jan4_jdn.saturating_sub(days_to_monday);
351 let monday_requested =
352 monday_wk1.saturating_add(((iso_wk as i64).saturating_sub(1)).saturating_mul(7));
353
354 monday_requested.saturating_add((wkday.wk_mon() - 1) as i64)
355 }
356
357 pub const fn ymd_to_jdn_from_wk_sun(yr: i64, wk: u8, wkday: Weekday) -> i64 {
359 let jan1_jdn = Self::ymd_to_jdn(yr, 1, 1);
360 let wd_jan1 = Self::jdn_to_wkday(jan1_jdn);
361
362 let days_to_first_sunday = ((7u8 - wd_jan1) % 7u8) as i64;
363 let first_sunday_jdn = jan1_jdn.saturating_add(days_to_first_sunday);
364
365 let sunday_of_wk =
366 first_sunday_jdn.saturating_add(((wk as i64).saturating_sub(1)).saturating_mul(7));
367
368 sunday_of_wk.saturating_add(wkday.wk_sun() as i64)
369 }
370
371 pub const fn ymd_to_jdn_from_wk_mon(yr: i64, wk: u8, wkday: Weekday) -> i64 {
373 let jan1_jdn = Self::ymd_to_jdn(yr, 1, 1);
374 let wd_jan1 = Self::jdn_to_wkday(jan1_jdn);
375
376 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
377 let first_monday_jdn = jan1_jdn.saturating_add(days_to_first_monday);
378
379 let monday_of_wk =
380 first_monday_jdn.saturating_add(((wk as i64).saturating_sub(1)).saturating_mul(7));
381
382 monday_of_wk.saturating_add((wkday.wk_mon() - 1) as i64)
383 }
384
385 pub const fn is_valid_ymd(yr: i64, mo: u8, day: u8) -> bool {
387 if mo < 1 || mo > 12 || day < 1 {
388 return false;
389 }
390 let days = match mo {
391 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31u8,
392 4 | 6 | 9 | 11 => 30u8,
393 2 => {
394 if Self::is_leap_yr(yr) {
395 29
396 } else {
397 28
398 }
399 }
400 _ => return false,
401 };
402 day <= days
403 }
404
405 pub const fn has_iso_wk_53(yr: i64) -> bool {
407 let jan1_jdn = Self::ymd_to_jdn(yr, 1, 1);
408 let wd_jan1 = Self::jdn_to_wkday(jan1_jdn);
409 wd_jan1 == 4 || (Self::is_leap_yr(yr) && wd_jan1 == 3)
410 }
411
412 pub const fn day_of_yr(&self, current: Scale, ymd: Option<(i64, u8, u8)>) -> u16 {
417 let (yr, month, day) = if let Some(ymd) = ymd {
418 ymd
419 } else {
420 let g = self.to_ymdhms(current);
421 (g.yr, g.mo, g.day)
422 };
423 let jdn = Self::ymd_to_jdn(yr, month, day);
424 let jdn_jan1 = Self::ymd_to_jdn(yr, 1, 1);
425
426 let doy = jdn.saturating_sub(jdn_jan1).saturating_add(1);
427 doy as u16
428 }
429
430 pub const fn wk_sun(&self, current: Scale, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
440 let (yr, _, _) = if let Some(ymd) = ymd {
441 ymd
442 } else {
443 let g = self.to_ymdhms(current);
444 (g.yr, g.mo, g.day)
445 };
446 let doy = if let Some(doy) = doy {
447 doy
448 } else {
449 self.day_of_yr(current, ymd)
450 };
451 let jan1_jdn = Self::ymd_to_jdn(yr, 1, 1);
452 let wd_jan1 = Self::jdn_to_wkday(jan1_jdn);
453 let days_to_first_sunday = (7u8 - wd_jan1) % 7u8;
454 let first_sunday_doy = days_to_first_sunday as u16 + 1;
455 if doy < first_sunday_doy {
456 0
457 } else {
458 let days_since_first_sunday = doy.saturating_sub(first_sunday_doy);
459 ((days_since_first_sunday / 7) + 1) as u8
460 }
461 }
462
463 pub const fn wk_mon(&self, current: Scale, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
472 let (yr, _, _) = if let Some(ymd) = ymd {
473 ymd
474 } else {
475 let g = self.to_ymdhms(current);
476 (g.yr, g.mo, g.day)
477 };
478 let doy = if let Some(doy) = doy {
479 doy
480 } else {
481 self.day_of_yr(current, ymd)
482 };
483 let jan1_jdn = Self::ymd_to_jdn(yr, 1, 1);
484 let wd_jan1 = Self::jdn_to_wkday(jan1_jdn);
485 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
486 let first_monday_doy = days_to_first_monday as u16 + 1;
487 if doy < first_monday_doy {
488 0
489 } else {
490 let days_since_first_monday = doy.saturating_sub(first_monday_doy);
491 ((days_since_first_monday / 7) + 1) as u8
492 }
493 }
494
495 pub const fn to_iso_wk_date(
510 &self,
511 current: Scale,
512 ymd: Option<(i64, u8, u8)>,
513 ) -> (i64, u8, Weekday) {
514 let (yr, month, day) = if let Some(ymd) = ymd {
515 ymd
516 } else {
517 let g = self.to_ymdhms(current);
518 (g.yr, g.mo, g.day)
519 };
520 let jdn = Self::ymd_to_jdn(yr, month, day);
521 let wd = Self::jdn_to_wkday(jdn);
522 let wd_iso = if wd == 0 { 7 } else { wd };
523
524 let jan4_jdn = Self::ymd_to_jdn(yr, 1, 4);
525 let wd_jan4 = Self::jdn_to_wkday(jan4_jdn);
526 let days_to_monday = {
527 let tmp = (wd_jan4 as i64) + 6;
528 let rem = tmp % 7;
529 if rem < 0 { rem + 7 } else { rem }
530 };
531
532 let monday_wk1 = jan4_jdn - days_to_monday;
533
534 let days_since = jdn - monday_wk1;
535
536 let wk = if days_since < 0 {
537 0u8
538 } else {
539 ((days_since / 7) + 1) as u8
540 };
541
542 let iso_yr = if wk == 0 {
543 yr - 1
544 } else if wk >= 53 && !Self::has_iso_wk_53(yr) {
545 yr + 1
546 } else {
547 yr
548 };
549
550 let iso_wk = if wk == 0 {
551 if Self::has_iso_wk_53(yr - 1) { 53 } else { 52 }
552 } else if (wk == 53 && !Self::has_iso_wk_53(yr)) || wk > 53 {
553 1
554 } else {
555 wk
556 };
557 let wkday_enum = match Weekday::from_monday_one_offset(wd_iso) {
558 Some(w) => w,
559 None => Weekday::Monday,
560 };
561
562 (iso_yr, iso_wk, wkday_enum)
563 }
564
565 pub(crate) const fn clamp_mdhms(
566 mo: u8,
567 day: u8,
568 hr: u8,
569 min: u8,
570 sec: u8,
571 ) -> (u8, u8, u8, u8, u8) {
572 let mo = Self::clamp_u8(mo, 1, 12);
573 let day = Self::clamp_u8(day, 1, 31);
574 let h = Self::clamp_u8(hr, 0, 23);
575 let m = Self::clamp_u8(min, 0, 59);
576 let s = Self::clamp_u8(sec, 0, 60);
577
578 (mo, day, h, m, s)
579 }
580}