yz_diary_date/
lib.rs

1// Copyright (c) 2021-2024 Alain Emilia Anna Zscheile
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use chrono::NaiveDate;
5use std::path::Path;
6
7#[cfg(test)]
8mod tests;
9
10fn fti_digits(s: &str) -> bool {
11    s.len() >= 2 && s.chars().take(2).all(|i| i.is_ascii_digit())
12}
13
14/// usually, `parse_from_path` should be used instead of this low-level function
15pub fn parse(par: &str, fin: &str) -> Option<NaiveDate> {
16    if !fti_digits(fin) || par.len() < 4 {
17        return None;
18    }
19
20    let y = par.parse::<i32>().ok()?;
21    let m = fin[..2].parse::<u32>().unwrap();
22
23    // verify the day info
24    let mut dayinf = &fin[2..];
25    if dayinf.starts_with(|i| matches!(i, '-' | '_')) {
26        dayinf = &dayinf[1..];
27    }
28    if !fti_digits(dayinf) {
29        return None;
30    }
31    let d = dayinf[..2].parse::<u32>().unwrap();
32
33    chrono::NaiveDate::from_ymd_opt(y, m, d)
34}
35
36/// tries to parse a diary entry path to extract the reference date
37/// should be usually given a path containing at least 2 components
38pub fn parse_from_path(x: &Path) -> Option<NaiveDate> {
39    let mut fin = x;
40    let mut fins = fin.file_name()?.to_str()?;
41
42    // we allow 1 additional path component after the date part
43    if !fti_digits(fins) {
44        fin = x.parent()?;
45        fins = fin.file_name()?.to_str()?;
46        if !fti_digits(fins) {
47            return None;
48        }
49    }
50
51    let par = fin.parent()?.file_name()?.to_str()?;
52    parse(par, fins)
53}
54
55/// tries to parse a diary entry path to extract the reference date
56/// should be usually given a path containing at least 2 components
57/// but additionally requires that the whole path must be UTF-8
58#[cfg(feature = "camino")]
59pub fn parse_from_utf8path(x: &camino::Utf8Path) -> Option<NaiveDate> {
60    let mut fin = x;
61    let mut fins = fin.file_name()?;
62
63    // we allow 1 additional path component after the date part
64    if !fti_digits(fins) {
65        fin = x.parent()?;
66        fins = fin.file_name()?;
67        if !fti_digits(fins) {
68            return None;
69        }
70    }
71
72    let par = fin.parent()?.file_name()?;
73    parse(par, fins)
74}
75
76pub fn fmt(date: &NaiveDate, daysep: Option<char>) -> String {
77    let tmp;
78    date.format(if let Some(daysep) = daysep {
79        assert!(matches!(daysep, '-' | '_'));
80        tmp = format!("%Y/%m{}%d", daysep);
81        &tmp
82    } else {
83        "%Y/%m%d"
84    })
85    .to_string()
86}