1use crate::{
2 ATTOS_PER_SEC, Dt, GregorianTime, SEC_PER_DAYI64, Scale, TSpan, Weekday,
3 leap_seconds::get_leap_seconds,
4};
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
8pub struct YmdHms {
9 pub yr: i64,
10 pub mo: u8,
11 pub day: u8,
12 pub hr: u8,
13 pub min: u8,
14 pub sec: u8, pub attos: u64, }
17
18impl Dt {
19 #[inline]
22 pub const fn unix_sec_to_gregorian_ymd(unix_sec: i64) -> (i64, u8, u8) {
23 let days_since_1970 = unix_sec.div_euclid(SEC_PER_DAYI64);
24 let jdn = days_since_1970.saturating_add(2440588);
26 Self::jdn_to_ymd(jdn)
27 }
28
29 pub const fn to_gregorian_time(&self) -> GregorianTime {
30 let ymdhms = self.to_ymdhms();
32 let unix_attosec = self.to_epoch(Dt::UNIX_EPOCH, Scale::UTC).to_attos();
33
34 let (iso_yr, iso_wk, iso_wkday) =
35 self.to_iso_week_date(Some((ymdhms.yr, ymdhms.mo, ymdhms.day)));
36 let day_of_yr = self.day_of_year(Some((ymdhms.yr, ymdhms.mo, ymdhms.day)));
37 let jdn = Self::ymd_to_jdn(ymdhms.yr, ymdhms.mo, ymdhms.day);
38 let wkday = Self::jdn_to_weekday(jdn);
39 let wk_of_yr_sun = self.wk_sun(Some((ymdhms.yr, ymdhms.mo, ymdhms.day)), Some(day_of_yr));
40 let wk_of_yr_mon = self.wk_mon(Some((ymdhms.yr, ymdhms.mo, ymdhms.day)), Some(day_of_yr));
41
42 GregorianTime {
43 unix_attosec,
44 yr: ymdhms.yr,
45 mo: ymdhms.mo,
46 day: ymdhms.day,
47 hr: ymdhms.hr,
48 min: ymdhms.min,
49 sec: ymdhms.sec,
50 attos: ymdhms.attos,
51 iso_yr,
52 iso_wk,
53 iso_wkday,
54 day_of_yr,
55 wkday,
56 wk_of_yr_sun,
57 wk_of_yr_mon,
58 offset_sec: None,
59 tz: None,
60 tz_abbrev: None,
61 }
62 }
63
64 #[inline]
71 pub const fn to_ymdhms(&self) -> YmdHms {
72 let canon = self.to_epoch(Dt::UNIX_EPOCH, Scale::UTC);
75
76 let unix_sec = canon.sec;
77 let attos = canon.attos;
78
79 let is_leap_second = get_leap_seconds(&self, false).is_leap_second;
80
81 let unix_sec_for_date = if is_leap_second {
84 unix_sec - 1
85 } else {
86 unix_sec
87 };
88
89 let (yr, mo, day) = Self::unix_sec_to_gregorian_ymd(unix_sec_for_date);
90
91 let (hr, min, sec) = if is_leap_second {
93 (23, 59, 60)
94 } else {
95 let seconds_since_midnight = unix_sec.rem_euclid(SEC_PER_DAYI64);
96 let hr = (seconds_since_midnight / 3600) as u8;
97 let min = ((seconds_since_midnight % 3600) / 60) as u8;
98 let sec = (seconds_since_midnight % 60) as u8;
99 (hr, min, sec)
100 };
101
102 YmdHms {
103 yr,
104 mo,
105 day,
106 hr,
107 min,
108 sec,
109 attos,
110 }
111 }
112
113 pub const fn ymdhms_to_unix_sec(
118 year: i64,
119 month: u8,
120 day: u8,
121 hour: u8,
122 minute: u8,
123 second: u8,
124 ) -> i64 {
125 let jdn = Self::ymd_to_jdn(year, month, day);
126 let days_since_1970 = jdn.saturating_sub(2440588);
128 let time_of_day = (hour as i64) * 3600 + (minute as i64) * 60 + (second as i64);
129 days_since_1970
130 .saturating_mul(SEC_PER_DAYI64)
131 .saturating_add(time_of_day)
132 }
133
134 pub const fn jdn_to_ymd(jdn: i64) -> (i64, u8, u8) {
142 let j = jdn as i128;
144
145 const fn floor_div(a: i128, b: i128) -> i128 {
147 let q = a / b;
148 let r = a % b;
149 if (r > 0 && b < 0) || (r < 0 && b > 0) {
150 q - 1
151 } else {
152 q
153 }
154 }
155
156 let a = j + 32044;
157 let b = floor_div(4 * a + 3, 146097);
158 let c = a - floor_div(b * 146097, 4);
159 let d = floor_div(4 * c + 3, 1461);
160 let e = c - floor_div(1461 * d, 4);
161 let m = floor_div(5 * e + 2, 153);
162 let day = (e - floor_div(153 * m + 2, 5) + 1) as u8;
163 let month = (m + 3 - 12 * floor_div(m, 10)) as u8;
164 let year = b * 100 + d - 4800 + floor_div(m, 10);
165
166 debug_assert!(day >= 1 && day <= 31);
167 debug_assert!(month >= 1 && month <= 12);
168
169 (year as i64, month, day)
170 }
171
172 pub const fn ymd_to_jdn(year: i64, month: u8, day: u8) -> i64 {
177 let a = (14 - month as i64) / 12;
178 let y = year.saturating_add(4800).saturating_sub(a);
179 let m = month as i64 + 12 * a - 3;
180
181 const fn floor_div(a: i64, b: i64) -> i64 {
182 let q = a / b;
183 let r = a % b;
184 if (r > 0 && b < 0) || (r < 0 && b > 0) {
185 q - 1
186 } else {
187 q
188 }
189 }
190
191 let y4 = floor_div(y, 4);
192 let y100 = floor_div(y, 100);
193 let y400 = floor_div(y, 400);
194
195 let result = (day as i64)
196 .saturating_add((153i64 * m + 2) / 5)
197 .saturating_add(365i64 * y)
198 .saturating_add(y4)
199 .saturating_sub(y100)
200 .saturating_add(y400)
201 .saturating_sub(32045);
202
203 result
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 pub const fn from_ymdhms(
224 yr: i64,
225 mo: u8,
226 day: u8,
227 hr: u8,
228 min: u8,
229 sec: u8,
230 attos: u64,
231 scale: Scale,
232 ) -> Self {
233 let mo = if mo > 12 { 12 } else { mo };
234 let day = if day > 31 { 31 } else { day };
235 let h = if hr > 23 { 23 } else { hr };
236 let m = if min > 59 { 59 } else { min };
237 let s = if sec > 60 { 60 } else { sec };
238
239 let extra_sec = (attos / ATTOS_PER_SEC) as i64;
240 let final_attos = attos % ATTOS_PER_SEC;
241
242 let is_exact_leap_second = s == 60 && extra_sec == 0;
251 let s_for_unix = if is_exact_leap_second { 59 } else { s };
252
253 let civil_unix_sec = Self::ymdhms_to_unix_sec(yr, mo, day, h, m, s_for_unix) + extra_sec;
254
255 let tp = Self::from_epoch(
256 TSpan::new(civil_unix_sec, final_attos),
257 Dt::UNIX_EPOCH,
258 scale.to_ut(),
259 );
260 if is_exact_leap_second {
261 tp.add(TSpan::from_sec(1))
262 } else {
263 tp
264 }
265 }
266
267 pub const fn from_ymd(yr: i64, mo: u8, day: u8, scale: Scale) -> Self {
273 let unix_sec = Self::ymdhms_to_unix_sec(yr, mo, day, 0, 0, 0);
274
275 Self::from_epoch(TSpan::new(unix_sec, 0), Dt::UNIX_EPOCH, scale.to_ut())
276 }
277
278 #[inline]
280 pub const fn ydoy_to_jdn(year: i64, day_of_year: u16) -> i64 {
281 let jdn_jan1 = Self::ymd_to_jdn(year, 1, 1);
282 jdn_jan1.saturating_add(day_of_year as i64 - 1)
283 }
284
285 #[inline]
287 pub const fn jdn_to_weekday(jdn: i64) -> u8 {
288 let rem = ((jdn as i128) + 1) % 7;
289 let positive = if rem < 0 { rem + 7 } else { rem };
290 positive as u8
291 }
292
293 pub const fn ymd_to_jdn_from_iso_week(iso_year: i64, iso_week: u8, weekday: Weekday) -> i64 {
295 let jan4_jdn = Self::ymd_to_jdn(iso_year, 1, 4);
296 let wd_jan4 = Self::jdn_to_weekday(jan4_jdn);
297
298 let days_to_monday = {
299 let tmp = (wd_jan4 as i64).saturating_add(6);
300 let rem = tmp % 7;
301 if rem < 0 { rem + 7 } else { rem }
302 };
303
304 let monday_week1 = jan4_jdn.saturating_sub(days_to_monday);
305
306 let monday_requested =
307 monday_week1.saturating_add(((iso_week as i64).saturating_sub(1)).saturating_mul(7));
308
309 let wd_offset = match weekday {
310 Weekday::Monday => 0,
311 Weekday::Tuesday => 1,
312 Weekday::Wednesday => 2,
313 Weekday::Thursday => 3,
314 Weekday::Friday => 4,
315 Weekday::Saturday => 5,
316 Weekday::Sunday => 6,
317 };
318
319 monday_requested.saturating_add(wd_offset as i64)
320 }
321
322 pub const fn ymd_to_jdn_from_week_sun(year: i64, week: u8, weekday: Weekday) -> i64 {
324 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
325 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
326
327 let days_to_first_sunday = ((7u8 - wd_jan1) % 7u8) as i64;
328 let first_sunday_jdn = jan1_jdn.saturating_add(days_to_first_sunday);
329
330 let sunday_of_week =
331 first_sunday_jdn.saturating_add(((week as i64).saturating_sub(1)).saturating_mul(7));
332
333 let wd_offset = match weekday {
334 Weekday::Sunday => 0,
335 Weekday::Monday => 1,
336 Weekday::Tuesday => 2,
337 Weekday::Wednesday => 3,
338 Weekday::Thursday => 4,
339 Weekday::Friday => 5,
340 Weekday::Saturday => 6,
341 };
342
343 sunday_of_week.saturating_add(wd_offset as i64)
344 }
345
346 pub const fn ymd_to_jdn_from_week_mon(year: i64, week: u8, weekday: Weekday) -> i64 {
348 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
349 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
350
351 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
352 let first_monday_jdn = jan1_jdn.saturating_add(days_to_first_monday);
353
354 let monday_of_week =
355 first_monday_jdn.saturating_add(((week as i64).saturating_sub(1)).saturating_mul(7));
356
357 let wd_offset = match weekday {
358 Weekday::Monday => 0,
359 Weekday::Tuesday => 1,
360 Weekday::Wednesday => 2,
361 Weekday::Thursday => 3,
362 Weekday::Friday => 4,
363 Weekday::Saturday => 5,
364 Weekday::Sunday => 6,
365 };
366
367 monday_of_week.saturating_add(wd_offset as i64)
368 }
369
370 pub const fn is_valid_ymd(year: i64, month: u8, day: u8) -> bool {
372 if month < 1 || month > 12 || day < 1 {
373 return false;
374 }
375 let days = match month {
376 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31u8,
377 4 | 6 | 9 | 11 => 30u8,
378 2 => {
379 if Self::is_leap_year(year) {
380 29
381 } else {
382 28
383 }
384 }
385 _ => return false,
386 };
387 day <= days
388 }
389
390 pub const fn has_iso_week_53(year: i64) -> bool {
392 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
393 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
394 wd_jan1 == 4 || (Self::is_leap_year(year) && wd_jan1 == 3)
395 }
396
397 pub const fn day_of_year(&self, ymd: Option<(i64, u8, u8)>) -> u16 {
402 let (year, month, day) = if let Some(ymd) = ymd {
403 ymd
404 } else {
405 let g = self.to_ymdhms();
406 (g.yr, g.mo, g.day)
407 };
408 let jdn = Self::ymd_to_jdn(year, month, day);
409 let jdn_jan1 = Self::ymd_to_jdn(year, 1, 1);
410
411 let doy = jdn.saturating_sub(jdn_jan1).saturating_add(1);
412 doy as u16
413 }
414
415 pub const fn wk_sun(&self, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
425 let (year, _, _) = if let Some(ymd) = ymd {
426 ymd
427 } else {
428 let g = self.to_ymdhms();
429 (g.yr, g.mo, g.day)
430 };
431 let doy = if let Some(doy) = doy {
432 doy
433 } else {
434 self.day_of_year(ymd)
435 };
436 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
437 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
438 let days_to_first_sunday = (7u8 - wd_jan1) % 7u8;
439 let first_sunday_doy = days_to_first_sunday as u16 + 1;
440 if doy < first_sunday_doy {
441 0
442 } else {
443 let days_since_first_sunday = doy.saturating_sub(first_sunday_doy);
444 ((days_since_first_sunday / 7) + 1) as u8
445 }
446 }
447
448 pub const fn wk_mon(&self, ymd: Option<(i64, u8, u8)>, doy: Option<u16>) -> u8 {
457 let (year, _, _) = if let Some(ymd) = ymd {
458 ymd
459 } else {
460 let g = self.to_ymdhms();
461 (g.yr, g.mo, g.day)
462 };
463 let doy = if let Some(doy) = doy {
464 doy
465 } else {
466 self.day_of_year(ymd)
467 };
468 let jan1_jdn = Self::ymd_to_jdn(year, 1, 1);
469 let wd_jan1 = Self::jdn_to_weekday(jan1_jdn);
470 let days_to_first_monday = (1i64 - wd_jan1 as i64).rem_euclid(7);
471 let first_monday_doy = days_to_first_monday as u16 + 1;
472 if doy < first_monday_doy {
473 0
474 } else {
475 let days_since_first_monday = doy.saturating_sub(first_monday_doy);
476 ((days_since_first_monday / 7) + 1) as u8
477 }
478 }
479
480 pub const fn to_iso_week_date(&self, ymd: Option<(i64, u8, u8)>) -> (i64, u8, Weekday) {
495 let (year, month, day) = if let Some(ymd) = ymd {
496 ymd
497 } else {
498 let g = self.to_ymdhms();
499 (g.yr, g.mo, g.day)
500 };
501 let jdn = Self::ymd_to_jdn(year, month, day);
502 let wd = Self::jdn_to_weekday(jdn);
503 let wd_iso = if wd == 0 { 7 } else { wd };
504
505 let jan4_jdn = Self::ymd_to_jdn(year, 1, 4);
506 let wd_jan4 = Self::jdn_to_weekday(jan4_jdn);
507 let days_to_monday = {
508 let tmp = (wd_jan4 as i64) + 6;
509 let rem = tmp % 7;
510 if rem < 0 { rem + 7 } else { rem }
511 };
512
513 let monday_week1 = jan4_jdn - days_to_monday;
514
515 let days_since = jdn - monday_week1;
516
517 let week = if days_since < 0 {
518 0u8
519 } else {
520 ((days_since / 7) + 1) as u8
521 };
522
523 let iso_year = if week == 0 {
524 year - 1
525 } else if (week == 53 || week > 53) && !Self::has_iso_week_53(year) {
526 year + 1
527 } else {
528 year
529 };
530
531 let iso_week = if week == 0 {
532 if Self::has_iso_week_53(year - 1) {
533 53
534 } else {
535 52
536 }
537 } else if week == 53 && !Self::has_iso_week_53(year) {
538 1
539 } else if week > 53 {
540 1
541 } else {
542 week
543 };
544
545 let weekday_enum = match wd_iso {
546 1 => Weekday::Monday,
547 2 => Weekday::Tuesday,
548 3 => Weekday::Wednesday,
549 4 => Weekday::Thursday,
550 5 => Weekday::Friday,
551 6 => Weekday::Saturday,
552 _ => Weekday::Sunday,
553 };
554
555 (iso_year, iso_week, weekday_enum)
556 }
557}