Last active
July 14, 2022 16:11
-
-
Save AlexAtkinson/24cb16a82c0314209202101ef82e20f9 to your computer and use it in GitHub Desktop.
BASH: A truly awful way to guarantee timestamp alignment across OS's...
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
#!/usr/bin/env bash | |
# timestamp-helper.sh | |
# ------------------------------------------------------------------------------ | |
# Command perfs observed on localhost. | |
# date : ~0.001-0.002s | |
# gdate : unknown | |
# python : ~0.015-0.030s | |
# c : ~0.025-0.049s | |
# this.sh : ~0.005-0.015s (with date, so + ~0.004-0.013s) | |
# | |
# ------------------------------------------------------------------------------ | |
# Defaults | |
# ------------------------------------------------------------------------------ | |
arg_g_val='ns' | |
arg_z_val="Z" | |
arg_t_val=9 | |
# ------------------------------------------------------------------------------ | |
# Help & Args | |
# ------------------------------------------------------------------------------ | |
show_help() { | |
cat << EOF | |
This is a timestamp helper script, ensuring alingment of timestamps across OS's. | |
NOTE: This script is mostly for addressing the limitations of MacOS's 'date' command, | |
but it will work anywhere 'cc' is available, and 'date' supports the '-u' | |
and '-d' arguments. | |
Use: ${0##*/} -e {-r} | |
-g GRANULARITY The environment in which to deploy. | |
Options: date, h|hour(s), m|minute(s), s|second(s), ns (default) | |
-z TZFORMAT The format for TZ/UTC indicator. | |
Options: 'Z', '0' (zero) | |
-t TRIM Trim the ns field to 'n' lenth. Default is '9'. | |
Options: 3, 6 | |
-p PRESERVE OSX: Preserves the c program 'cts' for your user elsewhere. This | |
file can be renamed. | |
Usage: ./cts | |
-d DEMO Demo this helper. | |
TIP: 'watch -n0.01 ./timestamp-helper.sh -d' | |
-h HELP Show this menu. | |
Examples: | |
Timestamp with nanosecond granularity: | |
./${0##*/} | |
Timestamp with hour granularity: | |
./${0##*/} -g hour | |
Timestamp with ns granularity to the 3rd place and +00:00 as the TZ indicator. | |
./${0##*/} -t 3 -z 0 | |
EOF | |
exit 1 | |
} | |
OPTIND=1 | |
while getopts "hg:z:t:dp" opt; do | |
case "$opt" in | |
h) | |
show_help | |
;; | |
g) | |
arg_g='set' | |
arg_g_val="$OPTARG" | |
;; | |
d) | |
arg_d='set' | |
;; | |
t) | |
arg_t_val="$OPTARG" | |
;; | |
z) | |
arg_z_val="$OPTARG" | |
;; | |
p) | |
arg_p='set' | |
;; | |
:) | |
echo "ERROR: Option -$OPTARG requires an argument." | |
show_help | |
;; | |
*) | |
echo "ERROR: Unknown option!" | |
show_help | |
;; | |
esac | |
done | |
shift "$((OPTIND-1))" | |
case $arg_t_val in | |
3) | |
n=3 | |
;; | |
6) | |
n=6 | |
;; | |
9) | |
n=9 | |
;; | |
*) | |
echo "ERROR: Unknown TRIM length." | |
show_help | |
;; | |
esac | |
case $arg_z_val in | |
Z) | |
z="Z" | |
;; | |
0) | |
z="+00:00" | |
;; | |
*) | |
echo "ERROR: Unknown TZ format option." | |
show_help | |
;; | |
esac | |
# ------------------------------------------------------------------------------ | |
# Main Operations | |
# ------------------------------------------------------------------------------ | |
# Detect OS | |
case "$OSTYPE" in | |
solaris*) export OS_TYPE="SOLARIS" ;; | |
darwin*) export OS_TYPE="OSX" ;; | |
linux*) export OS_TYPE="LINUX" ;; | |
bsd*) export OS_TYPE="BSD" ;; | |
msys*) export OS_TYPE="WINDOWS" ;; | |
cygwin*) export OS_TYPE="CYGWIN" ;; | |
*) export OS_TYPE="unknown: $OSTYPE" ;; | |
esac | |
# Linux is easy. | |
[[ $OS_TYPE == "LINUX" ]] && tsCmd="date -u +%s%${n}N" | |
# MacOS takes a bit of effort... | |
# The 'date' command doesn't support %N or --iso-8601='ns' | |
if [[ $OS_TYPE == "OSX" ]]; then | |
# Try to get nanoseconds with gdate if it's installed. | |
if command -v gdate &> /dev/null; then | |
tsCmd="gdate --utc +%FT%T.%${n}NZ" | |
elif command -v python &>/dev/null; then | |
# Try to get nanoseconds with time_ns | |
# REF: https://docs.python.org/3/library/time.html#time.clock_gettime_ns | |
for v in 3.{7..20}; do | |
if command -v python${v} &>/dev/null; then | |
tsCmd="python${v} -c 'import time; import math; ns = int(str(time.time_ns())[:16]); print(ns)'" | |
break | |
fi | |
done | |
else | |
# Finally force the matter by building a c program to print %s%N... | |
if [[ ! -f cts ]]; then | |
cat << EOF > epochInµsec.c | |
#include <stdio.h> | |
#include <sys/time.h> | |
int main(void) | |
{ | |
struct timeval time_now; | |
gettimeofday(&time_now,NULL); | |
int s = time_now.tv_sec; | |
int us = time_now.tv_usec; | |
printf("%10d%0${n}d\n",s,us); | |
return 0; | |
} | |
EOF | |
cc -o cts epochInµsec.c | |
rm -f epochInµsec.c | |
fi | |
tsCmd='./cts' | |
fi | |
fi | |
ts=$($tsCmd) | |
s=${ts:0:10} | |
ns=${ts:10} | |
function ts_demo() { | |
printf "%s\n" "This helper ensures timestamp format alignment across" \ | |
"Unix, Linux, MacOS, and maybe BSD operating systems." \ | |
"" \ | |
"s: $s" \ | |
"ns: $ns" \ | |
"s & ns: $ts" \ | |
"" \ | |
"Examples:" \ | |
" ./${0##*/} -g seconds" \ | |
" $(echo $(./${0##*/} -g seconds))" \ | |
" ./${0##*/} -g ns -t 3 -z 0" \ | |
" $(echo $(./${0##*/} -g ns -t 3 -z 0))" \ | |
" ./${0##*/}" \ | |
" $(echo $(./${0##*/}))" | |
} | |
function cleanup() { | |
if [[ ! -n ${arg_p+x} ]]; then | |
rm -f cts | |
fi | |
} | |
if [[ -n ${arg_d+x} ]]; then | |
ts_demo | |
cleanup | |
exit | |
else | |
case "$arg_g_val" in | |
d|date) | |
printf "%s\n" "$(date -ud @$s +"%F${z}")" | |
;; | |
h|hour|hours) | |
printf "%s\n" "$(date -ud @$s +"%FT%H${z}")" | |
;; | |
m|minute|minutes) | |
printf "%s\n" "$(date -ud @$s +"%FT%H:%M${z}")" | |
;; | |
s|second|seconds) | |
printf "%s\n" "$(date -ud @$s +"%FT%H:%M:%S${z}")" | |
;; | |
ns|:) | |
printf "%s\n" "$(date -ud @$s +"%FT%H:%M:%S,${ns}${z}")" | |
;; | |
*) | |
echo "ERROR: Unknown option!" | |
show_help | |
;; | |
esac | |
fi | |
cleanup |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Gawd it's awful...
Could remove the OS detection and sub in for simply checking if 'date -u +'%s%N' outputs 19 characters, then fallback to other mechanisms.