1use crate::{
2 ATTOS_PER_SEC, Dt, GregorianTime, SEC_PER_DAYI64, Scale, Weekday, YmdHms,
3 leap_seconds::get_leap_seconds,
4};
5
6impl Dt {
7 #[inline]
16 pub const fn to_unix(&self, current: Scale, target: Scale) -> Dt {
17 self.to(current, target)
18 .to_diff_raw(Dt::UNIX_EPOCH.to_internal(target))
19 }
20
21 #[inline]
24 pub const fn unix_sec_to_ymd(unix_sec: i64) -> (i64, u8, u8) {
25 let days_since_1970 = unix_sec.div_euclid(SEC_PER_DAYI64);
26 let jdn = days_since_1970.saturating_add(2440588);
28 Self::jdn_to_ymd(jdn)
29 }
30
31 pub const fn to_gregorian_time(&self, current: Scale) -> GregorianTime {
32 let ymdhms = self.to_ymdhms(current);
33 let (iso_yr, iso_wk, iso_wkday) =
34 self.to_iso_week_date(current, Some((ymdhms.yr, ymdhms.mo, ymdhms.day)));
35 let day_of_yr = self.day_of_year(current, Some((ymdhms.yr, ymdhms.mo, ymdhms.day)));
36 let jdn = Self::ymd_to_jdn(ymdhms.yr, ymdhms.mo, ymdhms.day);
37 let wkday = Self::jdn_to_weekday(jdn);
38 let wk_of_yr_sun = self.wk_sun(
39 current,
40 Some((ymdhms.yr, ymdhms.mo, ymdhms.day)),
41 Some(day_of_yr),
42 );
43 let wk_of_yr_mon = self.wk_mon(
44 current,
45 Some((ymdhms.yr, ymdhms.mo, ymdhms.day)),
46 Some(day_of_yr),
47 );
48
49 GregorianTime {
50 unix_attosec: ymdhms.unix_attosec,
51 yr: ymdhms.yr,
52 mo: ymdhms.mo,
53 day: ymdhms.day,
54 hr: ymdhms.hr,
55 min: ymdhms.min,
56 sec: ymdhms.sec,
57 attos: ymdhms.attos,
58 iso_yr,
59 iso_wk,
60 iso_wkday,
61 day_of_yr,
62 wkday,
63 wk_of_yr_sun,
64 wk_of_yr_mon,
65 offset_sec: None,
66 tz: None,
67 tz_abbrev: None,
68 }
69 }
70
71 #[inline]
76 pub const fn to_ymdhms(&self, current: Scale) -> YmdHms {
77 let tai = if current.is_tai() {
79 *self
80 } else {
81 self.to(current, Scale::TAI)
82 };
83 let canon = tai.to_scale_and_then_diff(Scale::UTC, Dt::UNIX_EPOCH);
84
85 let unix_sec = canon.sec;
86 let attos = canon.attos;
87
88 let is_leap_second = get_leap_seconds(&tai, false).is_leap_second;
89
90 let unix_sec_for_date = if is_leap_second {
93 unix_sec - 1
94 } else {
95 unix_sec
96 };
97
98 let (yr, mo, day) = Self::unix_sec_to_ymd(unix_sec_for_date);
99
100 let (hr, min, sec) = if is_leap_second {
102 (23, 59, 60)
103 } else {
104 let seconds_since_midnight = unix_sec.rem_euclid(SEC_PER_DAYI64);
105 let hr = (seconds_since_midnight / 3600) as u8;
106 let min = ((seconds_since_midnight % 3600) / 60) as u8;
107 let sec = (seconds_since_midnight % 60) as u8;
108 (hr, min, sec)
109 };
110
111 YmdHms {
112 unix_attosec: canon.to_attos(),
113 yr,
114 mo,
115 day,
116 hr,
117 min,
118 sec,
119 attos,
120 }
121 }
122
123 pub const fn ymdhms_to_unix_sec(yr: i64, mo: u8, day: u8, hr: u8, min: u8, sec: u8) -> i64 {
128 let jdn = Self::ymd_to_jdn(yr, mo, day);
129 let days_since_1970 = jdn.saturating_sub(2440588);
131 let time_of_day = (hr as i64) * 3600 + (min as i64) * 60 + (sec as i64);
132 days_since_1970
133 .saturating_mul(SEC_PER_DAYI64)
134 .saturating_add(time_of_day)
135 }
136
137 pub const fn jdn_to_ymd(jdn: i64) -> (i64, u8, u8) {
145 let j = jdn as i128;
147
148 const fn floor_div(a: i128, b: i128) -> i128 {
150 let q = a / b;
151 let r = a % b;
152 if (r > 0 && b < 0) || (r < 0 && b > 0) {
153 q - 1
154 } else {
155 q
156 }
157 }
158
159 let a = j + 32044;
160 let b = floor_div(4 * a + 3, 146097);
161 let c = a - floor_div(b * 146097, 4);
162 let d = floor_div(4 * c + 3, 1461);
163 let e = c - floor_div(1461 * d, 4);
164 let m = floor_div(5 * e + 2, 153);
165 let day = (e - floor_div(153 * m + 2, 5) + 1) as u8;
166 let month = (m + 3 - 12 * floor_div(m, 10)) as u8;
167 let year = b * 100 + d - 4800 + floor_div(m, 10);
168
169 debug_assert!(day >= 1 && day <= 31);
170 debug_assert!(month >= 1 && month <= 12);
171
172 (year as i64, month, day)
173 }
174
175 pub const fn ymd_to_jdn(year: i64, month: u8, day: u8) -> i64 {
180 let a = (14 - month as i64) / 12;
181 let y = year.saturating_add(4800).saturating_sub(a);
182 let m = month as i64 + 12 * a - 3;
183
184 const fn floor_div(a: i64, b: i64) -> i64 {
185 let q = a / b;
186 let r = a % b;
187 if (r > 0 && b < 0) || (r < 0 && b > 0) {
188 q - 1
189 } else {
190 q
191 }
192 }
193
194 let y4 = floor_div(y, 4);
195 let y100 = floor_div(y, 100);
196 let y400 = floor_div(y, 400);
197
198 let result = (day as i64)
199 .saturating_add((153i64 * m + 2) / 5)
200 .saturating_add(365i64 * y)
201 .saturating_add(y4)
202 .saturating_sub(y100)
203 .saturating_add(y400)
204 .saturating_sub(32045);
205
206 result
207 }
208
209 #[inline]
211 pub const fn is_leap_year(year: i64) -> bool {
212 year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
213 }
214
215 #[inline]
227 pub const fn from_ymdhms(
228 yr: i64,
229 mo: u8,
230 day: u8,
231 hr: u8,
232 min: u8,
233 sec: u8,
234 attos: u64,
235 ) -> Self {
236 Dt::from_ymdhms_on(yr, mo, day, hr, min, sec, attos, Scale::UTC)
237 }
238
239 pub const fn from_ymdhms_on(
240 yr: i64,
241 mo: u8,
242 day: u8,
243 hr: u8,
244 min: u8,
245 sec: u8,
246 attos: u64,
247 scale: Scale,
248 ) -> Self {
249 let mo = if mo > 12 { 12 } else { mo };
250 let day = if day > 31 { 31 } else { day };
251 let h = if hr > 23 { 23 } else { hr };
252 let m = if min > 59 { 59 } else { min };
253 let s = if sec > 60 { 60 } else { sec };
254
255 let extra_sec = (attos / ATTOS_PER_SEC) as i64;
256 let final_attos = attos % ATTOS_PER_SEC;
257
258 let is_exact_leap_second = s == 60 && extra_sec == 0;
267 let s_for_unix = if is_exact_leap_second { 59 } else { s };
268
269 let civil_unix_sec = Self::ymdhms_to_unix_sec(yr, mo, day, h, m, s_for_unix) + extra_sec;
270
271 let tp =
272 Self::from_diff_and_scale(Dt::new(civil_unix_sec, final_attos), Dt::UNIX_EPOCH, scale);
273 if is_exact_leap_second {
274 tp.add(Dt::from_sec(1, Scale::TAI))
275 } else {
276 tp
277 }
278 }
279
280 #[inline]
282 pub const fn from_ymd(yr: i64, mo: u8, day: u8) -> Self {
283 let unix_sec = Self::ymdhms_to_unix_sec(yr, mo, day, 0, 0, 0);
284 Self::from_diff_and_scale(Dt::new(unix_sec, 0), Dt::UNIX_EPOCH, Scale::UTC)
285 }
286
287 #[inline]
289 pub const fn from_ymd_on(yr: i64, mo: u8, day: u8, scale: Scale) -> Self {
290 let unix_sec = Self::ymdhms_to_unix_sec(yr, mo, day, 0, 0, 0);
291 Self::from_diff_and_scale(Dt::new(unix_sec, 0), Dt::UNIX_EPOCH, scale)
292 }
293
294 #[inline]
296 pub const fn ydoy_to_jdn(year: i64, day_of_year: u16) -> i64 {
297 let jdn_jan1 = Self::ymd_to_jdn(year, 1, 1);
298 jdn_jan1.saturating_add(day_of_year as i64 - 1)
299 }
300
301 #[inline]
303 pub const fn jdn_to_weekday(jdn: i64) -> u8 {
304 let rem = ((jdn as i128) + 1) % 7;
305 let positive = if rem < 0 { rem + 7 } else { rem };
306 positive as u8
307 }
308
309 pub const fn ymd_to_jdn_from_iso_week(iso_year: i64, iso_week: u8, weekday: Weekday) -> i64 {
311 let jan4_jdn = Self::ymd_to_jdn(iso_year, 1, 4);
312 let wd_jan4 = Self::jdn_to_weekday(jan4_jdn);
313
314 let days_to_monday = {
315 let tmp = (wd_jan4 as i64).saturating_add(6);
316 let rem = tmp % 7;
317 if rem < 0 { rem + 7 } else { rem }
318 };
319
320 let monday_week1 = jan4_jdn.saturating_sub(days_to_monday);
321
322 let monday_requested =
323 monday_week1.saturating_add(((iso_week as i64).saturating_sub(1)).saturating_mul(7));
324
325 let wd_offset = match weekday {
326 Weekday::Monday => 0,
327 Weekday::Tuesday => 1,
328 Weekday::Wednesday => 2,
329 Weekday::Thursday => 3,
330 Weekday::Friday => 4,
331 Weekday::Saturday => 5,
332 Weekday::Sunday => 6,
333 };
334
335 monday_requested.saturating_add(wd_offset as i64)
336 }
337
338 pub const fn ymd_to_jdn_from_week_sun(year: i64, week: u8, weekday: Weekday) -> i64 {
340 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
341 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
342
343 let days_to_first_sunday = ((7u8 - wd_jan1) % 7u8) as i64;
344 let first_sunday_jdn = jan1_jdn.saturating_add(days_to_first_sunday);
345
346 let sunday_of_week =
347 first_sunday_jdn.saturating_add(((week as i64).saturating_sub(1)).saturating_mul(7));
348
349 let wd_offset = match weekday {
350 Weekday::Sunday => 0,
351 Weekday::Monday => 1,
352 Weekday::Tuesday => 2,
353 Weekday::Wednesday => 3,
354 Weekday::Thursday => 4,
355 Weekday::Friday => 5,
356 Weekday::Saturday => 6,
357 };
358
359 sunday_of_week.saturating_add(wd_offset as i64)
360 }
361
362 pub const fn ymd_to_jdn_from_week_mon(year: i64, week: u8, weekday: Weekday) -> i64 {
364 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
365 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
366
367 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
368 let first_monday_jdn = jan1_jdn.saturating_add(days_to_first_monday);
369
370 let monday_of_week =
371 first_monday_jdn.saturating_add(((week as i64).saturating_sub(1)).saturating_mul(7));
372
373 let wd_offset = match weekday {
374 Weekday::Monday => 0,
375 Weekday::Tuesday => 1,
376 Weekday::Wednesday => 2,
377 Weekday::Thursday => 3,
378 Weekday::Friday => 4,
379 Weekday::Saturday => 5,
380 Weekday::Sunday => 6,
381 };
382
383 monday_of_week.saturating_add(wd_offset as i64)
384 }
385
386 pub const fn is_valid_ymd(year: i64, month: u8, day: u8) -> bool {
388 if month < 1 || month > 12 || day < 1 {
389 return false;
390 }
391 let days = match month {
392 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31u8,
393 4 | 6 | 9 | 11 => 30u8,
394 2 => {
395 if Self::is_leap_year(year) {
396 29
397 } else {
398 28
399 }
400 }
401 _ => return false,
402 };
403 day <= days
404 }
405
406 pub const fn has_iso_week_53(year: i64) -> bool {
408 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
409 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
410 wd_jan1 == 4 || (Self::is_leap_year(year) && wd_jan1 == 3)
411 }
412
413 pub const fn day_of_year(&self, current: Scale, ymd: Option<(i64, u8, u8)>) -> u16 {
418 let (year, month, day) = if let Some(ymd) = ymd {
419 ymd
420 } else {
421 let g = self.to_ymdhms(current);
422 (g.yr, g.mo, g.day)
423 };
424 let jdn = Self::ymd_to_jdn(year, month, day);
425 let jdn_jan1 = Self::ymd_to_jdn(year, 1, 1);
426
427 let doy = jdn.saturating_sub(jdn_jan1).saturating_add(1);
428 doy as u16
429 }
430
431 pub const fn wk_sun(&self, current: Scale, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
441 let (year, _, _) = if let Some(ymd) = ymd {
442 ymd
443 } else {
444 let g = self.to_ymdhms(current);
445 (g.yr, g.mo, g.day)
446 };
447 let doy = if let Some(doy) = doy {
448 doy
449 } else {
450 self.day_of_year(current, ymd)
451 };
452 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
453 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
454 let days_to_first_sunday = (7u8 - wd_jan1) % 7u8;
455 let first_sunday_doy = days_to_first_sunday as u16 + 1;
456 if doy < first_sunday_doy {
457 0
458 } else {
459 let days_since_first_sunday = doy.saturating_sub(first_sunday_doy);
460 ((days_since_first_sunday / 7) + 1) as u8
461 }
462 }
463
464 pub const fn wk_mon(&self, current: Scale, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
473 let (year, _, _) = if let Some(ymd) = ymd {
474 ymd
475 } else {
476 let g = self.to_ymdhms(current);
477 (g.yr, g.mo, g.day)
478 };
479 let doy = if let Some(doy) = doy {
480 doy
481 } else {
482 self.day_of_year(current, ymd)
483 };
484 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
485 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
486 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
487 let first_monday_doy = days_to_first_monday as u16 + 1;
488 if doy < first_monday_doy {
489 0
490 } else {
491 let days_since_first_monday = doy.saturating_sub(first_monday_doy);
492 ((days_since_first_monday / 7) + 1) as u8
493 }
494 }
495
496 pub const fn to_iso_week_date(
511 &self,
512 current: Scale,
513 ymd: Option<(i64, u8, u8)>,
514 ) -> (i64, u8, Weekday) {
515 let (year, month, day) = if let Some(ymd) = ymd {
516 ymd
517 } else {
518 let g = self.to_ymdhms(current);
519 (g.yr, g.mo, g.day)
520 };
521 let jdn = Self::ymd_to_jdn(year, month, day);
522 let wd = Self::jdn_to_weekday(jdn);
523 let wd_iso = if wd == 0 { 7 } else { wd };
524
525 let jan4_jdn = Self::ymd_to_jdn(year, 1, 4);
526 let wd_jan4 = Self::jdn_to_weekday(jan4_jdn);
527 let days_to_monday = {
528 let tmp = (wd_jan4 as i64) + 6;
529 let rem = tmp % 7;
530 if rem < 0 { rem + 7 } else { rem }
531 };
532
533 let monday_week1 = jan4_jdn - days_to_monday;
534
535 let days_since = jdn - monday_week1;
536
537 let week = if days_since < 0 {
538 0u8
539 } else {
540 ((days_since / 7) + 1) as u8
541 };
542
543 let iso_year = if week == 0 {
544 year - 1
545 } else if (week == 53 || week > 53) && !Self::has_iso_week_53(year) {
546 year + 1
547 } else {
548 year
549 };
550
551 let iso_week = if week == 0 {
552 if Self::has_iso_week_53(year - 1) {
553 53
554 } else {
555 52
556 }
557 } else if week == 53 && !Self::has_iso_week_53(year) {
558 1
559 } else if week > 53 {
560 1
561 } else {
562 week
563 };
564
565 let weekday_enum = match wd_iso {
566 1 => Weekday::Monday,
567 2 => Weekday::Tuesday,
568 3 => Weekday::Wednesday,
569 4 => Weekday::Thursday,
570 5 => Weekday::Friday,
571 6 => Weekday::Saturday,
572 _ => Weekday::Sunday,
573 };
574
575 (iso_year, iso_week, weekday_enum)
576 }
577}