【Linuxコマンド入門】Alpine Linux標準のdateコマンドを使う際のちょっとしたコツ


2020/12/28
2021/12/29
蛸壺の技術ブログ| Alpine Linuxのdateコマンドを使う

dateコマンドはLinux系であればだいたいのOSにも標準的に存在しているシステムの時間を操作するためのごく単純なユーティリティコマンドです。

ですが侮るなかれで、うっかりシステムの時間が狂ってるのをしらずにインターネットにアクセスするようなネットワーク操作を行ってしまうと、しばしばタイムゾーンエラーのように分かりにくいエラーに遭遇してしまうおそれがあります。そういう場合には、
dateコマンドで正しい時間を設定し直すと大抵治ります。

そんなdateコマンドはLinux OS毎にすこしづつ作法が違います。

今回はalpine上でdateを呼び出す際にちょっと躓いたときの注意点のお話です。


Alpine Linux (BusyBox) のdateコマンドについて

徐ろにAlpine Linux上で、早速タイムスタンプ用に日付をdateで取得しようとおもい、以下のコマンドを叩くと…

            
            $ date -d "2019/07/01 12:00:00"
date: invalid date '2019/07/01 12:00:00'
        
あれっ、なんで受け付けてくれないの…という事で、バージョンを確認します。

            
            $ date --version
BusyBox v1.29.3 (2019-01-24 07:45:07 UTC) multi-call binary.
...
Recognized TIME formats:
    hh:mm[:ss]
    [YYYY.]MM.DD-hh:mm[:ss]
    YYYY-MM-DD hh:mm[:ss]
    [[[[[YY]YY]MM]DD]hh]mm[.ss]
    'date TIME' form accepts MMDDhhmm[[YY]YY][.ss] instead
        
と下の方に、受け付けられるフォーマットのお品書きが出ております。なるほど…これはなかなかに限定的なフォーマットでしか読み取れないようです。

つまり、
Alpine linux標準のdateコマンドはBusyboxをベースにしています。

BusyboxはWindows系のOSでも簡単に導入できますので、以下の記事を参考にお手元のwindowsパソコンでも試してみられると良いと思います。

...話を戻すと、-dオプションで受け付けてくれる日付の書式というものに入力を合わせなくては使えないようです。

            
            $ date -d "2018-03-13"
Tue Mar 13 00:00:00 UTC 2018
        
と今度は受け付けられました。

でも出力形式がコレジャナイので、ちゃんとお目当の出力フォーマットを指定してあげましょう。

            
            $ date -d "2018-03-13" "+%Y年%m月%d日"
2018年03月13日
        
という具合に出力できました。

dateコマンドはOS毎に御作法が違いようですので、しっかりとヘルプを確認しておきましょう、というお話でした。

dateコマンド活用事例① 〜 sedコマンドを使わない文字列の置換

dateコマンドに限ったことではないのですが、シェル変数を呼び出すときに利用する${****//<置換前文字>/<置換後文字>}の文字置換パターンを上手くつかうことで、簡単な日付フォーマットならすぐに変換できるので、ここでご紹介します。

たとえば現在作成しているプログラムでは、設定ファイルのjsonから読み取った"YYYY/MM/DD"の形式の日付を読み取っていることもあり、それをそのままdateが読み込んでくれたら楽に作業できたんですが、このバックスラッシュをハイフネーションに置換する処理を加えます。

            
            #!/bin/bash

that_date="2018/03/13"
dt=$(date -d ${that_date//\//-} "+%Y年%m月%d日")
echo $dt
        
このスクリプトをthat_date.shとして保存し、以下のように実行してみますと、

            
            $ chmod +x that_date.sh
$ ./that_date.sh
2018年03月13日
        
当たり前ながら、正しい出力を得ます。

このスクリプトは
sed相当でおこなうと、

            
            #!/bin/bash

that_date="2018/03/13"
dt=$(echo "$that_date" | sed -e 's~/~-~g' | xargs date "+%Y年%m月%d日" -d)
echo $dt
        
標準出力した文字をパイプしてsedで受けて、xargsで更にパイプしてdateの後ろに引数として付けて...見ての通りでややこしくなりそうなので、文字置換程度ならsedコマンドを使わず、bashの文字置換を利用したら便利だなーと感じます。


dateコマンド活用事例② 〜 GNU系dateコマンドでも使えるエポック時間に変換するテクニック

二つの時間を比較するときなどはエポック時間(UNIX時間)に変えて使いたい場面あります。

そんなときも以下のようにすると、適当なフォーマットの日付からエポック時間に変換することが可能です。

先程のようなスクリプト仕立てにすると、

            
            #!/bin/bash

that_date="2018/03/13"
#出力の単位は秒
epoch_dt=$(date -u -d "${that_date//\//-} 00:00:00" +"%s")
#時刻の部分が00:00:00の場合には以下のように省略可
epoch_dt=$(date -u -d "${that_date//\//-}" +%s)

echo $epoch_dt
        
このスクリプトをthat_date.shとして保存し、以下のように実行してみますと、

            
            $ chmod +x that_date.sh
$ ./that_date.sh
1520899200
        
エポック時間として取得できました。


dateコマンド活用事例③ 〜 awkコマンドの中でdateを使うテクニック

例えば、csvデータの中に突如として日付列が...,2018/03/13,...みたいに現れる場合、これをエポック時間に変換する必要があるとしますと、sedで行ごとに日付に当たる部分を正規表現などで抽出して、そこの部分だけをエポック時間に置き換えて...などとやるのは面倒です。

どうせAwkでcsvデータを整えるのなら、エポック時間の変換もAwkのなかで行いたい場合のテクニックがあります。

例えば、csvファイルのとある一行とみなして
1,3.14,2018-03-30,tacoの三列目をエポック時間に変換してみます。

            
            $ echo '1,3.14,2018-03-30,taco' | awk -F"," '
    function _parse_epochtime(rawtime) {
        #👇文字をdateで使えるフォーマットに直す
        gsub("/", "-", rawtime);

        #👇Awk内部で外部のコマンドを利用できる
        cmd="date -u --date=\""rawtime" 00:00:00\" +\"%s\"";
        cmd | getline parsed_time
        close(cmd)
        #👆getlineで呼び出した外部コマンドは自動では閉じないので
        #明示にclose関数で閉じる

        return parsed_time;
    }
    BEGIN{
        OFS = ",";
    }
    {
        epochtime = _parse_epochtime($3);
        print $1,$2,epochtime,$4
    }
'
#👇出力
1,3.14,1522368000,taco
        

ポイントは少し上級者なAwkの使いかたになるかも知れません。

Awkスクリプト内部でも
""で囲うと外部コマンドが呼び出すことが可能で、その標準出力を組込関数のgetlineでパイプライン(|)することで、その出力結果をAwkのスクリプト内に取り込むことが可能になります。

もっと詳しいgetline関数の説明と用法は
ここらへんの記事を呼んで勉強いただくとして、同じシェルコマンドがAwk内で2回以上getlineと一緒に使われたときには、そのコマンドの実行は最初の1回だけとなるルールがあります。

同じコマンドを何回も実行したいような場合には一旦
close関数でパイプになっている処理を閉じないといけません。


dateコマンド活用事例④ 〜 dateコマンドで当年の一月一日からの今日までの日数が知りたい

気がつくと正月元旦からえらい日数が過ぎ去って何ヶ月も立ってしまうときに、「...今年は何%が終わってしまったの?」と気になる方に使えるコマンドです。

            
            #👇date -u +%sが今日の日付
$ echo $(( ($(date -u +%s) - $(date -u -d "2021-01-01" +%s))/86400 ))
231

#もしくはexpr
$ expr \( $(date -u +%s) - $(date -u -d "2021-01-01" +%s) \) \/ 86400
231
        
というように元日から現在までで既に231日も経過していることが簡単に分かります。

上のように整数の計算には
echo $((***))exprコマンドが使えますが、小数計算には使えません。厳密な割合計算の結果を知りたい場合にはbc -lパイプを添えて、

            
            $ echo "($(date -u +%s) - $(date -u -d "2021-01-01" +%s))/86400" | bc -l
231.09310185185185185185

#👇年消化率
$ echo "($(date -u +%s) - $(date -u -d "2021-01-01" +%s))/86400/365" | bc -l
.64193380272633744855
        

このコマンドを叩いた時刻をもって、「...わぁ、もう2021年の64.2%が過ぎていたのか〜」と実感できます。

なおこちらはUTC(国際標準時)基準なので、JST(日本時間)で知りたい場合には+7時間補正すると良いでしょう。


dateコマンド活用事例⑤ 〜 dateコマンドで1日前の1時間前の11分前を考える

linuxのdateコマンドで作法がバラける使い方の代表的なものに、N日前とかN時間先とかを換算して表示させることが挙げられます。

例えば、Gnu dateコマンドだと、通常の
--dateオプション(-dオプション)で以下のように現在の時刻から何日前、何時間先...などを簡単に割り出せます。

            
            $ date --version
date (GNU coreutils) 8.30
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

#👇1日前
$ date --date "-1day"
2021年 12月 28日 火曜日 15:23:04 JST

#👇7時間先
$ date -d "+7hour"
2021年 12月 29日 水曜日 22:24:19 JST
        
これをBusyboxのdateコマンドでもやると、

            
            $ date --date "-1day"
date: invalid date '-1day'
        
と書式エラーでレスポンスが返ってきます。

結論から先に言うと、機能としてシンプルで軽量であることが売りのBusybox標準のdateコマンドでは、
+1dayなどの相対的な時刻操作を行う特殊書式には対応してはいないので、この手の操作ができません。

その代替策として、
date +%sでエポック秒に変換し、$((評価式))で足し引きを計算させてから、date "@<換算後のエポック秒>"で再び日付に戻します。

つまり、

            
            #👇1日前
$ date -d "@$(($(date +%s) - 60*60*24))"
Tue Dec 28 07:36:58 UTC 2021

#👇7時間先
$ date -d "@$(($(date +%s) + 60*60*7))"
Wed Dec 29 14:41:45 UTC 2021
        
のように頭を少し捻らないといけないですが、慣れるとさほど難しい訳ではありません。

以上のことを踏まえると、
1日前の1時間前の11分前は、

            
            #👇1日前の1時間前の11分前
$ date -d "@$(($(date +%s) - 60*60*24 - 60*60*1 - 60*11))"
Tue Dec 28 06:37:37 UTC 2021
        
で出力されます。


参考

dateコマンド(Linux) フォーマット日付、Unixタイムスタンプ、X日前後の取得

コマンド date 現在の時刻 (日時) を表示・設定する

シェルスクリプトによる文字列処理:置換や削除をする方法