1use super::{
2 constants::{
3 MONTH_ABBREVIATED, MONTH_NARROW, MONTH_WIDE, NANOS_PER_SEC, SECS_PER_DAY, SECS_PER_HOUR,
4 SECS_PER_MINUTE, WDAY_ABBREVIATED, WDAY_NARROW, WDAY_SHORT, WDAY_WIDE,
5 },
6 date::convert::{days_to_date, days_to_doy, days_to_wday, days_to_wyear},
7 time::convert::nanos_to_time,
8};
9
10pub(crate) fn format_part(chars: &str, days: i32, nanoseconds: u64, offset: i32) -> String {
13 let first_char = chars.chars().next().unwrap();
15 match first_char {
16 'G' | 'y' | 'q' | 'M' | 'w' | 'd' | 'D' | 'e' => format_date_part(chars, days),
17 'a' | 'b' | 'h' | 'H' | 'K' | 'k' | 'm' | 's' | 'n' | 'X' | 'x' => {
18 format_time_part(chars, nanoseconds, offset)
19 }
20 _ => chars.to_string(),
21 }
22}
23
24pub(crate) fn format_date_part(chars: &str, days: i32) -> String {
27 let first_char = chars.chars().next().unwrap();
29 match first_char {
30 'G' => match chars.len() {
31 1..=3 => {
32 if days.is_negative() {
33 "BC".to_string()
34 } else {
35 "AD".to_string()
36 }
37 }
38 5 => {
39 if days.is_negative() {
40 "B".to_string()
41 } else {
42 "A".to_string()
43 }
44 }
45 _ => {
46 if days.is_negative() {
47 "Before Christ".to_string()
48 } else {
49 "Anno Domini".to_string()
50 }
51 }
52 },
53 'y' => match chars.len() {
54 2 => {
55 let mut year = days_to_date(days).0;
56 let year_string = year.to_string();
57
58 if year_string.len() > 2 {
59 let last_two = &year_string[year_string.len() - 2..];
60 year = last_two.parse::<i32>().unwrap();
62 }
63 zero_padded_i(year, 2)
64 }
65 _ => zero_padded_i(days_to_date(days).0, chars.len()),
66 },
67 'q' => {
68 let quarter = (days_to_date(days).1 - 1) / 3 + 1;
69 match chars.len() {
70 1 | 2 => zero_padded(quarter, chars.len()),
71 3 => format!("Q{}", quarter),
72 4 => {
73 let ordinal = add_ordinal_indicator(quarter);
74 format!("{} quarter", ordinal)
75 }
76 _ => zero_padded(quarter, 1),
77 }
78 }
79 'M' => format_month(chars.len(), days),
80 'w' => zero_padded(days_to_wyear(days), get_length(chars.len(), 2, 2)),
81 'd' => zero_padded(days_to_date(days).2, get_length(chars.len(), 2, 2)),
82 'D' => zero_padded(days_to_doy(days), get_length(chars.len(), 1, 3)),
83 'e' => format_wday(chars.len(), days),
84 _ => chars.to_string(),
85 }
86}
87
88pub(crate) fn format_time_part(chars: &str, nanoseconds: u64, offset: i32) -> String {
91 let first_char = chars.chars().next().unwrap();
93 match first_char {
94 'a' => format_period(nanoseconds, get_length(chars.len(), 3, 5), false),
95 'b' => format_period(nanoseconds, get_length(chars.len(), 3, 5), true),
96 'h' => {
97 let hour = if nanos_to_time(nanoseconds).0 % 12 == 0 {
98 12
99 } else {
100 nanos_to_time(nanoseconds).0 % 12
101 };
102 zero_padded(hour, get_length(chars.len(), 2, 2))
103 }
104 'H' => zero_padded(nanos_to_time(nanoseconds).0, get_length(chars.len(), 2, 2)),
105 'K' => zero_padded(
106 nanos_to_time(nanoseconds).0 % 12,
107 get_length(chars.len(), 2, 2),
108 ),
109 'k' => {
110 let hour = if nanos_to_time(nanoseconds).0 == 0 {
111 24
112 } else {
113 nanos_to_time(nanoseconds).0
114 };
115 zero_padded(hour, get_length(chars.len(), 2, 2))
116 }
117 'm' => zero_padded(nanos_to_time(nanoseconds).1, get_length(chars.len(), 2, 2)),
118 's' => zero_padded(nanos_to_time(nanoseconds).2, get_length(chars.len(), 2, 2)),
119 'n' => {
120 let mut length = get_length(chars.len(), 3, 5);
121 if length == 4 {
122 length = 6;
123 } else if length == 5 {
124 length = 9;
125 }
126
127 let subsec_nanos = (nanoseconds % NANOS_PER_SEC) as u32;
128
129 zero_padded(subsec_nanos / 10_u32.pow(9 - length as u32), length)
130 }
131 'X' => format_zone(chars.len(), offset, true),
132 'x' => format_zone(chars.len(), offset, false),
133 _ => chars.to_string(),
134 }
135}
136
137fn format_month(length: usize, days: i32) -> String {
139 let month = days_to_date(days).1;
140
141 match length {
142 1 | 2 => zero_padded(month, length),
143 3 => MONTH_ABBREVIATED
144 .into_iter()
145 .nth((month - 1) as usize)
146 .unwrap()
147 .to_string(),
148 5 => MONTH_NARROW
149 .into_iter()
150 .nth((month - 1) as usize)
151 .unwrap()
152 .to_string(),
153 _ => MONTH_WIDE
154 .into_iter()
155 .nth((month - 1) as usize)
156 .unwrap()
157 .to_string(),
158 }
159}
160
161fn format_wday(length: usize, days: i32) -> String {
163 match length {
164 1 | 2 => zero_padded(days_to_wday(days, false) + 1, length),
165 3 => WDAY_ABBREVIATED
166 .into_iter()
167 .nth(days_to_wday(days, false) as usize)
168 .unwrap()
169 .to_string(),
170 4 => WDAY_WIDE
171 .into_iter()
172 .nth(days_to_wday(days, false) as usize)
173 .unwrap()
174 .to_string(),
175 5 => WDAY_NARROW
176 .into_iter()
177 .nth(days_to_wday(days, false) as usize)
178 .unwrap()
179 .to_string(),
180 6 => WDAY_SHORT
181 .into_iter()
182 .nth(days_to_wday(days, false) as usize)
183 .unwrap()
184 .to_string(),
185 7 => zero_padded(days_to_wday(days, true) + 1, 1),
186 8 => zero_padded(days_to_wday(days, true) + 1, 2),
187 _ => zero_padded(days_to_wday(days, false) + 1, 1),
188 }
189}
190
191fn format_period(nanos: u64, length: usize, seperate_12: bool) -> String {
193 const FORMATS: [[&str; 4]; 5] = [
194 ["AM", "PM", "noon", "midnight"],
195 ["AM", "PM", "noon", "midnight"],
196 ["am", "pm", "noon", "midnight"],
197 ["a.m.", "p.m.", "noon", "midnight"],
198 ["a", "p", "n", "mi"],
199 ];
200 let time = (nanos / NANOS_PER_SEC) as u32 % SECS_PER_DAY;
201
202 match time {
203 time if seperate_12 && time == 0 => {
204 FORMATS.into_iter().nth(length - 1).unwrap()[3].to_string()
205 }
206 time if seperate_12 && time == 43200 => {
207 FORMATS.into_iter().nth(length - 1).unwrap()[2].to_string()
208 }
209 time if time < 43200 => FORMATS.into_iter().nth(length - 1).unwrap()[0].to_string(),
210 _ => FORMATS.into_iter().nth(length - 1).unwrap()[1].to_string(),
211 }
212}
213
214fn format_zone(length: usize, offset: i32, with_z: bool) -> String {
216 if with_z && offset == 0 {
217 return "Z".to_string();
218 }
219
220 let hour = offset.unsigned_abs() / SECS_PER_HOUR;
221 let minute = offset.unsigned_abs() % SECS_PER_HOUR / SECS_PER_MINUTE;
222 let second = offset.unsigned_abs() % SECS_PER_HOUR % SECS_PER_MINUTE;
223 let prefix = if offset.is_negative() { "-" } else { "+" };
224
225 match length {
226 1 => {
227 format!(
228 "{}{}{}",
229 prefix,
230 zero_padded(hour, 2),
231 if minute != 0 {
232 zero_padded(minute, 2)
233 } else {
234 "".to_string()
235 }
236 )
237 }
238 2 => {
239 format!(
240 "{}{}{}",
241 prefix,
242 zero_padded(hour, 2),
243 zero_padded(minute, 2)
244 )
245 }
246 4 => {
247 format!(
248 "{}{}{}{}",
249 prefix,
250 zero_padded(hour, 2),
251 zero_padded(minute, 2),
252 if second != 0 {
253 zero_padded(second, 2)
254 } else {
255 "".to_string()
256 }
257 )
258 }
259 5 => {
260 format!(
261 "{}{}:{}{}",
262 prefix,
263 zero_padded(hour, 2),
264 zero_padded(minute, 2),
265 if second != 0 {
266 format!(":{}", zero_padded(second, 2))
267 } else {
268 "".to_string()
269 }
270 )
271 }
272 _ => {
273 format!(
274 "{}{}:{}",
275 prefix,
276 zero_padded(hour, 2),
277 zero_padded(minute, 2)
278 )
279 }
280 }
281}
282
283pub(crate) fn zero_padded_i(number: i32, length: usize) -> String {
285 format!(
286 "{}{}",
287 if number.is_negative() { "-" } else { "" },
288 zero_padded(number.unsigned_abs(), length)
289 )
290}
291
292pub(crate) fn zero_padded(number: u32, length: usize) -> String {
294 format!("{:0width$}", number, width = length)
295}
296
297pub(crate) fn get_length(length: usize, default: usize, max: usize) -> usize {
299 if length > max {
300 default
301 } else {
302 length
303 }
304}
305
306pub(crate) fn add_ordinal_indicator(number: u32) -> String {
308 match number {
309 number if (number - 1) % 10 == 0 && number != 11 => format!("{}st", number),
310 number if (number - 2) % 10 == 0 && number != 12 => format!("{}nd", number),
311 number if (number - 3) % 10 == 0 && number != 13 => format!("{}rd", number),
312 _ => format!("{}th", number),
313 }
314}