deep_time/time_parts/
to_deep_time.rs1use crate::leap_seconds::leap_sec;
2use crate::tzdb::offset_info_at_local;
3use crate::{
4 Dt, JD_2000_2_451_545, SEC_PER_DAYI64, Scale, TAI_SECS_1970_MIDNIGHT_TO_2000_NOON, an_err,
5 error::{DtErr, DtErrKind},
6 {Meridiem, Offset, TimeParts, Weekday},
7};
8
9impl TimeParts {
10 pub fn to_dt(&self) -> Result<Dt, DtErr> {
15 if let Some(unix_secs) = self.unix_timestamp_seconds {
19 let total_sec = unix_secs.saturating_sub(TAI_SECS_1970_MIDNIGHT_TO_2000_NOON);
20 if self.scale == Scale::UTC {
21 return Ok(Dt::from_sec_and_attos(
22 total_sec + leap_sec(total_sec, true).offset,
23 self.attos.unwrap_or(0),
24 Scale::TAI,
25 )
26 .target(Scale::UTC));
27 } else {
28 return Ok(Dt::from_sec_and_attos(
29 total_sec,
30 self.attos.unwrap_or(0),
31 self.scale,
32 ));
33 }
34 }
35
36 let mut jd: Option<i64> = None;
40
41 if let Some(year) = self.yr {
42 if let (Some(m), Some(d)) = (self.mo, self.day) {
43 if !Dt::is_valid_ymd(year, m, d) {
45 return Err(an_err!(DtErrKind::InvalidInput, "ymd"));
46 }
47 jd = Some(Dt::ymd_to_jd(year, m, d));
48 } else if let Some(doy) = self.day_of_yr {
49 if doy == 0 || doy > 366 || (doy == 366 && !Dt::is_leap_yr(year)) {
51 return Err(an_err!(DtErrKind::OutOfRange, "day of year"));
52 }
53 jd = Some(Dt::ydoy_to_jd(year, doy));
54 }
55 }
56
57 if jd.is_none() {
58 if let (Some(iso_y), Some(iso_w)) = (self.iso_wk_yr, self.iso_wk) {
59 if iso_w == 0 || iso_w > 53 {
61 return Err(an_err!(DtErrKind::OutOfRange, "iso week"));
62 }
63 if iso_w == 53 && !Dt::has_iso_wk_53(iso_y) {
64 return Err(an_err!(DtErrKind::InvalidItem, "iso week"));
65 }
66 let wd = self.wkday.unwrap_or(Weekday::Monday);
67 jd = Some(Dt::ymd_to_jd_from_iso_wk(iso_y, iso_w, wd));
68 } else if let (Some(y), Some(w)) = (self.yr, self.wk_sun) {
69 if w > 53 {
71 return Err(an_err!(DtErrKind::OutOfRange, "week number"));
72 }
73 let wd = self.wkday.unwrap_or(Weekday::Sunday);
74 jd = Some(Dt::ymd_to_jd_from_wk_sun(y, w, wd));
75 } else if let (Some(y), Some(w)) = (self.yr, self.wk_mon) {
76 if w > 53 {
78 return Err(an_err!(DtErrKind::OutOfRange, "week number"));
79 }
80 let wd = self.wkday.unwrap_or(Weekday::Monday);
81 jd = Some(Dt::ymd_to_jd_from_wk_mon(y, w, wd));
82 }
83 }
84
85 let Some(jd) = jd else {
86 if self.yr.is_none() && self.iso_wk_yr.is_none() {
87 return Err(an_err!(DtErrKind::Incomplete, "no year"));
88 } else {
89 return Err(an_err!(DtErrKind::InvalidInput, "could not create julian"));
90 }
91 };
92
93 let hour = match (self.hr, self.meridiem) {
97 (Some(h), Some(m)) => {
98 if !(1..=12).contains(&h) {
99 return Err(an_err!(DtErrKind::OutOfRange, "hour: {}", h));
100 }
101 match (h, m) {
102 (12, Meridiem::AM) => 0,
103 (12, Meridiem::PM) => 12,
104 (h, Meridiem::AM) => h,
105 (h, Meridiem::PM) => h + 12,
106 }
107 }
108 (Some(h), None) => h,
109 (None, _) => 0,
110 };
111
112 let minute = self.min.unwrap_or(0) as i64;
113 let second = self.sec.unwrap_or(0) as i64;
114 let days_since_j2000 = jd.saturating_sub(JD_2000_2_451_545);
115 let seconds_from_noon_utc = (hour as i64 - 12) * 3600 + minute * 60 + second;
116 let mut total_sec: i64 = days_since_j2000
117 .saturating_mul(SEC_PER_DAYI64)
118 .saturating_add(seconds_from_noon_utc);
119
120 if let Some(name) = &self.iana_name {
125 let name_str = name.as_str().map_err(|e| {
126 an_err!(
127 DtErrKind::InvalidBytes,
128 "invalid iana ascii: {:?}: {}",
129 name,
130 e
131 )
132 })?;
133
134 if !name_str.is_empty() {
135 let provisional_unix =
136 total_sec.saturating_add(TAI_SECS_1970_MIDNIGHT_TO_2000_NOON);
137 match offset_info_at_local(name_str, provisional_unix) {
138 Some(info) => {
139 if info.is_gap {
140 total_sec = total_sec.saturating_add(info.gap_size);
142 total_sec = total_sec.saturating_sub(info.offset as i64);
143 } else {
144 total_sec = total_sec.saturating_sub(info.offset as i64);
145 }
146 }
147 None => {
148 return Err(an_err!(
149 DtErrKind::InvalidTimezoneOffset,
150 "invalid iana: {}",
151 name_str
152 ));
153 }
154 }
155 }
156 } else if let Some(Offset::Fixed(offset)) = self.offset {
157 total_sec = total_sec.saturating_sub(offset as i64);
159 }
160
161 let sec_is_60 = second == 60;
165
166 if self.scale.uses_leap_seconds() {
167 if !sec_is_60 {
168 Ok(Dt::from_sec_and_attos(
169 total_sec.saturating_add(leap_sec(total_sec, true).offset),
170 self.attos.unwrap_or(0),
171 Scale::TAI,
172 )
173 .target(self.scale))
174 } else {
175 let leap_info = leap_sec(total_sec, true);
176
177 let offset = if leap_info.is_leap_sec {
178 leap_info.offset - 1
179 } else {
180 total_sec = total_sec.saturating_sub(1);
181 leap_info.offset
182 };
183
184 Ok(Dt::from_sec_and_attos(
185 total_sec.saturating_add(offset),
186 self.attos.unwrap_or(0),
187 Scale::TAI,
188 )
189 .target(self.scale))
190 }
191 } else {
192 Ok(Dt::from_sec_and_attos(
193 if sec_is_60 {
194 total_sec.saturating_sub(1)
195 } else {
196 total_sec
197 },
198 self.attos.unwrap_or(0),
199 self.scale,
200 ))
201 }
202 }
203}