date コマンドつらい

タイトルどおりですが、date コマンド色々つらいですよね。 同じ名前の date コマンドが、OS や環境によって、似て非なるものであるという辛さがまず第一に挙げられるかと思います。

macOS 上でデフォルトで使える date コマンドは BSD 版の date です。 BSD 版かどうかは man date で確認できます。

DATE(1)                   BSD General Commands Manual                  DATE(1)

NAME
     date -- display or set date and time

   :

BSD という文字が見えますね。

一方、Ubuntu などの Linux 上では GNU coreutils 版の date コマンドが使えます。(Mac にも homebrew などで GNU coreutils をインストールすることもできますが、話がややこしくなるのでここでは一旦おいておきます)

こちらは date --help で確認してみましょう。

   :
Report date bugs to bug-coreutils@gnu.org
GNU coreutils home page: <http://www.gnu.org/software/coreutils/>
General help using GNU software: <http://www.gnu.org/gethelp/>
For complete documentation, run: info coreutils 'date invocation'

最後の方に GNU coreutils という文字列が見えるはずです。

これらの date コマンドのオプションは、共通なものもあればまったく異なるものもあり、時々ハマリます。 (たとえばローカルの Mac で動いてたシェルスクリプトLinux サーバー上で動かない等)

さらに、Linux の中でも Docker 用に最適化された Alpine などでは GNU coreutils 版ではなく BusyBox 版の date がインストールされています。 この BusyBox 版の date は GNU coreutils 版に似たオプションが使えますが、機能が削減されていたりして、これもまた別物と考えたほうがよさそうです。

BusyBox 版の date かどうかは以下のように確認できます。

$ date --help
BusyBox v1.26.2 (2017-02-21 17:41:44 GMT) multi-call binary.

  :

さて、たとえば単純に現在の日時を表示させるだけならどの date を使っていても、単に date と入力すれば良いだけです。

BSD date

$ date
Fri Jun 16 09:41:37 JST 2017

GNU coreutils date

$ date
Fri Jun 16 00:41:48 UTC 2017

BusyBox date

$ date
Fri Jun 16 00:42:05 UTC 2017

表示されるフォーマットも同じですね。

では、次に年月日だけを指定のフォーマットで表示させてみましょう。

BSD date

$ date +"%Y-%m-%d"
2017-06-16

GNU coreutils date

$ date +"%Y-%m-%d"
2017-06-16

BusyBox date

$ date +"%Y-%m-%d"
2017-06-16

これは同じ引数で同じ結果となります。

では、昨日(現在日時の1日前)の日付を表示させてみましょう。

BSD date

$ date -v-1d
Thu Jun 15 09:50:02 JST 2017

GNU coreutils date

$ date -d '1 day ago'
Thu Jun 15 00:53:07 UTC 2017

BusyBox date

$ date -d "1970.01.01-00:00:$(( $( date +%s ) - $(( 24 * 60 * 60 )) ))"
Thu Jun 15 00:54:03 UTC 2017

BSD 版と GNU coreutils 版で、オプション名(-v と -d)も違いますし、そこに指定する値についてもだいぶポリシーが違いそうに見えます。BusyBox 版は発狂しそうですね。

一応 BusyBox 版を解説してみましょう。 BusyBox 版の -d オプションは GNU coreutils 版のような便利な(human friendly な)指定ができず、自力で計算している感じです。 基本的には Unix time で現在時刻から 1 日分の秒数を引き、それをエポックタイムに足して、1970年1月1日0時0分1497574657秒、みたいな時刻を作ってそれを表示させている感じです。(あと、厳密なことを言うと、うるう秒が挿入された日、タイムゾーンによっては夏時間になった日や夏時間が終わった日あたりにこのコマンドを実行すると、タイミングによっては誤った日時を表示してしまうかもしれません)

他にもタイムゾーンの指定とか、フォーマットの変換のときなどにどの date を使っているのかを勘違いすると全然思ったように動かなかったり、複数の環境で動作するようなスクリプトを書こうとしたりするとこのあたりの統一感のなさの辛さを味わってしまう感じです。

  • 日付の計算(x 日後、とか)
  • 日付のフォーマットの変換

などの date コマンドの機能のサブセットに特化した小さなバイナリを Golang あたりで複数の環境向けに作ると幸せになれるのかな、、、などと考えているところです。

Gnu Coreutils: Core Gnu Utilities

Gnu Coreutils: Core Gnu Utilities

Using BusyBox (Digital Short Cut) (Prentice Hall Open Source Software Development Series)

Using BusyBox (Digital Short Cut) (Prentice Hall Open Source Software Development Series)