deep_time/time_parts/
to_deep_time.rs1use crate::leap_seconds::leap_sec;
2use crate::{
3 Dt, JD_2000_2_451_545, SEC_PER_DAYI64, 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 total_sec = unix_secs.saturating_sub(TAI_SECS_1970_MIDNIGHT_TO_2000_NOON);
19 return Ok(Dt::from_sec_and_attos(total_sec, self.attos, self.scale));
20 }
21
22 let mut jd: Option<i64> = None;
26
27 if let (Some(year), Some(m), Some(d)) = (self.yr, self.mo, self.day) {
29 if !Dt::is_valid_ymd(year, m, d) {
30 return Err(an_err!(DtErrKind::InvalidInput, "ymd"));
31 }
32 jd = Some(Dt::ymd_to_jd(year, m, d));
33 }
34 else if let (Some(year), Some(doy)) = (self.yr, self.day_of_yr) {
36 if doy == 0 || doy > 366 || (doy == 366 && !Dt::is_leap_yr(year)) {
37 return Err(an_err!(DtErrKind::OutOfRange, "day of year"));
38 }
39 jd = Some(Dt::ydoy_to_jd(year, doy));
40 }
41 else if let (Some(iso_y), Some(iso_w)) = (self.iso_wk_yr, self.iso_wk) {
43 if iso_w == 0 || iso_w > 53 {
44 return Err(an_err!(DtErrKind::OutOfRange, "iso week"));
45 }
46 if iso_w == 53 && !Dt::has_iso_wk_53(iso_y) {
47 return Err(an_err!(DtErrKind::InvalidItem, "iso week"));
48 }
49 let wd = self.wkday.unwrap_or(Weekday::Monday);
50 jd = Some(Dt::iso_wk_to_jd(iso_y, iso_w, wd));
51 }
52 else if let (Some(y), Some(w)) = (self.yr, self.wk_sun) {
54 if w > 53 {
55 return Err(an_err!(DtErrKind::OutOfRange, "week number"));
56 }
57 let wd = self.wkday.unwrap_or(Weekday::Sunday);
58 jd = Some(Dt::wk_sun_to_jd(y, w, wd));
59 }
60 else if let (Some(y), Some(w)) = (self.yr, self.wk_mon) {
62 if w > 53 {
63 return Err(an_err!(DtErrKind::OutOfRange, "week number"));
64 }
65 let wd = self.wkday.unwrap_or(Weekday::Monday);
66 jd = Some(Dt::wk_mon_to_jd(y, w, wd));
67 }
68
69 let Some(jd) = jd else {
70 if self.yr.is_none() && self.iso_wk_yr.is_none() {
71 return Err(an_err!(DtErrKind::Incomplete, "no year"));
72 } else {
73 return Err(an_err!(
74 DtErrKind::InvalidInput,
75 "could not create julian date"
76 ));
77 }
78 };
79
80 let hour = match self.meridiem {
84 None => self.hr,
85 Some(m) => {
86 if !(1..=12).contains(&self.hr) {
87 return Err(an_err!(DtErrKind::OutOfRange, "hour: {}", self.hr));
88 }
89 match (self.hr, m) {
90 (12, Meridiem::AM) => 0,
91 (12, Meridiem::PM) => 12,
92 (h, Meridiem::AM) => h,
93 (h, Meridiem::PM) => h + 12,
94 }
95 }
96 };
97
98 let minute = self.min as i64;
99 let mut second = self.sec as i64;
100 let sec_is_60 = second == 60;
101 if sec_is_60 {
102 second = second.saturating_sub(1)
103 }
104
105 let days_since_j2000 = jd.saturating_sub(JD_2000_2_451_545);
106 let seconds_from_noon_utc = (hour as i64 - 12) * 3600 + minute * 60 + second;
107 let mut total_sec: i64 = days_since_j2000
108 .saturating_mul(SEC_PER_DAYI64)
109 .saturating_add(seconds_from_noon_utc);
110
111 if let Some(name) = &self.iana_name {
115 let name_str = name.as_str();
116
117 if !name_str.is_empty() {
118 #[cfg(feature = "jiff-tz")]
119 {
120 use jiff::{Timestamp, tz::TimeZone};
121
122 let tz = TimeZone::get(name_str).map_err(|e| {
123 an_err!(
124 DtErrKind::InvalidTimezoneOffset,
125 "invalid tz {:?}: {}",
126 name,
127 e
128 )
129 })?;
130
131 let provisional_unix =
132 total_sec.saturating_add(TAI_SECS_1970_MIDNIGHT_TO_2000_NOON);
133
134 let civil = Timestamp::from_second(provisional_unix)
135 .map_err(|e| {
136 an_err!(
137 DtErrKind::InvalidNumber,
138 "invalid unix {:?}: {}",
139 provisional_unix,
140 e
141 )
142 })?
143 .to_zoned(jiff::tz::TimeZone::UTC)
144 .datetime();
145
146 let zoned = tz.to_zoned(civil).map_err(|e| {
147 an_err!(
148 DtErrKind::OutOfRange,
149 "jiff to_zoned failed for {}: {}",
150 name_str,
151 e
152 )
153 })?;
154
155 total_sec = zoned
156 .timestamp()
157 .as_second()
158 .saturating_sub(TAI_SECS_1970_MIDNIGHT_TO_2000_NOON);
159 }
160 #[cfg(not(feature = "jiff-tz"))]
161 {
162 use crate::tz::UTC_ALIASES;
163
164 if !UTC_ALIASES.contains(&name_str) {
165 return Err(an_err!(
166 DtErrKind::InvalidBytes,
167 "non-utc tz: {} requires jiff-tz feature",
168 name_str,
169 ));
170 }
171 }
172 }
173 } else if let Some(Offset::Fixed(offset)) = self.offset {
174 total_sec = total_sec.saturating_sub(offset as i64);
176 }
177
178 if !sec_is_60 {
182 Ok(Dt::from_sec_and_attos(total_sec, self.attos, self.scale))
183 } else {
184 if self.scale.uses_leap_seconds() {
185 let t = Dt::from_sec_and_attos(total_sec, self.attos, self.scale);
186 let is_leap_sec = match leap_sec(total_sec.saturating_add(1), true) {
187 Some(info) => info.is_leap_sec,
188 None => false,
189 };
190 if is_leap_sec { Ok(t.add_sec(1)) } else { Ok(t) }
191 } else {
192 Ok(Dt::from_sec_and_attos(total_sec, self.attos, self.scale))
193 }
194 }
195 }
196}