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 #[inline]
10 pub fn to_str(&self, current: Scale, fmt: &str) -> Result<alloc::string::String, DtErr> {
11 self.to_str_with_offset(current, fmt, 0)
12 }
13
14 #[inline]
16 pub fn to_str_with_offset(
17 &self,
18 current: Scale,
19 fmt: &str,
20 secs: i32,
21 ) -> Result<alloc::string::String, DtErr> {
22 let mut buf = [0u8; STRFTIME_SIZE];
23 let n = self.to_u8_with_offset(current, fmt, &mut buf, secs)?;
24 Ok(alloc::string::String::from_utf8_lossy(&buf[0..n]).into_owned())
25 }
26
27 #[inline]
29 pub fn to_str_with_tz(
30 &self,
31 current: Scale,
32 fmt: &str,
33 tz_name: &str,
34 ) -> Result<alloc::string::String, DtErr> {
35 let mut buf = [0u8; STRFTIME_SIZE];
36 let n = self.to_u8_with_tz(current, fmt, &mut buf, tz_name)?;
37 Ok(alloc::string::String::from_utf8_lossy(&buf[0..n]).into_owned())
38 }
39
40 pub fn to_iso_duration(&self) -> alloc::string::String {
46 if self.is_zero() {
47 return alloc::string::String::from("PT0S");
48 }
49
50 let total = self.to_attos();
51 let negative = total < 0;
52 let mut attos = total.unsigned_abs();
53
54 let mut s = alloc::string::String::with_capacity(48);
55 if negative {
56 s.push('-');
57 }
58 s.push_str("PT");
59
60 const A_PER_S: u128 = ATTOS_PER_SEC as u128;
61 const A_PER_M: u128 = A_PER_S * 60;
62 const A_PER_H: u128 = A_PER_M * 60;
63
64 let hours = attos / A_PER_H;
65 attos %= A_PER_H;
66 let minutes = attos / A_PER_M;
67 attos %= A_PER_M;
68 let seconds = attos / A_PER_S;
69 let frac_attos = attos % A_PER_S;
70
71 if hours > 0 {
72 s.push_str(&alloc::format!("{}", hours));
73 s.push('H');
74 }
75 if minutes > 0 {
76 s.push_str(&alloc::format!("{}", minutes));
77 s.push('M');
78 }
79
80 if seconds > 0 || frac_attos > 0 {
81 s.push_str(&alloc::format!("{}", seconds));
82
83 if frac_attos != 0 {
84 let frac_str = alloc::format!("{frac_attos:018}");
85 let trimmed = frac_str.trim_end_matches('0');
86 s.push('.');
87 s.push_str(trimmed);
88 }
89
90 s.push('S');
91 }
92
93 s
94 }
95}
96
97impl Dt {
98 pub fn to_str_bin(&self, current: Scale, fmt: &str) -> Result<AsciiStr<STRFTIME_SIZE>, DtErr> {
100 let mut gt = self.to_gregorian_time(current);
101 gt.set_offset(Some(0)).set_tz_abbrev(None);
102 let mut buf = [0u8; STRFTIME_SIZE];
103 let mut pos = 0usize;
104 gt.format_to_buffer(fmt.as_bytes(), &mut buf, &mut pos)?;
105 Ok(AsciiStr::from_filled_buffer(buf))
106 }
107
108 pub fn to_str_bin_with_offset(
110 &self,
111 current: Scale,
112 fmt: &str,
113 secs: i32,
114 ) -> Result<AsciiStr<STRFTIME_SIZE>, DtErr> {
115 let gt = self.gregorian_time_with_offset(current, secs);
116 let mut buf = [0u8; STRFTIME_SIZE];
117 let mut pos = 0usize;
118 gt.format_to_buffer(fmt.as_bytes(), &mut buf, &mut pos)?;
119 Ok(AsciiStr::from_filled_buffer(buf))
120 }
121
122 pub fn to_str_bin_with_tz(
124 &self,
125 current: Scale,
126 fmt: &str,
127 tz_name: &str,
128 ) -> Result<AsciiStr<STRFTIME_SIZE>, DtErr> {
129 let gt = self.gregorian_time_with_tz(current, tz_name);
130 let mut buf = [0u8; STRFTIME_SIZE];
131 let mut pos = 0usize;
132 gt.format_to_buffer(fmt.as_bytes(), &mut buf, &mut pos)?;
133 Ok(AsciiStr::from_filled_buffer(buf))
134 }
135
136 #[inline]
138 pub const fn sec_as_hhmm(seconds: i32) -> (bool, u8, u8) {
139 let total = seconds.saturating_abs();
140 let hours = (total / 3600) as u8;
141 let minutes = ((total % 3600) / 60) as u8;
142 (seconds < 0, hours, minutes)
143 }
144
145 pub fn to_u8_with_offset(
147 &self,
148 current: Scale,
149 fmt: &str,
150 dest: &mut [u8],
151 secs: i32,
152 ) -> Result<usize, DtErr> {
153 let gt = self.gregorian_time_with_offset(current, secs);
154 let mut internal_buf = [0u8; STRFTIME_SIZE];
155 let mut pos = 0usize;
156 gt.format_to_buffer(fmt.as_bytes(), &mut internal_buf, &mut pos)?;
157 let written = pos.min(dest.len());
158 if written > 0 {
159 dest[0..written].copy_from_slice(&internal_buf[0..written]);
160 }
161 Ok(written)
162 }
163
164 pub fn to_u8_with_tz(
166 &self,
167 current: Scale,
168 fmt: &str,
169 dest: &mut [u8],
170 tz_name: &str,
171 ) -> Result<usize, DtErr> {
172 let gt = self.gregorian_time_with_tz(current, tz_name);
173 let mut internal_buf = [0u8; STRFTIME_SIZE];
174 let mut pos = 0usize;
175 gt.format_to_buffer(fmt.as_bytes(), &mut internal_buf, &mut pos)?;
176 let written = pos.min(dest.len());
177 if written > 0 {
178 dest[0..written].copy_from_slice(&internal_buf[0..written]);
179 }
180 Ok(written)
181 }
182
183 pub(crate) fn gregorian_time_with_offset(&self, current: Scale, secs: i32) -> GregorianTime {
185 let local_tp = if secs != 0 {
186 *self + Dt::new(secs as i64, 0)
187 } else {
188 *self
189 };
190 let mut gt = local_tp.to_gregorian_time(current);
191 gt.set_offset(Some(secs));
192 gt
193 }
194
195 pub(crate) fn gregorian_time_with_tz(&self, current: Scale, tz_name: &str) -> GregorianTime {
201 let utc_unix = self
203 .to(current, current.to_ut())
204 .to_diff_raw(Dt::UNIX_EPOCH);
205
206 let (offset_secs, abbrev) = match offset_info_at_utc(tz_name, utc_unix.sec) {
208 Some(info) => (info.offset, info.abbrev),
209 None => (0, "UTC"), };
211
212 let span = Dt::new(offset_secs as i64, 0);
214 let local_tp = *self + span;
215
216 let mut gt = local_tp.to_gregorian_time(current);
217 gt.set_offset(Some(offset_secs));
218 gt.set_tz(Some(tz_name));
219 gt.set_tz_abbrev(Some(abbrev));
220 gt
221 }
222}