1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
//! Code adapted from Chrono StrftimeItems but for sqlite strftime compatibility
//! Sqlite reference https://www.sqlite.org/lang_datefunc.html
use chrono::format::{Fixed, Item, Numeric, Pad};
const fn num(numeric: Numeric) -> Item<'static> {
Item::Numeric(numeric, Pad::None)
}
const fn num0(numeric: Numeric) -> Item<'static> {
Item::Numeric(numeric, Pad::Zero)
}
const fn nums(numeric: Numeric) -> Item<'static> {
Item::Numeric(numeric, Pad::Space)
}
const fn fixed(fixed: Fixed) -> Item<'static> {
Item::Fixed(fixed)
}
#[derive(Clone, Debug)]
pub struct CustomStrftimeItems<'a> {
// Remaining portion of the string.
remainder: &'a str,
/// If the current specifier is composed of multiple formatting items (e.g. `%+`),
/// `queue` stores a slice of `Item`s that have to be returned one by one.
queue: &'static [Item<'static>],
}
impl<'a> CustomStrftimeItems<'a> {
pub const fn new(s: &'a str) -> CustomStrftimeItems<'a> {
CustomStrftimeItems {
remainder: s,
queue: &[],
}
}
}
// const HAVE_ALTERNATES: &str = "z";
impl<'a> Iterator for CustomStrftimeItems<'a> {
type Item = Item<'a>;
fn next(&mut self) -> Option<Item<'a>> {
// We have items queued to return from a specifier composed of multiple formatting items.
if let Some((item, remainder)) = self.queue.split_first() {
self.queue = remainder;
return Some(item.clone());
}
// Normal: we are parsing the formatting string.
let (remainder, item) = self.parse_next_item(self.remainder)?;
self.remainder = remainder;
Some(item)
}
}
impl<'a> CustomStrftimeItems<'a> {
fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> {
// use InternalInternal::*;
use Item::{Literal, Space};
use Numeric::*;
match remainder.chars().next() {
// we are done
None => None,
// the next item is a specifier
Some('%') => {
remainder = &remainder[1..];
macro_rules! next {
() => {
match remainder.chars().next() {
Some(x) => {
remainder = &remainder[x.len_utf8()..];
x
}
None => return Some((remainder, Item::Error)), // premature end of string
}
};
}
let spec = next!();
let pad_override = match spec {
'-' => Some(Pad::None),
'0' => Some(Pad::Zero),
'_' => Some(Pad::Space),
_ => None,
};
// let is_alternate = spec == '#';
// let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
// if is_alternate && !HAVE_ALTERNATES.contains(spec) {
// return Some((remainder, Item::Error));
// }
macro_rules! queue {
[$head:expr, $($tail:expr),+ $(,)*] => ({
const QUEUE: &'static [Item<'static>] = &[$($tail),+];
self.queue = QUEUE;
$head
})
}
// macro_rules! queue_from_slice {
// ($slice:expr) => {{
// self.queue = &$slice[1..];
// $slice[0].clone()
// }};
// }
let item = match spec {
// day of month: 01-31
'd' => num0(Day),
// day of month without leading zero: 1-31
'e' => nums(Day),
// fractional seconds: SS.SSS
'f' => {
queue![num0(Second), fixed(Fixed::Nanosecond3)]
}
// ISO 8601 date: YYYY-MM-DD
'F' => queue![
num0(Year),
Literal("-"),
num0(Month),
Literal("-"),
num0(Day)
],
// ISO 8601 year corresponding to %V
'G' => num0(IsoYear),
// 2-digit ISO 8601 year corresponding to %V
'g' => num0(IsoYearMod100),
// hour: 00-24
'H' => num0(Hour),
// hour for 12-hour clock: 01-12
'I' => num0(Hour12),
// day of year: 001-366
'j' => num0(Ordinal),
// hour without leading zero: 0-24
'k' => nums(Hour),
// %I without leading zero: 1-12
'l' => nums(Hour12),
// month: 01-12
'm' => num0(Month),
// minute: 00-59
'M' => num0(Minute),
// "AM" or "PM" depending on the hour
'p' => fixed(Fixed::UpperAmPm),
// "am" or "pm" depending on the hour
'P' => fixed(Fixed::LowerAmPm),
// ISO 8601 time: HH:MM
'R' => queue![num0(Hour), Literal(":"), num0(Minute)],
// seconds since 1970-01-01
's' => num(Timestamp),
// seconds: 00-59
'S' => num0(Second),
// ISO 8601 time: HH:MM:SS
'T' => {
queue![
num0(Hour),
Literal(":"),
num0(Minute),
Literal(":"),
num0(Second)
]
}
// week of year (00-53) - week 01 starts on the first Sunday
'U' => num0(WeekFromSun),
// day of week 1-7 with Monday==1
'u' => num(WeekdayFromMon),
// ISO 8601 week of year
'V' => num0(IsoWeek),
// day of week 0-6 with Sunday==0
'w' => num(NumDaysFromSun),
// week of year (00-53) - week 01 starts on the first Monday
'W' => num0(WeekFromMon),
// year: 0000-9999
'Y' => num0(Year),
// %
'%' => Literal("%"),
// TODO instead of doing a preprocessing of the %J specifier, it could be done post as postprocessing
// step by just emitting the formatter again to the string
// 'J' => Literal("%J"),
_ => Item::Error, // no such specifier
};
// Adjust `item` if we have any padding modifier.
// Not allowed on non-numeric items or on specifiers composed out of multiple
// formatting items.
if let Some(new_pad) = pad_override {
match item {
Item::Numeric(ref kind, _pad) if self.queue.is_empty() => {
Some((remainder, Item::Numeric(kind.clone(), new_pad)))
}
_ => Some((remainder, Item::Error)),
}
} else {
Some((remainder, item))
}
}
// the next item is space
Some(c) if c.is_whitespace() => {
// `%` is not a whitespace, so `c != '%'` is redundant
let nextspec = remainder
.find(|c: char| !c.is_whitespace())
.unwrap_or(remainder.len());
assert!(nextspec > 0);
let item = Space(&remainder[..nextspec]);
remainder = &remainder[nextspec..];
Some((remainder, item))
}
// the next item is literal
_ => {
let nextspec = remainder
.find(|c: char| c.is_whitespace() || c == '%')
.unwrap_or(remainder.len());
assert!(nextspec > 0);
let item = Literal(&remainder[..nextspec]);
remainder = &remainder[nextspec..];
Some((remainder, item))
}
}
}
}