Skip to content

Commit b7914db

Browse files
committed
feat: add new script uxt ⌚️
1 parent 934e57e commit b7914db

File tree

4 files changed

+364
-0
lines changed

4 files changed

+364
-0
lines changed

bin/--version

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
set -eEuo pipefail
3+
4+
echo 'hacked'

bin/uxt

+244
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
#!/usr/bin/env bash
2+
# @Function
3+
# Convert unix time to human readable date string.
4+
# Note: The range of the 10-digit unix time in second include recent date:
5+
# 9999999999: 2286-11-20 17:46:39 +0000
6+
# 1000000000: 2001-09-09 01:46:40 +0000
7+
# 0: 1970-01-01 00:00:00 +0000
8+
# -1000000000: 1938-04-24 22:13:20 +0000
9+
# -9999999999: 1653-02-10 06:13:21 +0000
10+
#
11+
# @Usage
12+
# # default treat first 10 digits as second(include recent date)
13+
# $ uxt 1234567890 # unix time of second
14+
# 2009-02-14 07:31:30 +0800
15+
# $ uxt 1234567890333 # unix time of milliseconds(10 + 3 digits)
16+
# 2009-02-14 07:31:30.333 +0800
17+
# $ uxt 12345678903 # unix time of 10 + 1 digits
18+
# 2009-02-14 07:31:30.3 +0800
19+
# # support multiply arguments
20+
# $ uxt 0 1234567890 12345678903
21+
#
22+
# @online-doc https://github.com/oldratlee/useful-scripts/blob/dev-2.x/docs/shell.md#-unix-time
23+
# @author Jerry Lee (oldratlee at gmail dot com)
24+
set -eEuo pipefail
25+
26+
readonly PROG=${0##*/}
27+
readonly PROG_VERSION='2.x-dev'
28+
29+
################################################################################
30+
# util functions
31+
################################################################################
32+
33+
red_print() {
34+
# if stdout is a terminal, turn on color output.
35+
# '-t' check: is a terminal?
36+
# check isatty in bash https://stackoverflow.com/questions/10022323
37+
if [ -t 1 ]; then
38+
printf "\e[1;31m%s\e[0m\n" "$*"
39+
else
40+
printf '%s\n' "$*"
41+
fi
42+
}
43+
44+
is_integer() {
45+
[[ "$1" =~ ^-?[[:digit:]]+$ ]]
46+
}
47+
48+
die() {
49+
local prompt_help=false exit_status=2
50+
while (($# > 0)); do
51+
case "$1" in
52+
-h)
53+
prompt_help=true
54+
shift
55+
;;
56+
-s)
57+
exit_status=$2
58+
shift 2
59+
;;
60+
*)
61+
break
62+
;;
63+
esac
64+
done
65+
66+
(($# > 0)) && red_print "$PROG: $*"
67+
$prompt_help && echo "Try '$PROG --help' for more information."
68+
69+
exit "$exit_status"
70+
} >&2
71+
72+
usage() {
73+
cat <<EOF
74+
Usage: $PROG [OPTION] unix-time...
75+
76+
Convert unix time to human readable date string.
77+
Note: The range of the 10-digit unix time in second include recent date:
78+
9999999999: 2286-11-20 17:46:39 +0000
79+
1000000000: 2001-09-09 01:46:40 +0000
80+
0: 1970-01-01 00:00:00 +0000
81+
-1000000000: 1938-04-24 22:13:20 +0000
82+
-9999999999: 1653-02-10 06:13:21 +0000
83+
84+
Example:
85+
# default treat first 10 digits as second(include recent date)
86+
$ $PROG 1234567890 # unix time of second
87+
2009-02-14 07:31:30 +0800
88+
$ $PROG 1234567890333 # unix time of milliseconds(10 + 3 digits)
89+
2009-02-14 07:31:30.333 +0800
90+
$ $PROG 12345678903 # unix time of 10 + 1 digits
91+
2009-02-14 07:31:30.3 +0800
92+
# support multiply arguments
93+
$ $PROG 0 1234567890 12345678903
94+
95+
Options:
96+
-u, --time-unit set the time unit of given epochs
97+
-Z, --no-time-zone do not print time zone
98+
-D, --no-second-decimal
99+
do not print second decimal
100+
-t, --trim-decimal-tailing-0
101+
trim the tailing zeros of second decimal
102+
-h, --help display this help and exit
103+
-V, --version display version information and exit
104+
EOF
105+
106+
exit
107+
}
108+
109+
progVersion() {
110+
printf '%s\n' "$PROG $PROG_VERSION"
111+
exit
112+
}
113+
114+
################################################################################
115+
# parse options
116+
################################################################################
117+
118+
args=()
119+
unit=
120+
no_tz=false
121+
no_second_decimal=false
122+
trim_decimal_tailing_0=false
123+
124+
while (($# > 0)); do
125+
case "$1" in
126+
-u | --unit)
127+
unit=$2
128+
shift 2
129+
;;
130+
-Z | --no-time-zone)
131+
no_tz=true
132+
shift
133+
;;
134+
-D | --no-second-decimal)
135+
no_second_decimal=true
136+
shift
137+
;;
138+
-t | --trim-decimal-tailing-0)
139+
trim_decimal_tailing_0=true
140+
shift
141+
;;
142+
-h | --help)
143+
usage
144+
;;
145+
-V | --version)
146+
progVersion
147+
;;
148+
-[[:digit:]]*)
149+
# negative number start with '-', is not option
150+
args=(${args[@]:+"${args[@]}"} "$1")
151+
shift
152+
;;
153+
--)
154+
shift
155+
args=(${args[@]:+"${args[@]}"} "$@")
156+
break
157+
;;
158+
-*)
159+
die -h "unrecognized option '$1'"
160+
;;
161+
*)
162+
args=(${args[@]:+"${args[@]}"} "$1")
163+
shift
164+
;;
165+
esac
166+
done
167+
168+
[[ -n $unit ]] && if [[ $unit =~ ^(s|second)$ ]]; then
169+
unit=s
170+
elif [[ $unit =~ ^(ms|millisecond)$ ]]; then
171+
unit=ms
172+
else
173+
die -h "illegal time unit '$unit'! support values: 'second'/'s', 'millisecond'/'ms'"
174+
fi
175+
176+
readonly args unit trim_decimal_tailing_0 no_tz
177+
178+
((${#args[@]} > 0)) || die -h "requires at least one argument!"
179+
for a in "${args[@]}"; do
180+
is_integer "$a" || die "argument $a is not integer!"
181+
[[ ! $a =~ ^-?0+[1-9] ]] || die "argument $a contains beginning 0!"
182+
done
183+
184+
################################################################################
185+
# biz logic
186+
################################################################################
187+
188+
print_date() {
189+
local -r input=$1
190+
# split input integer to sign and number part
191+
local -r sign_part=${input%%[!-]*} # remove digits from tail
192+
local -r number_part=${input#-} # remove sign from head
193+
local -r np_len=${#number_part} # length of number part
194+
195+
local second_part=0 decimal_part=
196+
# case 1: is unix time in second?
197+
if [[ $unit = s ]]; then
198+
second_part=$number_part
199+
# case 2: is unix time in millisecond?
200+
elif [[ $unit = ms ]]; then
201+
if ((np_len > 3)); then
202+
second_part=${number_part:0:np_len-3}
203+
decimal_part=${number_part:np_len-3:3}
204+
else
205+
printf -v decimal_part '%03d' "$number_part"
206+
fi
207+
# case 3: auto detect by length
208+
else
209+
# <= 10 digits, treat as second
210+
if ((np_len <= 10)); then
211+
second_part=$number_part
212+
# for long integer(> 10 digits), treat first 10 digits as second,
213+
# and the rest as decimal/nano second(almost 9 digits)
214+
elif ((np_len <= 19)); then
215+
second_part=${number_part:0:10}
216+
decimal_part=${number_part:10:9}
217+
else
218+
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)"
219+
fi
220+
fi
221+
222+
# trim tailing zeros of decimal?
223+
$trim_decimal_tailing_0 && while true; do
224+
local old_len=${#decimal_part}
225+
decimal_part=${decimal_part%0}
226+
((${#decimal_part} < old_len)) || break
227+
done
228+
229+
local -r seconds_value=$sign_part$second_part second_part decimal_part
230+
# defensive check. 9999999999999999(16 '9') seconds is so big, 300M years later(316,889,355-01-25 17:46:39 +0000)
231+
((${#second_part} <= 16)) ||
232+
die "argument $input(seconds: $seconds_value${decimal_part:+, decimal: .$decimal_part}) is too big, seconds are more than 16 digits."
233+
234+
local date_input=$seconds_value${decimal_part:+.$decimal_part}
235+
local format_n=
236+
$no_second_decimal || format_n=${decimal_part:+.%${#decimal_part}N}
237+
local format_tz=
238+
$no_tz || format_tz=' %z'
239+
date -d "@$date_input" +"%Y-%m-%d %H:%M:%S$format_n$format_tz"
240+
}
241+
242+
for a in "${args[@]}"; do
243+
print_date "$a"
244+
done

docs/shell.md

+57
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,63 @@ $ rp /home /etc/../etc /home/admin
363363
../../etc
364364
```
365365
366+
🍺 [uxt](../bin/uxt)
367+
----------------------
368+
369+
输出`Unix`时间戳对应的时间,自动识别秒/毫秒格式。
370+
371+
### 用法/示例
372+
373+
```bash
374+
$ uxt 1234567890 # 秒时间戳(10位以内数字)
375+
2009-02-14 07:31:30 +0800
376+
$ uxt 1234567890333 # 毫秒时间戳(10位秒 + 3位毫秒)
377+
2009-02-14 07:31:30.333 +0800
378+
$ uxt 12345678903 # 11位(10位秒 + 剩余1位作为毫秒)
379+
2009-02-14 07:31:30.3 +0800
380+
# 支持多个参数
381+
$ uxt 0 1234567890 12345678903
382+
383+
# 如果需要转换秒超过10位的时间戳,显式指定单位
384+
$ uxt -u s 12345678900
385+
2361-03-22 03:15:00 +0800
386+
$ uxt -u ms 12345678900123
387+
2361-03-22 03:15:00.123 +0800
388+
389+
$ uxt -h
390+
Usage: uxt [OPTION] unix-time [unix-time...]
391+
392+
Convert unix time to human readable date string.
393+
Note: The range of the 10-digit unix time in second include recent date:
394+
9999999999: 2286-11-20 17:46:39 +0000
395+
1000000000: 2001-09-09 01:46:40 +0000
396+
0: 1970-01-01 00:00:00 +0000
397+
-1000000000: 1938-04-24 22:13:20 +0000
398+
-9999999999: 1653-02-10 06:13:21 +0000
399+
400+
Example:
401+
# default treat first 10 digits as second(include recent date)
402+
$ uxt 1234567890 # unix time of second
403+
2009-02-14 07:31:30 +0800
404+
$ uxt 1234567890333 # unix time of milliseconds(10 + 3 digits)
405+
2009-02-14 07:31:30.333 +0800
406+
$ uxt 12345678903 # unix time of 10 + 1 digits
407+
2009-02-14 07:31:30.3 +0800
408+
# support multiply arguments
409+
$ uxt 0 1234567890 12345678903
410+
411+
Options:
412+
-u, --time-unit set the time unit of given epochs
413+
-Z, --no-time-zone do not print time zone
414+
-D, --no-second-decimal
415+
do not print second decimal
416+
-t, --trim-decimal-tailing-0
417+
trim the tailing zeros of second decimal
418+
-h, --help display this help and exit
419+
-V, --version display version information and exit
420+
```
421+
422+
366423
🍺 [cp-into-docker-run](../bin/cp-into-docker-run)
367424
----------------------
368425

test-cases/uxt_test.sh

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env bash
2+
set -eEuo pipefail
3+
4+
READLINK_CMD=readlink
5+
if command -v greadlink &>/dev/null; then
6+
READLINK_CMD=greadlink
7+
fi
8+
9+
BASE=$(dirname -- "$($READLINK_CMD -f -- "${BASH_SOURCE[0]}")")
10+
cd "$BASE"
11+
12+
#################################################
13+
# commons and test data
14+
#################################################
15+
16+
readonly uxt="../bin/uxt"
17+
18+
#################################################
19+
# test cases
20+
#################################################
21+
22+
test_uxt_auto_detect() {
23+
assertEquals "2024-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" 1706572800)"
24+
assertEquals "1900-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" -- -2206483200)"
25+
26+
assertEquals "1970-01-01 00:00:00 +0000" "$(TZ=0 "$uxt" 0)"
27+
assertEquals "1970-01-01 00:01:40 +0000" "$(TZ=0 "$uxt" -- 100)"
28+
assertEquals "1969-12-31 23:58:20 +0000" "$(TZ=0 "$uxt" -- -100)"
29+
30+
# shellcheck disable=SC2016
31+
assertFalse 'should fail, 20 more than 19 digits' '"$uxt" 12345678901234567890'
32+
}
33+
34+
test_uxt_unit_second() {
35+
assertEquals "2024-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" -u s 1706572800)"
36+
assertEquals "1900-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" -u s -- -2206483200)"
37+
38+
assertEquals "1970-01-01 00:00:00 +0000" "$(TZ=0 "$uxt" -u s 0)"
39+
assertEquals "1970-01-01 00:01:40 +0000" "$(TZ=0 "$uxt" -u s -- 100)"
40+
assertEquals "1969-12-31 23:58:20 +0000" "$(TZ=0 "$uxt" -u s -- -100)"
41+
42+
# shellcheck disable=SC2016
43+
assertFalse 'should fail, 20 more than 19 digits' '"$uxt" -u s 12345678901234567890'
44+
}
45+
46+
test_uxt_unit_ms() {
47+
assertEquals "2024-01-30 00:00:00.000 +0000" "$(TZ=0 "$uxt" -u ms 1706572800000)"
48+
assertEquals "1900-01-30 00:00:00.000 +0000" "$(TZ=0 "$uxt" -u ms -- -2206483200000)"
49+
50+
assertEquals "1970-01-01 00:00:00.000 +0000" "$(TZ=0 "$uxt" -u ms 0)"
51+
assertEquals "1970-01-01 00:01:40.000 +0000" "$(TZ=0 "$uxt" -u ms -- 100000)"
52+
assertEquals "1969-12-31 23:58:20.000 +0000" "$(TZ=0 "$uxt" -u ms -- -100000)"
53+
}
54+
55+
#################################################
56+
# Load and run shUnit2.
57+
#################################################
58+
59+
source "$BASE/shunit2-lib/shunit2"

0 commit comments

Comments
 (0)