root/src/lib.rs

// lib.rs
//
// Copyright 2021-2023 nee <nee-git@patchouli.garden>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-or-later

pub mod date;
pub mod time;

pub use crate::date::*;
pub use crate::time::*;

pub fn print_format(fr: &FrDate, dt: &DecimalTime, format: &str) {
    let parts = format.split('%');
    let mut buffer: String = String::new();
    for (count, p) in parts.enumerate() {
        // not a format
        if count == 0 && !format.starts_with('%') {
            buffer.push_str(p);
            // %%   a literal %
        } else if p.is_empty() && (count != 0 || format.starts_with("%%")) {
            buffer.push('%');
            // CUSTOM FORMATS
            // %dth   day with 'nth' attachment (e.g., 2nd)
        } else if let Some(text) = p.strip_prefix("dth") {
            buffer.push_str(&(fr.day + 1).to_string());
            buffer.push_str(nth_str(fr.day));
            buffer.push_str(text);
            // %di   day as number without prefix (e.g., 2)
        } else if let Some(text) = p.strip_prefix("di") {
            buffer.push_str(&(fr.day + 1).to_string());
            buffer.push_str(text);
            // %L   Link to wikipedia for thing of the day (e.g., https://en.wikipedia.org/wiki/Lily_of_the_valley)
        } else if let Some(text) = p.strip_prefix('L') {
            buffer.push_str(&day_wiki_link(fr.day_name()));
            buffer.push_str(text);

            // TODO find some other %name for this.
            // %WT   label of the thing of the day (e.g., Lily of the valley)
        } else if let Some(text) = p.strip_prefix("name") {
            buffer.push_str(day_wiki_label(fr.day_name()));
            buffer.push_str(text);

            // %a   locale's abbreviated weekday name (e.g., Sun)
        } else if let Some(text) = p.strip_prefix('a') {
            buffer.push_str(fr.day_of_week_name_short());
            buffer.push_str(text);

            // %A   locale's full weekday name (e.g., Sunday)
        } else if let Some(text) = p.strip_prefix('A') {
            buffer.push_str(fr.day_of_week_name());
            buffer.push_str(text);
            // %b   locale's abbreviated month name (e.g., Jan)
            // %h   same as %b
        } else if let Some(text) = p.strip_prefix('b').or(p.strip_prefix('h')) {
            buffer.push_str(fr.month_name_short());
            buffer.push_str(text);
            // %B   locale's full month name (e.g., January)
        } else if let Some(text) = p.strip_prefix('B') {
            buffer.push_str(fr.month_name());
            buffer.push_str(text);
            // %c   locale's date and time (e.g., Thu Mar  3 23:05:25 2005) TODO
            // %C   century; like %Y, except omit last two digits (e.g., 20)
        } else if let Some(text) = p.strip_prefix('C') {
            buffer.push_str(&(fr.year - (fr.year % 100)).to_string());
            buffer.push_str(text);
            // %d   day of month (e.g., 01)
        } else if let Some(text) = p.strip_prefix('d') {
            if fr.day < 10 {
                buffer.push('0');
            }
            buffer.push_str(&fr.day.to_string());
            buffer.push_str(text);
            // %D   date; same as %m/%d/%y
        } else if let Some(text) = p.strip_prefix('D') {
            buffer.push_str(&fr.month.to_string());
            buffer.push('/');
            buffer.push_str(&fr.day.to_string());
            buffer.push('/');
            buffer.push_str(&fr.year.to_string());
            buffer.push_str(text);
            // %e   day of month, space padded; same as %_d
        } else if let Some(text) = p.strip_prefix('e') {
            if fr.day < 10 {
                buffer.push(' ');
            }
            buffer.push_str(&fr.day.to_string());
            buffer.push_str(text);
            // %f   fractional time (percentage of the day passed)
        } else if let Some(text) = p.strip_prefix('f') {
            buffer.push_str(&dt.fraction().to_string());
            buffer.push_str(text);
            // %F   full date; same as %Y-%m-%d
        } else if let Some(text) = p.strip_prefix('F') {
            buffer.push_str(&fr.year.to_string());
            buffer.push('-');
            buffer.push_str(&fr.month.to_string());
            buffer.push('-');
            buffer.push_str(&fr.day.to_string());
            buffer.push_str(text);
            // %g   last two digits of year of ISO week number (see %G) TODO
            // %G   year of ISO week number (see %V); normally useful only with %V TODO

            // %H   hour (00..23)
            // %I   hour (01..12) - same as %H
        } else if let Some(text) = p.strip_prefix('H').or(p.strip_prefix('I')) {
            buffer.push_str(&dt.hours().to_string());
            buffer.push_str(text);
            // %j   day of year (001..366)
        } else if let Some(text) = p.strip_prefix('j') {
            buffer.push_str(&(fr.ordinal() + 1).to_string());
            buffer.push_str(text);
            // %k   hour, space padded ( 0..23); same as %_H
            // %l   hour, space padded ( 1..12); same as %_I
        } else if let Some(text) = p.strip_prefix('k').or(p.strip_prefix('l')) {
            // not sure if this is a good idea
            buffer.push(' ');
            buffer.push_str(&dt.hours().to_string());
            buffer.push_str(text);
            // %m   month (01..12)
        } else if let Some(text) = p.strip_prefix('m') {
            buffer.push_str(&(fr.month + 1).to_string());
            buffer.push_str(text);
            // %M   minute (00..59)
        } else if let Some(text) = p.strip_prefix('M') {
            buffer.push_str(&dt.minutes().to_string());
            buffer.push_str(text);
            // %n   a newline
        } else if let Some(text) = p.strip_prefix('n') {
            buffer.push('\n');
            buffer.push_str(text);
            // %N   nanoseconds (000000000..999999999)
        } else if let Some(text) = p.strip_prefix('N') {
            buffer.push_str(&dt.standard_nanoseconds().to_string());
            buffer.push_str(text);
            // %p   locale's equivalent of either AM or PM; blank if not known
            // %P   like %p, but lower case
        } else if let Some(text) = p.strip_prefix('p').or(p.strip_prefix('P')) {
            buffer.push_str(text); // NO AM/PM
                                   // %q   quarter of year (1..4) TODO
                                   // } else if let Some(text) = p.strip_prefix('q') {
                                   //     buffer.push_str(&((fr.month as f32 / 4.0).round()+1).to_string());
                                   //     buffer.push_str(text);
                                   // %R   24-hour hour and minute; same as %H:%M
        } else if let Some(text) = p.strip_prefix('R') {
            buffer.push_str(&dt.hours().to_string());
            buffer.push(':');
            buffer.push_str(&dt.minutes().to_string());
            buffer.push_str(text);

            // %s   seconds since 1970-01-01 00:00:00 UTC

            // } else if let Some(text) = p.strip_prefix('s') {
            //     // TODO TIMEZONE
            //     // TODO LEAP YEARS
            //     let year_secs = ((fr.year - 179) * SECONDS_IN_YEAR) + dt.secs;
            //     buffer.push_str(&year_secs.to_string());
            //     buffer.push_str(text);

            // %S   second (00..60)
        } else if let Some(text) = p.strip_prefix('S') {
            buffer.push_str(&dt.seconds().to_string());
            buffer.push_str(text);
            // %t   a tab
        } else if let Some(text) = p.strip_prefix('t') {
            buffer.push('\t');
            buffer.push_str(text);

            // %T   time; same as %H:%M:%S
            // %r   locale's 12-hour clock time (e.g., 11:11:04 PM)
        } else if let Some(text) = p.strip_prefix('T').or(p.strip_prefix('r')) {
            buffer.push_str(&dt.hours().to_string());
            buffer.push(':');
            buffer.push_str(&dt.minutes().to_string());
            buffer.push(':');
            buffer.push_str(&dt.seconds().to_string());
            buffer.push_str(text);
            // %u   day of week (1..7); 1 is Monday
        } else if let Some(text) = p.strip_prefix('u') {
            buffer.push_str(&(fr.day_of_week() + 1).to_string());
            buffer.push_str(text);
            // %U   week number of year, with Sunday as first day of week (00..53) TODO
            // %W   week number of year, with Monday as first day of week (00..53) TODO
            // %V   ISO week number, with Monday as first day of week (01..53) TODO
            // %w   day of week (0..6); 0 is Sunday
        } else if let Some(text) = p.strip_prefix('w') {
            buffer.push_str(&fr.day_of_week().to_string());
            buffer.push_str(text);
            // %x   locale's date representation (e.g., 12/31/99) TODO
            // %X   locale's time representation (e.g., 23:13:48) TODO

            // %y   last two digits of year (00..99)
        } else if let Some(text) = p.strip_prefix('y') {
            buffer.push_str(&(fr.year % 100).to_string());
            buffer.push_str(text);
            // %Y   year
        } else if let Some(text) = p.strip_prefix('Y') {
            buffer.push_str(&fr.year.to_string());
            buffer.push_str(text);
            // %z   +hhmm numeric time zone (e.g., -0400) TODO
            // %:z  +hh:mm numeric time zone (e.g., -04:00) TODO
            // %::z  +hh:mm:ss numeric time zone (e.g., -04:00:00) TODO
            // %:::z  numeric time zone with : to necessary precision (e.g., -04, +05:30) TODO
            // %Z   alphabetic time zone abbreviation (e.g., EDT) TODO
        } else {
            buffer.push_str(p);
        }
    }
    println!("{}", buffer);
}

pub fn now() -> (DecimalTime, FrDate) {
    let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
    let dt = crate::DecimalTime::from(now.naive_local().time());
    let fr = crate::FrDate::from(now.naive_local().date());
    (dt, fr)
}

fn nth_str(n: u32) -> &'static str {
    match n {
        1 => "st",
        2 => "nd",
        3 => "rd",
        _ => "th",
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn hi() {
        let (dt, fr) = now();
        crate::print_format(
            &fr,
            &dt,
            "It's %T, today is the %dth of %B in %Y. It's the day of the %name.",
        );
    }
}