deep_time/time_parts/
to_deep_time.rs1use crate::tzdb::offset_info_at_local;
2use crate::{
3 Dt, JD_2000_2_451_545, SEC_PER_DAYI64, Scale, TAI_SECS_1970_MIDNIGHT_TO_2000_NOON, an_err,
4 error::{DtErr, DtErrKind},
5 {Meridiem, Offset, TimeParts, Weekday},
6};
7
8impl TimeParts {
9 pub fn to_dt(&self) -> Result<Dt, DtErr> {
14 if let Some(unix_secs) = self.unix_timestamp_seconds {
18 let sec = unix_secs - TAI_SECS_1970_MIDNIGHT_TO_2000_NOON;
19 let subsec = self.attos.unwrap_or(0);
20 return Ok(Dt::from(sec, subsec, Scale::UTC));
21 }
22
23 let mut jdn: Option<i64> = None;
27
28 if let Some(year) = self.year {
29 if let (Some(m), Some(d)) = (self.month, self.day) {
30 if !Dt::is_valid_ymd(year, m, d) {
32 return Err(an_err!(DtErrKind::InvalidInput, "ymd"));
33 }
34 jdn = Some(Dt::ymd_to_jdn(year, m, d));
35 } else if let Some(doy) = self.day_of_year {
36 if doy == 0 || doy > 366 || (doy == 366 && !Dt::is_leap_year(year)) {
38 return Err(an_err!(DtErrKind::OutOfRange, "day of year"));
39 }
40 jdn = Some(Dt::ydoy_to_jdn(year, doy));
41 }
42 }
43
44 if jdn.is_none() {
45 if let (Some(iso_y), Some(iso_w)) = (self.iso_week_year, self.iso_week) {
46 if iso_w == 0 || iso_w > 53 {
48 return Err(an_err!(DtErrKind::OutOfRange, "iso week"));
49 }
50 if iso_w == 53 && !Dt::has_iso_week_53(iso_y) {
51 return Err(an_err!(DtErrKind::InvalidItem, "iso week"));
52 }
53 let wd = self.weekday.unwrap_or(Weekday::Monday);
54 jdn = Some(Dt::ymd_to_jdn_from_iso_week(iso_y, iso_w, wd));
55 } else if let (Some(y), Some(w)) = (self.year, self.week_sun) {
56 if w > 53 {
58 return Err(an_err!(DtErrKind::OutOfRange, "week number"));
59 }
60 let wd = self.weekday.unwrap_or(Weekday::Sunday);
61 jdn = Some(Dt::ymd_to_jdn_from_week_sun(y, w, wd));
62 } else if let (Some(y), Some(w)) = (self.year, self.week_mon) {
63 if w > 53 {
65 return Err(an_err!(DtErrKind::OutOfRange, "week number"));
66 }
67 let wd = self.weekday.unwrap_or(Weekday::Monday);
68 jdn = Some(Dt::ymd_to_jdn_from_week_mon(y, w, wd));
69 }
70 }
71
72 let Some(jdn) = jdn else {
73 if self.year.is_none() && self.iso_week_year.is_none() {
74 return Err(an_err!(DtErrKind::Incomplete, "no year"));
75 } else {
76 return Err(an_err!(DtErrKind::InvalidInput, "could not create julian"));
77 }
78 };
79
80 let hour = match (self.hour, self.meridiem) {
84 (Some(h), Some(m)) => {
85 if !(1..=12).contains(&h) {
86 return Err(an_err!(DtErrKind::OutOfRange, "hour: {}", h));
87 }
88 match (h, m) {
89 (12, Meridiem::AM) => 0,
90 (12, Meridiem::PM) => 12,
91 (h, Meridiem::AM) => h,
92 (h, Meridiem::PM) => h + 12,
93 }
94 }
95 (Some(h), None) => h,
96 (None, _) => 0,
97 };
98
99 let minute = self.minute.unwrap_or(0) as i64;
100 let second = self.second.unwrap_or(0) as i64;
101 let days_since_j2000 = jdn.saturating_sub(JD_2000_2_451_545);
102 let seconds_from_noon_utc = (hour as i64 - 12) * 3600 + minute * 60 + second;
103 let mut sec_utc: i64 = days_since_j2000
104 .saturating_mul(SEC_PER_DAYI64)
105 .saturating_add(seconds_from_noon_utc);
106
107 if let Some(name) = &self.iana_name {
112 let name_str = name.as_str().map_err(|e| {
113 an_err!(
114 DtErrKind::InvalidBytes,
115 "invalid iana ascii: {:?}: {}",
116 name,
117 e
118 )
119 })?;
120
121 if !name_str.is_empty() {
122 let provisional_unix = sec_utc.saturating_add(TAI_SECS_1970_MIDNIGHT_TO_2000_NOON);
123 match offset_info_at_local(name_str, provisional_unix) {
124 Some(info) => {
125 if info.is_gap {
126 sec_utc = sec_utc.saturating_add(info.gap_size); sec_utc = sec_utc.saturating_sub(info.offset as i64); } else {
130 sec_utc = sec_utc.saturating_sub(info.offset as i64);
131 }
132 }
133 None => {
134 return Err(an_err!(
135 DtErrKind::InvalidTimezoneOffset,
136 "invalid iana: {}",
137 name_str
138 ));
139 }
140 }
141 }
142 } else if let Some(Offset::Fixed(offset)) = self.offset {
143 sec_utc = sec_utc.saturating_sub(offset as i64);
145 }
146 Ok(Dt::from(sec_utc, self.attos.unwrap_or(0), Scale::UTC))
147 }
148}