tiempo 1.6.0

A command line time tracker
Documentation
use std::io::Write;

use chrono::{DateTime, Utc, Local, LocalResult, TimeZone};
use ansi_term::{Color::Yellow, Style};

use crate::error::{Error::*, Result};
use crate::models::Entry;
use crate::database::{Database, DBVersion};
use crate::env::Env;

/// Treat t as if it wasnt actually in Utc but in the local timezone and return
/// the actual Utc time.
///
/// Used to convert times from the old database version.
fn local_to_utc(t: DateTime<Utc>) -> Result<DateTime<Utc>> {
    let local_time = match Local.from_local_datetime(&t.naive_utc()) {
        LocalResult::None => return Err(NoneLocalTime(t.naive_utc().to_string())),
        LocalResult::Single(t) => t,
        LocalResult::Ambiguous(t1, t2) => return Err(AmbiguousLocalTime {
            orig: t.naive_utc().to_string(),
            t1: t1.naive_local(),
            t2: t2.naive_local(),
        }),
    };

    Ok(Utc.from_utc_datetime(&local_time.naive_utc()))
}

/// takes an otherwise perfectly good timestamp in Utc and turns it into the
/// local timezone, but using the same DateTime<Utc> type.
///
/// Used to insert times into the old database format.
fn utc_to_local(t: DateTime<Utc>) -> DateTime<Utc> {
    Utc.from_utc_datetime(&t.with_timezone(&Local).naive_local())
}

/// Maps an entire vector of entries from the old database format to the new,
/// converting their timestamps from the local timezone to Utc.
fn local_to_utc_vec(entries: Vec<Entry>) -> Result<Vec<Entry>> {
    entries
        .into_iter()
        .map(|e| {
            Ok(Entry {
                start: local_to_utc(e.start)?,
                end: e.end.map(local_to_utc).transpose()?,
                ..e
            })
        })
        .collect()
}

/// the logic used by many subcommands that transform entries from the old
/// format to the new. Used in conjunction with warn_if_needed.
pub fn entries_or_warning<D: Database>(entries: Vec<Entry>, db: &D) -> Result<(Vec<Entry>, bool)> {
    if let DBVersion::Timetrap = db.version()? {
        // this indicates that times in the database are specified in the
        // local time and need to be converted to utc before displaying

        Ok((local_to_utc_vec(entries)?, true))
    } else {
        Ok((entries, false))
    }
}

/// Wrapper around utc_to_local that also returns a flag in case a warning is
/// needed
pub fn time_or_warning<D: Database>(time: DateTime<Utc>, db: &D) -> Result<(DateTime<Utc>, bool)> {
    if let DBVersion::Timetrap = db.version()? {
        Ok((utc_to_local(time), true))
    } else {
        Ok((time, false))
    }
}

/// emits the appropiate warning if the old database format was detected.
pub fn warn_if_needed<E: Write>(err: &mut E, needs_warning: bool, env: &Env) -> Result<()> {
    if needs_warning && !env.supress_warming {
        writeln!(
            err,
            "{} You are using the old timetrap format, it is advised that \
            you update your database using t migrate. To supress this warning \
            set TIEMPO_SUPRESS_TIMETRAP_WARNING=1",
            if env.stderr_is_tty {
                Yellow.bold().paint("[WARNING]")
            } else {
                Style::new().paint("[WARNING]")
            },
        ).map_err(IOError)?;
    }

    Ok(())
}