deep_time/time_parts/
to_deep_time.rs1use crate::leap_seconds::leap_sec;
2use crate::tz::offset_for_local;
3use crate::{
4 Dt, JD_2000_2_451_545, SEC_PER_DAYI64, 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 return Ok(Dt::from_sec_and_attos(
21 total_sec,
22 self.attos.unwrap_or(0),
23 self.scale,
24 ));
25 }
26
27 let mut jd: Option<i64> = None;
31
32 if let Some(year) = self.yr {
33 if let (Some(m), Some(d)) = (self.mo, self.day) {
34 if !Dt::is_valid_ymd(year, m, d) {
36 return Err(an_err!(DtErrKind::InvalidInput, "ymd"));
37 }
38 jd = Some(Dt::ymd_to_jd(year, m, d));
39 } else if let Some(doy) = self.day_of_yr {
40 if doy == 0 || doy > 366 || (doy == 366 && !Dt::is_leap_yr(year)) {
42 return Err(an_err!(DtErrKind::OutOfRange, "day of year"));
43 }
44 jd = Some(Dt::ydoy_to_jd(year, doy));
45 }
46 }
47
48 if jd.is_none() {
49 if let (Some(iso_y), Some(iso_w)) = (self.iso_wk_yr, self.iso_wk) {
50 if iso_w == 0 || iso_w > 53 {
52 return Err(an_err!(DtErrKind::OutOfRange, "iso week"));
53 }
54 if iso_w == 53 && !Dt::has_iso_wk_53(iso_y) {
55 return Err(an_err!(DtErrKind::InvalidItem, "iso week"));
56 }
57 let wd = self.wkday.unwrap_or(Weekday::Monday);
58 jd = Some(Dt::iso_wk_to_jd(iso_y, iso_w, wd));
59 } else if let (Some(y), Some(w)) = (self.yr, self.wk_sun) {
60 if w > 53 {
62 return Err(an_err!(DtErrKind::OutOfRange, "week number"));
63 }
64 let wd = self.wkday.unwrap_or(Weekday::Sunday);
65 jd = Some(Dt::wk_sun_to_jd(y, w, wd));
66 } else if let (Some(y), Some(w)) = (self.yr, self.wk_mon) {
67 if w > 53 {
69 return Err(an_err!(DtErrKind::OutOfRange, "week number"));
70 }
71 let wd = self.wkday.unwrap_or(Weekday::Monday);
72 jd = Some(Dt::wk_mon_to_jd(y, w, wd));
73 }
74 }
75
76 let Some(jd) = jd else {
77 if self.yr.is_none() && self.iso_wk_yr.is_none() {
78 return Err(an_err!(DtErrKind::Incomplete, "no year"));
79 } else {
80 return Err(an_err!(DtErrKind::InvalidInput, "could not create julian"));
81 }
82 };
83
84 let hour = match (self.hr, self.meridiem) {
88 (Some(h), Some(m)) => {
89 if !(1..=12).contains(&h) {
90 return Err(an_err!(DtErrKind::OutOfRange, "hour: {}", h));
91 }
92 match (h, m) {
93 (12, Meridiem::AM) => 0,
94 (12, Meridiem::PM) => 12,
95 (h, Meridiem::AM) => h,
96 (h, Meridiem::PM) => h + 12,
97 }
98 }
99 (Some(h), None) => h,
100 (None, _) => 0,
101 };
102
103 let minute = self.min.unwrap_or(0) as i64;
104 let mut second = self.sec.unwrap_or(0) as i64;
105 let sec_is_60 = second == 60;
106 if sec_is_60 {
107 second = second.saturating_sub(1)
108 }
109
110 let days_since_j2000 = jd.saturating_sub(JD_2000_2_451_545);
111 let seconds_from_noon_utc = (hour as i64 - 12) * 3600 + minute * 60 + second;
112 let mut total_sec: i64 = days_since_j2000
113 .saturating_mul(SEC_PER_DAYI64)
114 .saturating_add(seconds_from_noon_utc);
115
116 if let Some(name) = &self.iana_name {
120 let name_str = name.as_str().map_err(|e| {
121 an_err!(
122 DtErrKind::InvalidBytes,
123 "invalid iana ascii: {:?}: {}",
124 name,
125 e
126 )
127 })?;
128
129 if !name_str.is_empty() {
130 let provisional_unix =
131 total_sec.saturating_add(TAI_SECS_1970_MIDNIGHT_TO_2000_NOON);
132 match offset_for_local(name_str, provisional_unix) {
133 Some(info) => {
134 if info.is_gap {
135 total_sec = total_sec.saturating_add(info.gap_size);
137 total_sec = total_sec.saturating_sub(info.offset as i64);
138 } else {
139 total_sec = total_sec.saturating_sub(info.offset as i64);
140 }
141 }
142 None => {
143 return Err(an_err!(
144 DtErrKind::InvalidTimezoneOffset,
145 "invalid iana: {}",
146 name_str
147 ));
148 }
149 }
150 }
151 } else if let Some(Offset::Fixed(offset)) = self.offset {
152 total_sec = total_sec.saturating_sub(offset as i64);
154 }
155
156 if !sec_is_60 {
160 Ok(Dt::from_sec_and_attos(
161 total_sec,
162 self.attos.unwrap_or(0),
163 self.scale,
164 ))
165 } else {
166 if self.scale.uses_leap_seconds() {
167 let t = Dt::from_sec_and_attos(total_sec, self.attos.unwrap_or(0), self.scale);
168 let is_leap_sec = match leap_sec(total_sec.saturating_add(1), true) {
169 Some(info) => info.is_leap_sec,
170 None => false,
171 };
172 if is_leap_sec { Ok(t.add_sec(1)) } else { Ok(t) }
173 } else {
174 Ok(Dt::from_sec_and_attos(
175 total_sec,
176 self.attos.unwrap_or(0),
177 self.scale,
178 ))
179 }
180 }
181 }
182}