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;
146
147 const fn floor_div(a: i128, b: i128) -> i128 {
149 let q = a / b;
150 let r = a % b;
151 if (r > 0 && b < 0) || (r < 0 && b > 0) {
152 q - 1
153 } else {
154 q
155 }
156 }
157
158 let a = j + 32044;
159 let b = floor_div(4 * a + 3, 146097);
160 let c = a - floor_div(b * 146097, 4);
161 let d = floor_div(4 * c + 3, 1461);
162 let e = c - floor_div(1461 * d, 4);
163 let m = floor_div(5 * e + 2, 153);
164 let day = (e - floor_div(153 * m + 2, 5) + 1) as u8;
165 let month = (m + 3 - 12 * floor_div(m, 10)) as u8;
166 let year = b * 100 + d - 4800 + floor_div(m, 10);
167
168 debug_assert!(day >= 1 && day <= 31);
169 debug_assert!(month >= 1 && month <= 12);
170
171 (year as i64, month, day)
172 }
173
174 pub const fn ymd_to_jdn(year: i64, month: u8, day: u8) -> i64 {
179 let a = (14 - month as i64) / 12;
180 let y = year.saturating_add(4800).saturating_sub(a);
181 let m = month as i64 + 12 * a - 3;
182
183 const fn floor_div(a: i64, b: i64) -> i64 {
184 let q = a / b;
185 let r = a % b;
186 if (r > 0 && b < 0) || (r < 0 && b > 0) {
187 q - 1
188 } else {
189 q
190 }
191 }
192
193 let y4 = floor_div(y, 4);
194 let y100 = floor_div(y, 100);
195 let y400 = floor_div(y, 400);
196
197 (day as i64)
198 .saturating_add((153i64 * m + 2) / 5)
199 .saturating_add(365i64 * y)
200 .saturating_add(y4)
201 .saturating_sub(y100)
202 .saturating_add(y400)
203 .saturating_sub(32045)
204 }
205
206 #[inline]
208 pub const fn is_leap_year(year: i64) -> bool {
209 year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
210 }
211
212 #[inline]
224 pub const fn from_ymdhms(
225 yr: i64,
226 mo: u8,
227 day: u8,
228 hr: u8,
229 min: u8,
230 sec: u8,
231 attos: u64,
232 ) -> Self {
233 Dt::from_ymdhms_on(yr, mo, day, hr, min, sec, attos, Scale::UTC)
234 }
235
236 pub const fn from_ymdhms_on(
237 yr: i64,
238 mo: u8,
239 day: u8,
240 hr: u8,
241 min: u8,
242 sec: u8,
243 attos: u64,
244 scale: Scale,
245 ) -> Self {
246 let mo = if mo > 12 { 12 } else { mo };
247 let day = if day > 31 { 31 } else { day };
248 let h = if hr > 23 { 23 } else { hr };
249 let m = if min > 59 { 59 } else { min };
250 let s = if sec > 60 { 60 } else { sec };
251
252 let extra_sec = (attos / ATTOS_PER_SEC) as i64;
253 let final_attos = attos % ATTOS_PER_SEC;
254
255 let is_exact_leap_second = s == 60 && extra_sec == 0;
264 let s_for_unix = if is_exact_leap_second { 59 } else { s };
265
266 let civil_unix_sec = Self::ymdhms_to_unix_sec(yr, mo, day, h, m, s_for_unix) + extra_sec;
267
268 let tp =
269 Self::from_diff_and_scale(Dt::new(civil_unix_sec, final_attos), Dt::UNIX_EPOCH, scale);
270 if is_exact_leap_second {
271 tp.add(Dt::from_sec(1, Scale::TAI))
272 } else {
273 tp
274 }
275 }
276
277 #[inline]
279 pub const fn from_ymd(yr: i64, mo: u8, day: u8) -> Self {
280 let unix_sec = Self::ymdhms_to_unix_sec(yr, mo, day, 0, 0, 0);
281 Self::from_diff_and_scale(Dt::new(unix_sec, 0), Dt::UNIX_EPOCH, Scale::UTC)
282 }
283
284 #[inline]
286 pub const fn from_ymd_on(yr: i64, mo: u8, day: u8, scale: Scale) -> Self {
287 let unix_sec = Self::ymdhms_to_unix_sec(yr, mo, day, 0, 0, 0);
288 Self::from_diff_and_scale(Dt::new(unix_sec, 0), Dt::UNIX_EPOCH, scale)
289 }
290
291 #[inline]
293 pub const fn ydoy_to_jdn(year: i64, day_of_year: u16) -> i64 {
294 let jdn_jan1 = Self::ymd_to_jdn(year, 1, 1);
295 jdn_jan1.saturating_add(day_of_year as i64 - 1)
296 }
297
298 #[inline]
300 pub const fn jdn_to_weekday(jdn: i64) -> u8 {
301 let rem = ((jdn as i128) + 1) % 7;
302 let positive = if rem < 0 { rem + 7 } else { rem };
303 positive as u8
304 }
305
306 pub const fn ymd_to_jdn_from_iso_week(iso_year: i64, iso_week: u8, weekday: Weekday) -> i64 {
308 let jan4_jdn = Self::ymd_to_jdn(iso_year, 1, 4);
309 let wd_jan4 = Self::jdn_to_weekday(jan4_jdn);
310
311 let days_to_monday = {
312 let tmp = (wd_jan4 as i64).saturating_add(6);
313 let rem = tmp % 7;
314 if rem < 0 { rem + 7 } else { rem }
315 };
316
317 let monday_week1 = jan4_jdn.saturating_sub(days_to_monday);
318
319 let monday_requested =
320 monday_week1.saturating_add(((iso_week as i64).saturating_sub(1)).saturating_mul(7));
321
322 let wd_offset = match weekday {
323 Weekday::Monday => 0,
324 Weekday::Tuesday => 1,
325 Weekday::Wednesday => 2,
326 Weekday::Thursday => 3,
327 Weekday::Friday => 4,
328 Weekday::Saturday => 5,
329 Weekday::Sunday => 6,
330 };
331
332 monday_requested.saturating_add(wd_offset as i64)
333 }
334
335 pub const fn ymd_to_jdn_from_week_sun(year: i64, week: u8, weekday: Weekday) -> i64 {
337 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
338 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
339
340 let days_to_first_sunday = ((7u8 - wd_jan1) % 7u8) as i64;
341 let first_sunday_jdn = jan1_jdn.saturating_add(days_to_first_sunday);
342
343 let sunday_of_week =
344 first_sunday_jdn.saturating_add(((week as i64).saturating_sub(1)).saturating_mul(7));
345
346 let wd_offset = match weekday {
347 Weekday::Sunday => 0,
348 Weekday::Monday => 1,
349 Weekday::Tuesday => 2,
350 Weekday::Wednesday => 3,
351 Weekday::Thursday => 4,
352 Weekday::Friday => 5,
353 Weekday::Saturday => 6,
354 };
355
356 sunday_of_week.saturating_add(wd_offset as i64)
357 }
358
359 pub const fn ymd_to_jdn_from_week_mon(year: i64, week: u8, weekday: Weekday) -> i64 {
361 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
362 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
363
364 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
365 let first_monday_jdn = jan1_jdn.saturating_add(days_to_first_monday);
366
367 let monday_of_week =
368 first_monday_jdn.saturating_add(((week as i64).saturating_sub(1)).saturating_mul(7));
369
370 let wd_offset = match weekday {
371 Weekday::Monday => 0,
372 Weekday::Tuesday => 1,
373 Weekday::Wednesday => 2,
374 Weekday::Thursday => 3,
375 Weekday::Friday => 4,
376 Weekday::Saturday => 5,
377 Weekday::Sunday => 6,
378 };
379
380 monday_of_week.saturating_add(wd_offset as i64)
381 }
382
383 pub const fn is_valid_ymd(year: i64, month: u8, day: u8) -> bool {
385 if month < 1 || month > 12 || day < 1 {
386 return false;
387 }
388 let days = match month {
389 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31u8,
390 4 | 6 | 9 | 11 => 30u8,
391 2 => {
392 if Self::is_leap_year(year) {
393 29
394 } else {
395 28
396 }
397 }
398 _ => return false,
399 };
400 day <= days
401 }
402
403 pub const fn has_iso_week_53(year: i64) -> bool {
405 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
406 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
407 wd_jan1 == 4 || (Self::is_leap_year(year) && wd_jan1 == 3)
408 }
409
410 pub const fn day_of_year(&self, current: Scale, ymd: Option<(i64, u8, u8)>) -> u16 {
415 let (year, month, day) = if let Some(ymd) = ymd {
416 ymd
417 } else {
418 let g = self.to_ymdhms(current);
419 (g.yr, g.mo, g.day)
420 };
421 let jdn = Self::ymd_to_jdn(year, month, day);
422 let jdn_jan1 = Self::ymd_to_jdn(year, 1, 1);
423
424 let doy = jdn.saturating_sub(jdn_jan1).saturating_add(1);
425 doy as u16
426 }
427
428 pub const fn wk_sun(&self, current: Scale, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
438 let (year, _, _) = if let Some(ymd) = ymd {
439 ymd
440 } else {
441 let g = self.to_ymdhms(current);
442 (g.yr, g.mo, g.day)
443 };
444 let doy = if let Some(doy) = doy {
445 doy
446 } else {
447 self.day_of_year(current, ymd)
448 };
449 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
450 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
451 let days_to_first_sunday = (7u8 - wd_jan1) % 7u8;
452 let first_sunday_doy = days_to_first_sunday as u16 + 1;
453 if doy < first_sunday_doy {
454 0
455 } else {
456 let days_since_first_sunday = doy.saturating_sub(first_sunday_doy);
457 ((days_since_first_sunday / 7) + 1) as u8
458 }
459 }
460
461 pub const fn wk_mon(&self, current: Scale, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
470 let (year, _, _) = if let Some(ymd) = ymd {
471 ymd
472 } else {
473 let g = self.to_ymdhms(current);
474 (g.yr, g.mo, g.day)
475 };
476 let doy = if let Some(doy) = doy {
477 doy
478 } else {
479 self.day_of_year(current, ymd)
480 };
481 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
482 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
483 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
484 let first_monday_doy = days_to_first_monday as u16 + 1;
485 if doy < first_monday_doy {
486 0
487 } else {
488 let days_since_first_monday = doy.saturating_sub(first_monday_doy);
489 ((days_since_first_monday / 7) + 1) as u8
490 }
491 }
492
493 pub const fn to_iso_week_date(
508 &self,
509 current: Scale,
510 ymd: Option<(i64, u8, u8)>,
511 ) -> (i64, u8, Weekday) {
512 let (year, month, day) = if let Some(ymd) = ymd {
513 ymd
514 } else {
515 let g = self.to_ymdhms(current);
516 (g.yr, g.mo, g.day)
517 };
518 let jdn = Self::ymd_to_jdn(year, month, day);
519 let wd = Self::jdn_to_weekday(jdn);
520 let wd_iso = if wd == 0 { 7 } else { wd };
521
522 let jan4_jdn = Self::ymd_to_jdn(year, 1, 4);
523 let wd_jan4 = Self::jdn_to_weekday(jan4_jdn);
524 let days_to_monday = {
525 let tmp = (wd_jan4 as i64) + 6;
526 let rem = tmp % 7;
527 if rem < 0 { rem + 7 } else { rem }
528 };
529
530 let monday_week1 = jan4_jdn - days_to_monday;
531
532 let days_since = jdn - monday_week1;
533
534 let week = if days_since < 0 {
535 0u8
536 } else {
537 ((days_since / 7) + 1) as u8
538 };
539
540 let iso_year = if week == 0 {
541 year - 1
542 } else if week >= 53 && !Self::has_iso_week_53(year) {
543 year + 1
544 } else {
545 year
546 };
547
548 let iso_week = if week == 0 {
549 if Self::has_iso_week_53(year - 1) {
550 53
551 } else {
552 52
553 }
554 } else if (week == 53 && !Self::has_iso_week_53(year)) || week > 53 {
555 1
556 } else {
557 week
558 };
559
560 let weekday_enum = match wd_iso {
561 1 => Weekday::Monday,
562 2 => Weekday::Tuesday,
563 3 => Weekday::Wednesday,
564 4 => Weekday::Thursday,
565 5 => Weekday::Friday,
566 6 => Weekday::Saturday,
567 _ => Weekday::Sunday,
568 };
569
570 (iso_year, iso_week, weekday_enum)
571 }
572}