now

dbohdan 2017-08-23: now is a little date and time calculator utility for the command line. It leverages Tcl's clock add for date arithmetic.

Download with wiki-reaper: wiki-reaper -x 48963 1 | tee now.tcl

Use examples

$ now
Wed Aug 23 19:46:15 DST 2017
$ now -3491 days
Fri Feb 01 19:46:37 STD 2008
$ now -timezone :UTC -9 years -6 months -22 days +5 hours 5 minutes
Fri Feb 01 22:54:59 UTC 2008

Code

#! /usr/bin/env tclsh
# now v0.4.0 copyright (c) 2017-2018, 2021 D. Bohdan. License: MIT.
package require Tcl 8.5 9

proc usage {} {
    puts stderr [format {usage: %s [-format fmt] [-gmt 0/1] [-locale loc]\
                         [-timezone tz] [count unit ...]} \
                        [file tail [info script]]]
}

# Parse the command line options. Give an informative message on error.
proc parse opts {
    set formatOpts {}
    while {[llength $opts] > 0 &&
           ![string is integer -strict [lindex $opts 0]]} {
        set opts [lassign $opts k v]
        switch -exact -- $k {
            -h         -
            --help     { return -code 1 -errorcode {PARSE usage} }
            -format    -
            -gmt       -
            -locale    -
            -timezone  { lappend formatOpts $k $v }
            default    {
                return -code 1 -errorcode {PARSE unknownOption} \
                       "unknown option \"$k\""
            }
        }
    }

    set delta {}
    foreach {count unit} $opts {
        set units {
            y    year
            m    month
            d    day
            h    hour
            min  minute
            s    second
        }

        if {![string is integer -strict $count]} {
            return -code 1 "expected integer count, but got \"$count\""
        }

        set unit [string tolower $unit]
        # Strip "s" at the end, but only if $unit isn't just "s".
        regexp ^(.+?)s?$ $unit _ unit
        # Expand abbreviations.
        if {[dict exists $units $unit]} {
            set unit [dict get $units $unit]
        }

        if {$unit eq {}} {
            error "no unit given for count $count"
        } elseif {$unit ni [dict values $units]} {
            error [format {unknown unit "%s", must be "year" ("y"),\
                           "month" ("m"), "day" ("d"), "hour" ("h"),\
                           "minute" ("min") or "second" ("s") with an optional\
                           suffix "-s"} \
                          $unit]
        }

        lappend delta $count $unit
    }

    return [list $formatOpts $delta]
}

# Output the date and time.
if {[catch {
    lassign [parse $argv] formatOpts delta
    puts [clock format [clock add [clock seconds] {*}$delta] {*}$formatOpts]
} res errOpts]} {
    set ec [dict get $errOpts -errorcode]
    if {$ec eq {PARSE usage}} {
        usage
        exit 0
    } else {
        puts stderr "error: $res"
        if {$ec eq {PARSE unknownOption}} {
            usage
        }
        exit 1
    }
}