Skip to content

Commit

Permalink
First release
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrian Ho committed Jan 27, 2019
1 parent 02e6767 commit 625ddd9
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 1 deletion.
59 changes: 58 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,59 @@
# dateh
GNU date for humans
A GNU date wrapper that adds several format specifiers that may prove useful to some folks.

The first set deals with relative date outputs (English only):

| Spec | Description |
| ---- | ----------- |
| `@{d}` | relative date, abbrev date names (e.g. yesterday, next Fri, 17 days ago) |
| `@{D}` | like @{d}, only with full date names (e.g. next Friday, 17 days' time) |
| `@{d+}` | like @{d}, but falls back to user-configurable date representation if outside 1 week's range (default: %Y-%m-%d) |
| `@{w}` | relative week (e.g. last week, 3 weeks' time) |
| `@{m}` | relative month (e.g. last month, 3 months' time) |
| `@{y}` | relative year (e.g. last year, 3 years' time) |
| `@{h}` | auto-select relative representation (abbreviated day name) |
| `@{H}` | auto-select relative representation (full day name) |

*NOTE*: `@{d}` and `@{D}` outputs follow GNU `date` conventions for relative date input:
* `last XYZ` for dates up to 7 days ago
* `next XYZ` for dates up to 7 days in the future

The second set deals with ordinal day-of-month representations:

| Spec | Description |
| ---- | ----------- |
| `@{o}` | ordinal day-of-month, short-form (e.g. 29th) |
| `@{O}` | ordinal day-of-month, long-form (e.g. twenty-ninth) |

## Dependencies

GNU `date` must be in your `PATH`.

## Installation

Copy `dateh` to a directory in your `PATH`.

## Usage

`dateh` takes the same options and format specifiers as GNU `date`, in addition to:
* `-h|--help` to print `dateh`-specific help
* `-H|--longhelp` to print both `dateh` and `date` help

If the `DATEH_DEFAULT_FORMAT` environment variable is set, `dateh` uses its value as the fallback date representation for the `@{d+}` specifier (default: `%Y-%m-%d`).

## Examples

```
$ dateh
Sun Jan 27 20:05:00 +08 2019
$ dateh -d "now" "+@{D} %X"
today 20:05:00
$ dateh -d "now + 3 weeks" "+the @{O} of %B, %Y"
the seventeenth of February, 2019
$ DATEH_DEFAULT_FORMAT=%m/%d/%Y dateh -d "last month" \
"+@{d+}, @{w}, @{m}, @{y} %H:%M %P"
12/27/2018, 4 weeks ago, last month, last year 20:05 pm
```
155 changes: 155 additions & 0 deletions dateh
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/usr/bin/env bash
DATEH_DEFAULT_FORMAT="${DATEH_DEFAULT_FORMAT:-%Y-%m-%d}"
short_ordinals=(invalid 1st 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th
12th 13th 14th 15th 16th 17th 18th 19th 20th 21st 22nd 23rd 24th 25th
26th 27th 28th 29th 30th 31st)
long_ordinals=(invalid first second third fourth fifth sixth seventh
eighth ninth tenth eleventh twelfth thirteenth fourteenth fifteenth
sixteenth seventeenth eighteenth nineteenth twentieth twenty-first
twenty-second twenty-third twenty-fourth twenty-fifth twenty-sixth
twenty-seventh twenty-eighth twenty-ninth thirtieth thirty-first)

# human_date [<date command options>]
human_date() {
local date_args=("${@:1:$(($# - 1))}") format_spec="${@: -1}" h_date h_week h_month h_year
local date_secs=$(date -u "${date_args[@]}" +%s) now_secs=$(date -u +%s)
[[ $(date "${date_args[@]}" +%Y-%m-%d-%a-%A) =~ (.*)-(.*)-(.*)-(.*)-(.*) ]] &&
local date_year="${BASH_REMATCH[1]}" date_month="${BASH_REMATCH[2]}" date_dom="${BASH_REMATCH[3]}" \
date_dow="${BASH_REMATCH[4]}" date_DOW="${BASH_REMATCH[5]}"
[[ $(date +%Y-%m) =~ (.*)-(.*) ]] && local now_year="${BASH_REMATCH[1]}" now_month="${BASH_REMATCH[2]}"

### ORDINAL DAYS OF MONTH
format_spec="${format_spec//@\{o\}/${short_ordinals[${date_dom##0}]}}"
format_spec="${format_spec//@\{O\}/${long_ordinals[${date_dom##0}]}}"

### RELATIVE DAYS
local date_days=$((date_secs / 86400)) now_days=$((now_secs / 86400))
# Now let's look at the difference in days
case $(( date_days - now_days )) in
-[2-7])
h_date="last $date_dow"
h_DATE="last $date_DOW"
;;
-1)
h_date="yesterday"
;;
0)
h_date="today"
;;
1)
h_date="tomorrow"
;;
[2-7])
h_date="next $date_dow"
h_DATE="next $date_DOW"
;;
*)
# Outside one week's range, we enable two different representations
h_date="$(relative_interval day $((date_days - now_days)))"
h_dateplus="$(date "${date_args[@]}" +"${DATEH_DEFAULT_FORMAT:-%Y-%m-%d}")"
;;
esac
format_spec="${format_spec//@\{d\}/${h_date}}"
format_spec="${format_spec//@\{d+\}/${h_dateplus:-${h_date}}}"
format_spec="${format_spec//@\{D\}/${h_DATE:-${h_date}}}"

### RELATIVE WEEKS
# Weeks are counted from Unix epoch (midnight 1970-01-01),
# with midnight 1970-01-05 (Mon) being the start of week 1
local date_weeks=$(((date_days + 3) / 7)) now_weeks=$(((now_days + 3) / 7))
h_week="$(relative_interval week $((date_weeks - now_weeks)))"
format_spec="${format_spec//@\{w\}/${h_week}}"

### RELATIVE MONTHS & YEARS
h_month="$(relative_interval month $(((date_year * 12 + date_month) - (now_year * 12 + now_month))))"
h_year="$(relative_interval year $((date_year - now_year)))"
format_spec="${format_spec//@\{m\}/${h_month}}"
format_spec="${format_spec//@\{y\}/${h_year}}"

### AUTO-SELECT
if [[ "$h_year" == [1-9]* ]]; then
h_auto="$h_year"
elif [[ "$h_month" == [1-9]* ]]; then
h_auto="$h_month"
elif [[ "$h_week" == [1-9]* ]]; then
h_auto="$h_week"
else
h_auto="$h_date"
h_AUTO="$h_DATE"
fi
format_spec="${format_spec//@\{h\}/${h_auto}}"
format_spec="${format_spec//@\{H\}/${h_AUTO:-${h_auto}}}"

# OK, now run the format_spec through GNU date for final result
date "${date_args[@]}" "${format_spec}"
}

# relative_interval <week|month|year> <interval_offset>
relative_interval() {
case "$2" in
-1) echo "last ${1}";;
0) echo "this ${1}";;
1) echo "next ${1}";;
*) [[ $2 -lt 0 ]] && echo "${2#-} ${1}s ago" || echo "${2} ${1}s' time";;
esac
}

usage() {
cat <<EOF
USAGE: $0 [OPTION]... [+FORMAT]
or: $0 [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]
Display the current time in the given FORMAT, or set the system date.
NOTE: This replicates GNU date functionality, and adds the following
format sequences:
@{d} relative date w/ abbrev day name (e.g. yesterday, next Fri, 17 days ago)
@{D} relative date w/ full day name (e.g. next Friday, 17 days' time)
@{d+} like @{d}, unless ref date exceeds now +/- 1 week, then use
\$DATEH_DEFAULT_FORMAT instead (default: $DATEH_DEFAULT_FORMAT))
@{w} relative week (e.g. last week, 3 weeks' time)
@{m} relative month (e.g. last month, 3 months' time)
@{y} relative year (e.g. last year, 3 years' time)
@{h} auto-select relative representation (abbreviated day name)
@{H} auto-select relative representation (full day name)
@{o} short ordinal day of month (e.g. 1st, 25th)
@{O} long ordinal day of month (e.g. first, twenty-fifth)
Examples:
$0
$0 -d "now" "+@{D} %X"
$0 -d "now + 3 weeks" "+the @{O} of %B, %Y"
DATEH_DEFAULT_FORMAT=%m/%d/%Y $0 -d "last month" \\
"+@{d+}, @{w}, @{m}, @{y} %H:%M %P"
EOF
if [[ $1 == long ]]; then
cat <<EOF
..... GNU date help follows .....
$(date --help)
EOF
else
cat <<EOF
See date(1) man page or \`$0 --longhelp\` for GNU date format sequences.
EOF
fi
exit 0
}

while true; do
case "$1" in
-h|--help) usage;;
-H|--longhelp) usage long;;
*) break;;
esac
shift
done

if [[ $# -eq 0 || "${@: -1}" != +*\@\{*\}* ]]; then
# No human format specifier, just call "date" as normal
date "$@"
else
human_date "$@"
fi

0 comments on commit 625ddd9

Please sign in to comment.