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