-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
Showing
2 changed files
with
274 additions
and
0 deletions.
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 |
---|---|---|
@@ -0,0 +1,215 @@ | ||
#!/usr/bin/env bash | ||
# @Function | ||
# Convert unix time to human readable date string. | ||
# Note: The range of the 10-digit unix time in second include recent date. | ||
# 1000000000: 2001-09-09 01:46:40 +0000 | ||
# 9999999999: 2286-11-20 17:46:39 +0000 | ||
# | ||
# @Usage | ||
# $ uxt 0 # date 1970-01-01 00:00:00 GMT | ||
# 1970-01-01 08:00:00 +0800 | ||
# # for large integer, default auto treat first 10 digits as second | ||
# $ uxt 1234567890 # unix time of second | ||
# 2009-02-14 07:31:30 +0800 | ||
# $ uxt 1234567890333 # unix time of milliseconds(10 + 3 digits) | ||
# 2009-02-14 07:31:30.333 +0800 | ||
# $ uxt 12345678903 # unix time of 10 + 1 digits | ||
# 2009-02-14 07:31:30.3 +0800 | ||
# # support multiply arguments | ||
# $ uxt 0 1234567890 12345678903 | ||
# | ||
# @online-doc https://github.com/oldratlee/useful-scripts/blob/dev-2.x/docs/shell.md#-unix-time | ||
# @author Jerry Lee (oldratlee at gmail dot com) | ||
set -eEuo pipefail | ||
|
||
readonly PROG=${0##*/} | ||
readonly PROG_VERSION='2.x-dev' | ||
|
||
################################################################################ | ||
# util functions | ||
################################################################################ | ||
|
||
red_print() { | ||
# if stdout is a terminal, turn on color output. | ||
# '-t' check: is a terminal? | ||
# check isatty in bash https://stackoverflow.com/questions/10022323 | ||
if [ -t 1 ]; then | ||
printf "\e[1;31m%s\e[0m\n" "$*" | ||
else | ||
printf '%s\n' "$*" | ||
fi | ||
} | ||
|
||
is_integer() { | ||
[[ "$1" =~ ^-?[[:digit:]]+$ ]] | ||
} | ||
|
||
die() { | ||
red_print "Error: $*" >&2 | ||
exit 1 | ||
} | ||
|
||
usage() { | ||
local -r exit_code=${1:-0} | ||
(($# > 0)) && shift | ||
local -r out=$(((exit_code != 0) + 1)) | ||
|
||
# NOTE: $'foo' is the escape sequence syntax of bash | ||
(($# > 0)) && red_print "$*"$'\n' >&"$out" | ||
|
||
cat >&"$out" <<EOF | ||
Usage: $PROG [OPTION] unix-time [unix-time...] | ||
Convert unix time to human readable date string. | ||
Note: The range of the 10-digit unix time in second include recent date: | ||
1000000000: 2001-09-09 01:46:40 +0000 | ||
9999999999: 2286-11-20 17:46:39 +0000 | ||
Example: | ||
# for large integer, default auto treat first 10 digits as second | ||
$ $PROG 1234567890 # unix time of second | ||
2009-02-14 07:31:30 +0800 | ||
$ $PROG 1234567890333 # unix time of milliseconds(10 + 3 digits) | ||
2009-02-14 07:31:30.333 +0800 | ||
$ $PROG 12345678903 # unix time of 10 + 1 digits | ||
2009-02-14 07:31:30.3 +0800 | ||
# support multiply arguments | ||
$ $PROG 0 1234567890 12345678903 | ||
Options: | ||
-u, --time-unit set the time unit of given epochs | ||
-Z, --no-time-zone do not print time zone | ||
-t, --trim-decimal-tailing-0 | ||
trim the tailing zeros of second decimal | ||
-h, --help display this help and exit | ||
-V, --version display version information and exit | ||
EOF | ||
|
||
exit "$exit_code" | ||
} | ||
|
||
progVersion() { | ||
printf '%s\n' "$PROG $PROG_VERSION" | ||
exit | ||
} | ||
|
||
################################################################################ | ||
# parse options | ||
################################################################################ | ||
|
||
declare -a args=() | ||
unit= | ||
trim_decimal_tailing_0=false | ||
no_tz=false | ||
while (($# > 0)); do | ||
case "$1" in | ||
-u | --unit) | ||
unit=$2 | ||
shift 2 | ||
;; | ||
-Z | --no-time-zone) | ||
no_tz=true | ||
shift | ||
;; | ||
-t | --trim-decimal-tailing-0) | ||
trim_decimal_tailing_0=true | ||
shift | ||
;; | ||
-h | --help) | ||
usage | ||
;; | ||
-V | --version) | ||
progVersion | ||
;; | ||
--) | ||
shift | ||
args=(${args[@]:+"${args[@]}"} "$@") | ||
break | ||
;; | ||
-*) | ||
usage 2 "Error: unrecognized option '$1'" | ||
;; | ||
*) | ||
args=(${args[@]:+"${args[@]}"} "$1") | ||
shift | ||
;; | ||
esac | ||
done | ||
|
||
[[ -z $unit || $unit =~ ^(s|second|ms|millisecond)$ ]] || | ||
usage 1 "illegal time unit '$unit'! support values: 'second'/'s', 'millisecond'/'ms'" | ||
[[ $unit = second ]] && unit=s | ||
[[ $unit = millisecond ]] && unit=ms | ||
|
||
readonly args unit trim_decimal_tailing_0 no_tz | ||
|
||
if [ ${#args[@]} = 0 ]; then | ||
usage 1 'No argument provided!' >&2 | ||
fi | ||
|
||
################################################################################ | ||
# biz logic | ||
################################################################################ | ||
|
||
for a in "${args[@]}"; do | ||
is_integer "$a" || die "argument $a is not integer!" | ||
[[ ! $a =~ ^-?0+[1-9] ]] || die "argument $a contains beginning 0!" | ||
done | ||
|
||
readonly MIN_DATE_SEC=-67768040609769943 MAX_DATE_SEC=67768036191647999 | ||
|
||
print_date() { | ||
local -r input=$1 | ||
# split input integer to sign and number part | ||
local -r sign_part=${input%%[!-]*} # remove digits from tail | ||
local -r number_part=${input#-} # remove sign from head | ||
local -r np_len=${#number_part} # length of number part | ||
|
||
local second_part=0 decimal_part= | ||
# case 1: is unix time in second? | ||
if [[ $unit = s ]]; then | ||
second_part=$number_part | ||
# case 2: is unix time in millisecond? | ||
elif [[ $unit = ms ]]; then | ||
if ((np_len > 3)); then | ||
second_part=${number_part:0:np_len-3} | ||
decimal_part=${number_part:np_len-3:3} | ||
else | ||
printf -v decimal_part '%03d' "$number_part" | ||
fi | ||
# case 3: auto detect by length | ||
else | ||
# <= 10 digits, treat as second | ||
if ((np_len <= 10)); then | ||
second_part=$number_part | ||
# for long integer(> 10 digits), treat first 10 digits as second, | ||
# and the rest as decimal/nano second(almost 9 digits) | ||
elif ((np_len <= 19)); then | ||
second_part=${number_part:0:10} | ||
decimal_part=${number_part:10:9} | ||
else | ||
die "argument $input contains $np_len digits(>19), too many to treat as a recent date(first 10-digits as seconds, rest at most 9 digits as decimal)" | ||
fi | ||
fi | ||
|
||
# trim tailing zeros of decimal? | ||
$trim_decimal_tailing_0 && while true; do | ||
local old_len=${#decimal_part} | ||
decimal_part=${decimal_part%0} | ||
((${#decimal_part} < old_len)) || break | ||
done | ||
|
||
local -r seconds_value=$sign_part$second_part second_part decimal_part | ||
((${#second_part} <= 17 || (seconds_value >= MIN_DATE_SEC && seconds_value <= MAX_DATE_SEC))) || | ||
die "argument $input(seconds: $seconds_value${decimal_part:+, decimal_part: .$decimal_part}) is out of range! seconds should be in range [$MIN_DATE_SEC, $MAX_DATE_SEC]." | ||
|
||
local date_input=$seconds_value${decimal_part:+.$decimal_part} | ||
local format_n=${decimal_part:+.%${#decimal_part}N} | ||
local format_tz=' %z' | ||
$no_tz && format_tz= | ||
date -d "@$date_input" +"%Y-%m-%d %H:%M:%S$format_n$format_tz" | ||
} | ||
|
||
for a in "${args[@]}"; do | ||
print_date "$a" | ||
done |
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,59 @@ | ||
#!/usr/bin/env bash | ||
set -eEuo pipefail | ||
|
||
READLINK_CMD=readlink | ||
if command -v greadlink &>/dev/null; then | ||
READLINK_CMD=greadlink | ||
fi | ||
|
||
BASE=$(dirname -- "$($READLINK_CMD -f -- "${BASH_SOURCE[0]}")") | ||
cd "$BASE" | ||
|
||
################################################# | ||
# commons and test data | ||
################################################# | ||
|
||
readonly uxt="../bin/uxt" | ||
|
||
################################################# | ||
# test cases | ||
################################################# | ||
|
||
test_utx_auto_detect() { | ||
assertEquals "2024-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" 1706572800)" | ||
assertEquals "1900-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" -- -2206483200)" | ||
|
||
assertEquals "1970-01-01 00:00:00 +0000" "$(TZ=0 "$uxt" 0)" | ||
assertEquals "1970-01-01 00:01:40 +0000" "$(TZ=0 "$uxt" -- 100)" | ||
assertEquals "1969-12-31 23:58:20 +0000" "$(TZ=0 "$uxt" -- -100)" | ||
|
||
# shellcheck disable=SC2016 | ||
assertFalse 'should fail, 20 more than 19 digits' '"$uxt" 12345678901234567890' | ||
} | ||
|
||
test_utx_unit_second() { | ||
assertEquals "2024-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" -u s 1706572800)" | ||
assertEquals "1900-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" -u s -- -2206483200)" | ||
|
||
assertEquals "1970-01-01 00:00:00 +0000" "$(TZ=0 "$uxt" -u s 0)" | ||
assertEquals "1970-01-01 00:01:40 +0000" "$(TZ=0 "$uxt" -u s -- 100)" | ||
assertEquals "1969-12-31 23:58:20 +0000" "$(TZ=0 "$uxt" -u s -- -100)" | ||
|
||
# shellcheck disable=SC2016 | ||
assertFalse 'should fail, 20 more than 19 digits' '"$uxt" -u s 12345678901234567890' | ||
} | ||
|
||
test_utx_unit_ms() { | ||
assertEquals "2024-01-30 00:00:00.000 +0000" "$(TZ=0 "$uxt" -u ms 1706572800000)" | ||
assertEquals "1900-01-30 00:00:00.000 +0000" "$(TZ=0 "$uxt" -u ms -- -2206483200000)" | ||
|
||
assertEquals "1970-01-01 00:00:00.000 +0000" "$(TZ=0 "$uxt" -u ms 0)" | ||
assertEquals "1970-01-01 00:01:40.000 +0000" "$(TZ=0 "$uxt" -u ms -- 100000)" | ||
assertEquals "1969-12-31 23:58:20.000 +0000" "$(TZ=0 "$uxt" -u ms -- -100000)" | ||
} | ||
|
||
################################################# | ||
# Load and run shUnit2. | ||
################################################# | ||
|
||
source "$BASE/shunit2-lib/shunit2" |