• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jtmoon79 / super-speedy-syslog-searcher / 28303449102

27 Jun 2026 10:16PM UTC coverage: 68.549% (+0.02%) from 68.533%
28303449102

push

github

jtmoon79
(TOOLS) NFC s4-easy-install.sh arg comment

16362 of 23869 relevant lines covered (68.55%)

182833.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.35
/src/data/datetime.rs
1
// src/data/datetime.rs
2

3
// ‥
4
// …
5

6
//! Functions to perform regular expression ("regex") searches on bytes and
7
//! transform matches to chrono [`DateTime`] instances.
8
//!
9
//! Parsing bytes and finding datetime strings requires:
10
//! 1. searching some slice of bytes from a [`Line`] for a regular expression match.
11
//! 2. using a [`DateTimeParseInstr`], attempting to transform the matched regular expression named
12
//!    capture groups into data passable to chrono [`DateTime::parse_from_str`] or
13
//!    [`NaiveDateTime::parse_from_str`].
14
//! 3. return chrono `DateTime` instances along with byte offsets of the found matches to a caller
15
//!    (who will presumably use it create a new [`Sysline`]).
16
//!
17
//! The most relevant documents to understand this file are:
18
//! - `chrono` crate [`strftime`] format.
19
//! - `regex` crate [Regular Expression syntax].
20
//!
21
//! The most relevant functions are:
22
//! 1. [`bytes_to_regex_to_datetime`] which calls private function:
23
//! 2. [`captures_to_buffer_bytes`]
24
//!
25
//! Uses regular expressions defined in [`DATETIME_PARSE_DATAS`].
26
//!
27
//! [`DATETIME_PARSE_DATAS`]: crate::data::datetime::DATETIME_PARSE_DATAS
28
//! [`Line`]: crate::data::line::Line
29
//! [`Sysline`]: crate::data::sysline::Sysline
30
//! [`DateTime`]: https://docs.rs/chrono/0.4.38/chrono/struct.DateTime.html
31
//! [`DateTime::parse_from_str`]: https://docs.rs/chrono/0.4.38/chrono/struct.DateTime.html#method.parse_from_str
32
//! [`NaiveDateTime::parse_from_str`]: https://docs.rs/chrono/0.4.38/chrono/naive/struct.NaiveDateTime.html#method.parse_from_str
33
//! [`strftime`]: https://docs.rs/chrono/0.4.38/chrono/format/strftime/index.html
34
//! [Regular Expression syntax]: https://docs.rs/regex/1.10.5/regex/index.html#syntax
35

36
#![allow(non_camel_case_types)]
37
#![allow(non_upper_case_globals)]
38

39
use std::time::Duration as StdDuration;
40
#[doc(hidden)]
41
pub use std::time::{
42
    SystemTime,
43
    UNIX_EPOCH,
44
};
45

46
#[doc(hidden)]
47
pub use ::chrono::{
48
    DateTime,
49
    Datelike, // adds method `.year()` onto `DateTime`
50
    Duration,
51
    FixedOffset,
52
    Local,
53
    LocalResult,
54
    NaiveDateTime,
55
    NaiveTime,
56
    Offset,
57
    TimeZone,
58
    Utc,
59
};
60
use ::more_asserts::{
61
    debug_assert_ge,
62
    debug_assert_le,
63
    debug_assert_lt,
64
};
65
use ::numtoa::NumToA; // adds `numtoa` method to numbers
66
use ::phf::{
67
    phf_map,
68
    Map as PhfMap,
69
};
70
#[allow(unused_imports)]
71
use ::si_trace_print::{
72
    defn,
73
    defo,
74
    defx,
75
    defñ,
76
    den,
77
    deo,
78
    dex,
79
    deñ,
80
};
81

82
pub use ::ere_datetimes_impl::{
83
    CaptureGroupName,
84
    CaptureGroupPattern,
85
    CGI_YEAR,
86
    CGI_MONTH,
87
    CGI_DAY,
88
    CGI_DAY_IGNORE,
89
    CGI_HOUR,
90
    CGI_MINUTE,
91
    CGI_SECOND,
92
    CGI_FRACTIONAL,
93
    CGI_TZ,
94
    CGI_UPTIME,
95
    CGI_EPOCH,
96
    CGN_ALL,
97
    DateTimeParseInstr,
98
    DateTimePattern_str,
99
    DateTimePattern_string,
100
    DATETIME_PARSE_DATAS,
101
    DATETIME_PARSE_DATAS_LEN,
102
    DATETIME_PARSE_DATAS_LEN_MAX,
103
    GROUP_NAMES_MAP_STR,
104
    MatchType,
105
    MatchesType,
106
    fos,
107
    REGEX_ALL_COMPILED,
108
    RegexPattern,
109
    DTFSSet,
110
    DTFS_Year,
111
    DTFS_Month,
112
    DTFS_Day,
113
    DTFS_Hour,
114
    DTFS_Minute,
115
    DTFS_Second,
116
    DTFS_Fractional,
117
    DTFS_Tz,
118
    DTFS_Epoch,
119
    DTFS_Uptime,
120
    MINUS_SIGN,
121
    HYPHEN_MINUS,
122
    regex_id_compiled,
123
    O_L,
124
    YEAR_FALLBACKDUMMY_VAL,
125
    YEAR_FALLBACKDUMMY,
126
    Uptime,
127
};
128

129
/// A _Year_ in a date
130
pub type Year = i32;
131

132
#[cfg(any(debug_assertions, test))]
133
use crate::common::FPath;
134
#[doc(hidden)]
135
pub use crate::data::line::{
136
    LineIndex,
137
    RangeLineIndex,
138
};
139
#[cfg(any(debug_assertions, test))]
140
use crate::debug::printers::{
141
    buffer_to_string_noraw,
142
    str_to_string_noraw,
143
};
144
use crate::{
145
    de_err,
146
    de_wrn,
147
    debug_panic,
148
};
149

150
// -----------------------------------------------
151
// DateTime Regex Matching and strftime formatting
152

153
/// A chrono [`DateTime`] type used in _s4lib_.
154
///
155
/// [`DateTime`]: https://docs.rs/chrono/0.4.38/chrono/struct.DateTime.html
156
// TODO: rename to `DateTimeS4`
157
pub type DateTimeL = DateTime<FixedOffset>;
158
pub type DateTimeLOpt = Option<DateTimeL>;
159

160
pub(crate) const UPTIME_DEFAULT_OFFSET: SystemTime = UNIX_EPOCH;
161

162
/// Convert a `T` to a [`SystemTime`].
163
///
164
/// [`SystemTime`]: std::time::SystemTime
165
pub fn convert_to_systemtime<T>(epoch_seconds: T) -> SystemTime
×
166
where
×
167
    u64: From<T>,
×
168
{
169
    UNIX_EPOCH + std::time::Duration::from_secs(epoch_seconds.into())
×
170
}
×
171

172
/// create a `DateTime`
173
///
174
/// wrapper for chrono DateTime creation function
175
#[allow(unused)]
176
pub fn ymdhms(
16✔
177
    fixedoffset: &FixedOffset,
16✔
178
    year: i32,
16✔
179
    month: u32,
16✔
180
    day: u32,
16✔
181
    hour: u32,
16✔
182
    min: u32,
16✔
183
    sec: u32,
16✔
184
) -> DateTimeL {
16✔
185
    fixedoffset.with_ymd_and_hms(
16✔
186
        year,
16✔
187
        month,
16✔
188
        day,
16✔
189
        hour,
16✔
190
        min,
16✔
191
        sec,
16✔
192
    ).unwrap()
16✔
193
}
16✔
194

195
/// create a `DateTime` with FixedOffset `+00:00`
196
///
197
/// wrapper for chrono DateTime creation function
198
#[allow(unused)]
199
pub fn ymdhms0(
2✔
200
    year: i32,
2✔
201
    month: u32,
2✔
202
    day: u32,
2✔
203
    hour: u32,
2✔
204
    min: u32,
2✔
205
    sec: u32,
2✔
206
) -> DateTimeL {
2✔
207
    let fixedoffset = FixedOffset::east_opt(0).unwrap();
2✔
208
    fixedoffset.with_ymd_and_hms(
2✔
209
        year,
2✔
210
        month,
2✔
211
        day,
2✔
212
        hour,
2✔
213
        min,
2✔
214
        sec,
2✔
215
    ).unwrap()
2✔
216
}
2✔
217

218
/// create a `DateTime` with milliseconds
219
///
220
/// wrapper for chrono DateTime creation function
221
#[allow(clippy::too_many_arguments)]
222
pub fn ymdhmsl(
8✔
223
    fixedoffset: &FixedOffset,
8✔
224
    year: i32,
8✔
225
    month: u32,
8✔
226
    day: u32,
8✔
227
    hour: u32,
8✔
228
    min: u32,
8✔
229
    sec: u32,
8✔
230
    milli: i64,
8✔
231
) -> DateTimeL {
8✔
232
    fixedoffset.with_ymd_and_hms(
8✔
233
        year,
8✔
234
        month,
8✔
235
        day,
8✔
236
        hour,
8✔
237
        min,
8✔
238
        sec,
8✔
239
    )
8✔
240
    .unwrap()
8✔
241
    + Duration::try_milliseconds(milli).unwrap()
8✔
242
}
8✔
243

244
/// create a `DateTime` with microseconds
245
///
246
/// wrapper for chrono DateTime creation function
247
#[allow(clippy::too_many_arguments)]
248
pub fn ymdhmsm(
126✔
249
    fixedoffset: &FixedOffset,
126✔
250
    year: i32,
126✔
251
    month: u32,
126✔
252
    day: u32,
126✔
253
    hour: u32,
126✔
254
    min: u32,
126✔
255
    sec: u32,
126✔
256
    micro: i64,
126✔
257
) -> DateTimeL {
126✔
258
    fixedoffset.with_ymd_and_hms(
126✔
259
        year,
126✔
260
        month,
126✔
261
        day,
126✔
262
        hour,
126✔
263
        min,
126✔
264
        sec,
126✔
265
    )
126✔
266
    .unwrap()
126✔
267
    + Duration::microseconds(micro)
126✔
268
}
126✔
269

270
/// create a `DateTime` with nanoseconds
271
///
272
/// wrapper for chrono DateTime creation function
273
#[allow(unused)]
274
#[allow(clippy::too_many_arguments)]
275
pub fn ymdhmsn(
216✔
276
    fixedoffset: &FixedOffset,
216✔
277
    year: i32,
216✔
278
    month: u32,
216✔
279
    day: u32,
216✔
280
    hour: u32,
216✔
281
    min: u32,
216✔
282
    sec: u32,
216✔
283
    nano: i64,
216✔
284
) -> DateTimeL {
216✔
285
    fixedoffset
216✔
286
    .with_ymd_and_hms(
216✔
287
        year,
216✔
288
        month,
216✔
289
        day,
216✔
290
        hour,
216✔
291
        min,
216✔
292
        sec,
216✔
293
    )
216✔
294
    .unwrap()
216✔
295
    + Duration::nanoseconds(nano)
216✔
296
}
216✔
297

298
/// All named timezone abbreviations, maps all chrono strftime `%Z` values
299
/// (e.g. `"EDT"`) to equivalent `%:z` value (e.g. `"-04:00"`).
300
/// Crate `chrono` does not parse named timezone. This mapping bridges that gap.
301
///
302
/// _Super Speedy Syslog Searcher_ attempts to be more lenient than chrono
303
/// about matching named abbreviated timezones, e.g. `"EDT"`.
304
/// Chrono provides `%Z` strftime specifier
305
/// yet rejects named timezones when passed to [`DateTime::parse_from_str`].
306
/// `MAP_TZZ_TO_TZz` provides the necessary mapping.
307
///
308
/// However, due to duplicate timezone names, some valid timezone names
309
/// will result in the default timezone. For example, there are three named
310
/// timezones `"IST"` that refer to different timezone offsets. If `"IST"` is
311
/// parsed as a timezone in a sysline then the resultant value will be the
312
/// default timezone offset value, e.g. the value passed to `--tz-offset`.
313
/// See the opening paragraph in [_List of time zone abbreviations_].
314
///
315
/// In this structure, ambiguous timezone names have their values set to empty
316
/// string, e.g. `"SST"` maps to `""`. See [Issue #59].
317
///
318
/// The listing of timezone abbreviations and values can be scraped from
319
/// Wikipedia with this code snippet:
320
///
321
/// ```text
322
/// $ curl "https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations" \
323
///     | grep -Ee '^<td>[[:upper:]]{2,4}</td>' \
324
///     | grep -oEe '[[:upper:]]{2,4}' \
325
///     | sort \
326
///     | uniq \
327
///     | sed -Ee ':a;N;$!ba;s/\n/|/g'
328
///
329
/// $ curl "https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations" \
330
///     | rg -or '$1 $2' -e '^<td>([[:upper:]]{2,5})</td>' -e '^<td data-sort-value.*>UTC(.*)</a>' \
331
///     | sed -e '/^$/d' \
332
///     | rg -r '("$1", ' -e '^([[:upper:]]{2,5})' -C5 \
333
///     | rg -r '"$1"), ' -e '^[[:blank:]]*([[:print:]−±+]*[0-9]{1,4}.*$)' -C5 \
334
///     | rg -r '"$1:00"' -e '"(.?[[:digit:]][[:digit:]])"' -C5 \
335
///     | sed -e 's/\n"/"/g' -e 'N;s/\n/ /' -e 's/−/-/g' -e 's/±/-/g' \
336
///     | tr -s ' '
337
/// ```
338
///
339
/// See also:
340
/// - Applicable tz offsets <https://en.wikipedia.org/wiki/List_of_UTC_offsets>
341
/// - Applicable tz abbreviations <https://en.wikipedia.org/wiki/List_of_time_zone_abbreviations>
342
///
343
/// [Issue #59]: https://github.com/jtmoon79/super-speedy-syslog-searcher/issues/59
344
/// [_List of time zone abbreviations_]: https://en.wikipedia.org/w/index.php?title=List_of_time_zone_abbreviations&oldid=1106679802
345
/// [`DateTime::parse_from_str`]: https://docs.rs/chrono/0.4.38/chrono/format/strftime/#fn7
346
// TODO: why not map directly to a `FixedOffset` to skip having chrono do another
347
//       step of translating from `str` to `FixedOffset`?
348
pub static MAP_TZZ_TO_TZz: PhfMap<&'static str, &'static str> = phf_map! {
349
        // uppercase
350
        "ACDT" => "+10:30",
351
        "ACST" => "+09:30",
352
        "ACT" => "",
353
        //"ACT" => "-05:00",
354
        //"ACT" => "+08:00",
355
        "ACWST" => "+08:45",
356
        "ADT" => "-03:00",
357
        "AEDT" => "+11:00",
358
        "AEST" => "+10:00",
359
        "AET" => "+11:00",
360
        "AFT" => "+04:30",
361
        "AKDT" => "-08:00",
362
        "AKST" => "-09:00",
363
        "ALMT" => "+06:00",
364
        "AMST" => "-03:00",
365
        "AMT" => "",
366
        //"AMT" => "-04:00",
367
        //"AMT" => "+04:00",
368
        "ANAT" => "+12:00",
369
        "AQTT" => "+05:00",
370
        "ART" => "-03:00",
371
        "AST" => "",
372
        //"AST" => "+03:00",
373
        //"AST" => "-04:00",
374
        "AWST" => "+08:00",
375
        "AZOST" => "+00:00",
376
        "AZOT" => "-01:00",
377
        "AZT" => "+04:00",
378
        "BNT" => "+08:00",
379
        "BIOT" => "+06:00",
380
        "BIT" => "-12:00",
381
        "BOT" => "-04:00",
382
        "BRST" => "-02:00",
383
        "BRT" => "-03:00",
384
        "BST" => "",
385
        //"BST" => "+06:00",
386
        //"BST" => "+11:00",
387
        //"BST" => "+01:00",
388
        "BTT" => "+06:00",
389
        "CAT" => "+02:00",
390
        "CCT" => "+06:30",
391
        "CDT" => "",
392
        //"CDT" => "-05:00",
393
        //"CDT" => "-04:00",
394
        "CEST" => "+02:00",
395
        "CET" => "+01:00",
396
        "CHADT" => "+13:45",
397
        "CHAST" => "+12:45",
398
        "CHOT" => "+08:00",
399
        "CHOST" => "+09:00",
400
        "CHST" => "+10:00",
401
        "CHUT" => "+10:00",
402
        "CIST" => "-08:00",
403
        "CKT" => "-10:00",
404
        "CLST" => "-03:00",
405
        "CLT" => "-04:00",
406
        "COST" => "-04:00",
407
        "COT" => "-05:00",
408
        "CST" => "",
409
        //"CST" => "-06:00",
410
        //"CST" => "+08:00",
411
        //"CST" => "-05:00",
412
        "CT" => "-05:00",
413
        "CVT" => "-01:00",
414
        "CWST" => "+08:45",
415
        "CXT" => "+07:00",
416
        "DAVT" => "+07:00",
417
        "DDUT" => "+10:00",
418
        "DFT" => "+01:00",
419
        "EASST" => "-05:00",
420
        "EAST" => "-06:00",
421
        "EAT" => "+03:00",
422
        "ECT" => "",
423
        //"ECT" => "-04:00",
424
        //"ECT" => "-05:00",
425
        "EDT" => "-04:00",
426
        "EEST" => "+03:00",
427
        "EET" => "+02:00",
428
        "EGST" => "-00:00",
429
        "EGT" => "-01:00",
430
        "EST" => "-05:00",
431
        "ET" => "-04:00",
432
        "FET" => "+03:00",
433
        "FJT" => "+12:00",
434
        "FKST" => "-03:00",
435
        "FKT" => "-04:00",
436
        "FNT" => "-02:00",
437
        "GALT" => "-06:00",
438
        "GAMT" => "-09:00",
439
        "GET" => "+04:00",
440
        "GFT" => "-03:00",
441
        "GILT" => "+12:00",
442
        "GIT" => "-09:00",
443
        "GMT" => "-00:00",
444
        "GST" => "",
445
        //"GST" => "-02:00",
446
        //"GST" => "+04:00",
447
        "GYT" => "-04:00",
448
        "HDT" => "-09:00",
449
        "HAEC" => "+02:00",
450
        "HST" => "-10:00",
451
        "HKT" => "+08:00",
452
        "HMT" => "+05:00",
453
        "HOVST" => "+08:00",
454
        "HOVT" => "+07:00",
455
        "ICT" => "+07:00",
456
        "IDLW" => "-12:00",
457
        "IDT" => "+03:00",
458
        "IOT" => "+03:00",
459
        "IRDT" => "+04:30",
460
        "IRKT" => "+08:00",
461
        "IRST" => "+03:30",
462
        "IST" => "",
463
        //"IST" => "+05:30",
464
        //"IST" => "+01:00",
465
        //"IST" => "+02:00",
466
        "JST" => "+09:00",
467
        "KALT" => "+02:00",
468
        "KGT" => "+06:00",
469
        "KOST" => "+11:00",
470
        "KRAT" => "+07:00",
471
        "KST" => "+09:00",
472
        "LHST" => "",
473
        //"LHST" => "+10:30",
474
        //"LHST" => "+11:00",
475
        "LINT" => "+14:00",
476
        "MAGT" => "+12:00",
477
        "MART" => "-09:30",
478
        "MAWT" => "+05:00",
479
        "MDT" => "-06:00",
480
        "MET" => "+01:00",
481
        "MEST" => "+02:00",
482
        "MHT" => "+12:00",
483
        "MIST" => "+11:00",
484
        "MIT" => "-09:30",
485
        "MMT" => "+06:30",
486
        "MSK" => "+03:00",
487
        "MST" => "",
488
        //"MST" => "+08:00",
489
        //"MST" => "-07:00",
490
        "MUT" => "+04:00",
491
        "MVT" => "+05:00",
492
        "MYT" => "+08:00",
493
        "NCT" => "+11:00",
494
        "NDT" => "-02:30",
495
        "NFT" => "+11:00",
496
        "NOVT" => "+07:00",
497
        "NPT" => "+05:45",
498
        "NST" => "-03:30",
499
        "NT" => "-03:30",
500
        "NUT" => "-11:00",
501
        "NZDT" => "+13:00",
502
        "NZST" => "+12:00",
503
        "OMST" => "+06:00",
504
        "ORAT" => "+05:00",
505
        "PDT" => "-07:00",
506
        "PET" => "-05:00",
507
        "PETT" => "+12:00",
508
        "PGT" => "+10:00",
509
        "PHOT" => "+13:00",
510
        "PHT" => "+08:00",
511
        "PHST" => "+08:00",
512
        "PKT" => "+05:00",
513
        "PMDT" => "-02:00",
514
        "PMST" => "-03:00",
515
        "PONT" => "+11:00",
516
        "PST" => "-08:00",
517
        "PWT" => "+09:00",
518
        "PYST" => "-03:00",
519
        "PYT" => "-04:00",
520
        "RET" => "+04:00",
521
        "ROTT" => "-03:00",
522
        "SAKT" => "+11:00",
523
        "SAMT" => "+04:00",
524
        "SAST" => "+02:00",
525
        "SBT" => "+11:00",
526
        "SCT" => "+04:00",
527
        "SDT" => "-10:00",
528
        "SGT" => "+08:00",
529
        "SLST" => "+05:30",
530
        "SRET" => "+11:00",
531
        "SRT" => "-03:00",
532
        "SST" => "",
533
        //"SST" => "-11:00",
534
        //"SST" => "+08:00",
535
        "SYOT" => "+03:00",
536
        "TAHT" => "-10:00",
537
        "THA" => "+07:00",
538
        "TFT" => "+05:00",
539
        "TJT" => "+05:00",
540
        "TKT" => "+13:00",
541
        "TLT" => "+09:00",
542
        "TMT" => "+05:00",
543
        "TRT" => "+03:00",
544
        "TOT" => "+13:00",
545
        "TVT" => "+12:00",
546
        "ULAST" => "+09:00",
547
        "ULAT" => "+08:00",
548
        "UT" => "-00:00",
549
        "UTC" => "-00:00",
550
        "UYST" => "-02:00",
551
        "UYT" => "-03:00",
552
        "UZT" => "+05:00",
553
        "VET" => "-04:00",
554
        "VLAT" => "+10:00",
555
        "VOLT" => "+03:00",
556
        "VOST" => "+06:00",
557
        "VUT" => "+11:00",
558
        "WAKT" => "+12:00",
559
        "WAST" => "+02:00",
560
        "WAT" => "+01:00",
561
        "WEST" => "+01:00",
562
        "WET" => "-00:00",
563
        "WIB" => "+07:00",
564
        "WIT" => "+09:00",
565
        "WITA" => "+08:00",
566
        "WGST" => "-02:00",
567
        "WGT" => "-03:00",
568
        "WST" => "+08:00",
569
        "YAKT" => "+09:00",
570
        "YEKT" => "+05:00",
571
        "ZULU" => "+00:00",
572
        "Z" => "+00:00",
573
        // lowercase
574
        "acdt" => "+10:30",
575
        "acst" => "+09:30",
576
        "act" => "",
577
        //"act" => "-05:00",
578
        //"act" => "+08:00",
579
        "acwst" => "+08:45",
580
        "adt" => "-03:00",
581
        "aedt" => "+11:00",
582
        "aest" => "+10:00",
583
        "aet" => "+11:00",
584
        "aft" => "+04:30",
585
        "akdt" => "-08:00",
586
        "akst" => "-09:00",
587
        "almt" => "+06:00",
588
        "amst" => "-03:00",
589
        "amt" => "",
590
        //"amt" => "-04:00",
591
        //"amt" => "+04:00",
592
        "anat" => "+12:00",
593
        "aqtt" => "+05:00",
594
        "art" => "-03:00",
595
        "ast" => "",
596
        //"ast" => "+03:00",
597
        //"ast" => "-04:00",
598
        "awst" => "+08:00",
599
        "azost" => "-00:00",
600
        "azot" => "-01:00",
601
        "azt" => "+04:00",
602
        "bnt" => "+08:00",
603
        "biot" => "+06:00",
604
        "bit" => "-12:00",
605
        "bot" => "-04:00",
606
        "brst" => "-02:00",
607
        "brt" => "-03:00",
608
        "bst" => "",
609
        //"bst" => "+06:00",
610
        //"bst" => "+11:00",
611
        //"bst" => "+01:00",
612
        "btt" => "+06:00",
613
        "cat" => "+02:00",
614
        "cct" => "+06:30",
615
        "cdt" => "",
616
        //"cdt" => "-05:00",
617
        //"cdt" => "-04:00",
618
        "cest" => "+02:00",
619
        "cet" => "+01:00",
620
        "chadt" => "+13:45",
621
        "chast" => "+12:45",
622
        "chot" => "+08:00",
623
        "chost" => "+09:00",
624
        "chst" => "+10:00",
625
        "chut" => "+10:00",
626
        "cist" => "-08:00",
627
        "ckt" => "-10:00",
628
        "clst" => "-03:00",
629
        "clt" => "-04:00",
630
        "cost" => "-04:00",
631
        "cot" => "-05:00",
632
        "cst" => "",
633
        //"cst" => "-06:00",
634
        //"cst" => "+08:00",
635
        //"cst" => "-05:00",
636
        "ct" => "-05:00",
637
        "cvt" => "-01:00",
638
        "cwst" => "+08:45",
639
        "cxt" => "+07:00",
640
        "davt" => "+07:00",
641
        "ddut" => "+10:00",
642
        "dft" => "+01:00",
643
        "easst" => "-05:00",
644
        "east" => "-06:00",
645
        "eat" => "+03:00",
646
        "ect" => "",
647
        //"ect" => "-04:00",
648
        //"ect" => "-05:00",
649
        "edt" => "-04:00",
650
        "eest" => "+03:00",
651
        "eet" => "+02:00",
652
        "egst" => "-00:00",
653
        "egt" => "-01:00",
654
        "est" => "-05:00",
655
        "et" => "-04:00",
656
        "fet" => "+03:00",
657
        "fjt" => "+12:00",
658
        "fkst" => "-03:00",
659
        "fkt" => "-04:00",
660
        "fnt" => "-02:00",
661
        "galt" => "-06:00",
662
        "gamt" => "-09:00",
663
        "get" => "+04:00",
664
        "gft" => "-03:00",
665
        "gilt" => "+12:00",
666
        "git" => "-09:00",
667
        "gmt" => "-00:00",
668
        "gst" => "",
669
        //"gst" => "-02:00",
670
        //"gst" => "+04:00",
671
        "gyt" => "-04:00",
672
        "hdt" => "-09:00",
673
        "haec" => "+02:00",
674
        "hst" => "-10:00",
675
        "hkt" => "+08:00",
676
        "hmt" => "+05:00",
677
        "hovst" => "+08:00",
678
        "hovt" => "+07:00",
679
        "ict" => "+07:00",
680
        "idlw" => "-12:00",
681
        "idt" => "+03:00",
682
        "iot" => "+03:00",
683
        "irdt" => "+04:30",
684
        "irkt" => "+08:00",
685
        "irst" => "+03:30",
686
        "ist" => "",
687
        //"ist" => "+05:30",
688
        //"ist" => "+01:00",
689
        //"ist" => "+02:00",
690
        "jst" => "+09:00",
691
        "kalt" => "+02:00",
692
        "kgt" => "+06:00",
693
        "kost" => "+11:00",
694
        "krat" => "+07:00",
695
        "kst" => "+09:00",
696
        "lhst" => "",
697
        //"lhst" => "+10:30",
698
        //"lhst" => "+11:00",
699
        "lint" => "+14:00",
700
        "magt" => "+12:00",
701
        "mart" => "-09:30",
702
        "mawt" => "+05:00",
703
        "mdt" => "-06:00",
704
        "met" => "+01:00",
705
        "mest" => "+02:00",
706
        "mht" => "+12:00",
707
        "mist" => "+11:00",
708
        "mit" => "-09:30",
709
        "mmt" => "+06:30",
710
        "msk" => "+03:00",
711
        "mst" => "",
712
        //"mst" => "+08:00",
713
        //"mst" => "-07:00",
714
        "mut" => "+04:00",
715
        "mvt" => "+05:00",
716
        "myt" => "+08:00",
717
        "nct" => "+11:00",
718
        "ndt" => "-02:30",
719
        "nft" => "+11:00",
720
        "novt" => "+07:00",
721
        "npt" => "+05:45",
722
        "nst" => "-03:30",
723
        "nt" => "-03:30",
724
        "nut" => "-11:00",
725
        "nzdt" => "+13:00",
726
        "nzst" => "+12:00",
727
        "omst" => "+06:00",
728
        "orat" => "+05:00",
729
        "pdt" => "-07:00",
730
        "pet" => "-05:00",
731
        "pett" => "+12:00",
732
        "pgt" => "+10:00",
733
        "phot" => "+13:00",
734
        "pht" => "+08:00",
735
        "phst" => "+08:00",
736
        "pkt" => "+05:00",
737
        "pmdt" => "-02:00",
738
        "pmst" => "-03:00",
739
        "pont" => "+11:00",
740
        "pst" => "-08:00",
741
        "pwt" => "+09:00",
742
        "pyst" => "-03:00",
743
        "pyt" => "-04:00",
744
        "ret" => "+04:00",
745
        "rott" => "-03:00",
746
        "sakt" => "+11:00",
747
        "samt" => "+04:00",
748
        "sast" => "+02:00",
749
        "sbt" => "+11:00",
750
        "sct" => "+04:00",
751
        "sdt" => "-10:00",
752
        "sgt" => "+08:00",
753
        "slst" => "+05:30",
754
        "sret" => "+11:00",
755
        "srt" => "-03:00",
756
        "sst" => "",
757
        //"sst" => "-11:00",
758
        //"sst" => "+08:00",
759
        "syot" => "+03:00",
760
        "taht" => "-10:00",
761
        "tha" => "+07:00",
762
        "tft" => "+05:00",
763
        "tjt" => "+05:00",
764
        "tkt" => "+13:00",
765
        "tlt" => "+09:00",
766
        "tmt" => "+05:00",
767
        "trt" => "+03:00",
768
        "tot" => "+13:00",
769
        "tvt" => "+12:00",
770
        "ulast" => "+09:00",
771
        "ulat" => "+08:00",
772
        "ut" => "-00:00",
773
        "utc" => "-00:00",
774
        "uyst" => "-02:00",
775
        "uyt" => "-03:00",
776
        "uzt" => "+05:00",
777
        "vet" => "-04:00",
778
        "vlat" => "+10:00",
779
        "volt" => "+03:00",
780
        "vost" => "+06:00",
781
        "vut" => "+11:00",
782
        "wakt" => "+12:00",
783
        "wast" => "+02:00",
784
        "wat" => "+01:00",
785
        "west" => "+01:00",
786
        "wet" => "-00:00",
787
        "wib" => "+07:00",
788
        "wit" => "+09:00",
789
        "wita" => "+08:00",
790
        "wgst" => "-02:00",
791
        "wgt" => "-03:00",
792
        "wst" => "+08:00",
793
        "yakt" => "+09:00",
794
        "yekt" => "+05:00",
795
        "zulu" => "+00:00",
796
        "z" => "+00:00",
797
};
798

799
/// Index into the global [`DATETIME_PARSE_DATAS`]
800
pub type DateTimeParseInstrsIndex = usize;
801

802
pub const DateTimeParseDatasCompiledCount: usize = 0;
803

804
// TODO: Issue #6 handle all Unicode whitespace.
805
//       This fn is essentially counteracting an errant call to
806
//       `std::string:trim` within `Local.datetime_from_str`.
807
//       `trim` removes "Unicode Derived Core Property White_Space".
808
//       This implementation handles three whitespace chars. There are
809
//       twenty-five whitespace chars according to
810
//       <https://en.wikipedia.org/wiki/Unicode_character_property#Whitespace>.
811
//
812
/// Match spaces at beginning and ending of `value`.
813
/// Return `true` if mismatch of whitespace was found between `value` and
814
/// `pattern`, e.g. `value` is `"2022-01-01T02:03:04"`
815
/// but pattern is `"    %Y-%d-%mT%H:%M:%S"`.
816
/// Else return `false`.
817
/// Workaround for chrono
818
/// [Issue #660](https://github.com/chronotope/chrono/issues/660).
819
#[allow(non_snake_case)]
820
pub fn datetime_from_str_workaround_Issue660(
79,282✔
821
    value: &str,
79,282✔
822
    pattern: &DateTimePattern_str,
79,282✔
823
) -> bool {
79,282✔
824
    const SPACES: &str = " ";
825
    const TABS: &str = "\t";
826
    const LINE_ENDS: &str = "\n\r";
827

828
    // match whitespace forwards from beginning
829
    let mut v_sc: u32 = 0; // `value` spaces count
79,282✔
830
    let mut v_tc: u32 = 0; // `value` tabs count
79,282✔
831
    let mut v_ec: u32 = 0; // `value` line ends count
79,282✔
832
    let mut v_brk: bool = false;
79,282✔
833
    for v_ in value.chars() {
79,325✔
834
        if SPACES.contains(v_) {
79,325✔
835
            v_sc += 1;
41✔
836
        } else if TABS.contains(v_) {
79,284✔
837
            v_tc += 1;
9✔
838
        } else if LINE_ENDS.contains(v_) {
79,275✔
839
            v_ec += 1;
9✔
840
        } else {
9✔
841
            v_brk = true;
79,266✔
842
            break;
79,266✔
843
        }
844
    }
845
    let mut p_sc: u32 = 0; // `pattern` space count
79,282✔
846
    let mut p_tc: u32 = 0; // `pattern` tab count
79,282✔
847
    let mut p_ec: u32 = 0; // `pattern` line ends count
79,282✔
848
    let mut p_brk: bool = false;
79,282✔
849
    for p_ in pattern.chars() {
79,328✔
850
        if SPACES.contains(p_) {
79,328✔
851
            p_sc += 1;
43✔
852
        } else if TABS.contains(p_) {
79,285✔
853
            p_tc += 1;
9✔
854
        } else if LINE_ENDS.contains(p_) {
79,276✔
855
            p_ec += 1;
9✔
856
        } else {
9✔
857
            p_brk = true;
79,267✔
858
            break;
79,267✔
859
        }
860
    }
861
    if v_sc != p_sc || v_tc != p_tc || v_ec != p_ec {
79,282✔
862
        return false;
12✔
863
    }
79,270✔
864

865
    // match whitespace backwards from ending
866
    v_sc = 0;
79,270✔
867
    v_tc = 0;
79,270✔
868
    v_ec = 0;
79,270✔
869
    if v_brk {
79,270✔
870
        for v_ in value.chars().rev() {
79,274✔
871
            if SPACES.contains(v_) {
79,274✔
872
                v_sc += 1;
15✔
873
            } else if TABS.contains(v_) {
79,259✔
874
                v_tc += 1;
×
875
            } else if LINE_ENDS.contains(v_) {
79,259✔
876
                v_ec += 1;
1✔
877
            } else {
1✔
878
                break;
79,258✔
879
            }
880
        }
881
    }
12✔
882
    p_sc = 0;
79,270✔
883
    p_tc = 0;
79,270✔
884
    p_ec = 0;
79,270✔
885
    if p_brk {
79,270✔
886
        for p_ in pattern.chars().rev() {
79,280✔
887
            if SPACES.contains(p_) {
79,280✔
888
                p_sc += 1;
16✔
889
            } else if TABS.contains(p_) {
79,264✔
890
                p_tc += 1;
3✔
891
            } else if LINE_ENDS.contains(p_) {
79,261✔
892
                p_ec += 1;
2✔
893
            } else {
2✔
894
                break;
79,259✔
895
            }
896
        }
897
    }
11✔
898
    if v_sc != p_sc || v_tc != p_tc || v_ec != p_ec {
79,270✔
899
        return false;
6✔
900
    }
79,264✔
901

902
    true
79,264✔
903
}
79,282✔
904

905
/// Decoding [\[`u8`\]] bytes to a [`str`] takes a surprisingly long amount of
906
/// time, according to script `tools/flamegraph.sh`.
907
///
908
/// First check `u8` slice with custom simplistic checker that, in case of
909
/// complications, falls back to using higher-resource and more-precise checker
910
/// [`encoding_rs::mem::utf8_latin1_up_to`].
911
///
912
/// This uses built-in unsafe [`from_utf8_unchecked`].
913
///
914
/// See `benches/bench_decode_utf.rs` for comparison of `bytes` → `str`
915
/// decode strategies.
916
///
917
/// [\[`u8`\]]: u8
918
/// [`str`]: str
919
/// [`encoding_rs::mem::utf8_latin1_up_to`]: <https://docs.rs/encoding_rs/0.8.31/encoding_rs/mem/fn.utf8_latin1_up_to.html>
920
/// [`from_utf8_unchecked`]: std::str::from_utf8_unchecked
921
#[inline(always)]
922
pub fn u8_to_str(data: &[u8]) -> Option<&str> {
250,365✔
923
    let dts: &str;
924
    let mut fallback = false;
250,365✔
925
    // custom check for UTF8; fast but imperfect
926
    if !data.is_ascii() {
250,365✔
927
        fallback = true;
4✔
928
    }
250,361✔
929
    if fallback {
250,365✔
930
        // found non-ASCII, fallback to checking with `utf8_latin1_up_to`
931
        // which is a thorough check
932
        let va = encoding_rs::mem::utf8_latin1_up_to(data);
4✔
933
        if va != data.len() {
4✔
934
            // TODO: this needs a better resolution
935
            de_wrn!("u8_to_str return None; va {} != {} data.len()", va, data.len());
4✔
936
            return None; // invalid UTF8
4✔
937
        }
×
938
    }
250,361✔
939
    unsafe {
250,361✔
940
        dts = std::str::from_utf8_unchecked(data);
250,361✔
941
    };
250,361✔
942

943
    Some(dts)
250,361✔
944
}
250,365✔
945

946
/// Convert `data` to a chrono [`Option<DateTime<FixedOffset>>`] instance.
947
///
948
/// Compensate for a missing timezone.
949
///
950
/// - `data` to parse that has a datetime string
951
/// - strftime `pattern` to use for parsing, must complement `data`
952
/// - `has_tz`, the `pattern` has a timezone (`%Z`, `%z`, etc.)?
953
/// - `tz_offset` fallback timezone offset when `!has_tz`
954
///
955
/// [`Option<DateTime<FixedOffset>>`]: https://docs.rs/chrono/0.4.38/chrono/struct.DateTime.html#impl-DateTime%3CFixedOffset%3E
956
pub fn datetime_parse_from_str(
45,889✔
957
    data: &str,
45,889✔
958
    pattern: &DateTimePattern_str,
45,889✔
959
    has_tz: bool,
45,889✔
960
    tz_offset: &FixedOffset,
45,889✔
961
) -> DateTimeLOpt {
45,889✔
962
    defn!("(pattern {:?}, has_tz {}, tz_offset {:?}, data {:?})", pattern, has_tz, tz_offset, str_to_string_noraw(data));
45,889✔
963

964
    // saved rust playground for quick testing chrono `DateTime::parse_from_str`
965
    // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e6f44c79dbb3d2c05c55ffba9bd91c76
966

967
    // if `has_tz` then create a `DateTime`.
968
    // else if `!has_tz` then create a `NaiveDateTime`, then convert that to `DateTime` with aid
969
    // of crate `chrono_tz`.
970
    if has_tz {
45,889✔
971
        match DateTime::parse_from_str(data, pattern) {
4,991✔
972
            Ok(val) => {
623✔
973
                defo!(
623✔
974
                    "DateTime::parse_from_str({:?}, {:?}) extrapolated DateTime {:?}",
975
                    str_to_string_noraw(data),
623✔
976
                    pattern,
977
                    val,
978
                );
979
                // HACK: workaround chrono Issue #660 by checking for matching begin, end of `data`
980
                //       and `dt_pattern`
981
                //       See Issue #6
982
                if !datetime_from_str_workaround_Issue660(data, pattern) {
623✔
983
                    defn!("skip match due to chrono Issue #660");
1✔
984
                    return None;
1✔
985
                }
622✔
986
                defx!("return Some({:?})", val);
622✔
987

988
                Some(val)
622✔
989
            }
990
            Err(_err) => {
4,368✔
991
                defx!("DateTime::parse_from_str({:?}, {:?}) failed ParseError: {}", data, pattern, _err);
4,368✔
992

993
                None
4,368✔
994
            }
995
        }
996
    } else {
997
        // !has_tz (no timezone in `data`)
998
        // first convert to a `NaiveDateTime` instance
999
        let dt_naive = match NaiveDateTime::parse_from_str(data, pattern) {
40,898✔
1000
            Ok(val) => {
39,312✔
1001
                defo!(
39,312✔
1002
                    "NaiveDateTime.parse_from_str({:?}, {:?}) extrapolated NaiveDateTime {:?}",
1003
                    str_to_string_noraw(data),
39,312✔
1004
                    pattern,
1005
                    val,
1006
                );
1007
                // HACK: workaround chrono Issue #660 by checking for matching begin, end of `data`
1008
                //       and `pattern`
1009
                if !datetime_from_str_workaround_Issue660(data, pattern) {
39,312✔
1010
                    defx!("skip match due to chrono Issue #660");
1✔
1011
                    return None;
1✔
1012
                }
39,311✔
1013
                defo!("dt_naive={:?}", val);
39,311✔
1014

1015
                val
39,311✔
1016
            }
1017
            Err(_err) => {
1,586✔
1018
                defx!("NaiveDateTime.parse_from_str({:?}, {:?}) failed ParseError: {}", data, pattern, _err);
1,586✔
1019
                return None;
1,586✔
1020
            }
1021
        };
1022
        // second convert the `NaiveDateTime` instance to a `DateTime<FixedOffset>` instance
1023
        match tz_offset
39,311✔
1024
            .from_local_datetime(&dt_naive)
39,311✔
1025
            .earliest()
39,311✔
1026
        {
1027
            Some(val) => {
39,311✔
1028
                defo!(
39,311✔
1029
                    "tz_offset.from_local_datetime({:?}).earliest() extrapolated NaiveDateTime {:?}",
1030
                    dt_naive,
1031
                    val,
1032
                );
1033
                // print a warning if the `earliest()` and `latest()` values are not equal
1034
                // merely for my own curiosity, since this is a rare case and I don't know how to handle it.
1035
                if cfg!(debug_assertions) || cfg!(test) {
39,311✔
1036
                    match tz_offset.from_local_datetime(&dt_naive).latest() {
39,311✔
1037
                        Some(val2) => {
39,311✔
1038
                            if val != val2 {
39,311✔
1039
                                de_wrn!("tz_offset.from_local_datetime({dt_naive:?}).earliest() {val:?} != latest() {val2:?}");
×
1040
                            }
39,311✔
1041
                        }
1042
                        None => de_wrn!("tz_offset.from_local_datetime({dt_naive:?}).latest() returned None"),
×
1043
                    }
1044
                }
×
1045
                // HACK: workaround chrono Issue #660 by checking for matching begin, end of `data`
1046
                //       and `pattern`
1047
                if !datetime_from_str_workaround_Issue660(data, pattern) {
39,311✔
1048
                    defx!("skip match due to chrono Issue #660, return None");
×
1049
                    return None;
×
1050
                }
39,311✔
1051
                defx!("return {:?}", Some(val));
39,311✔
1052

1053
                Some(val)
39,311✔
1054
            }
1055
            None => {
1056
                defx!("tz_offset.from_local_datetime({:?}, {:?}) returned None, return None", data, pattern);
×
1057
                None
×
1058
            }
1059
        }
1060
    }
1061
}
45,889✔
1062

1063
/// Call [`datetime_parse_from_str`] with a `pattern` containing a timezone.
1064
pub fn datetime_parse_from_str_w_tz(
27✔
1065
    data: &str,
27✔
1066
    pattern: &DateTimePattern_str,
27✔
1067
) -> DateTimeLOpt {
27✔
1068
    datetime_parse_from_str(
27✔
1069
        data,
27✔
1070
        pattern,
27✔
1071
        true,
1072
        &FixedOffset::east_opt(-9999).unwrap(),
27✔
1073
    )
1074
}
27✔
1075

1076
/// Data of interest from a set of [`regex::Captures`] for a datetime
1077
/// substring found in a [`Line`].
1078
///
1079
/// - datetime substring begin index
1080
/// - datetime substring end index
1081
/// - datetime
1082
///
1083
/// [`Line`]: crate::data::line::Line
1084
/// [`regex::Captures`]: https://docs.rs/regex/1.10.5/regex/bytes/struct.Captures.html
1085
// TODO: change to a typed `struct CapturedDtData(...)`
1086
pub type CapturedDtData = (LineIndex, LineIndex, DateTimeL);
1087

1088
/// Macro helper to [`captures_to_buffer_bytes`].
1089
/// - `$cgi_index` is the `GroupsIndex` index into `GROUP_NAMES` for the capture group name.
1090
/// - `$data` is the `&[u8]` data to copy from.
1091
/// - `$captures` is the `Vec<MatchType>` = `Vec<(SpanS4, GroupsIndex)>` = `Vec<({usize, usize}, usize)>` of capture groups from the regex match.
1092
/// - `$buffer` is the `&mut [u8]` buffer to copy into.
1093
/// - `$at` is the `usize` index into `buffer` to copy at and is updated.
1094
macro_rules! copy_capturegroup_to_buffer {
1095
    (
1096
        // `GroupsIndex`
1097
        $cgi_index:ident,
1098
        // `&[u8]`
1099
        $data:ident,
1100
         //  `Vec<MatchType>` = `Vec<(SpanS4, GroupsIndex)>` = `Vec<({usize, usize}, usize)>`
1101
        $captures:ident,
1102
        // `&mut [u8]`
1103
        $buffer:ident,
1104
        // `usize`
1105
        $at:ident
1106
    ) => {
1107
        {
1108
            $captures.iter().find(|cgn| cgn.group_index() == $cgi_index).map(|cgn| {
634,318✔
1109
                let a = cgn.start();
177,389✔
1110
                let b = cgn.end();
177,389✔
1111
                debug_assert_le!(a, b);
177,389✔
1112
                let len_: usize = b - a;
177,389✔
1113
                defo!("copy_capturegroup_to_buffer! buffer[{:?}‥{:?}] = {:?}", $at, $at + len_, &$data[a..b]);
177,389✔
1114
                $buffer[$at..$at + len_].copy_from_slice(&$data[a..b]);
177,389✔
1115
                $at += len_;
177,389✔
1116
            });
177,389✔
1117
        }
1118
    };
1119
}
1120

1121
/// Macro helper to [`captures_to_buffer_bytes`].
1122
macro_rules! copy_slice_to_buffer {
1123
    (
1124
        $u8_slice:expr,
1125
        $buffer:ident,
1126
        $at:ident
1127
    ) => {
1128
        {
1129
            let len_: usize = $u8_slice.len();
1130
            defo!("copy_slice_to_buffer! buffer[{:?}‥{:?}]", $at, $at + len_);
1131
            $buffer[$at..$at + len_].copy_from_slice($u8_slice);
1132
            $at += len_;
1133
        }
1134
    };
1135
}
1136

1137
/// Macro helper to [`captures_to_buffer_bytes`].
1138
macro_rules! copy_u8_to_buffer {
1139
    (
1140
        $u8_:expr,
1141
        $buffer:ident,
1142
        $at:ident
1143
    ) => {
1144
        {
1145
            defo!("copy_slice_to_buffer! buffer[{:?}] = {:?}", $at, $u8_);
1146
            $buffer[$at] = $u8_;
1147
            $at += 1;
1148
        }
1149
    };
1150
}
1151

1152
// Variables `const MONTH_` are helpers to [`month_bB_to_month_m_bytes`].
1153
//
1154
// MONTH_XY_B_l, month XY as `%B` form, lowercase
1155
// MONTH_XY_b_l, month XY as `%b` form, lowercase
1156
// MONTH_XY_B_u, month XY as `%B` form, uppercase
1157
// MONTH_XY_b_u, month XY as `%b` form, uppercase
1158
// MONTH_XY_b_U, month XY as `%b` form, uppercase all
1159

1160
const MONTH_01_b_l: &[u8] = b"jan";
1161
const MONTH_01_b_u: &[u8] = b"Jan";
1162
const MONTH_01_b_U: &[u8] = b"JAN";
1163
const MONTH_01_b_ld: &[u8] = b"jan.";
1164
const MONTH_01_b_ud: &[u8] = b"Jan.";
1165
const MONTH_01_b_Ud: &[u8] = b"JAN.";
1166
const MONTH_01_B_l: &[u8] = b"january";
1167
const MONTH_01_B_u: &[u8] = b"January";
1168
const MONTH_01_B_U: &[u8] = b"JANUARY";
1169
const MONTH_01_m: &[u8] = b"01";
1170
const MONTH_02_b_l: &[u8] = b"feb";
1171
const MONTH_02_b_u: &[u8] = b"Feb";
1172
const MONTH_02_b_U: &[u8] = b"FEB";
1173
const MONTH_02_b_ld: &[u8] = b"feb.";
1174
const MONTH_02_b_ud: &[u8] = b"Feb.";
1175
const MONTH_02_b_Ud: &[u8] = b"FEB.";
1176
const MONTH_02_B_l: &[u8] = b"february";
1177
const MONTH_02_B_u: &[u8] = b"February";
1178
const MONTH_02_B_U: &[u8] = b"FEBRUARY";
1179
const MONTH_02_m: &[u8] = b"02";
1180
const MONTH_03_b_l: &[u8] = b"mar";
1181
const MONTH_03_b_u: &[u8] = b"Mar";
1182
const MONTH_03_b_U: &[u8] = b"MAR";
1183
const MONTH_03_b_ld: &[u8] = b"mar.";
1184
const MONTH_03_b_ud: &[u8] = b"Mar.";
1185
const MONTH_03_b_Ud: &[u8] = b"MAR.";
1186
const MONTH_03_B_l: &[u8] = b"march";
1187
const MONTH_03_B_u: &[u8] = b"March";
1188
const MONTH_03_B_U: &[u8] = b"MARCH";
1189
const MONTH_03_m: &[u8] = b"03";
1190
const MONTH_04_b_l: &[u8] = b"apr";
1191
const MONTH_04_b_u: &[u8] = b"Apr";
1192
const MONTH_04_b_U: &[u8] = b"APR";
1193
const MONTH_04_b_ld: &[u8] = b"apr.";
1194
const MONTH_04_b_ud: &[u8] = b"Apr.";
1195
const MONTH_04_b_Ud: &[u8] = b"APR.";
1196
const MONTH_04_B_l: &[u8] = b"april";
1197
const MONTH_04_B_u: &[u8] = b"April";
1198
const MONTH_04_B_U: &[u8] = b"APRIL";
1199
const MONTH_04_m: &[u8] = b"04";
1200
const MONTH_05_b_l: &[u8] = b"may";
1201
const MONTH_05_b_u: &[u8] = b"May";
1202
const MONTH_05_b_U: &[u8] = b"MAY";
1203
#[allow(dead_code)]
1204
const MONTH_05_B_l: &[u8] = b"may"; // not used, defined for completeness
1205
#[allow(dead_code)]
1206
const MONTH_05_B_u: &[u8] = b"May"; // not used, defined for completeness
1207
#[allow(dead_code)]
1208
const MONTH_05_B_U: &[u8] = b"MAY"; // not used, defined for completeness
1209
const MONTH_05_m: &[u8] = b"05";
1210
const MONTH_06_b_l: &[u8] = b"jun";
1211
const MONTH_06_b_u: &[u8] = b"Jun";
1212
const MONTH_06_b_U: &[u8] = b"JUN";
1213
const MONTH_06_b_ld: &[u8] = b"jun.";
1214
const MONTH_06_b_ud: &[u8] = b"Jun.";
1215
const MONTH_06_b_Ud: &[u8] = b"JUN.";
1216
const MONTH_06_B_l: &[u8] = b"june";
1217
const MONTH_06_B_u: &[u8] = b"June";
1218
const MONTH_06_B_U: &[u8] = b"JUNE";
1219
const MONTH_06_m: &[u8] = b"06";
1220
const MONTH_07_b_l: &[u8] = b"jul";
1221
const MONTH_07_b_u: &[u8] = b"Jul";
1222
const MONTH_07_b_U: &[u8] = b"JUL";
1223
const MONTH_07_b_ld: &[u8] = b"jul.";
1224
const MONTH_07_b_ud: &[u8] = b"Jul.";
1225
const MONTH_07_b_Ud: &[u8] = b"JUL.";
1226
const MONTH_07_B_l: &[u8] = b"july";
1227
const MONTH_07_B_u: &[u8] = b"July";
1228
const MONTH_07_B_U: &[u8] = b"JULY";
1229
const MONTH_07_m: &[u8] = b"07";
1230
const MONTH_08_b_l: &[u8] = b"aug";
1231
const MONTH_08_b_u: &[u8] = b"Aug";
1232
const MONTH_08_b_U: &[u8] = b"AUG";
1233
const MONTH_08_b_ld: &[u8] = b"aug.";
1234
const MONTH_08_b_ud: &[u8] = b"Aug.";
1235
const MONTH_08_b_Ud: &[u8] = b"AUG.";
1236
const MONTH_08_B_l: &[u8] = b"august";
1237
const MONTH_08_B_u: &[u8] = b"August";
1238
const MONTH_08_B_U: &[u8] = b"AUGUST";
1239
const MONTH_08_m: &[u8] = b"08";
1240
const MONTH_09_b_l: &[u8] = b"sep";
1241
const MONTH_09_b_u: &[u8] = b"Sep";
1242
const MONTH_09_b_U: &[u8] = b"SEP";
1243
const MONTH_09_b_ld: &[u8] = b"sep.";
1244
const MONTH_09_b_ud: &[u8] = b"Sep.";
1245
const MONTH_09_b_Ud: &[u8] = b"SEP.";
1246
const MONTH_09_B_l: &[u8] = b"september";
1247
const MONTH_09_B_u: &[u8] = b"September";
1248
const MONTH_09_B_U: &[u8] = b"SEPTEMBER";
1249
const MONTH_09_m: &[u8] = b"09";
1250
const MONTH_10_b_l: &[u8] = b"oct";
1251
const MONTH_10_b_u: &[u8] = b"Oct";
1252
const MONTH_10_b_U: &[u8] = b"OCT";
1253
const MONTH_10_b_ld: &[u8] = b"oct.";
1254
const MONTH_10_b_ud: &[u8] = b"Oct.";
1255
const MONTH_10_b_Ud: &[u8] = b"OCT.";
1256
const MONTH_10_B_l: &[u8] = b"october";
1257
const MONTH_10_B_u: &[u8] = b"October";
1258
const MONTH_10_B_U: &[u8] = b"OCTOBER";
1259
const MONTH_10_m: &[u8] = b"10";
1260
const MONTH_11_b_l: &[u8] = b"nov";
1261
const MONTH_11_b_u: &[u8] = b"Nov";
1262
const MONTH_11_b_U: &[u8] = b"NOV";
1263
const MONTH_11_b_ld: &[u8] = b"nov.";
1264
const MONTH_11_b_ud: &[u8] = b"Nov.";
1265
const MONTH_11_b_Ud: &[u8] = b"NOV.";
1266
const MONTH_11_B_l: &[u8] = b"november";
1267
const MONTH_11_B_u: &[u8] = b"November";
1268
const MONTH_11_B_U: &[u8] = b"NOVEMBER";
1269
const MONTH_11_m: &[u8] = b"11";
1270
const MONTH_12_b_l: &[u8] = b"dec";
1271
const MONTH_12_b_u: &[u8] = b"Dec";
1272
const MONTH_12_b_U: &[u8] = b"DEC";
1273
const MONTH_12_b_ld: &[u8] = b"dec.";
1274
const MONTH_12_b_ud: &[u8] = b"Dec.";
1275
const MONTH_12_b_Ud: &[u8] = b"DEC.";
1276
const MONTH_12_B_l: &[u8] = b"december";
1277
const MONTH_12_B_u: &[u8] = b"December";
1278
const MONTH_12_B_U: &[u8] = b"DECEMBER";
1279
const MONTH_12_m: &[u8] = b"12";
1280

1281
/// Transform strftime `%B`, `%b` (i.e. `"January"`, `"Jan"`) to
1282
/// strftime `%m` (i.e. `"01"`).
1283
///
1284
/// Helper to [`captures_to_buffer_bytes`].
1285
#[allow(non_snake_case)]
1286
fn month_bB_to_month_m_bytes(
234✔
1287
    data: &[u8],
234✔
1288
    buffer: &mut [u8],
234✔
1289
) {
234✔
1290
    match data {
234✔
1291
        // try *b* matches first; it is more common
1292
        MONTH_01_b_l | MONTH_01_b_u | MONTH_01_b_U => buffer.copy_from_slice(MONTH_01_m),
234✔
1293
        MONTH_02_b_l | MONTH_02_b_u | MONTH_02_b_U => buffer.copy_from_slice(MONTH_02_m),
22✔
1294
        MONTH_03_b_l | MONTH_03_b_u | MONTH_03_b_U => buffer.copy_from_slice(MONTH_03_m),
20✔
1295
        MONTH_04_b_l | MONTH_04_b_u | MONTH_04_b_U => buffer.copy_from_slice(MONTH_04_m),
5✔
1296
        MONTH_05_b_l | MONTH_05_b_u | MONTH_05_b_U => buffer.copy_from_slice(MONTH_05_m),
8✔
1297
        MONTH_06_b_l | MONTH_06_b_u | MONTH_06_b_U => buffer.copy_from_slice(MONTH_06_m),
5✔
1298
        MONTH_07_b_l | MONTH_07_b_u | MONTH_07_b_U => buffer.copy_from_slice(MONTH_07_m),
7✔
1299
        MONTH_08_b_l | MONTH_08_b_u | MONTH_08_b_U => buffer.copy_from_slice(MONTH_08_m),
1✔
1300
        MONTH_09_b_l | MONTH_09_b_u | MONTH_09_b_U => buffer.copy_from_slice(MONTH_09_m),
3✔
1301
        MONTH_10_b_l | MONTH_10_b_u | MONTH_10_b_U => buffer.copy_from_slice(MONTH_10_m),
69✔
1302
        MONTH_11_b_l | MONTH_11_b_u | MONTH_11_b_U => buffer.copy_from_slice(MONTH_11_m),
3✔
1303
        MONTH_12_b_l | MONTH_12_b_u | MONTH_12_b_U => buffer.copy_from_slice(MONTH_12_m),
×
1304
        // then try *b*dot matches
1305
        MONTH_01_b_ld | MONTH_01_b_ud | MONTH_01_b_Ud => buffer.copy_from_slice(MONTH_01_m),
12✔
1306
        MONTH_02_b_ld | MONTH_02_b_ud | MONTH_02_b_Ud => buffer.copy_from_slice(MONTH_02_m),
×
1307
        MONTH_03_b_ld | MONTH_03_b_ud | MONTH_03_b_Ud => buffer.copy_from_slice(MONTH_03_m),
×
1308
        MONTH_04_b_ld | MONTH_04_b_ud | MONTH_04_b_Ud => buffer.copy_from_slice(MONTH_04_m),
×
1309
        // MONTH_05_b_ld not needed
1310
        MONTH_06_b_ld | MONTH_06_b_ud | MONTH_06_b_Ud => buffer.copy_from_slice(MONTH_06_m),
×
1311
        MONTH_07_b_ld | MONTH_07_b_ud | MONTH_07_b_Ud => buffer.copy_from_slice(MONTH_07_m),
×
1312
        MONTH_08_b_ld | MONTH_08_b_ud | MONTH_08_b_Ud => buffer.copy_from_slice(MONTH_08_m),
×
1313
        MONTH_09_b_ld | MONTH_09_b_ud | MONTH_09_b_Ud => buffer.copy_from_slice(MONTH_09_m),
×
1314
        MONTH_10_b_ld | MONTH_10_b_ud | MONTH_10_b_Ud => buffer.copy_from_slice(MONTH_10_m),
×
1315
        MONTH_11_b_ld | MONTH_11_b_ud | MONTH_11_b_Ud => buffer.copy_from_slice(MONTH_11_m),
×
1316
        MONTH_12_b_ld | MONTH_12_b_ud | MONTH_12_b_Ud => buffer.copy_from_slice(MONTH_12_m),
×
1317
        // then try *B* matches
1318
        MONTH_01_B_l | MONTH_01_B_u | MONTH_01_B_U => buffer.copy_from_slice(MONTH_01_m),
12✔
1319
        MONTH_02_B_l | MONTH_02_B_u | MONTH_02_B_U => buffer.copy_from_slice(MONTH_02_m),
8✔
1320
        MONTH_03_B_l | MONTH_03_B_u | MONTH_03_B_U => buffer.copy_from_slice(MONTH_03_m),
8✔
1321
        MONTH_04_B_l | MONTH_04_B_u | MONTH_04_B_U => buffer.copy_from_slice(MONTH_04_m),
×
1322
        //MONTH_05_B_l | MONTH_05_B_u | MONTH_05_B_U => buffer.copy_from_slice(MONTH_05_m),
1323
        MONTH_06_B_l | MONTH_06_B_u | MONTH_06_B_U => buffer.copy_from_slice(MONTH_06_m),
×
1324
        MONTH_07_B_l | MONTH_07_B_u | MONTH_07_B_U => buffer.copy_from_slice(MONTH_07_m),
×
1325
        MONTH_08_B_l | MONTH_08_B_u | MONTH_08_B_U => buffer.copy_from_slice(MONTH_08_m),
8✔
1326
        MONTH_09_B_l | MONTH_09_B_u | MONTH_09_B_U => buffer.copy_from_slice(MONTH_09_m),
8✔
1327
        MONTH_10_B_l | MONTH_10_B_u | MONTH_10_B_U => buffer.copy_from_slice(MONTH_10_m),
×
1328
        MONTH_11_B_l | MONTH_11_B_u | MONTH_11_B_U => buffer.copy_from_slice(MONTH_11_m),
×
1329
        MONTH_12_B_l | MONTH_12_B_u | MONTH_12_B_U => buffer.copy_from_slice(MONTH_12_m),
×
1330
        data_ => {
×
1331
            panic!("month_bB_to_month_m_bytes: unexpected month value {:?}", data_);
×
1332
        }
1333
    }
1334
}
234✔
1335

1336
/// Put [`Captures`] into `buffer` in a particular order and formatting.
1337
/// This is to prepare the regex matched data for passing to a later call to
1338
/// [`DateTime::parse_from_str`] (called outside of this function).
1339
///
1340
/// This bridges the crate `regex` regular expression pattern strings,
1341
/// [`DateTimeParseInstr::regex_pattern`], to crate `chrono` strftime strings,
1342
/// [`DateTimeParseInstr::dtfs`].
1343
///
1344
/// Directly relates to datetime format `dtfs` values in
1345
/// [`test_DATETIME_PARSE_DATAS_test_cases`] which use `DTFSS_YmdHMS`, etc.
1346
///
1347
/// Transforms `%B` acceptable value to `%m` acceptable value.
1348
///
1349
/// Transforms `%e` acceptable value to `%d` acceptable value.
1350
///
1351
/// Transforms an uptime (seconds since system boot) to current seconds offset
1352
/// since UNIX_EPOCH. This allows later conversion via chrono strftime `%s`.
1353
///
1354
/// Transforms timezone offset inidicator MINUS SIGN `−` (U+2212) into
1355
/// HYPHEN-MINUS `-` (U+2D), e.g `−0700` becomes `-0700`.
1356
///
1357
/// [`Captures`]: https://docs.rs/regex/1.10.5/regex/bytes/struct.Captures.html
1358
/// [`DateTime::parse_from_str`]: https://docs.rs/chrono/0.4.38/chrono/struct.DateTime.html#method.parse_from_str
1359
// TODO: allow returning an `Error` instead of `panic!`
1360
// TODO: [2026/06] prior to calling `captures_to_buffer_bytes`, non UTF-8 bytes must
1361
//       be converted to UTF-8. If `captures_to_buffer_bytes` handled all
1362
//       "conversions" like the function `month_bB_to_month_m_bytes` does, having a match expression
1363
//       for all known input values, and that match statement included the non-UTF-8 forms of values,
1364
//       then all these steps within `captures_to_buffer_bytes` to `copy_capturegroup_to_buffer!` would be unnecessary.
1365
//       And in turn, the temporary conversion from non-UTF8 to UTF-8 done before this would be unnecessary.
1366
//       This might be a nice performance improvement, but that should be measured.
1367
#[inline(always)]
1368
pub(crate) fn captures_to_buffer_bytes(
35,943✔
1369
    buffer: &mut [u8],
35,943✔
1370
    data: &[u8],
35,943✔
1371
    captures: &MatchesType,
35,943✔
1372
    year_opt: &Option<Year>,
35,943✔
1373
    systemtime_at_uptime_zero: &Option<SystemTime>,
35,943✔
1374
    tz_offset_string: &String,
35,943✔
1375
    dtfs: &DTFSSet,
35,943✔
1376
) -> usize {
35,943✔
1377
    defn!("(…, …, year_opt {:?}, systemtime_at_uptime_zero {:?}, tz_offset {:?}, …)",
35,943✔
1378
          year_opt, systemtime_at_uptime_zero, tz_offset_string);
1379

1380
    let mut at: usize = 0;
35,943✔
1381

1382
    defo!("process <epoch> {:?}…", dtfs.epoch);
35,943✔
1383
    match dtfs.epoch {
35,943✔
1384
        DTFS_Epoch::s => {
1385
            copy_capturegroup_to_buffer!(CGI_EPOCH, data, captures, buffer, at);
×
1386
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
×
1387
        }
1388
        DTFS_Epoch::ms => {
1389
            // special case
1390
            // copy the epoch milliseconds but only the seconds part
1391
            // then copy the milliseconds, the last 3 chars, as a fractional
1392
            captures.iter().find(|cgn| cgn.group_index() == CGI_EPOCH).map(|cgn| {
×
1393
                // copy the seconds part of the epoch milliseconds
1394
                let a = cgn.start();
×
1395
                let mut b = cgn.end();
×
1396
                debug_assert_le!(a, b);
×
1397
                if (b - a) > 3 {
×
1398
                    b -= 3;
×
1399
                } else {
×
1400
                    debug_panic!("epoch ms capture group too short to have milliseconds part: {:?}", &data[a..b]);
×
1401
                }
1402
                let len_: usize = b - a;
×
1403
                defo!("copy buffer[{:?}‥{:?}] = {:?}", at, at + len_, &data[a..b]);
×
1404
                buffer[at..at + len_].copy_from_slice(&data[a..b]);
×
1405
                at += len_;
×
1406
                // separate seconds and milliseconds with `.`
1407
                copy_u8_to_buffer!(b'.', buffer, at);
×
1408
                // copy the milliseconds part of the epoch milliseconds, last 3 chars
1409
                let a = b;
×
1410
                let b = cgn.end();
×
1411
                debug_assert_le!(a, b);
×
1412
                let len_: usize = b - a;
×
1413
                debug_assert_eq!(len_, 3, "last milliseconds part should be 3 chars");
×
1414
                defo!("copy buffer[{:?}‥{:?}] = {:?}", at, at + len_, &data[a..b]);
×
1415
                buffer[at..at + len_].copy_from_slice(&data[a..b]);
×
1416
                at += len_;
×
1417
            });
×
1418
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
×
1419
        }
1420
        DTFS_Epoch::_none => {}
35,943✔
1421
    }
1422

1423
    defo!("process <uptime> {:?}…", dtfs.uptime);
35,943✔
1424
    match dtfs.uptime {
35,943✔
1425
        DTFS_Uptime::u => {
1426
            // Here is where an important conversion happens:
1427
            // get the log uptime string, e.g. `"1.340"`, and convert it to a
1428
            // Duration. Then add that to the `systemtime_at_uptime_zero`.
1429
            // So value `1.340` is added to the wrapped `SystemTime` value.
1430
            // Then that is written into a buffer as seconds since UNIX_EPOCH.
1431
            // Later, in `datetime_parse_from_str`, this buffer is converted to
1432
            // a `DateTime` value.
1433

1434
            // copy the `uptime` capture group to a temporary local buffer
1435
            let mut at_uptime = 0;
×
1436
            const BUFLEN: usize = 30;
1437
            let mut buf_uptime: [u8; BUFLEN] = [0; BUFLEN];
×
1438
            copy_capturegroup_to_buffer!(CGI_UPTIME, data, captures, buf_uptime, at_uptime);
×
1439
            defo!("buf_uptime {:?}", buffer_to_string_noraw(&buf_uptime));
×
1440
            // TODO: use `u8_to_str`
1441
            let buf_uptime_s: &str = match std::str::from_utf8(&buf_uptime[..at_uptime]) {
×
1442
                Ok(s) => s,
×
1443
                Err(_err) => {
×
1444
                    de_err!("uptime str::from_utf8 error: {}", _err);
×
1445
                    // fallback to zero
1446
                    "0"
×
1447
                }
1448
            };
1449
            defo!("buf_uptime_s {:?}", buf_uptime_s);
×
1450
            // extract the uptime string to an `Uptime` value
1451
            let uptime_val: Uptime = match buf_uptime_s.parse::<Uptime>() {
×
1452
                Ok(uptime_) => uptime_,
×
1453
                Err(_err) => {
×
1454
                    de_err!("uptime parse error: {}", _err);
×
1455
                    // fallback to zero
1456
                    0
×
1457
                }
1458
            };
1459
            defo!("uptime_val {:?}", uptime_val);
×
1460

1461
            let uptime_zero: SystemTime = match systemtime_at_uptime_zero {
×
1462
                Some(val) => *val,
×
1463
                None => UPTIME_DEFAULT_OFFSET,
×
1464
            };
1465
            defo!("uptime_zero {:?}", uptime_zero);
×
1466

1467
            // convert the uptime value to a `std::time::Duration`
1468
            let uptime_dur: StdDuration = StdDuration::new(uptime_val as u64, 0);
×
1469
            defo!("uptime_dur {:?}", uptime_dur);
×
1470
            // add the uptime value to the uptime_zero value
1471
            let uptime_zero_plus_uptime: SystemTime = match uptime_zero.checked_add(uptime_dur) {
×
1472
                Some(st) => st,
×
1473
                None => {
1474
                    debug_panic!("failed checked_add({:?})", uptime_dur);
×
1475
                    // I'm not sure what else to do here in a release build
1476

1477
                    UPTIME_DEFAULT_OFFSET
×
1478
                },
1479
            };
1480
            defo!("uptime_zero_plus_uptime {:?}", uptime_zero_plus_uptime);
×
1481
            // convert the `uptime_zero_plus_uptime` value to a string to a [u8]
1482
            buf_uptime.fill(0);
×
1483
            let uptime_zero_plus_uptime_dur = match uptime_zero_plus_uptime.duration_since(SystemTime::UNIX_EPOCH) {
×
1484
                Ok(dur) => dur,
×
1485
                Err(_err) => {
×
1486
                    debug_panic!("uptime_zero_plus_uptime.duration_since(UPTIME_DEFAULT_OFFSET) failed: {}", _err);
×
1487
                    // fallback to zero
1488
                    StdDuration::from_secs(0)
×
1489
                }
1490
            };
1491
            let uptime_zero_plus_uptime_n = uptime_zero_plus_uptime_dur.as_secs();
×
1492
            defo!("uptime_zero_plus_uptime_n {:?}", uptime_zero_plus_uptime_n);
×
1493
            let buf_uptime_plus: &[u8] = uptime_zero_plus_uptime_n.numtoa(10, &mut buf_uptime);
×
1494
            defo!("buf_uptime_plus {:?}", buffer_to_string_noraw(buf_uptime_plus));
×
1495
            // copy the local temporary buffer to the main buffer
1496
            copy_slice_to_buffer!(&buf_uptime_plus, buffer, at);
×
1497
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
×
1498
        }
1499
        DTFS_Uptime::_none => {}
35,943✔
1500
    }
1501

1502
    // year
1503
    defo!("process <year> {:?}…", dtfs.year);
35,943✔
1504
    match dtfs.year {
35,943✔
1505
        DTFS_Year::Y
1506
        | DTFS_Year::y => {
1507
            copy_capturegroup_to_buffer!(CGI_YEAR, data, captures, buffer, at);
35,809✔
1508
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
35,809✔
1509
        }
1510
        | DTFS_Year::_fill => {
1511
            let mut found_year: bool = false;
134✔
1512
            captures.iter().find(|cgn| cgn.group_index() == CGI_YEAR).map(|match_type| {
670✔
1513
                let a = match_type.start();
×
1514
                let b = match_type.end();
×
1515
                let year: &[u8] = &data[a..b];
×
1516
                copy_slice_to_buffer!(year, buffer, at);
×
1517
                defo!("buffer {:?}", buffer_to_string_noraw(buffer));
×
1518
                found_year = true;
×
1519
            });
×
1520
            if !found_year{
134✔
1521
                match year_opt {
134✔
1522
                    Some(year) => {
76✔
1523
                        // TODO: 2022/07/11 cost-savings: pass in `Option<&[u8]>`, avoid creating `String`
1524
                        let year_s: String = year.to_string();
76✔
1525
                        debug_assert_eq!(year_s.len(), 4, "Bad year string {:?}", year_s);
76✔
1526
                        defo!("using fallback year {:?}", year_s);
76✔
1527
                        copy_slice_to_buffer!(year_s.as_bytes(), buffer, at);
76✔
1528
                        defo!("buffer {:?}", buffer_to_string_noraw(buffer));
76✔
1529
                    }
1530
                    None => {
1531
                        defo!("using hardcoded dummy year {:?}", YEAR_FALLBACKDUMMY);
58✔
1532
                        copy_slice_to_buffer!(YEAR_FALLBACKDUMMY.as_bytes(), buffer, at);
58✔
1533
                        defo!("buffer {:?}", buffer_to_string_noraw(buffer));
58✔
1534
                    }
1535
                }
1536
            }
×
1537
        }
1538
        DTFS_Year::_none => {}
×
1539
    }
1540
    // month
1541
    defo!("process <month> {:?}…", dtfs.month);
35,943✔
1542
    match dtfs.month {
35,943✔
1543
        DTFS_Month::m => {
1544
            copy_capturegroup_to_buffer!(CGI_MONTH, data, captures, buffer, at);
35,709✔
1545
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
35,709✔
1546
        }
1547
        DTFS_Month::ms => {
1548
            let month: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_MONTH).map(|match_type| {
×
1549
                let a = match_type.start();
×
1550
                let b = match_type.end();
×
1551
                &data[a..b]
×
1552
            }).expect("missing month capture group");
×
1553
            // chrono strftime expects numeric months to be two-digit
1554
            // so prepend `0` if necessary
1555
            match month.len() {
×
1556
                1 => {
1557
                    copy_slice_to_buffer!(b"0", buffer, at);
×
1558
                    copy_slice_to_buffer!(month, buffer, at);
×
1559
                }
1560
                _val => {
×
1561
                    debug_assert_eq!(_val, 2, "unexpected Month length {}", _val);
×
1562
                    copy_slice_to_buffer!(month, buffer, at);
×
1563
                }
1564
            }
1565
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
×
1566
        }
1567
        DTFS_Month::b | DTFS_Month::B => {
1568
            let month: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_MONTH).map(|match_type| {
334✔
1569
                let a = match_type.start();
234✔
1570
                let b = match_type.end();
234✔
1571
                &data[a..b]
234✔
1572
            }).expect("missing month capture group");
234✔
1573
            month_bB_to_month_m_bytes(
234✔
1574
                month,
234✔
1575
                &mut buffer[at..at + 2],
234✔
1576
            );
1577
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
234✔
1578
            at += 2;
234✔
1579
        }
1580
        DTFS_Month::_none => {}
×
1581
    }
1582
    // day
1583
    defo!("process <day> {:?}…", dtfs.day);
35,943✔
1584
    match dtfs.day {
35,943✔
1585
        DTFS_Day::_e_or_d => {
1586
            let day: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_DAY).map(|match_type| {
107,695✔
1587
                let a = match_type.start();
35,943✔
1588
                let b = match_type.end();
35,943✔
1589

1590
                &data[a..b]
35,943✔
1591
            }).expect("missing day capture group");
35,943✔
1592
            debug_assert_ge!(day.len(), 1, "bad named group 'day' data {:?}, expected data ge 1", day);
35,943✔
1593
            debug_assert_le!(day.len(), 2, "bad named group 'day' data {:?}, expected data le 2", day);
35,943✔
1594
            match day.len() {
35,943✔
1595
                1 => {
1596
                    // change day "8" (%e) to "08" (%d)
1597
                    copy_u8_to_buffer!(b'0', buffer, at);
74✔
1598
                    copy_u8_to_buffer!(day[0], buffer, at);
74✔
1599
                    defo!("buffer {:?}", buffer_to_string_noraw(buffer));
74✔
1600
                }
1601
                2 => {
1602
                    match day[0] {
35,869✔
1603
                        b' ' => {
1604
                            // change day " 8" (%e) to "08" (%d)
1605
                            copy_u8_to_buffer!(b'0', buffer, at);
×
1606
                            copy_u8_to_buffer!(day[1], buffer, at);
×
1607
                        }
1608
                        _ => {
1609
                            copy_slice_to_buffer!(day, buffer, at);
35,869✔
1610
                        }
1611
                    }
1612
                    defo!("buffer {:?}", buffer_to_string_noraw(buffer));
35,869✔
1613
                }
1614
                _ => {
1615
                    panic!("bad day.len() {}", day.len());
×
1616
                }
1617
            }
1618
        }
1619
        DTFS_Day::_none => {}
×
1620
    }
1621
    // Day pattern `%a` (`Monday`, 'Tue`, etc.) (capture group `CGN_DAY_IGNORE`) is captured but not
1622
    // passed along to chrono functions.
1623

1624
    // day-time divider
1625
    defo!("process date-time divider…");
35,943✔
1626
    copy_u8_to_buffer!(b'T', buffer, at);
35,943✔
1627
    defo!("buffer {:?}", buffer_to_string_noraw(buffer));
35,943✔
1628
    // hour
1629
    defo!("process <hour> {:?}…", dtfs.hour);
35,943✔
1630
    match dtfs.hour {
35,943✔
1631
        DTFS_Hour::I
1632
        | DTFS_Hour::l
1633
        | DTFS_Hour::H => {
1634
            copy_capturegroup_to_buffer!(CGI_HOUR, data, captures, buffer, at);
35,943✔
1635
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
35,943✔
1636
        }
1637
        DTFS_Hour::k => {
1638
            let hour: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_HOUR).map(|match_type| {
×
1639
                let a = match_type.start();
×
1640
                let b = match_type.end();
×
1641
                &data[a..b]
×
1642
            }).expect("missing hour capture group");
×
1643
            // chrono strftime expects numeric hour to be two-digit
1644
            // so prepend `0` if necessary
1645
            match hour.len() {
×
1646
                1 => {
1647
                    copy_slice_to_buffer!(b"0", buffer, at);
×
1648
                    copy_slice_to_buffer!(hour, buffer, at);
×
1649
                }
1650
                _val => {
×
1651
                    debug_assert_eq!(_val, 2, "unexpected Month length {}", _val);
×
1652
                    copy_slice_to_buffer!(hour, buffer, at);
×
1653
                }
1654
            }
1655
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
×
1656
        }
1657
        DTFS_Hour::_none => {}
×
1658
    }
1659
    // minute
1660
    defo!("process <minute> {:?}…", dtfs.minute);
35,943✔
1661
    match dtfs.minute {
35,943✔
1662
        DTFS_Minute::M => {
1663
            copy_capturegroup_to_buffer!(CGI_MINUTE, data, captures, buffer, at);
35,943✔
1664
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
35,943✔
1665
        },
1666
        DTFS_Minute::m => {
1667
            let minute: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_MINUTE).map(|match_type| {
×
1668
                let a = match_type.start();
×
1669
                let b = match_type.end();
×
1670
                &data[a..b]
×
1671
            }).expect("missing minute capture group");
×
1672
            // chrono strftime expects numeric minute to be two-digit
1673
            // so prepend `0` if necessary
1674
            match minute.len() {
×
1675
                1 => {
1676
                    copy_slice_to_buffer!(b"0", buffer, at);
×
1677
                    copy_slice_to_buffer!(minute, buffer, at);
×
1678
                }
1679
                _val => {
×
1680
                    debug_assert_eq!(_val, 2, "unexpected Minute length {}", _val);
×
1681
                    copy_slice_to_buffer!(minute, buffer, at);
×
1682
                }
1683
            }
1684
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
×
1685
        },
1686
        DTFS_Minute::_none => {}
×
1687
    }
1688
    // second
1689
    defo!("process <second> {:?}…", dtfs.second);
35,943✔
1690
    match dtfs.second {
35,943✔
1691
        DTFS_Second::S => {
1692
            copy_capturegroup_to_buffer!(CGI_SECOND, data, captures, buffer, at);
33,985✔
1693
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
33,985✔
1694
        }
1695
        DTFS_Second::s => {
1696
            let second: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_SECOND).map(|match_type| {
×
1697
                let a = match_type.start();
×
1698
                let b = match_type.end();
×
1699
                &data[a..b]
×
1700
            }).expect("missing second capture group");
×
1701
            // chrono strftime expects numeric second to be two-digit
1702
            // so prepend `0` if necessary
1703
            match second.len() {
×
1704
                1 => {
1705
                    copy_slice_to_buffer!(b"0", buffer, at);
×
1706
                    copy_slice_to_buffer!(second, buffer, at);
×
1707
                }
1708
                _val => {
×
1709
                    debug_assert_eq!(_val, 2, "unexpected Second length {}", _val);
×
1710
                    copy_slice_to_buffer!(second, buffer, at);
×
1711
                }
1712
            }
1713
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
×
1714
        }
1715
        DTFS_Second::_fill => {
1716
            copy_slice_to_buffer!(b"00", buffer, at);
×
1717
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
×
1718
        }
1719
        DTFS_Second::_none => {}
1,958✔
1720
    }
1721
    // fractional
1722
    defo!("process <fractional> {:?}…", dtfs.fractional);
35,943✔
1723
    match dtfs.fractional {
35,943✔
1724
        DTFS_Fractional::f => {
1725
            defo!("matched DTFS_Fractional::f");
192✔
1726
            copy_u8_to_buffer!(b'.', buffer, at);
192✔
1727
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
192✔
1728
            let fractional: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_FRACTIONAL).map(|match_type| {
1,344✔
1729
                let a = match_type.start();
192✔
1730
                let b = match_type.end();
192✔
1731
                &data[a..b]
192✔
1732
            }).expect("missing fractional capture group");
192✔
1733
            let len = fractional.len();
192✔
1734
            defo!("match len {:?}", len);
192✔
1735
            match len {
192✔
1736
                0 => {
1737
                    copy_slice_to_buffer!(fractional, buffer, at);
×
1738
                    copy_slice_to_buffer!(b"000000000", buffer, at);
×
1739
                }
1740
                1 => {
1741
                    copy_slice_to_buffer!(fractional, buffer, at);
4✔
1742
                    copy_slice_to_buffer!(b"00000000", buffer, at);
4✔
1743
                }
1744
                2 => {
1745
                    copy_slice_to_buffer!(fractional, buffer, at);
44✔
1746
                    copy_slice_to_buffer!(b"0000000", buffer, at);
44✔
1747
                }
1748
                3 => {
1749
                    copy_slice_to_buffer!(fractional, buffer, at);
44✔
1750
                    copy_slice_to_buffer!(b"000000", buffer, at);
44✔
1751
                }
1752
                4 => {
1753
                    copy_slice_to_buffer!(fractional, buffer, at);
4✔
1754
                    copy_slice_to_buffer!(b"00000", buffer, at);
4✔
1755
                }
1756
                5 => {
1757
                    copy_slice_to_buffer!(fractional, buffer, at);
4✔
1758
                    copy_slice_to_buffer!(b"0000", buffer, at);
4✔
1759
                }
1760
                6 => {
1761
                    copy_slice_to_buffer!(fractional, buffer, at);
68✔
1762
                    copy_slice_to_buffer!(b"000", buffer, at);
68✔
1763
                }
1764
                7 => {
1765
                    copy_slice_to_buffer!(fractional, buffer, at);
×
1766
                    copy_slice_to_buffer!(b"00", buffer, at);
×
1767
                }
1768
                8 => {
1769
                    copy_slice_to_buffer!(fractional, buffer, at);
×
1770
                    copy_slice_to_buffer!(b"0", buffer, at);
×
1771
                }
1772
                9 => {
1773
                    copy_slice_to_buffer!(fractional, buffer, at);
24✔
1774
                }
1775
                10 | 11 | 12 => {
1776
                    // fractional is too large; copy only left-most 9 chars
1777
                    copy_slice_to_buffer!(&fractional[..9], buffer, at);
×
1778
                    de_wrn!("fractional string {:?} is length {} bytes, only copying 9 bytes", fractional, len)
×
1779
                }
1780
                _ => {
1781
                    // something is wrong with this matched string; ignore it
1782
                    de_err!("unexpected fractional string match {:?} length {} bytes", fractional, len)
×
1783
                }
1784
            }
1785
            defo!("buffer {:?}", buffer_to_string_noraw(buffer));
192✔
1786
        }
1787
        DTFS_Fractional::_none => {}
35,751✔
1788
    }
1789

1790
    // tz
1791
    defo!("process <tz> {:?}…", dtfs.tz);
35,943✔
1792
    match dtfs.tz {
35,943✔
1793
        DTFS_Tz::_fill => {
1794
            copy_slice_to_buffer!(tz_offset_string.as_bytes(), buffer, at);
35,467✔
1795
        }
1796
        DTFS_Tz::z | DTFS_Tz::zc | DTFS_Tz::zp => {
1797
            // for data passed to chrono `DateTime::parse_from_str`,
1798
            // replace Unicode "minus sign" to ASCII "hyphen-minus"
1799
            // see Issue https://github.com/chronotope/chrono/issues/835
1800
            // XXX: chrono 0.4.27 handles MINUS SIGN (U+2212)
1801
            //      see PR https://github.com/chronotope/chrono/pull/1087
1802
            //      however, keep this code here as it works fine
1803
            let captureb: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_TZ).map(|match_type| {
2,336✔
1804
                let a = match_type.start();
320✔
1805
                let b = match_type.end();
320✔
1806
                &data[a..b]
320✔
1807
            }).expect("missing tz capture group");
320✔
1808
            match captureb.starts_with(MINUS_SIGN) {
320✔
1809
                true => {
1810
                    defo!("found Unicode 'minus sign', transform to ASCII 'hyphen-minus'");
4✔
1811
                    // found Unicode "minus sign", replace with ASCII
1812
                    // "hyphen-minus"
1813
                    copy_slice_to_buffer!(HYPHEN_MINUS, buffer, at);
4✔
1814
                    defo!("buffer {:?}", buffer_to_string_noraw(buffer));
4✔
1815
                    // copy data remaining after Unicode "minus sign"
1816
                    // TODO: use u8_to_str
1817
                    match std::str::from_utf8(captureb) {
4✔
1818
                        Ok(val) => {
4✔
1819
                            match val.char_indices().nth(1) {
4✔
1820
                                Some((offset, _)) => {
4✔
1821
                                    copy_slice_to_buffer!(val[offset..].as_bytes(), buffer, at);
4✔
1822
                                    defo!("buffer {:?}", buffer_to_string_noraw(buffer));
4✔
1823
                                }
1824
                                None => {
×
1825
                                    // something is wrong with captured value
×
1826
                                    // ignore it
×
1827
                                }
×
1828
                            }
1829
                        }
1830
                        Err(_err) => {
×
1831
                            // something is wrong with captured value, ignore it
×
1832
                        }
×
1833
                    }
1834
                }
1835
                false => {
1836
                    copy_slice_to_buffer!(captureb, buffer, at);
316✔
1837
                    defo!("buffer {:?}", buffer_to_string_noraw(buffer));
316✔
1838
                }
1839
            }
1840
        }
1841
        DTFS_Tz::Z => {
1842
            #[allow(non_snake_case)]
1843
            let tzZ: &str = u8_to_str(
156✔
1844
                captures.iter().find(|cgn| cgn.group_index() == CGI_TZ).map(|match_type| {
1,220✔
1845
                    let a = match_type.start();
156✔
1846
                    let b = match_type.end();
156✔
1847
                    &data[a..b]
156✔
1848
                }).expect("missing tz capture group")
156✔
1849
            ).unwrap_or_default();
156✔
1850
            if tzZ.is_empty() {
156✔
1851
                debug_panic!("tzZ.is_empty()");
×
1852
                // `u8_to_str` failed, fallback to using passed TZ offset
1853
                copy_slice_to_buffer!(tz_offset_string.as_bytes(), buffer, at);
×
1854
            } else {
1855
                match MAP_TZZ_TO_TZz.get_entry(tzZ) {
156✔
1856
                    Some((_tz_abbr, tz_offset_val)) => {
156✔
1857
                        match tz_offset_val.is_empty() {
156✔
1858
                            true => {
1859
                                // given an ambiguous timezone name, fallback to
1860
                                // passed TZ offset
1861
                                copy_slice_to_buffer!(tz_offset_string.as_bytes(), buffer, at);
×
1862
                            }
1863
                            false => {
1864
                                // given an unambiguous timezone name, use associated offset
1865
                                copy_slice_to_buffer!(tz_offset_val.as_bytes(), buffer, at);
156✔
1866
                            }
1867
                        }
1868
                    }
1869
                    None => {
1870
                        // cannot find entry in MAP_TZZ_TO_TZz, use passed TZ offset
1871
                        debug_panic!("captured named timezone {:?} not found in MAP_TZZ_TO_TZz", tzZ);
×
1872
                        copy_slice_to_buffer!(tz_offset_string.as_bytes(), buffer, at);
×
1873
                    }
1874
                }
1875
            }
1876
        }
1877
        DTFS_Tz::_none => {}
×
1878
    }
1879
    defo!("buffer {:?}", buffer_to_string_noraw(buffer));
35,943✔
1880

1881
    defx!("return {:?}", at);
35,943✔
1882

1883
    at
35,943✔
1884
}
35,943✔
1885

1886
/// Run [`regex::Captures`] on the `data` then convert to a chrono
1887
/// [`Option<DateTime<FixedOffset>>`] instance. Uses matching and pattern
1888
/// information hardcoded in `DATETIME_PARSE_DATAS`.
1889
///
1890
/// [`regex::Captures`]: https://docs.rs/regex/1.10.5/regex/bytes/struct.Regex.html#method.captures
1891
/// [`Option<DateTime<FixedOffset>>`]: https://docs.rs/chrono/0.4.38/chrono/struct.DateTime.html#impl-DateTime%3CFixedOffset%3E
1892
pub fn bytes_to_regex_to_datetime(
283,979✔
1893
    data: &[u8],
283,979✔
1894
    index: &DateTimeParseInstrsIndex,
283,979✔
1895
    year_opt: &Option<Year>,
283,979✔
1896
    systemtime_at_uptime_zero: &Option<SystemTime>,
283,979✔
1897
    tz_offset: &FixedOffset,
283,979✔
1898
    tz_offset_string: &String,
283,979✔
1899
    #[cfg(any(debug_assertions, test))]
283,979✔
1900
    _path: &FPath,
283,979✔
1901
) -> Option<CapturedDtData> {
283,979✔
1902
    defn!("(data {} bytes, index={:?}, year_opt={:?}, tz_offset={:?}, tz_offset_string={:?})",
283,979✔
1903
        data.len(), index, year_opt, tz_offset, tz_offset_string);
283,979✔
1904

1905
    let dtpd: &DateTimeParseInstr = &DATETIME_PARSE_DATAS[*index];
283,979✔
1906
    // here is the regular expression function call!
1907
    defo!("regex #{}: regex_fn({:?})…", dtpd.regex_id, buffer_to_string_noraw(data));
283,979✔
1908
    let captures: MatchesType = match (dtpd.regex_fn)(data) {
283,979✔
1909
        None => {
1910
            defx!(
248,036✔
1911
                "regex: no captures (returned None) for regex #{} at index {}, line {}",
1912
                dtpd.regex_id, index, dtpd._line_num,
1913
            );
1914
            return None;
248,036✔
1915
        }
1916
        Some(captures) => captures,
35,943✔
1917
    };
1918
    defo!("regex #{}: captured {} matches at index {}, line {}",
35,943✔
1919
        dtpd.regex_id, captures.len(), index, dtpd._line_num);
35,943✔
1920
    #[cfg(any(debug_assertions, test))]
1921
    {
1922
        for (i, mi) in captures.iter().enumerate() {
214,266✔
1923
            let mi_name: &str = CGN_ALL[mi.group_index()];
214,266✔
1924
            let m_data = &data[mi.start()..mi.end()];
214,266✔
1925
            let m_data_s = u8_to_str(m_data).unwrap_or("ERROR DECODING");
214,266✔
1926
            defo!("regex: match[{}] = [{}‥{}] = [{:?}] = {:?}", i, mi.start(), mi.end(), mi_name, m_data_s);
214,266✔
1927
        }
1928
    }
1929

1930
    // copy regex matches into a buffer with predictable ordering
1931
    // this ordering relates to datetime format strings in `test_DATETIME_PARSE_DATAS_test_cases`
1932
    const BUFLEN: usize = 35;
1933
    let mut buffer: [u8; BUFLEN] = [0; BUFLEN];
35,943✔
1934
    let copiedn = captures_to_buffer_bytes(
35,943✔
1935
        &mut buffer,
35,943✔
1936
        data,
35,943✔
1937
        &captures,
35,943✔
1938
        year_opt,
35,943✔
1939
        systemtime_at_uptime_zero,
35,943✔
1940
        tz_offset_string,
35,943✔
1941
        &dtpd.dtfs
35,943✔
1942
    );
1943

1944
    // use the `dt_format` to parse the buffer of regex matches
1945
    let buffer_s: &str = match u8_to_str(&buffer[0..copiedn]) {
35,943✔
1946
        Some(s) => s,
35,943✔
1947
        None => {
1948
            defx!("u8_to_str failed to convert slice of {} bytes", copiedn);
×
1949
            return None;
×
1950
        }
1951
    };
1952
    let dt = match datetime_parse_from_str(
35,943✔
1953
        buffer_s,
35,943✔
1954
        dtpd.dtfs.pattern,
35,943✔
1955
        dtpd.dtfs.has_tz(),
35,943✔
1956
        tz_offset,
35,943✔
1957
    ) {
35,943✔
1958
        Some(dt_) => dt_,
35,893✔
1959
        None => {
1960
            defx!("return None; datetime_parse_from_str returned None");
50✔
1961
            return None;
50✔
1962
        }
1963
    };
1964

1965
    // derive the `LineIndex` bounds of the datetime substring within `data`
1966
    let cgi_first: LineIndex = match GROUP_NAMES_MAP_STR
35,893✔
1967
        .get(dtpd.cgn_first) {
35,893✔
1968
        Some(cgi) => *cgi,
35,893✔
1969
        None => panic!("missing cgn_first {:?} in GROUP_NAMES_MAP_STR", dtpd.cgn_first),
×
1970
    };
1971
    let dt_beg: LineIndex = match captures.iter().find(|cgn| cgn.group_index() == cgi_first) {
36,061✔
1972
        Some(match_) => match_.start() as LineIndex,
35,893✔
1973
        None => 0,
×
1974
    };
1975
    let cgi_last: LineIndex = match GROUP_NAMES_MAP_STR
35,893✔
1976
        .get(dtpd.cgn_last) {
35,893✔
1977
        Some(cgi) => *cgi,
35,893✔
1978
        None => panic!("missing cgn_last {:?} in GROUP_NAMES_MAP_STR", dtpd.cgn_last),
×
1979
    };
1980
    let dt_end: LineIndex = match captures.iter().find(|cgn| cgn.group_index() == cgi_last) {
213,646✔
1981
        Some(match_) => match_.end() as LineIndex,
35,893✔
1982
        None => 0,
×
1983
    };
1984
    debug_assert_lt!(dt_beg, dt_end, "bad dt_beg {} dt_end {}, index {}", dt_beg, dt_end, index);
35,893✔
1985

1986
    defx!("return Some({:?}, {:?}, {:?})", dt_beg, dt_end, dt);
35,893✔
1987
    Some((dt_beg, dt_end, dt))
35,893✔
1988
}
283,979✔
1989

1990
// --------------------
1991
// DateTime comparisons
1992

1993
/// Describe the result of comparing one [`DateTimeL`] to one DateTime Filter.
1994
#[allow(non_camel_case_types)]
1995
#[derive(Debug, Eq, PartialEq)]
1996
pub enum Result_Filter_DateTime1 {
1997
    /// like Skip
1998
    Pass,
1999
    /// the `DateTimeL` instance occurs at or after the datetime filter
2000
    OccursAtOrAfter,
2001
    /// the `DateTimeL` instance occurs before the datetime filter
2002
    OccursBefore,
2003
}
2004

2005
impl Result_Filter_DateTime1 {
2006
    /// Returns `true` if the result is `OccursAfter`.
2007
    #[inline(always)]
2008
    pub const fn is_after(&self) -> bool {
×
2009
        matches!(*self, Result_Filter_DateTime1::OccursAtOrAfter)
×
2010
    }
×
2011

2012
    /// Returns `true` if the result is `OccursBefore`.
2013
    #[inline(always)]
2014
    pub const fn is_before(&self) -> bool {
×
2015
        matches!(*self, Result_Filter_DateTime1::OccursBefore)
×
2016
    }
×
2017
}
2018

2019
/// Describe the result of comparing one [`DateTimeL`] to two DateTime Filters
2020
/// `(after, before)`.
2021
#[allow(non_camel_case_types)]
2022
#[derive(Debug, Eq, PartialEq)]
2023
pub enum Result_Filter_DateTime2 {
2024
    /// like Pass
2025
    InRange,
2026
    /// like Fail
2027
    BeforeRange,
2028
    /// like Fail
2029
    AfterRange,
2030
}
2031

2032
impl Result_Filter_DateTime2 {
2033
    #[inline(always)]
2034
    pub const fn is_pass(&self) -> bool {
×
2035
        matches!(*self, Result_Filter_DateTime2::InRange)
×
2036
    }
×
2037

2038
    #[inline(always)]
2039
    pub const fn is_fail(&self) -> bool {
×
2040
        matches!(*self, Result_Filter_DateTime2::AfterRange | Result_Filter_DateTime2::BeforeRange)
×
2041
    }
×
2042
}
2043

2044
/// Compare passed [`DateTimeL`] `dt` to the passed filter `dt_filter`.
2045
///
2046
/// If `dt` is at or after `dt_filter` then return [`Result_Filter_DateTime1::OccursAtOrAfter`]<br/>
2047
/// If `dt` is before `dt_filter` then return [`Result_Filter_DateTime1::OccursBefore`]<br/>
2048
/// Else return [`Result_Filter_DateTime1::Pass`] (including if `dt_filter` is `None`)
2049
pub fn dt_after_or_before(
31,511✔
2050
    dt: &DateTimeL,
31,511✔
2051
    dt_filter: &DateTimeLOpt,
31,511✔
2052
) -> Result_Filter_DateTime1 {
31,511✔
2053
    let dt_a: &DateTimeL = match dt_filter {
31,511✔
2054
        Some(dt_) => dt_,
27,517✔
2055
        None => {
2056
            defñ!("return Pass; (no dt filters)");
3,994✔
2057
            return Result_Filter_DateTime1::Pass;
3,994✔
2058
        }
2059
    };
2060
    if dt < dt_a {
27,517✔
2061
        defñ!("return OccursBefore; (dt {:?} is before dt_filter {:?})", dt, dt_a);
13,398✔
2062
        return Result_Filter_DateTime1::OccursBefore;
13,398✔
2063
    }
14,119✔
2064
    defñ!("return OccursAtOrAfter; (dt {:?} is at or after dt_filter {:?})", dt, dt_a);
14,119✔
2065

2066
    Result_Filter_DateTime1::OccursAtOrAfter
14,119✔
2067
}
31,511✔
2068

2069
/// How does the passed `dt` pass the optional `DateTimeLOpt`
2070
/// filter instances, `dt_filter_after` and `dt_filter_before`?
2071
/// Is `dt` before ([`BeforeRange`]), after ([`AfterRange`]),
2072
/// or in between ([`InRange`])?
2073
///
2074
/// If both filters are `Some` and `dt: DateTimeL` is "between" the filters then
2075
/// return `InRange`.<br/>
2076
/// If before then return `BeforeRange`.<br/>
2077
/// If after then return `AfterRange`.
2078
///
2079
/// If filter `dt_filter_after` is `Some` and `dt: DateTimeL` is after that
2080
/// filter then return `InRange`.<br/>
2081
/// If before then return `BeforeRange`.
2082
///
2083
/// If filter `dt_filter_before` is `Some` and `dt: DateTimeL` is before that
2084
/// filter then return `InRange`.<br/>
2085
/// If after then return `AfterRange`.
2086
///
2087
/// If both filters are `None` then return `InRange`.
2088
///
2089
/// Comparisons are "inclusive" i.e. `dt` == `dt_filter_after` will return
2090
/// `InRange`.
2091
///
2092
/// [`AfterRange`]: crate::data::datetime::Result_Filter_DateTime2::AfterRange
2093
/// [`BeforeRange`]: crate::data::datetime::Result_Filter_DateTime2::BeforeRange
2094
/// [`InRange`]: crate::data::datetime::Result_Filter_DateTime2::InRange
2095
pub fn dt_pass_filters(
4,518✔
2096
    dt: &DateTimeL,
4,518✔
2097
    dt_filter_after: &DateTimeLOpt,
4,518✔
2098
    dt_filter_before: &DateTimeLOpt,
4,518✔
2099
) -> Result_Filter_DateTime2 {
4,518✔
2100
    defn!("({:?}, {:?}, {:?})", dt, dt_filter_after, dt_filter_before);
4,518✔
2101
    match (dt_filter_after, dt_filter_before) {
4,518✔
2102
        (None, None) => {
2103
            defx!("return InRange; (no dt filters)");
4,016✔
2104

2105
            Result_Filter_DateTime2::InRange
4,016✔
2106
        }
2107
        (Some(da), Some(db)) => {
404✔
2108
            debug_assert_le!(da, db, "Bad datetime range values filter_after {:?} {:?} filter_before", da, db);
404✔
2109
            if dt < da {
404✔
2110
                defx!("return BeforeRange");
3✔
2111
                return Result_Filter_DateTime2::BeforeRange;
3✔
2112
            }
401✔
2113
            if db < dt {
401✔
2114
                defx!("return AfterRange");
2✔
2115
                return Result_Filter_DateTime2::AfterRange;
2✔
2116
            }
399✔
2117
            debug_assert_le!(da, dt, "Unexpected range values da dt");
399✔
2118
            debug_assert_le!(dt, db, "Unexpected range values dt db");
399✔
2119
            defx!("return InRange");
399✔
2120

2121
            Result_Filter_DateTime2::InRange
399✔
2122
        }
2123
        (Some(da), None) => {
50✔
2124
            if dt < da {
50✔
2125
                defx!("return BeforeRange");
32✔
2126
                return Result_Filter_DateTime2::BeforeRange;
32✔
2127
            }
18✔
2128
            defx!("return InRange");
18✔
2129

2130
            Result_Filter_DateTime2::InRange
18✔
2131
        }
2132
        (None, Some(db)) => {
48✔
2133
            if db < dt {
48✔
2134
                defx!("return AfterRange");
15✔
2135
                return Result_Filter_DateTime2::AfterRange;
15✔
2136
            }
33✔
2137
            defx!("return InRange");
33✔
2138

2139
            Result_Filter_DateTime2::InRange
33✔
2140
        }
2141
    }
2142
}
4,518✔
2143

2144
// ---------------------------------------------
2145
// other miscellaneous DateTime function helpers
2146

2147
/// Create a new [`DateTimeL`] instance that uses the passed `DateTimeL`
2148
/// month, day, and time, combined with the passed `Year`.
2149
///
2150
/// In case of error, return a copy of the passed `DateTimeL`.
2151
pub fn datetime_with_year(
×
2152
    datetime: &DateTimeL,
×
2153
    year: &Year,
×
2154
) -> DateTimeL {
×
2155
    match datetime.with_year(*year) {
×
2156
        Some(datetime_) => datetime_,
×
2157
        None => *datetime,
×
2158
    }
2159
}
×
2160

2161
/// Convert passed [`SystemTime`] to [`DateTimeL`] with passed [`FixedOffset`].
2162
///
2163
/// [`FixedOffset`]: https://docs.rs/chrono/0.4.38/chrono/offset/struct.FixedOffset.html
2164
/// [`SystemTime`]: std::time::SystemTime
2165
pub fn systemtime_to_datetime(
515✔
2166
    fixedoffset: &FixedOffset,
515✔
2167
    systemtime: &SystemTime,
515✔
2168
) -> DateTimeL {
515✔
2169
    // https://users.rust-lang.org/t/convert-std-time-systemtime-to-chrono-datetime-datetime/7684/6
2170
    let dtu: DateTime<Utc> = (*systemtime).into();
515✔
2171

2172
    dtu.with_timezone(fixedoffset)
515✔
2173
}
515✔
2174

2175
/// Subtract a [`SystemTime`] from a [`DateTimeL`].
2176
pub fn datetime_minus_systemtime(
×
2177
    datetime: &DateTimeL,
×
2178
    systemtime: &SystemTime,
×
2179
) -> Duration {
×
2180
    *datetime - systemtime_to_datetime(
×
2181
        datetime.offset(),
×
2182
        systemtime,
×
2183
    )
×
2184
}
×
2185

2186
/// Convert passed seconds since Unix Epoch to a `SystemTime`.
2187
pub fn seconds_to_systemtime(
74✔
2188
    seconds: &u64,
74✔
2189
) -> SystemTime {
74✔
2190
    let duration = std::time::Duration::from_secs(*seconds);
74✔
2191

2192
    // TODO: [2024/06] handle `None`
2193
    // TODO: if `checked_add` becomes `const` then multiple other functions
2194
    //       could become `const`
2195
    SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()
74✔
2196
}
74✔
2197

2198
/// Return the year of the `systemtime`
2199
pub fn systemtime_year(
5✔
2200
    systemtime: &SystemTime,
5✔
2201
) -> Year {
5✔
2202
    let dtu: DateTime<Utc> = (*systemtime).into();
5✔
2203

2204
    dtu.year()
5✔
2205
}
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc