-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Adrian Ho
committed
Jan 27, 2019
1 parent
02e6767
commit 625ddd9
Showing
2 changed files
with
213 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |