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

jtmoon79 / super-speedy-syslog-searcher / 26005256403

17 May 2026 11:01PM UTC coverage: 68.041% (-0.1%) from 68.152%
26005256403

push

github

jtmoon79
(CI) update cross targets for linux

15452 of 22710 relevant lines covered (68.04%)

138680.18 hits per line

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

85.94
/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
//! The most relevant constant is [`test_DATETIME_PARSE_DATAS_test_cases`].
26
//!
27
//! [`test_DATETIME_PARSE_DATAS_test_cases`]: self::test_DATETIME_PARSE_DATAS_test_cases
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
    GROUP_NAMES_MAP_STR,
103
    MatchType,
104
    MatchesType,
105
    fos,
106
    REGEX_ALL,
107
    RegexPattern,
108
    DTFSSet,
109
    DTFS_Year,
110
    DTFS_Month,
111
    DTFS_Day,
112
    DTFS_Hour,
113
    DTFS_Minute,
114
    DTFS_Second,
115
    DTFS_Fractional,
116
    DTFS_Tz,
117
    DTFS_Epoch,
118
    DTFS_Uptime,
119
    MINUS_SIGN,
120
    HYPHEN_MINUS,
121
    regex_id_compiled,
122
    O_L,
123
    YEAR_FALLBACKDUMMY_VAL,
124
    YEAR_FALLBACKDUMMY,
125
    Uptime,
126
};
127

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

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

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

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

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

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

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

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

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

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

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

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

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

801
pub const DateTimeParseDatasCompiledCount: usize = 0;
802

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

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

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

901
    true
58,401✔
902
}
58,419✔
903

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

942
    Some(dts)
179,895✔
943
}
179,943✔
944

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

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

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

987
                Some(val)
2,247✔
988
            }
989
            Err(_err) => {
3,079✔
990
                defx!("DateTime::parse_from_str({:?}, {:?}) failed ParseError: {}", data, pattern, _err);
3,079✔
991

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

1014
                val
28,067✔
1015
            }
1016
            Err(_err) => {
1,157✔
1017
                defx!("NaiveDateTime.parse_from_str({:?}, {:?}) failed ParseError: {}", data, pattern, _err);
1,157✔
1018
                return None;
1,157✔
1019
            }
1020
        };
1021
        // second convert the `NaiveDateTime` instance to a `DateTime<FixedOffset>` instance
1022
        match tz_offset
28,067✔
1023
            .from_local_datetime(&dt_naive)
28,067✔
1024
            .earliest()
28,067✔
1025
        {
1026
            Some(val) => {
28,067✔
1027
                defo!(
28,067✔
1028
                    "tz_offset.from_local_datetime({:?}).earliest() extrapolated NaiveDateTime {:?}",
1029
                    dt_naive,
1030
                    val,
1031
                );
1032
                // HACK: workaround chrono Issue #660 by checking for matching begin, end of `data`
1033
                //       and `pattern`
1034
                if !datetime_from_str_workaround_Issue660(data, pattern) {
28,067✔
1035
                    defx!("skip match due to chrono Issue #660, return None");
×
1036
                    return None;
×
1037
                }
28,067✔
1038
                defx!("return {:?}", Some(val));
28,067✔
1039

1040
                Some(val)
28,067✔
1041
            }
1042
            None => {
1043
                defx!("tz_offset.from_local_datetime({:?}, {:?}) returned None, return None", data, pattern);
×
1044
                None
×
1045
            }
1046
        }
1047
    }
1048
}
34,552✔
1049

1050
/// Call [`datetime_parse_from_str`] with a `pattern` containing a timezone.
1051
pub fn datetime_parse_from_str_w_tz(
27✔
1052
    data: &str,
27✔
1053
    pattern: &DateTimePattern_str,
27✔
1054
) -> DateTimeLOpt {
27✔
1055
    datetime_parse_from_str(
27✔
1056
        data,
27✔
1057
        pattern,
27✔
1058
        true,
1059
        &FixedOffset::east_opt(-9999).unwrap(),
27✔
1060
    )
1061
}
27✔
1062

1063
/// Data of interest from a set of [`regex::Captures`] for a datetime
1064
/// substring found in a [`Line`].
1065
///
1066
/// - datetime substring begin index
1067
/// - datetime substring end index
1068
/// - datetime
1069
///
1070
/// [`Line`]: crate::data::line::Line
1071
/// [`regex::Captures`]: https://docs.rs/regex/1.10.5/regex/bytes/struct.Captures.html
1072
// TODO: change to a typed `struct CapturedDtData(...)`
1073
pub type CapturedDtData = (LineIndex, LineIndex, DateTimeL);
1074

1075
/// Macro helper to [`captures_to_buffer_bytes`].
1076
/// - `$cgi_index` is the `GroupsIndex` index into `GROUP_NAMES` for the capture group name.
1077
/// - `$data` is the `&[u8]` data to copy from.
1078
/// - `$captures` is the `Vec<MatchType>` = `Vec<(SpanS4, GroupsIndex)>` = `Vec<({usize, usize}, usize)>` of capture groups from the regex match.
1079
/// - `$buffer` is the `&mut [u8]` buffer to copy into.
1080
/// - `$at` is the `usize` index into `buffer` to copy at and is updated.
1081
macro_rules! copy_capturegroup_to_buffer {
1082
    (
1083
        // `GroupsIndex`
1084
        $cgi_index:ident,
1085
        // `&[u8]`
1086
        $data:ident,
1087
         //  `Vec<MatchType>` = `Vec<(SpanS4, GroupsIndex)>` = `Vec<({usize, usize}, usize)>`
1088
        $captures:ident,
1089
        // `&mut [u8]`
1090
        $buffer:ident,
1091
        // `usize`
1092
        $at:ident
1093
    ) => {
1094
        {
1095
            $captures.iter().find(|cgn| cgn.group_index() == $cgi_index).map(|cgn| {
439,049✔
1096
                let a = cgn.start();
121,750✔
1097
                let b = cgn.end();
121,750✔
1098
                debug_assert_le!(a, b);
121,750✔
1099
                let len_: usize = b - a;
121,750✔
1100
                defo!("copy_capturegroup_to_buffer! buffer[{:?}‥{:?}] = {:?}", $at, $at + len_, &$data[a..b]);
121,750✔
1101
                $buffer[$at..$at + len_].copy_from_slice(&$data[a..b]);
121,750✔
1102
                $at += len_;
121,750✔
1103
            });
121,750✔
1104
        }
1105
    };
1106
}
1107

1108
/// Macro helper to [`captures_to_buffer_bytes`].
1109
macro_rules! copy_slice_to_buffer {
1110
    (
1111
        $u8_slice:expr,
1112
        $buffer:ident,
1113
        $at:ident
1114
    ) => {
1115
        {
1116
            let len_: usize = $u8_slice.len();
1117
            defo!("copy_slice_to_buffer! buffer[{:?}‥{:?}]", $at, $at + len_);
1118
            $buffer[$at..$at + len_].copy_from_slice($u8_slice);
1119
            $at += len_;
1120
        }
1121
    };
1122
}
1123

1124
/// Macro helper to [`captures_to_buffer_bytes`].
1125
macro_rules! copy_u8_to_buffer {
1126
    (
1127
        $u8_:expr,
1128
        $buffer:ident,
1129
        $at:ident
1130
    ) => {
1131
        {
1132
            defo!("copy_slice_to_buffer! buffer[{:?}] = {:?}", $at, $u8_);
1133
            $buffer[$at] = $u8_;
1134
            $at += 1;
1135
        }
1136
    };
1137
}
1138

1139
// Variables `const MONTH_` are helpers to [`month_bB_to_month_m_bytes`].
1140
//
1141
// MONTH_XY_B_l, month XY as `%B` form, lowercase
1142
// MONTH_XY_b_l, month XY as `%b` form, lowercase
1143
// MONTH_XY_B_u, month XY as `%B` form, uppercase
1144
// MONTH_XY_b_u, month XY as `%b` form, uppercase
1145
// MONTH_XY_b_U, month XY as `%b` form, uppercase all
1146

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

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

1323
/// Put [`Captures`] into `buffer` in a particular order and formatting.
1324
/// This is to prepare the regex matched data for passing to a later call to
1325
/// [`DateTime::parse_from_str`] (called outside of this function).
1326
///
1327
/// This bridges the crate `regex` regular expression pattern strings,
1328
/// [`DateTimeParseInstr::regex_pattern`], to crate `chrono` strftime strings,
1329
/// [`DateTimeParseInstr::dtfs`].
1330
///
1331
/// Directly relates to datetime format `dtfs` values in
1332
/// [`test_DATETIME_PARSE_DATAS_test_cases`] which use `DTFSS_YmdHMS`, etc.
1333
///
1334
/// Transforms `%B` acceptable value to `%m` acceptable value.
1335
///
1336
/// Transforms `%e` acceptable value to `%d` acceptable value.
1337
///
1338
/// Transforms an uptime (seconds since system boot) to current seconds offset
1339
/// since UNIX_EPOCH. This allows later conversion via chrono strftime `%s`.
1340
///
1341
/// Transforms timezone offset inidicator MINUS SIGN `−` (U+2212) into
1342
/// HYPHEN-MINUS `-` (U+2D), e.g `−0700` becomes `-0700`.
1343
///
1344
/// [`Captures`]: https://docs.rs/regex/1.10.5/regex/bytes/struct.Captures.html
1345
/// [`DateTime::parse_from_str`]: https://docs.rs/chrono/0.4.38/chrono/struct.DateTime.html#method.parse_from_str
1346
// TODO: allow returning an `Error` instead of `panic!`
1347
#[inline(always)]
1348
pub(crate) fn captures_to_buffer_bytes(
25,835✔
1349
    buffer: &mut [u8],
25,835✔
1350
    data: &[u8],
25,835✔
1351
    captures: &MatchesType,
25,835✔
1352
    year_opt: &Option<Year>,
25,835✔
1353
    systemtime_at_uptime_zero: &Option<SystemTime>,
25,835✔
1354
    tz_offset_string: &String,
25,835✔
1355
    dtfs: &DTFSSet,
25,835✔
1356
) -> usize {
25,835✔
1357
    defn!("(…, …, year_opt {:?}, systemtime_at_uptime_zero {:?}, tz_offset {:?}, …)",
25,835✔
1358
          year_opt, systemtime_at_uptime_zero, tz_offset_string);
1359

1360
    let mut at: usize = 0;
25,835✔
1361

1362
    defo!("process <epoch> {:?}…", dtfs.epoch);
25,835✔
1363
    match dtfs.epoch {
25,835✔
1364
        DTFS_Epoch::s => {
1365
            copy_capturegroup_to_buffer!(CGI_EPOCH, data, captures, buffer, at);
828✔
1366
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
828✔
1367
        }
1368
        DTFS_Epoch::_none => {}
25,007✔
1369
    }
1370

1371
    defo!("process <uptime> {:?}…", dtfs.uptime);
25,835✔
1372
    match dtfs.uptime {
25,835✔
1373
        DTFS_Uptime::u => {
1374
            // Here is where an important conversion happens:
1375
            // get the log uptime string, e.g. `"1.340"`, and convert it to a
1376
            // Duration. Then add that to the `systemtime_at_uptime_zero`.
1377
            // So value `1.340` is added to the wrapped `SystemTime` value.
1378
            // Then that is written into a buffer as seconds since UNIX_EPOCH.
1379
            // Later, in `datetime_parse_from_str`, this buffer is converted to
1380
            // a `DateTime` value.
1381

1382
            // copy the `uptime` capture group to a temporary local buffer
1383
            let mut at_uptime = 0;
18✔
1384
            const BUFLEN: usize = 30;
1385
            let mut buf_uptime: [u8; BUFLEN] = [0; BUFLEN];
18✔
1386
            copy_capturegroup_to_buffer!(CGI_UPTIME, data, captures, buf_uptime, at_uptime);
18✔
1387
            defo!("buf_uptime {:?}", buffer_to_String_noraw(&buf_uptime));
18✔
1388
            // TODO: use `u8_to_str`
1389
            let buf_uptime_s: &str = match std::str::from_utf8(&buf_uptime[..at_uptime]) {
18✔
1390
                Ok(s) => s,
18✔
1391
                Err(_err) => {
×
1392
                    de_err!("uptime str::from_utf8 error: {}", _err);
×
1393
                    // fallback to zero
1394
                    "0"
×
1395
                }
1396
            };
1397
            defo!("buf_uptime_s {:?}", buf_uptime_s);
18✔
1398
            // extract the uptime string to an `Uptime` value
1399
            let uptime_val: Uptime = match buf_uptime_s.parse::<Uptime>() {
18✔
1400
                Ok(uptime_) => uptime_,
18✔
1401
                Err(_err) => {
×
1402
                    de_err!("uptime parse error: {}", _err);
×
1403
                    // fallback to zero
1404
                    0
×
1405
                }
1406
            };
1407
            defo!("uptime_val {:?}", uptime_val);
18✔
1408

1409
            let uptime_zero: SystemTime = match systemtime_at_uptime_zero {
18✔
1410
                Some(val) => *val,
12✔
1411
                None => UPTIME_DEFAULT_OFFSET,
6✔
1412
            };
1413
            defo!("uptime_zero {:?}", uptime_zero);
18✔
1414

1415
            // convert the uptime value to a `std::time::Duration`
1416
            let uptime_dur: StdDuration = StdDuration::new(uptime_val as u64, 0);
18✔
1417
            defo!("uptime_dur {:?}", uptime_dur);
18✔
1418
            // add the uptime value to the uptime_zero value
1419
            let uptime_zero_plus_uptime: SystemTime = match uptime_zero.checked_add(uptime_dur) {
18✔
1420
                Some(st) => st,
18✔
1421
                None => {
1422
                    debug_panic!("failed checked_add({:?})", uptime_dur);
×
1423
                    // I'm not sure what else to do here in a release build
1424

1425
                    UPTIME_DEFAULT_OFFSET
×
1426
                },
1427
            };
1428
            defo!("uptime_zero_plus_uptime {:?}", uptime_zero_plus_uptime);
18✔
1429
            // convert the `uptime_zero_plus_uptime` value to a string to a [u8]
1430
            buf_uptime.fill(0);
18✔
1431
            let uptime_zero_plus_uptime_dur = match uptime_zero_plus_uptime.duration_since(SystemTime::UNIX_EPOCH) {
18✔
1432
                Ok(dur) => dur,
18✔
1433
                Err(_err) => {
×
1434
                    debug_panic!("uptime_zero_plus_uptime.duration_since(UPTIME_DEFAULT_OFFSET) failed: {}", _err);
×
1435
                    // fallback to zero
1436
                    StdDuration::from_secs(0)
×
1437
                }
1438
            };
1439
            let uptime_zero_plus_uptime_n = uptime_zero_plus_uptime_dur.as_secs();
18✔
1440
            defo!("uptime_zero_plus_uptime_n {:?}", uptime_zero_plus_uptime_n);
18✔
1441
            let buf_uptime_plus = uptime_zero_plus_uptime_n.numtoa(10, &mut buf_uptime);
18✔
1442
            defo!("buf_uptime_plus {:?}", buffer_to_String_noraw(buf_uptime_plus));
18✔
1443
            // copy the local temporary buffer to the main buffer
1444
            copy_slice_to_buffer!(&buf_uptime_plus, buffer, at);
18✔
1445
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
18✔
1446
        }
1447
        DTFS_Uptime::_none => {}
25,817✔
1448
    }
1449

1450
    // year
1451
    defo!("process <year> {:?}…", dtfs.year);
25,835✔
1452
    match dtfs.year {
25,835✔
1453
        DTFS_Year::Y
1454
        | DTFS_Year::y => {
1455
            copy_capturegroup_to_buffer!(CGI_YEAR, data, captures, buffer, at);
24,831✔
1456
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
24,831✔
1457
        }
1458
        | DTFS_Year::_fill => {
1459
            let mut found_year: bool = false;
158✔
1460
            captures.iter().find(|cgn| cgn.group_index() == CGI_YEAR).map(|match_type| {
802✔
1461
                let a = match_type.start();
×
1462
                let b = match_type.end();
×
1463
                let year: &[u8] = &data[a..b];
×
1464
                copy_slice_to_buffer!(year, buffer, at);
×
1465
                defo!("buffer {:?}", buffer_to_String_noraw(buffer));
×
1466
                found_year = true;
×
1467
            });
×
1468
            if !found_year{
158✔
1469
                match year_opt {
158✔
1470
                    Some(year) => {
94✔
1471
                        // TODO: 2022/07/11 cost-savings: pass in `Option<&[u8]>`, avoid creating `String`
1472
                        let year_s: String = year.to_string();
94✔
1473
                        debug_assert_eq!(year_s.len(), 4, "Bad year string {:?}", year_s);
94✔
1474
                        defo!("using fallback year {:?}", year_s);
94✔
1475
                        copy_slice_to_buffer!(year_s.as_bytes(), buffer, at);
94✔
1476
                        defo!("buffer {:?}", buffer_to_String_noraw(buffer));
94✔
1477
                    }
1478
                    None => {
1479
                        defo!("using hardcoded dummy year {:?}", YEAR_FALLBACKDUMMY);
64✔
1480
                        copy_slice_to_buffer!(YEAR_FALLBACKDUMMY.as_bytes(), buffer, at);
64✔
1481
                        defo!("buffer {:?}", buffer_to_String_noraw(buffer));
64✔
1482
                    }
1483
                }
1484
            }
×
1485
        }
1486
        DTFS_Year::_none => {}
846✔
1487
    }
1488
    // month
1489
    defo!("process <month> {:?}…", dtfs.month);
25,835✔
1490
    match dtfs.month {
25,835✔
1491
        DTFS_Month::m => {
1492
            copy_capturegroup_to_buffer!(CGI_MONTH, data, captures, buffer, at);
23,101✔
1493
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
23,101✔
1494
        }
1495
        DTFS_Month::ms => {
1496
            let month: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_MONTH).map(|match_type| {
78✔
1497
                let a = match_type.start();
39✔
1498
                let b = match_type.end();
39✔
1499
                &data[a..b]
39✔
1500
            }).expect("missing month capture group");
39✔
1501
            // chrono strftime expects numeric months to be two-digit
1502
            // so prepend `0` if necessary
1503
            match month.len() {
39✔
1504
                1 => {
1505
                    copy_slice_to_buffer!(b"0", buffer, at);
30✔
1506
                    copy_slice_to_buffer!(month, buffer, at);
30✔
1507
                }
1508
                _val => {
9✔
1509
                    debug_assert_eq!(_val, 2, "unexpected Month length {}", _val);
9✔
1510
                    copy_slice_to_buffer!(month, buffer, at);
9✔
1511
                }
1512
            }
1513
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
39✔
1514
        }
1515
        DTFS_Month::b | DTFS_Month::B => {
1516
            let month: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_MONTH).map(|match_type| {
3,546✔
1517
                let a = match_type.start();
1,849✔
1518
                let b = match_type.end();
1,849✔
1519
                &data[a..b]
1,849✔
1520
            }).expect("missing month capture group");
1,849✔
1521
            month_bB_to_month_m_bytes(
1,849✔
1522
                month,
1,849✔
1523
                &mut buffer[at..at + 2],
1,849✔
1524
            );
1525
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
1,849✔
1526
            at += 2;
1,849✔
1527
        }
1528
        DTFS_Month::_none => {}
846✔
1529
    }
1530
    // day
1531
    defo!("process <day> {:?}…", dtfs.day);
25,835✔
1532
    match dtfs.day {
25,835✔
1533
        DTFS_Day::_e_or_d => {
1534
            let day: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_DAY).map(|match_type| {
74,809✔
1535
                let a = match_type.start();
24,989✔
1536
                let b = match_type.end();
24,989✔
1537

1538
                &data[a..b]
24,989✔
1539
            }).expect("missing day capture group");
24,989✔
1540
            debug_assert_ge!(day.len(), 1, "bad named group 'day' data {:?}, expected data ge 1", day);
24,989✔
1541
            debug_assert_le!(day.len(), 2, "bad named group 'day' data {:?}, expected data le 2", day);
24,989✔
1542
            match day.len() {
24,989✔
1543
                1 => {
1544
                    // change day "8" (%e) to "08" (%d)
1545
                    copy_u8_to_buffer!(b'0', buffer, at);
388✔
1546
                    copy_u8_to_buffer!(day[0], buffer, at);
388✔
1547
                    defo!("buffer {:?}", buffer_to_String_noraw(buffer));
388✔
1548
                }
1549
                2 => {
1550
                    match day[0] {
24,601✔
1551
                        b' ' => {
1552
                            // change day " 8" (%e) to "08" (%d)
1553
                            copy_u8_to_buffer!(b'0', buffer, at);
30✔
1554
                            copy_u8_to_buffer!(day[1], buffer, at);
30✔
1555
                        }
1556
                        _ => {
1557
                            copy_slice_to_buffer!(day, buffer, at);
24,571✔
1558
                        }
1559
                    }
1560
                    defo!("buffer {:?}", buffer_to_String_noraw(buffer));
24,601✔
1561
                }
1562
                _ => {
1563
                    panic!("bad day.len() {}", day.len());
×
1564
                }
1565
            }
1566
        }
1567
        DTFS_Day::_none => {}
846✔
1568
    }
1569
    // Day pattern `%a` (`Monday`, 'Tue`, etc.) (capture group `CGN_DAY_IGNORE`) is captured but not
1570
    // passed along to chrono functions.
1571

1572
    // day-time divider
1573
    defo!("process date-time divider…");
25,835✔
1574
    copy_u8_to_buffer!(b'T', buffer, at);
25,835✔
1575
    defo!("buffer {:?}", buffer_to_String_noraw(buffer));
25,835✔
1576
    // hour
1577
    defo!("process <hour> {:?}…", dtfs.hour);
25,835✔
1578
    match dtfs.hour {
25,835✔
1579
        DTFS_Hour::I
1580
        | DTFS_Hour::l
1581
        | DTFS_Hour::H => {
1582
            copy_capturegroup_to_buffer!(CGI_HOUR, data, captures, buffer, at);
24,950✔
1583
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
24,950✔
1584
        }
1585
        DTFS_Hour::k => {
1586
            let hour: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_HOUR).map(|match_type| {
156✔
1587
                let a = match_type.start();
39✔
1588
                let b = match_type.end();
39✔
1589
                &data[a..b]
39✔
1590
            }).expect("missing hour capture group");
39✔
1591
            // chrono strftime expects numeric hour to be two-digit
1592
            // so prepend `0` if necessary
1593
            match hour.len() {
39✔
1594
                1 => {
1595
                    copy_slice_to_buffer!(b"0", buffer, at);
27✔
1596
                    copy_slice_to_buffer!(hour, buffer, at);
27✔
1597
                }
1598
                _val => {
12✔
1599
                    debug_assert_eq!(_val, 2, "unexpected Month length {}", _val);
12✔
1600
                    copy_slice_to_buffer!(hour, buffer, at);
12✔
1601
                }
1602
            }
1603
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
39✔
1604
        }
1605
        DTFS_Hour::_none => {}
846✔
1606
    }
1607
    // minute
1608
    defo!("process <minute> {:?}…", dtfs.minute);
25,835✔
1609
    match dtfs.minute {
25,835✔
1610
        DTFS_Minute::M => {
1611
            copy_capturegroup_to_buffer!(CGI_MINUTE, data, captures, buffer, at);
24,989✔
1612
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
24,989✔
1613
        },
1614
        DTFS_Minute::_none => {}
846✔
1615
    }
1616
    // second
1617
    defo!("process <second> {:?}…", dtfs.second);
25,835✔
1618
    match dtfs.second {
25,835✔
1619
        DTFS_Second::S => {
1620
            copy_capturegroup_to_buffer!(CGI_SECOND, data, captures, buffer, at);
23,033✔
1621
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
23,033✔
1622
        }
1623
        DTFS_Second::_fill => {
1624
            copy_slice_to_buffer!(b"00", buffer, at);
×
1625
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
×
1626
        }
1627
        DTFS_Second::_none => {}
2,802✔
1628
    }
1629
    // fractional
1630
    defo!("process <fractional> {:?}…", dtfs.fractional);
25,835✔
1631
    match dtfs.fractional {
25,835✔
1632
        DTFS_Fractional::f => {
1633
            defo!("matched DTFS_Fractional::f");
1,424✔
1634
            copy_u8_to_buffer!(b'.', buffer, at);
1,424✔
1635
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
1,424✔
1636
            let fractional: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_FRACTIONAL).map(|match_type| {
4,922✔
1637
                let a = match_type.start();
1,424✔
1638
                let b = match_type.end();
1,424✔
1639
                &data[a..b]
1,424✔
1640
            }).expect("missing fractional capture group");
1,424✔
1641
            let len = fractional.len();
1,424✔
1642
            defo!("match len {:?}", len);
1,424✔
1643
            match len {
1,424✔
1644
                0 => {
1645
                    copy_slice_to_buffer!(fractional, buffer, at);
×
1646
                    copy_slice_to_buffer!(b"000000000", buffer, at);
×
1647
                }
1648
                1 => {
1649
                    copy_slice_to_buffer!(fractional, buffer, at);
25✔
1650
                    copy_slice_to_buffer!(b"00000000", buffer, at);
25✔
1651
                }
1652
                2 => {
1653
                    copy_slice_to_buffer!(fractional, buffer, at);
64✔
1654
                    copy_slice_to_buffer!(b"0000000", buffer, at);
64✔
1655
                }
1656
                3 => {
1657
                    copy_slice_to_buffer!(fractional, buffer, at);
213✔
1658
                    copy_slice_to_buffer!(b"000000", buffer, at);
213✔
1659
                }
1660
                4 => {
1661
                    copy_slice_to_buffer!(fractional, buffer, at);
9✔
1662
                    copy_slice_to_buffer!(b"00000", buffer, at);
9✔
1663
                }
1664
                5 => {
1665
                    copy_slice_to_buffer!(fractional, buffer, at);
3✔
1666
                    copy_slice_to_buffer!(b"0000", buffer, at);
3✔
1667
                }
1668
                6 => {
1669
                    copy_slice_to_buffer!(fractional, buffer, at);
933✔
1670
                    copy_slice_to_buffer!(b"000", buffer, at);
933✔
1671
                }
1672
                7 => {
1673
                    copy_slice_to_buffer!(fractional, buffer, at);
×
1674
                    copy_slice_to_buffer!(b"00", buffer, at);
×
1675
                }
1676
                8 => {
1677
                    copy_slice_to_buffer!(fractional, buffer, at);
×
1678
                    copy_slice_to_buffer!(b"0", buffer, at);
×
1679
                }
1680
                9 => {
1681
                    copy_slice_to_buffer!(fractional, buffer, at);
177✔
1682
                }
1683
                10 | 11 | 12 => {
1684
                    // fractional is too large; copy only left-most 9 chars
1685
                    copy_slice_to_buffer!(&fractional[..9], buffer, at);
×
1686
                    de_wrn!("fractional string {:?} is length {} bytes, only copying 9 bytes", fractional, len)
×
1687
                }
1688
                _ => {
1689
                    // something is wrong with this matched string; ignore it
1690
                    de_err!("unexpected fractional string match {:?} length {} bytes", fractional, len)
×
1691
                }
1692
            }
1693
            defo!("buffer {:?}", buffer_to_String_noraw(buffer));
1,424✔
1694
        }
1695
        DTFS_Fractional::_none => {}
24,411✔
1696
    }
1697

1698
    // tz
1699
    defo!("process <tz> {:?}…", dtfs.tz);
25,835✔
1700
    match dtfs.tz {
25,835✔
1701
        DTFS_Tz::_fill => {
1702
            copy_slice_to_buffer!(tz_offset_string.as_bytes(), buffer, at);
22,866✔
1703
        }
1704
        DTFS_Tz::z | DTFS_Tz::zc | DTFS_Tz::zp => {
1705
            // for data passed to chrono `DateTime::parse_from_str`,
1706
            // replace Unicode "minus sign" to ASCII "hyphen-minus"
1707
            // see Issue https://github.com/chronotope/chrono/issues/835
1708
            // XXX: chrono 0.4.27 handles MINUS SIGN (U+2212)
1709
            //      see PR https://github.com/chronotope/chrono/pull/1087
1710
            //      however, keep this code here as it works fine
1711
            let captureb: &[u8] = captures.iter().find(|cgn| cgn.group_index() == CGI_TZ).map(|match_type| {
11,624✔
1712
                let a = match_type.start();
1,516✔
1713
                let b = match_type.end();
1,516✔
1714
                &data[a..b]
1,516✔
1715
            }).expect("missing tz capture group");
1,516✔
1716
            match captureb.starts_with(MINUS_SIGN) {
1,516✔
1717
                true => {
1718
                    defo!("found Unicode 'minus sign', transform to ASCII 'hyphen-minus'");
48✔
1719
                    // found Unicode "minus sign", replace with ASCII
1720
                    // "hyphen-minus"
1721
                    copy_slice_to_buffer!(HYPHEN_MINUS, buffer, at);
48✔
1722
                    defo!("buffer {:?}", buffer_to_String_noraw(buffer));
48✔
1723
                    // copy data remaining after Unicode "minus sign"
1724
                    // TODO: use u8_to_str
1725
                    match std::str::from_utf8(captureb) {
48✔
1726
                        Ok(val) => {
48✔
1727
                            match val.char_indices().nth(1) {
48✔
1728
                                Some((offset, _)) => {
48✔
1729
                                    copy_slice_to_buffer!(val[offset..].as_bytes(), buffer, at);
48✔
1730
                                    defo!("buffer {:?}", buffer_to_String_noraw(buffer));
48✔
1731
                                }
1732
                                None => {
×
1733
                                    // something is wrong with captured value
×
1734
                                    // ignore it
×
1735
                                }
×
1736
                            }
1737
                        }
1738
                        Err(_err) => {
×
1739
                            // something is wrong with captured value, ignore it
×
1740
                        }
×
1741
                    }
1742
                }
1743
                false => {
1744
                    copy_slice_to_buffer!(captureb, buffer, at);
1,468✔
1745
                    defo!("buffer {:?}", buffer_to_String_noraw(buffer));
1,468✔
1746
                }
1747
            }
1748
        }
1749
        DTFS_Tz::Z => {
1750
            #[allow(non_snake_case)]
1751
            let tzZ: &str = u8_to_str(
607✔
1752
                captures.iter().find(|cgn| cgn.group_index() == CGI_TZ).map(|match_type| {
4,688✔
1753
                    let a = match_type.start();
607✔
1754
                    let b = match_type.end();
607✔
1755
                    &data[a..b]
607✔
1756
                }).expect("missing tz capture group")
607✔
1757
            ).unwrap_or_default();
607✔
1758
            if tzZ.is_empty() {
607✔
1759
                debug_panic!("tzZ.is_empty()");
×
1760
                // `u8_to_str` failed, fallback to using passed TZ offset
1761
                copy_slice_to_buffer!(tz_offset_string.as_bytes(), buffer, at);
×
1762
            } else {
1763
                match MAP_TZZ_TO_TZz.get_entry(tzZ) {
607✔
1764
                    Some((_tz_abbr, tz_offset_val)) => {
607✔
1765
                        match tz_offset_val.is_empty() {
607✔
1766
                            true => {
1767
                                // given an ambiguous timezone name, fallback to
1768
                                // passed TZ offset
1769
                                copy_slice_to_buffer!(tz_offset_string.as_bytes(), buffer, at);
×
1770
                            }
1771
                            false => {
1772
                                // given an unambiguous timezone name, use associated offset
1773
                                copy_slice_to_buffer!(tz_offset_val.as_bytes(), buffer, at);
607✔
1774
                            }
1775
                        }
1776
                    }
1777
                    None => {
1778
                        // cannot find entry in MAP_TZZ_TO_TZz, use passed TZ offset
1779
                        debug_panic!("captured named timezone {:?} not found in MAP_TZZ_TO_TZz", tzZ);
×
1780
                        copy_slice_to_buffer!(tz_offset_string.as_bytes(), buffer, at);
×
1781
                    }
1782
                }
1783
            }
1784
        }
1785
        DTFS_Tz::_none => {}
846✔
1786
    }
1787
    defo!("buffer {:?}", buffer_to_String_noraw(buffer));
25,835✔
1788

1789
    defx!("return {:?}", at);
25,835✔
1790

1791
    at
25,835✔
1792
}
25,835✔
1793

1794
/// Run [`regex::Captures`] on the `data` then convert to a chrono
1795
/// [`Option<DateTime<FixedOffset>>`] instance. Uses matching and pattern
1796
/// information hardcoded in [`test_DATETIME_PARSE_DATAS_test_cases`].
1797
///
1798
/// [`regex::Captures`]: https://docs.rs/regex/1.10.5/regex/bytes/struct.Regex.html#method.captures
1799
/// [`Option<DateTime<FixedOffset>>`]: https://docs.rs/chrono/0.4.38/chrono/struct.DateTime.html#impl-DateTime%3CFixedOffset%3E
1800
pub fn bytes_to_regex_to_datetime(
203,349✔
1801
    data: &[u8],
203,349✔
1802
    index: &DateTimeParseInstrsIndex,
203,349✔
1803
    year_opt: &Option<Year>,
203,349✔
1804
    systemtime_at_uptime_zero: &Option<SystemTime>,
203,349✔
1805
    tz_offset: &FixedOffset,
203,349✔
1806
    tz_offset_string: &String,
203,349✔
1807
    #[cfg(any(debug_assertions, test))]
203,349✔
1808
    _path: &FPath,
203,349✔
1809
) -> Option<CapturedDtData> {
203,349✔
1810
    defn!("(data {} bytes, index={:?}, year_opt={:?}, tz_offset={:?}, tz_offset_string={:?})",
203,349✔
1811
        data.len(), index, year_opt, tz_offset, tz_offset_string);
203,349✔
1812

1813
    let dtpd: &DateTimeParseInstr = &DATETIME_PARSE_DATAS[*index];
203,349✔
1814
    // here is the regular expression function call!
1815
    defo!("regex_fn({:?})…", buffer_to_String_noraw(data));
203,349✔
1816
    let captures: MatchesType = match (dtpd.regex_fn)(data) {
203,349✔
1817
        None => {
1818
            defx!(
177,514✔
1819
                "regex: no captures (returned None) for regex #{} at index {}, line {}",
1820
                dtpd.regex_id, index, dtpd._line_num,
1821
            );
1822
            return None;
177,514✔
1823
        }
1824
        Some(captures) => captures,
25,835✔
1825
    };
1826
    defo!("regex: captured {} matches for regex #{} at index {}, line {}",
25,835✔
1827
        captures.len(), dtpd.regex_id, index, dtpd._line_num);
25,835✔
1828
    #[cfg(any(debug_assertions, test))]
1829
    {
1830
        for (i, mi) in captures.iter().enumerate() {
153,501✔
1831
            let mi_name: &str = CGN_ALL[mi.group_index()];
153,501✔
1832
            let m_data = &data[mi.start()..mi.end()];
153,501✔
1833
            let m_data_s = u8_to_str(m_data).unwrap_or("ERROR DECODING");
153,501✔
1834
            defo!("regex: match[{}] = [{}‥{}] = [{:?}] = {:?}", i, mi.start(), mi.end(), mi_name, m_data_s);
153,501✔
1835
        }
1836
    }
1837

1838
    // copy regex matches into a buffer with predictable ordering
1839
    // this ordering relates to datetime format strings in `test_DATETIME_PARSE_DATAS_test_cases`
1840
    const BUFLEN: usize = 35;
1841
    let mut buffer: [u8; BUFLEN] = [0; BUFLEN];
25,835✔
1842
    let copiedn = captures_to_buffer_bytes(
25,835✔
1843
        &mut buffer,
25,835✔
1844
        data,
25,835✔
1845
        &captures,
25,835✔
1846
        year_opt,
25,835✔
1847
        systemtime_at_uptime_zero,
25,835✔
1848
        tz_offset_string,
25,835✔
1849
        &dtpd.dtfs
25,835✔
1850
    );
1851

1852
    // use the `dt_format` to parse the buffer of regex matches
1853
    let buffer_s: &str = match u8_to_str(&buffer[0..copiedn]) {
25,835✔
1854
        Some(s) => s,
25,835✔
1855
        None => {
1856
            defx!("u8_to_str failed to convert slice of {} bytes", copiedn);
×
1857
            return None;
×
1858
        }
1859
    };
1860
    let dt = match datetime_parse_from_str(
25,835✔
1861
        buffer_s,
25,835✔
1862
        dtpd.dtfs.pattern,
25,835✔
1863
        dtpd.dtfs.has_tz(),
25,835✔
1864
        tz_offset,
25,835✔
1865
    ) {
25,835✔
1866
        Some(dt_) => dt_,
25,775✔
1867
        None => {
1868
            defx!("return None; datetime_parse_from_str returned None");
60✔
1869
            return None;
60✔
1870
        }
1871
    };
1872

1873
    // derive the `LineIndex` bounds of the datetime substring within `data`
1874
    let cgi_first = GROUP_NAMES_MAP_STR
25,775✔
1875
        .get(dtpd.cgn_first)
25,775✔
1876
        .expect(&format!("missing cgn_first {:?} in GROUP_NAMES_MAP_STR", dtpd.cgn_first));
25,775✔
1877
    let dt_beg: LineIndex = match captures.iter().find(|cgn| cgn.group_index() == *cgi_first) {
30,999✔
1878
        Some(match_) => match_.start() as LineIndex,
25,775✔
1879
        None => 0,
×
1880
    };
1881
    let cgi_last = GROUP_NAMES_MAP_STR
25,775✔
1882
        .get(dtpd.cgn_last)
25,775✔
1883
        .expect(&format!("missing cgn_last {:?} in GROUP_NAMES_MAP_STR", dtpd.cgn_last));
25,775✔
1884
    let dt_end: LineIndex = match captures.iter().find(|cgn| cgn.group_index() == *cgi_last) {
149,540✔
1885
        Some(match_) => match_.end() as LineIndex,
25,775✔
1886
        None => 0,
×
1887
    };
1888
    debug_assert_lt!(dt_beg, dt_end, "bad dt_beg {} dt_end {}, index {}", dt_beg, dt_end, index);
25,775✔
1889

1890
    defx!("return Some({:?}, {:?}, {:?})", dt_beg, dt_end, dt);
25,775✔
1891
    Some((dt_beg, dt_end, dt))
25,775✔
1892
}
203,349✔
1893

1894
// --------------------
1895
// DateTime comparisons
1896

1897
/// Describe the result of comparing one [`DateTimeL`] to one DateTime Filter.
1898
#[allow(non_camel_case_types)]
1899
#[derive(Debug, Eq, PartialEq)]
1900
pub enum Result_Filter_DateTime1 {
1901
    /// like Skip
1902
    Pass,
1903
    /// the `DateTimeL` instance occurs at or after the datetime filter
1904
    OccursAtOrAfter,
1905
    /// the `DateTimeL` instance occurs before the datetime filter
1906
    OccursBefore,
1907
}
1908

1909
impl Result_Filter_DateTime1 {
1910
    /// Returns `true` if the result is `OccursAfter`.
1911
    #[inline(always)]
1912
    pub const fn is_after(&self) -> bool {
×
1913
        matches!(*self, Result_Filter_DateTime1::OccursAtOrAfter)
×
1914
    }
×
1915

1916
    /// Returns `true` if the result is `OccursBefore`.
1917
    #[inline(always)]
1918
    pub const fn is_before(&self) -> bool {
×
1919
        matches!(*self, Result_Filter_DateTime1::OccursBefore)
×
1920
    }
×
1921
}
1922

1923
/// Describe the result of comparing one [`DateTimeL`] to two DateTime Filters
1924
/// `(after, before)`.
1925
#[allow(non_camel_case_types)]
1926
#[derive(Debug, Eq, PartialEq)]
1927
pub enum Result_Filter_DateTime2 {
1928
    /// like Pass
1929
    InRange,
1930
    /// like Fail
1931
    BeforeRange,
1932
    /// like Fail
1933
    AfterRange,
1934
}
1935

1936
impl Result_Filter_DateTime2 {
1937
    #[inline(always)]
1938
    pub const fn is_pass(&self) -> bool {
×
1939
        matches!(*self, Result_Filter_DateTime2::InRange)
×
1940
    }
×
1941

1942
    #[inline(always)]
1943
    pub const fn is_fail(&self) -> bool {
×
1944
        matches!(*self, Result_Filter_DateTime2::AfterRange | Result_Filter_DateTime2::BeforeRange)
×
1945
    }
×
1946
}
1947

1948
/// Compare passed [`DateTimeL`] `dt` to the passed filter `dt_filter`.
1949
///
1950
/// If `dt` is at or after `dt_filter` then return [`OccursAtOrAfter`]<br/>
1951
/// If `dt` is before `dt_filter` then return [`OccursBefore`]<br/>
1952
/// Else return [`Pass`] (including if `dt_filter` is `None`)
1953
pub fn dt_after_or_before(
36,297✔
1954
    dt: &DateTimeL,
36,297✔
1955
    dt_filter: &DateTimeLOpt,
36,297✔
1956
) -> Result_Filter_DateTime1 {
36,297✔
1957
    let dt_a: &DateTimeL = match dt_filter {
36,297✔
1958
        Some(dt_) => dt_,
31,289✔
1959
        None => {
1960
            defñ!("return Pass; (no dt filters)");
5,008✔
1961
            return Result_Filter_DateTime1::Pass;
5,008✔
1962
        }
1963
    };
1964
    if dt < dt_a {
31,289✔
1965
        defñ!("return OccursBefore; (dt {:?} is before dt_filter {:?})", dt, dt_a);
15,234✔
1966
        return Result_Filter_DateTime1::OccursBefore;
15,234✔
1967
    }
16,055✔
1968
    defñ!("return OccursAtOrAfter; (dt {:?} is at or after dt_filter {:?})", dt, dt_a);
16,055✔
1969

1970
    Result_Filter_DateTime1::OccursAtOrAfter
16,055✔
1971
}
36,297✔
1972

1973
/// How does the passed `dt` pass the optional `DateTimeLOpt`
1974
/// filter instances, `dt_filter_after` and `dt_filter_before`?
1975
/// Is `dt` before ([`BeforeRange`]), after ([`AfterRange`]),
1976
/// or in between ([`InRange`])?
1977
///
1978
/// If both filters are `Some` and `dt: DateTimeL` is "between" the filters then
1979
/// return `InRange`.<br/>
1980
/// If before then return `BeforeRange`.<br/>
1981
/// If after then return `AfterRange`.
1982
///
1983
/// If filter `dt_filter_after` is `Some` and `dt: DateTimeL` is after that
1984
/// filter then return `InRange`.<br/>
1985
/// If before then return `BeforeRange`.
1986
///
1987
/// If filter `dt_filter_before` is `Some` and `dt: DateTimeL` is before that
1988
/// filter then return `InRange`.<br/>
1989
/// If after then return `AfterRange`.
1990
///
1991
/// If both filters are `None` then return `InRange`.
1992
///
1993
/// Comparisons are "inclusive" i.e. `dt` == `dt_filter_after` will return
1994
/// `InRange`.
1995
///
1996
/// [`AfterRange`]: crate::data::datetime::Result_Filter_DateTime2::AfterRange
1997
/// [`BeforeRange`]: crate::data::datetime::Result_Filter_DateTime2::BeforeRange
1998
/// [`InRange`]: crate::data::datetime::Result_Filter_DateTime2::InRange
1999
pub fn dt_pass_filters(
4,796✔
2000
    dt: &DateTimeL,
4,796✔
2001
    dt_filter_after: &DateTimeLOpt,
4,796✔
2002
    dt_filter_before: &DateTimeLOpt,
4,796✔
2003
) -> Result_Filter_DateTime2 {
4,796✔
2004
    defn!("({:?}, {:?}, {:?})", dt, dt_filter_after, dt_filter_before);
4,796✔
2005
    match (dt_filter_after, dt_filter_before) {
4,796✔
2006
        (None, None) => {
2007
            defx!("return InRange; (no dt filters)");
4,250✔
2008

2009
            Result_Filter_DateTime2::InRange
4,250✔
2010
        }
2011
        (Some(da), Some(db)) => {
448✔
2012
            debug_assert_le!(da, db, "Bad datetime range values filter_after {:?} {:?} filter_before", da, db);
448✔
2013
            if dt < da {
448✔
2014
                defx!("return BeforeRange");
3✔
2015
                return Result_Filter_DateTime2::BeforeRange;
3✔
2016
            }
445✔
2017
            if db < dt {
445✔
2018
                defx!("return AfterRange");
2✔
2019
                return Result_Filter_DateTime2::AfterRange;
2✔
2020
            }
443✔
2021
            debug_assert_le!(da, dt, "Unexpected range values da dt");
443✔
2022
            debug_assert_le!(dt, db, "Unexpected range values dt db");
443✔
2023
            defx!("return InRange");
443✔
2024

2025
            Result_Filter_DateTime2::InRange
443✔
2026
        }
2027
        (Some(da), None) => {
50✔
2028
            if dt < da {
50✔
2029
                defx!("return BeforeRange");
32✔
2030
                return Result_Filter_DateTime2::BeforeRange;
32✔
2031
            }
18✔
2032
            defx!("return InRange");
18✔
2033

2034
            Result_Filter_DateTime2::InRange
18✔
2035
        }
2036
        (None, Some(db)) => {
48✔
2037
            if db < dt {
48✔
2038
                defx!("return AfterRange");
15✔
2039
                return Result_Filter_DateTime2::AfterRange;
15✔
2040
            }
33✔
2041
            defx!("return InRange");
33✔
2042

2043
            Result_Filter_DateTime2::InRange
33✔
2044
        }
2045
    }
2046
}
4,796✔
2047

2048
// ---------------------------------------------
2049
// other miscellaneous DateTime function helpers
2050

2051
/// Create a new [`DateTimeL`] instance that uses the passed `DateTimeL`
2052
/// month, day, and time, combined with the passed `Year`.
2053
///
2054
/// In case of error, return a copy of the passed `DateTimeL`.
2055
pub fn datetime_with_year(
×
2056
    datetime: &DateTimeL,
×
2057
    year: &Year,
×
2058
) -> DateTimeL {
×
2059
    match datetime.with_year(*year) {
×
2060
        Some(datetime_) => datetime_,
×
2061
        None => *datetime,
×
2062
    }
2063
}
×
2064

2065
/// Convert passed [`SystemTime`] to [`DateTimeL`] with passed [`FixedOffset`].
2066
///
2067
/// [`FixedOffset`]: https://docs.rs/chrono/0.4.38/chrono/offset/struct.FixedOffset.html
2068
/// [`SystemTime`]: std::time::SystemTime
2069
pub fn systemtime_to_datetime(
425✔
2070
    fixedoffset: &FixedOffset,
425✔
2071
    systemtime: &SystemTime,
425✔
2072
) -> DateTimeL {
425✔
2073
    // https://users.rust-lang.org/t/convert-std-time-systemtime-to-chrono-datetime-datetime/7684/6
2074
    let dtu: DateTime<Utc> = (*systemtime).into();
425✔
2075

2076
    dtu.with_timezone(fixedoffset)
425✔
2077
}
425✔
2078

2079
/// Subtract a [`SystemTime`] from a [`DateTimeL`].
2080
pub fn datetime_minus_systemtime(
×
2081
    datetime: &DateTimeL,
×
2082
    systemtime: &SystemTime,
×
2083
) -> Duration {
×
2084
    *datetime - systemtime_to_datetime(
×
2085
        datetime.offset(),
×
2086
        systemtime,
×
2087
    )
×
2088
}
×
2089

2090
/// Convert passed seconds since Unix Epoch to a `SystemTime`.
2091
pub fn seconds_to_systemtime(
74✔
2092
    seconds: &u64,
74✔
2093
) -> SystemTime {
74✔
2094
    let duration = std::time::Duration::from_secs(*seconds);
74✔
2095

2096
    // TODO: [2024/06] handle `None`
2097
    // TODO: if `checked_add` becomes `const` then multiple other functions
2098
    //       could become `const`
2099
    SystemTime::UNIX_EPOCH.checked_add(duration).unwrap()
74✔
2100
}
74✔
2101

2102
/// Return the year of the `systemtime`
2103
pub fn systemtime_year(
5✔
2104
    systemtime: &SystemTime,
5✔
2105
) -> Year {
5✔
2106
    let dtu: DateTime<Utc> = (*systemtime).into();
5✔
2107

2108
    dtu.year()
5✔
2109
}
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