1use crate::{ATTOS_PER_SEC_I128, Dt, LiteStr, Scale, Weekday};
2
3mod to_str;
4
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34#[cfg_attr(feature = "js", derive(tsify::Tsify))]
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
36pub struct YmdHms {
37 pub(crate) yr: i64,
38 pub(crate) mo: u8,
39 pub(crate) day: u8,
40 pub(crate) hr: u8,
41 pub(crate) min: u8,
42 pub(crate) sec: u8, pub(crate) attos: u64, pub(crate) unix_attosec: i128,
45 pub(crate) scale: Scale,
46}
47
48impl YmdHms {
49 #[inline]
51 pub const fn to_dt(&self) -> Dt {
52 Dt::from_ymdhms_on(
53 self.yr, self.mo, self.day, self.hr, self.min, self.sec, self.attos, self.scale,
54 )
55 }
56
57 #[inline(always)]
58 const fn reconstruct(
59 yr: i64,
60 mo: u8,
61 day: u8,
62 hr: u8,
63 min: u8,
64 sec: u8,
65 attos: u64,
66 scale: Scale,
67 ) -> Self {
68 Dt::from_ymdhms_on(yr, mo, day, hr, min, sec, attos, scale).to_ymdhms_on(Scale::TAI, scale)
69 }
70
71 pub const fn add_yr(&self, years: i64) -> Self {
74 if years == 0 {
75 return *self;
76 }
77 let new_yr = self.yr.saturating_add(years);
78 let max_day = Dt::days_in_month(new_yr, self.mo);
79 let new_day = Dt::clamp_u8(self.day, 1, max_day);
80 Self::reconstruct(
81 new_yr, self.mo, new_day, self.hr, self.min, self.sec, self.attos, self.scale,
82 )
83 }
84
85 pub const fn add_mo(&self, months: i64) -> Self {
88 if months == 0 {
89 return *self;
90 }
91 let yr = self.yr as i128;
92 let mo = self.mo as i128;
93 let delta = months as i128;
94
95 let total_months = yr * 12 + (mo - 1) + delta;
96
97 let new_yr = Dt::clamp_i128_to_i64(total_months.div_euclid(12));
98 let new_mo = Dt::clamp_u8((total_months.rem_euclid(12) + 1) as u8, 1, 12);
99
100 let max_day = Dt::days_in_month(new_yr, new_mo);
101 let new_day = Dt::clamp_u8(self.day, 1, max_day);
102
103 Self::reconstruct(
104 new_yr, new_mo, new_day, self.hr, self.min, self.sec, self.attos, self.scale,
105 )
106 }
107
108 pub const fn add_days(&self, days: i64) -> Self {
111 if days == 0 {
112 return *self;
113 }
114 let jd = Dt::ymd_to_jd(self.yr, self.mo, self.day);
115 let new_jd = jd.saturating_add(days);
116 let (new_yr, new_mo, new_day) = Dt::jd_to_ymd(new_jd);
117 Self::reconstruct(
118 new_yr, new_mo, new_day, self.hr, self.min, self.sec, self.attos, self.scale,
119 )
120 }
121
122 #[inline]
123 pub const fn add_wk(&self, weeks: i64) -> Self {
124 self.add_days(weeks.saturating_mul(7))
125 }
126
127 #[inline(never)]
128 const fn _add_attos(&self, attos_delta: i128) -> Self {
129 let tai = Dt::from_ymdhms_on(
130 self.yr, self.mo, self.day, self.hr, self.min, self.sec, self.attos, self.scale,
131 );
132 let delta_dt = Dt::from_attos(attos_delta, Scale::TAI);
133 let new_tai = tai.add(delta_dt);
134 new_tai.to_ymdhms_on(Scale::TAI, self.scale)
135 }
136
137 #[inline]
138 pub const fn add_attos(&self, attos: i128) -> Self {
139 self._add_attos(attos)
140 }
141
142 #[inline]
143 pub const fn add_sec(&self, sec: i64) -> Self {
144 self._add_attos(sec as i128 * ATTOS_PER_SEC_I128)
145 }
146
147 #[inline]
148 pub const fn add_min(&self, min: i64) -> Self {
149 self._add_attos(min as i128 * 60 * ATTOS_PER_SEC_I128)
150 }
151
152 #[inline]
153 pub const fn add_hr(&self, hr: i64) -> Self {
154 self._add_attos(hr as i128 * 3600 * ATTOS_PER_SEC_I128)
155 }
156
157 #[inline]
158 pub const fn yr(&self) -> i64 {
159 self.yr
160 }
161
162 #[inline]
163 pub const fn mo(&self) -> u8 {
164 self.mo
165 }
166
167 #[inline]
168 pub const fn day(&self) -> u8 {
169 self.day
170 }
171
172 #[inline]
173 pub const fn hr(&self) -> u8 {
174 self.hr
175 }
176
177 #[inline]
178 pub const fn min(&self) -> u8 {
179 self.min
180 }
181
182 #[inline]
183 pub const fn sec(&self) -> u8 {
184 self.sec
185 }
186
187 #[inline]
188 pub const fn attos(&self) -> u64 {
189 self.attos
190 }
191
192 #[inline]
195 pub const fn unix_attosec(&self) -> i128 {
196 self.unix_attosec
197 }
198
199 #[inline]
201 pub const fn scale(&self) -> Scale {
202 self.scale
203 }
204
205 pub(crate) const fn to_ymdhms_rich(
206 &self,
207 iso_yr: i64,
208 iso_wk: u8,
209 iso_wkday: Weekday,
210 day_of_yr: u16,
211 wkday: u8,
212 wk_of_yr_sun: u8,
213 wk_of_yr_mon: u8,
214 ) -> YmdHmsRich {
215 YmdHmsRich::new(
216 self.unix_attosec,
217 self.yr,
218 self.mo,
219 self.day,
220 self.hr,
221 self.min,
222 self.sec,
223 self.attos,
224 iso_yr,
225 iso_wk,
226 iso_wkday,
227 day_of_yr,
228 wkday,
229 wk_of_yr_sun,
230 wk_of_yr_mon,
231 self.scale,
232 )
233 }
234}
235
236#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
238#[cfg_attr(feature = "js", derive(tsify::Tsify))]
239#[derive(Clone, Copy, Debug, PartialEq, Eq)]
240pub struct YmdHmsRich {
241 pub(crate) unix_attosec: i128,
243 pub(crate) yr: i64,
245 pub(crate) mo: u8,
247 pub(crate) day: u8,
249 pub(crate) hr: u8,
251 pub(crate) min: u8,
253 pub(crate) sec: u8,
255 pub(crate) attos: u64,
257 pub(crate) iso_yr: i64,
259 pub(crate) iso_wk: u8,
261 pub(crate) iso_wkday: Weekday,
263 pub(crate) day_of_yr: u16,
265 pub(crate) wkday: u8,
267 pub(crate) wk_of_yr_sun: u8,
269 pub(crate) wk_of_yr_mon: u8,
271 pub(crate) offset_sec: Option<i32>,
274 pub(crate) tz: Option<LiteStr<49>>,
276 pub(crate) tz_abbrev: Option<LiteStr<49>>,
278 pub(crate) scale: Scale,
280}
281
282impl YmdHmsRich {
283 #[inline]
285 pub(crate) const fn new(
286 unix_attosec: i128,
287 yr: i64,
288 mo: u8,
289 day: u8,
290 hr: u8,
291 min: u8,
292 sec: u8,
293 attos: u64,
294 iso_yr: i64,
295 iso_wk: u8,
296 iso_wkday: Weekday,
297 day_of_yr: u16,
298 wkday: u8,
299 wk_of_yr_sun: u8,
300 wk_of_yr_mon: u8,
301 scale: Scale,
302 ) -> Self {
303 Self {
304 unix_attosec,
305 yr,
306 mo,
307 day,
308 hr,
309 min,
310 sec,
311 attos,
312 iso_yr,
313 iso_wk,
314 iso_wkday,
315 day_of_yr,
316 wkday,
317 wk_of_yr_sun,
318 wk_of_yr_mon,
319 offset_sec: None,
320 tz: None,
321 tz_abbrev: None,
322 scale,
323 }
324 }
325
326 #[inline]
328 pub const fn to_dt(&self) -> Dt {
329 Dt::from_ymdhms_on(
330 self.yr, self.mo, self.day, self.hr, self.min, self.sec, self.attos, self.scale,
331 )
332 }
333
334 #[inline]
337 pub const fn unix_attosec(&self) -> i128 {
338 self.unix_attosec
339 }
340
341 #[inline]
343 pub const fn scale(&self) -> Scale {
344 self.scale
345 }
346
347 #[inline]
354 pub const fn unix_timestamp(&self) -> (i64, u64) {
355 const ATTOS_PER_SEC_I128: i128 = 1_000_000_000_000_000_000;
356 let total = self.unix_attosec;
357 let secs = (total / ATTOS_PER_SEC_I128) as i64;
358 let frac = (total % ATTOS_PER_SEC_I128).unsigned_abs() as u64;
359 (secs, frac)
360 }
361
362 #[inline]
364 pub const fn yr(&self) -> i64 {
365 self.yr
366 }
367
368 #[inline]
370 pub const fn mo(&self) -> u8 {
371 self.mo
372 }
373
374 #[inline]
376 pub const fn day(&self) -> u8 {
377 self.day
378 }
379
380 #[inline]
382 pub const fn hr(&self) -> u8 {
383 self.hr
384 }
385
386 #[inline]
388 pub const fn min(&self) -> u8 {
389 self.min
390 }
391
392 #[inline]
394 pub const fn sec(&self) -> u8 {
395 self.sec
396 }
397
398 #[inline]
400 pub const fn attos(&self) -> u64 {
401 self.attos
402 }
403
404 #[inline]
406 pub const fn iso_yr(&self) -> i64 {
407 self.iso_yr
408 }
409
410 #[inline]
412 pub const fn iso_wk(&self) -> u8 {
413 self.iso_wk
414 }
415
416 #[inline]
418 pub const fn iso_wkday(&self) -> Weekday {
419 self.iso_wkday
420 }
421
422 #[inline]
424 pub const fn day_of_yr(&self) -> u16 {
425 self.day_of_yr
426 }
427
428 #[inline]
430 pub const fn wkday_sun(&self) -> u8 {
431 self.wkday
432 }
433
434 #[inline]
436 pub const fn wkday_mon(&self) -> u8 {
437 self.iso_wkday.wk_mon()
438 }
439
440 #[inline]
442 pub const fn wk_of_yr_sun(&self) -> u8 {
443 self.wk_of_yr_sun
444 }
445
446 #[inline]
448 pub const fn wk_of_yr_mon(&self) -> u8 {
449 self.wk_of_yr_mon
450 }
451
452 #[inline]
453 pub(crate) const fn offset_sec(&self) -> Option<i32> {
454 self.offset_sec
455 }
456
457 #[inline]
458 pub(crate) const fn tz(&self) -> Option<&LiteStr<49>> {
459 self.tz.as_ref()
460 }
461
462 #[inline]
463 pub(crate) const fn tz_abbrev(&self) -> Option<&LiteStr<49>> {
464 self.tz_abbrev.as_ref()
465 }
466
467 #[inline]
468 pub(crate) fn set_offset(&mut self, offset_sec: Option<i32>) -> &mut Self {
469 self.offset_sec = offset_sec;
470 self
471 }
472
473 #[inline]
474 pub(crate) fn set_tz(&mut self, tz: Option<&str>) -> &mut Self {
475 self.tz = tz.map(LiteStr::new);
476 self
477 }
478
479 #[inline]
480 pub(crate) fn set_tz_abbrev(&mut self, tz_abbrev: Option<&str>) -> &mut Self {
481 self.tz_abbrev = tz_abbrev.map(LiteStr::new);
482 self
483 }
484}