1use crate::{AsciiStr, Dt, DtErr, GregorianTime, STRFTIME_SIZE, Scale, tzdb::offset_info_at_utc};
2
3#[cfg(feature = "alloc")]
4use crate::ATTOS_PER_SEC;
5
6#[cfg(feature = "alloc")]
7impl Dt {
8 pub fn to_iso_duration(&self) -> alloc::string::String {
14 if self.is_zero() {
15 return alloc::string::String::from("PT0S");
16 }
17
18 let total = self.to_attos();
19 let negative = total < 0;
20 let mut attos = total.unsigned_abs();
21
22 let mut s = alloc::string::String::with_capacity(48);
23 if negative {
24 s.push('-');
25 }
26 s.push_str("PT");
27
28 const A_PER_S: u128 = ATTOS_PER_SEC as u128;
29 const A_PER_M: u128 = A_PER_S * 60;
30 const A_PER_H: u128 = A_PER_M * 60;
31
32 let hours = attos / A_PER_H;
33 attos %= A_PER_H;
34 let minutes = attos / A_PER_M;
35 attos %= A_PER_M;
36 let seconds = attos / A_PER_S;
37 let frac_attos = attos % A_PER_S;
38
39 if hours > 0 {
40 s.push_str(&alloc::format!("{}", hours));
41 s.push('H');
42 }
43 if minutes > 0 {
44 s.push_str(&alloc::format!("{}", minutes));
45 s.push('M');
46 }
47
48 if seconds > 0 || frac_attos > 0 {
49 s.push_str(&alloc::format!("{}", seconds));
50
51 if frac_attos != 0 {
52 let frac_str = alloc::format!("{frac_attos:018}");
53 let trimmed = frac_str.trim_end_matches('0');
54 s.push('.');
55 s.push_str(trimmed);
56 }
57
58 s.push('S');
59 }
60
61 s
62 }
63
64 #[inline]
70 pub fn to_str(&self, current: Scale, fmt: &str) -> Result<alloc::string::String, DtErr> {
71 self.to_str_with_offset(current, fmt, 0)
72 }
73
74 #[inline]
82 pub fn to_str_with_offset(
83 &self,
84 current: Scale,
85 fmt: &str,
86 secs: i32,
87 ) -> Result<alloc::string::String, DtErr> {
88 let mut buf = [0u8; STRFTIME_SIZE];
89 let n = self.to_u8_with_offset(current, fmt, &mut buf, secs)?;
90 Ok(alloc::string::String::from_utf8_lossy(&buf[0..n]).into_owned())
91 }
92
93 #[inline]
105 pub fn to_str_with_tz(
106 &self,
107 current: Scale,
108 fmt: &str,
109 tz_name: &str,
110 ) -> Result<alloc::string::String, DtErr> {
111 let mut buf = [0u8; STRFTIME_SIZE];
112 let n = self.to_u8_with_tz(current, fmt, &mut buf, tz_name)?;
113 Ok(alloc::string::String::from_utf8_lossy(&buf[0..n]).into_owned())
114 }
115}
116
117impl Dt {
118 pub fn to_str_bin(&self, current: Scale, fmt: &str) -> Result<AsciiStr<STRFTIME_SIZE>, DtErr> {
130 let mut gt = self.to_gregorian_time(current);
131 gt.set_offset(Some(0)).set_tz_abbrev(None);
132 let mut buf = [0u8; STRFTIME_SIZE];
133 let mut pos = 0usize;
134 gt.format_to_buffer(fmt.as_bytes(), &mut buf, &mut pos)?;
135 Ok(AsciiStr::from_filled_buffer(buf))
136 }
137
138 pub fn to_str_bin_with_offset(
145 &self,
146 current: Scale,
147 fmt: &str,
148 secs: i32,
149 ) -> Result<AsciiStr<STRFTIME_SIZE>, DtErr> {
150 let gt = self.gregorian_time_with_offset(current, secs);
151 let mut buf = [0u8; STRFTIME_SIZE];
152 let mut pos = 0usize;
153 gt.format_to_buffer(fmt.as_bytes(), &mut buf, &mut pos)?;
154 Ok(AsciiStr::from_filled_buffer(buf))
155 }
156
157 pub fn to_str_bin_with_tz(
170 &self,
171 current: Scale,
172 fmt: &str,
173 tz_name: &str,
174 ) -> Result<AsciiStr<STRFTIME_SIZE>, DtErr> {
175 let gt = self.gregorian_time_with_tz(current, tz_name);
176 let mut buf = [0u8; STRFTIME_SIZE];
177 let mut pos = 0usize;
178 gt.format_to_buffer(fmt.as_bytes(), &mut buf, &mut pos)?;
179 Ok(AsciiStr::from_filled_buffer(buf))
180 }
181
182 pub fn to_u8_with_offset(
188 &self,
189 current: Scale,
190 fmt: &str,
191 dest: &mut [u8],
192 secs: i32,
193 ) -> Result<usize, DtErr> {
194 let gt = self.gregorian_time_with_offset(current, secs);
195 let mut internal_buf = [0u8; STRFTIME_SIZE];
196 let mut pos = 0usize;
197 gt.format_to_buffer(fmt.as_bytes(), &mut internal_buf, &mut pos)?;
198 let written = pos.min(dest.len());
199 if written > 0 {
200 dest[0..written].copy_from_slice(&internal_buf[0..written]);
201 }
202 Ok(written)
203 }
204
205 pub fn to_u8_with_tz(
211 &self,
212 current: Scale,
213 fmt: &str,
214 dest: &mut [u8],
215 tz_name: &str,
216 ) -> Result<usize, DtErr> {
217 let gt = self.gregorian_time_with_tz(current, tz_name);
218 let mut internal_buf = [0u8; STRFTIME_SIZE];
219 let mut pos = 0usize;
220 gt.format_to_buffer(fmt.as_bytes(), &mut internal_buf, &mut pos)?;
221 let written = pos.min(dest.len());
222 if written > 0 {
223 dest[0..written].copy_from_slice(&internal_buf[0..written]);
224 }
225 Ok(written)
226 }
227
228 #[inline]
230 pub const fn sec_as_hhmm(seconds: i32) -> (bool, u8, u8) {
231 let total = seconds.saturating_abs();
232 let hours = (total / 3600) as u8;
233 let minutes = ((total % 3600) / 60) as u8;
234 (seconds < 0, hours, minutes)
235 }
236
237 pub(crate) fn gregorian_time_with_offset(&self, current: Scale, secs: i32) -> GregorianTime {
239 let local_tp = if secs != 0 {
240 *self + Dt::new(secs as i64, 0)
241 } else {
242 *self
243 };
244 let mut gt = local_tp.to_gregorian_time(current);
245 gt.set_offset(Some(secs));
246 gt
247 }
248
249 pub(crate) fn gregorian_time_with_tz(&self, current: Scale, tz_name: &str) -> GregorianTime {
255 let utc_unix = self
257 .to(current, current.to_ut())
258 .to_diff_raw(Dt::UNIX_EPOCH);
259
260 let (offset_secs, abbrev) = match offset_info_at_utc(tz_name, utc_unix.sec) {
262 Some(info) => (info.offset, info.abbrev),
263 None => (0, "UTC"), };
265
266 let span = Dt::new(offset_secs as i64, 0);
268 let local_tp = *self + span;
269
270 let mut gt = local_tp.to_gregorian_time(current);
271 gt.set_offset(Some(offset_secs));
272 gt.set_tz(Some(tz_name));
273 gt.set_tz_abbrev(Some(abbrev));
274 gt
275 }
276}