はじめに
参考としてシェルスクリプトで UNIXTIME と日時を取得・変換する "一般的な" コマンドです。
# 現在のUNIXTIMEを出力$ date +%s
1586250991
# 指定した日時のUNIXTIMEを出力$ date +%s --date'2020-01-02 03:04:05'# Linux$ date-j-f"%Y-%m-%d %H:%M:%S"'2020-01-02 03:04:05' +%s # macOS
1577901845
# UNIXTIMEで日時を指定して指定したフォーマットで出力$ date--date @1577901845 +"%Y-%m-%d %H:%M:%S"# Linux$ date-r 1577901845 +"%Y-%m-%d %H:%M:%S"# macOS
2020-01-02 03:04:05
何を言わんとしているかはわかると思いますが dateコマンド自体は POSIX で規定されていますが、よく使いそうなこれらのオプションは POSIX 準拠ではありません。UNIXTIME を出力する +%sは macOS でも使えますが、これも POSIX 準拠ではありません。(ちょっと忘れてしまいましたが実際に動かない環境があります。古い Solaris?)
UNIXTIME は(一秒単位ですが)シリアル値として手軽に使えるのでこれが使えないとなるとちょっと不便です。そこで dateコマンドのうちPOSIX 準拠のオプションのみを使って実装した UNIXTIME 出力関数と、UNIXTIME と日時との相互変換関数を実装しました。
実装
特に解説するようなこともないのでいきなり実装です。
# 現在の UNIXTIME の取得
unixtime(){
datetime2unixtime "$(date-u +'%Y-%m-%d %H:%M:%S')"}# 日時(%Y-%m-%d %H:%M:%S 形式)-> UNIXTIME
datetime2unixtime(){set--"${1%% *}""${1##* }"set--"${1%%-*}""${1#*-}""${2%%:*}""${2#*:}"set--"$1""${2%%-*}""${2#*-}""$3""${4%%:*}""${4#*:}"set--"$1""${2#0}""${3#0}""${4#0}""${5#0}""${6#0}"["$2"-lt 3 ]&&set--$(($1-1))$(($2+12))"$3""$4""$5""$6"set--$(((365*$1)+($1/4)-($1/100)+($1/400)))"$2""$3""$4""$5""$6"set--"$1"$(((306*($2+1)/10)-428))"$3""$4""$5""$6"set--$((($1+$2+$3-719163)*86400+$4*3600+$5*60+$6))echo"$1"}# UNIXTIME -> 日時(%Y-%m-%d %H:%M:%S 形式)
unixtime2datetime(){set--$(($1%86400))$(($1/86400+719468)) 146097 36524 1461
set--"$1""$2"$(($2-(($2+2+3*$2/$3)/$5)+($2-$2/$3)/$4-(($2+1)/$3)))set--"$1""$2"$(($3/365))set--"$@"$(($2-((365*$3)+($3/4)-($3/100)+($3/400))))set--"$@"$((($4-($4+20)/50)/30))set--"$@"$((12*$3+$5+2))set--"$1"$(($6/12))$(($6%12+1))$(($4-(30*$5+3*($5+4)/5-2)+1))set--"$2""$3""$4"$(($1/3600))$(($1%3600))set--"$1""$2""$3""$4"$(($5/60))$(($5%60))printf"%04d-%02d-%02d %02d:%02d:%02d\n""$@"}# 使用例
unixtime # => 現在の UNIXTIMEdate +%s # 同等のコマンド (Linux)
datetime2unixtime "2020-04-07 01:23:45"# => 1586222625date-u +%s --date"2020-04-07 01:23:45"# 同等のコマンド (Linux)
unixtime2datetime "1586222625"# => 2020-04-07 01:23:45date-u--date @1586222625 +"%Y-%m-%d %H:%M:%S"# 同等のコマンド (Linux)一見してなんじゃこりゃ?と思うかもしれませんが、たくさんの setは変数を使用しないようにしているためです。シェルスクリプトは POSIX 準拠の範囲ではグローバル変数しかないので使用している変数名がぶつからないように代わりに位置パラメータを使っています。
計算式についてはネットで検索して見つけたものを(書き方の変更程度で)使用しているだけなので解説はできません。date2unixtimeは有名なフェアフィールドの公式らしいです。参考にした場所は忘れてしまいましたが検索すればいくつも出てくると思います。unixtime2dateはこちらのコードを参考にしました。
タイムゾーンや ISO 8601、RFC 3339 の対応は意図的にやっていません。これらに対応するのは難しくないはずなので必要な人がやればよいというスタンスです。スクリプトに組み込んで再利用しやすいようにコアのみをシェル関数として実装しています。(コードは修正の有無に関係なく自由に使ってもらって構いません。参照元とかも明記しなくて良いです。)
テスト
こういうのは計算式を見た所で正しいかなんて判断のしようがないので dateコマンドの結果と比較してテストします。時・分・秒に関しては計算は難しくないので軽く済ませて、日付についてはエポック日の 1970-01-01から 1日(86400秒)単位で 3000 年まで一致するかをテストしています。(テストコードは Linux用です。POSIX 準拠にはしていません。)
i=0 t=5025 # 1970-01-01 01:23:45while[$t-lt 32503647600 ];do# 3000-01-01 00:00:00a=$(unixtime2datetime "$t")b=$(date-u +"%Y-%m-%d %H:%M:%S"--date @$t)c=$(datetime2unixtime "$b")if["$a"="$b"]&&["$c"="$t"];then[$(( i %1000))-eq 0 ]&&date-u--date @$tt=$((t+86400))i=$((i+1))else
echo"error $a$b : $c$t"exit 1
fi
done