Skip to content

Instantly share code, notes, and snippets.

@AlexAtkinson
Last active July 14, 2022 16:11
Show Gist options
  • Save AlexAtkinson/24cb16a82c0314209202101ef82e20f9 to your computer and use it in GitHub Desktop.
Save AlexAtkinson/24cb16a82c0314209202101ef82e20f9 to your computer and use it in GitHub Desktop.
BASH: A truly awful way to guarantee timestamp alignment across OS's...
#!/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
@AlexAtkinson
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment