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 second = self.sec.unwrap_or(0) as i64;
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 {
116 let name_str = name.as_str().map_err(|e| {
117 an_err!(
118 DtErrKind::InvalidBytes,
119 "invalid iana ascii: {:?}: {}",
120 name,
121 e
122 )
123 })?;
124
125 if !name_str.is_empty() {
126 let provisional_unix =
127 total_sec.saturating_add(TAI_SECS_1970_MIDNIGHT_TO_2000_NOON);
128 match offset_for_local(name_str, provisional_unix) {
129 Some(info) => {
130 if info.is_gap {
131 total_sec = total_sec.saturating_add(info.gap_size);
133 total_sec = total_sec.saturating_sub(info.offset as i64);
134 } else {
135 total_sec = total_sec.saturating_sub(info.offset as i64);
136 }
137 }
138 None => {
139 return Err(an_err!(
140 DtErrKind::InvalidTimezoneOffset,
141 "invalid iana: {}",
142 name_str
143 ));
144 }
145 }
146 }
147 } else if let Some(Offset::Fixed(offset)) = self.offset {
148 total_sec = total_sec.saturating_sub(offset as i64);
150 }
151
152 if second == 60 {
156 if self.scale.uses_leap_seconds() {
157 let t = Dt::from_sec_and_attos(
158 total_sec.saturating_sub(1),
159 self.attos.unwrap_or(0),
160 self.scale,
161 );
162 let is_leap_sec = match leap_sec(total_sec, true) {
163 Some(info) => info.is_leap_sec,
164 None => false,
165 };
166 if is_leap_sec { Ok(t.add_sec(1)) } else { Ok(t) }
167 } else {
168 Ok(Dt::from_sec_and_attos(
169 total_sec.saturating_sub(1),
170 self.attos.unwrap_or(0),
171 self.scale,
172 ))
173 }
174 } else {
175 Ok(Dt::from_sec_and_attos(
176 total_sec,
177 self.attos.unwrap_or(0),
178 self.scale,
179 ))
180 }
181 }
182}