日付入力の書式
*******

まず、引用から始める。

     時間を計るための我々の単位は、秒から月にいたるまで、あまりにも複雑で、一貫性がなく、ばらばらなので、時間について間違わずに暗算をすることなど、ほとんど不可能なほどだ。まったくの話、どこかの横暴な神様が、人間の知能を時間の奴隷にしてやろうと企んだとしても --- 腐った決まりごとや不愉快な不意打ちに隷従せずに済ますことなど、人間にはほとんど不可能にしてやろうと企んだとしても --- 現在我々が使っているシステムをお下げ渡しになる以上のことは、できなかったに違いない。我々の時間計算のシステムは、垂直な面も水平な面もない不等辺四辺形の建築ブロックの集まりのようなものだ。あるいは、ほんの簡単な考えを述べるにも、凝りに凝った構成や、無駄な不変化詞や、長々しい婉曲表現が必要な言語のようなものだ。言語や科学のもっとうまく行っている思考様式は、我々が経験に果敢に立ち向かうことを、少なくとも冷静に立ち向かうことを可能にしてくれる。ところが、そうしたものとは違って、我々の時間計算のシステムは、密かに、しかも執拗に時間に対する我々の恐怖をかきたてるのである。

     ... それは、建築家が長さをフィートで、幅をメートルで、高さをエルで測らなければならないようなものだ。あるいは、基本的な使用説明書を読むのに、五つの異なった言語の知識が必要なようなものだ。だから、我々が、自分にとってすぐ最近の過去や未来を表す、この前の火曜日 (last Tuesday) とか、来週の日曜日 (a week from Sunday) とかいった表現について、それは一体いつを指しているのだろうと考え込んでは、どうしようもない混乱を覚えることがよくあるのも、無理からぬことなのである。...

     --- Robert Grudin, Time and the Art of Living.

この章では、GNU のプログラムが認識する日付表現文字列について説明する。そうした文字列は、ユーザである読者が、様々なプログラムに引数として渡すことのできるものだ。 C のインターフェース (parse_datetime 関数で使用する) については、ここでは説明しない。

日付書式の全体
=======

日付 (date) は、空文字列のこともある文字列であり、空白 (whitespace) で区切られた多くの項目を含んでいる。各項目の意味に曖昧さが生じないなら、空白は省略できる。空の文字列は、今日の始まり (すなわち、真夜中) を意味している。項目の順序は重要ではない。日付文字列では、様々な種類の項目を指定することができる。

  * 暦日の項目

  * 時刻の項目

  * タイムゾーンの項目

  * 日付と時刻を組み合わせた項目

  * 曜日の項目

  * 相対表現の項目

  * 純粋な数値

個々の項目については、次節以下で順番に説明する。

何番目かを示す序数の中には、現れる場所によっては、単語で表現できるものがいくつかある。これは、曜日や相対的表現の項目を指定するとき、たいへん便利だ (下記参照)。使用頻度のきわめて高い序数を取り上げると、last という単語は -1 を表し、this は 0 を、first と next は両方とも 1 を表している。second という単語は時間の単位でもあるので、序数の 2 を単語で表現する方法はない。だが、便宜を考えて、third は 3 を、fourth は 4 を、fifth は 5 を、sixth は 6 を、seventh は 7 を、eighth は 8 を、ninthは 9 を、tenth は 10 を、eleventh は 11 を、twelfth は 12 を表すことになっている。

月がこの形で表現されたときも、やはり数値として (訳注: たとえば、今月から何ヶ月後と) 指定されたと見なされるのであって、月の名前の省略なしの表記と解釈されるわけではない (訳注: たとえば、third month を March の別名の完全表記とは考えない)。そのため、March のような月名を使った場合とは、指定できる文字列が違ってくる。

現在の実装で使用できる単語は、英語の単語とその省略形のみである。すなわち、AM, DST, EST, first, January, Sunday, tomorrow, year などだ。

date コマンドの出力だからと言って、必ずしも日付文字列としてプログラムに渡せるとはかぎらない。言語の問題のせいばかりではない。IST のようなタイムゾーン項目には、標準的な意味が存在しないからでもある。後でプログラムに構文解析をさせる予定の日付文字列を、date を使用して生成するのなら、日付の書式には、言語が何であるかに依存せず、UTC と Z 以外のタイムゾーン項目を使用しないものを指定するべきだ。それを行う方法をいくつか挙げておく。

     $ LC_ALL=C TZ=UTC0 date
     Mon Mar  1 00:21:42 UTC 2004
     $ TZ=UTC0 date +'%Y-%m-%d %H:%M:%SZ'
     2004-03-01 00:21:42Z
     $ date --rfc-3339=ns  # --rfc-3339 は GNU の拡張。
     2004-02-29 16:21:42.692722128-08:00
     $ date --rfc-2822  # GNU の拡張。
     Sun, 29 Feb 2004 16:21:42 -0800
     $ date +'%Y-%m-%d %H:%M:%S %z'  # %z は GNU の拡張。
     2004-02-29 16:21:42 -0800
     $ date +'@%s.%N'  # %s と %N も GNU の拡張。
     @1078100502.692722128

日付を指定する際、アルファベットの大文字と小文字は全く区別されない。丸カッコで囲めば、コメントを入れることができる。ただし、丸カッコを入れ子にするときは、開きカッコと閉じカッコがきちんと対応していなければならない。数字が後に続かないハイフンは、現在のところ無視される。また、数値の前に付けた 0 も無視される。

2005-02-29 のような無効な日付や 24:00 のような無効な時刻は、却下される。閏秒をサポートしていないホストでは、23:59:60 のような時刻は、たとえ閏秒に正しく対応する値であっても、却下されるのが普通である。

暦日の項目
=====

暦日の項目 (calendar date item) では、ある年のある一日を指定する。月を数字で指定するか、文字で指定するかにかによって、日付の指定法が変わってくる。以下に挙げる文字列は、すべて暦の上の同じ日付を指定している。

     1972-09-24     # ISO 8601 形式。
     72-9-24        # 69 から 99 までは、19xx 年と見なし、
                    # 00 から 68 までは、20xx 年と見なす。
     72-09-24       # 先頭の 0 は無視される。
     9/24/72        # アメリカでよく使われる表記。
     24 September 1972
     24 Sept 72     # September には、4 文字の省略形もある。
     24 Sep 72      # 3 文字の省略形は、何月に対しても使用できる。
     Sep 24, 1972
     24-sep-72
     24sep72

年を省略することもできる。その場合は、日付文字列の後方で指定された年が使用される (訳注: たとえば、date -d "5/3 2:00 UTC 2015" のような場合だろう)。それもない場合は、今年が使われる。例を挙げると、次のような形である。

     9/24
     sep 24

要するに、ルールはこうだ。

月を数字で表す場合、ISO 8601 の形式、すなわち YEAR-MONTH-DAY が使用できる。ここで YEAR は任意の正の数であり、MONTH は 01 から 12 までの数、DAY は 01 から 31 までの数である。数が 10 未満のときは、0 を前に付けることになる。YEAR が 68 以下の場合は、YEAR に2000 が加算される。また、YEAR が 69 以上、100 未満ならば、1900 が加算される。アメリカ合衆国で一般的な MONTH/DAY/YEAR という書式も使うことができる。年を省略した MONTH/DAY も可能である。

月の名前を使用する場合は、フルスペルで書いてもよい。すなわち、January, February, March, April, May, June, July, August, September, October, November, December である。月の名前は、最初の 3 文字に省略することができる。その場合、省略の印のピリオドは、付けても付けなくてもよい。また、September の代わりに、Sept と書くことも認められている。

月名を使用する場合、暦の上の日付は、以下のどの形で指定してもよい。

     DAY MONTH YEAR
     DAY MONTH
     MONTH DAY YEAR
     DAY-MONTH-YEAR

年を省略することもできる。

     MONTH DAY

時刻の項目
=====

日付文字列中の時刻の項目 (time of day item) では、当日の時刻を指定する。以下に挙げるいくつかの例は、すべて同じ時刻を表している。

     20:02:00.000000
     20:02
     8:02pm
     20:02-0500      # EST (U.S. Eastern Standard Time)

もっと一般的に言うと、時刻は HOUR:MINUTE:SECOND の形で指定できる。 HOUR は 0 から 23 までの数であり、MINUTE は 0 から 59 までの数である。 SECOND は 0 から 59 までの数であり、. や , を後ろに付けて、一桁以上の数字からなる小数を続けてもよい。なお、:SECOND は省略することもでき、その場合は 0 を指定したことになる。閏秒をサポートするホストもまれにはあり、そうしたところでは SECOND に 60 を指定することができる。

時刻に am や pm (または a.m. や p.m.) が続く場合は、 HOUR が 1 から 12 までになる。:MINUTE は省略してもよい (0 を指定したものと見なされる)。am は一日の前半を示し、pm は一日の後半を示す。この表記法では、1 の前が 12 になる。すなわち、真夜中は 12am であり、正午は 12pm である。(これは 12am や 12pm の 12 を 0 のように見なす解釈であり、正午を 12m、深夜を 12pm とするラテン文化から来た従来の習慣とは逆になっている。)

また、時刻にはタイムゾーン補正を続けてもよい。補正は SHHMM という形で表現され、S は + または - 符号、HH は時間帯の時間差、MM は分差の部分である。分差の部分 MM は指定しないでもよく、その場合は、1 桁ないし 2 桁の補正は、時間差の指定と見なされる。なお、HH と MM の間をコロンで区切ってもよい。タイムゾーン補正をこの方法で指定した場合、それが、それまでに指定したいかなるタイムゾーンよりも、また、ローカル・タイムゾーンよりも優先され、指定した時刻は、協定世界時 (UTC) よりも補正分進んでいる (あるいは、遅れている) タイムゾーンの時刻であると解釈されることになる。一例を挙げると、+0530 と +05:30 は両方とも、UTC より 5.5 時間進んだタイムゾーンを表している (たとえば、インドである)。これは、タイムゾーン補正を 1 時間以下の部分まで指定する最善の方法である。タイムゾーン補正の最大値は、24 時間である。

am/pm とタイムゾーン補正は、どちらか一方のみが指定できる。両方を指定することはできない。

タイムゾーンの項目
=========

タイムゾーン項目 (time zone item) では、国際時間帯を指定する。これは数個の文字によって表されるもので、たとえば、協定世界時 (Coordinated Universal Time) なら UTC または Z である。省略の印のピリオドは、すべて無視される。標準時のタイムゾーン (a non-daylight-saving time zone) の後ろに、文字列 DST を独立した単語として (すなわち、空白、タブなどを間に置いて) 続ければ、対応する夏時間のタイムゾーンを指定することができる。ちなみに、標準時のタイムゾーンの後ろに、タイムゾーン補正を続けて、両方の値を加算することもできる。ただし、その指定法は、通常 UTC に対してしか行われない。たとえば、UTC+05:30 は +05:30 と同じことである。

UTC と Z 以外のタイムゾーン項目は、時代遅れになりかかっているので、使用しない方がよい。理由は解釈が一定しないからであり、たとえば、EST はオーストラリアとアメリカ合衆国で違った意味を持っている。タイムゾーン項目を使用するよりも、前節で述べた -0500 のような、数値によるタイムゾーン補正を使う方が、曖昧さがない分優れている。

タイムゾーン項目とタイムゾーン補正のどちらも指定されていない場合、日付の解釈は、デフォルトのタイムゾーンのルールを用いて行われる (see Specifying time zone rules)。

暦日と時刻を組み合わせた項目
==============

ISO 8601 の拡張日時書式は、ISO 8601 形式の日付、T という区切り文字、ISO 8601 形式の時刻という構成になっている。 T の代わりにスペースが使われていても、この書式として認識される。

この書式では、時刻は 24 時間表記を使用するべきである。秒については、コンマまたはピリオドに小数部分を続けることで、小数点以下も指定できる。 ISO 8601 で認められている分や時の小数表現には対応していない。たいていのホストがナノセコンドの精度のタイムスタンプをサポートしている。サポートを超えた精度は、エラーや警告を出さずに単に除去される。

例をいくつか挙げてみよう。

     2012-09-24T20:02:00.052-05:00
     2012-12-31T23:59:59,999999999+11:00
     1970-01-01 00:00Z

曜日の項目
=====

曜日を明示的に指定すると、未来のその曜日にまで日付を前に進めることになる (これが行われるのは、その必要があるときだけである)。

曜日は省略なしに書いてもよい。すなわち、Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday である。最初の 3 文字に短縮することもでき、その場合、省略の印のピリオドは付けても付けなくてもよい。 Tuesday には Tues、Wednesday には Wednes、 Thursday には Thur または Thurs という特殊な省略形もある。

曜日項目の前に数値を付けてもよい。週がその分だけ前に進むことになる。これは、third monday のような表現で使うのが、一番よい。その伝で、last DAY や next DAY という表現も許されている (訳注: DAY の部分には Sunday, Monday などの曜日を指定する)。それぞれ、単独の DAY が表すことになる日付より 1 週間前、または 1 週間後の日付になる。

(訳注: next について上記のことが成り立つのは、DAY が今日の曜日のときだけである。それ以外の場合は、DAY と next DAY は同じになる。)

曜日項目の後ろにコンマがあっても、無視される。

(訳注: 確かに -d "Thu, 2013-03-07" といった指定では、コンマが無視されるが、 -d "next Thu," や -d "3 Thu," といった指定では、 ``invalid date'' と言われ、エラーになる。)

相対表現の項目
=======

相対表現の項目 (Relative items) は、日付を (指定しない場合は、現在の日付を) 前後に移動させる。また、相対表現の項目の作用は、加算されていく。相対表現の項目とは、次のようなものである。

     1 year
     1 year ago
     3 years
     2 days

時間をどれくらい移動するかの単位は、文字列を使って選択する。まる数年、あるいは、まる数ヶ月ずらすのなら、year や month という文字列を使う。年も月も、曖昧な単位である。すべての年や月が、同じ長さをしているわけではないからだ。もっと厳密な単位としては、fortnight (14 日間)、week (7 日間)、day (24 時間)、hour (60 分間)、minute または min (60 秒間)、second または sec (1 秒間) がある。こうした単位には、複数語尾の s を付けてもよいが、付けても無視される。

時間の単位の前に、何倍かを示す乗数を置くことができる。乗数には、+ または - の符号を付けてもよい。符号なしの数値には、+ の符号が付いているものと見なされる。数値を指定しなければ、乗数に 1 を指定したことになる。相対表現の項目に ago を続けるのは、単位の前にマイナスの乗数を置くのと同じことである。

tomorrow という文字列は、1 日分未来ということである (day と等しい)。yesterday は、1 日分過去ということだ (day ago と等しい)。

now や today という文字列は、値 0 の時間移動に対応する相対表現の項目である。値 0 の時間移動は、先行する項目によって別の日時に変更されていないかぎり、今現在の日時を表すということから、now (今) や today (今日) という言い方ができるわけだ。こうしたものは、他の項目を強調するために、たとえば、12:00 today といった具合に使うこともできる。this という文字列にも、値 0 の時間移動という意味があるが、こちらは this thursday のような日付文字列で使用される。

相対表現の項目によって生成される日付が、標準時と夏時間の切り替えを典型とするような、時刻調整の境目を越えたものになる場合、生成される日時は適切に調整される。

単位の曖昧さが、相対表現の項目では問題を起こすことがある。たとえば、2003-07-31 -1 month は、2003-07-01 と評価されるかもしれない。 2003-06-31 が無効な日付だからだ。先月が何月かをもっと確実に引き出すためには、今月の 15 日よりも前に、先行する月を求めればよい。例を挙げる。

     $ date -R
     Thu, 31 Jul 2003 13:02:39 -0700
     $ date --date='-1 month' +'Last month was %B?'
     Last month was July?
     $ date --date="$(date +%Y-%m-15) -1 month" +'Last month was %B!'
     Last month was June!

また、標準時と夏時間の切り替えのような時刻変更があるときの前後で日付の操作を行う場合も、気を付けた方がよい。 24 時間も加算されたり、減算されたりしてしまうことも、ないとは言えないからだ。そこで、たいていの場合、日付の計算に取りかかる前に、環境変数 TZ を UTC0 に設定して、協定世界時を採用するのが賢明である。

日付文字列中の純粋な数値
============

純粋な 10 進数をどう解釈するかは、日付文字列の文脈次第である。

10 進数が YYYYMMDD の形をとり、日付文字列中のそれより前に他の暦日の項目 (see Calendar date items) が存在しない場合は、YYYY は暦の上のある 1 日の年の部分と見なされる。 MM は何番目の月かということ、DD はその月の日にちである。

10 進数が HHMM の形をとり、日付文字列中のそれより前に他の時刻の項目が存在しない場合は、 HH はある 1 日の時刻の何時の部分と、MM は何分の部分と見なされる。 MM を省略してもよい。

日付文字列中で、ある数値の左側に暦の上の日にちとその日の時刻の両方が存在し、相対表現の項目が存在しない場合は、その数値が今年の代わりに使われる。

(訳注: たとえば、date -d "3/27 10:10 2012" と指定すると、今年の代わりに 2012 が使われるということらしい。 date -d "3/27/2001 10:10 2012" や date -d "2001-03-27 10:10 2012" は無効な日付になるし、date -d "3/27 1010 2012" は、1010 年 3 月 27 日 20:12:00 になる。)

Unix 紀元 (the Epoch) からの秒数
=========================

@ に数値を続けると、それは、秒数として、システム内部で使われるタイムスタンプを表すことになる。数値には、小数点 (. または ,) が含まれていてもよい。内部表現がサポートしていない余分な精度は、マイナスの無限に向けて切り詰められる。 @ で始まるこうした数値は、他の日付項目のいかなるものとも組み合わせて使うことができない。欠けるところのない完全なタイムスタンプの指定だからである。

コンピュータの内部では、時間は、ある紀元 (an epoch) --- きちんと定義された時間中のある一点 --- からの秒数として表現されている。 GNU や POSIX のシステムでは、紀元は 1970-01-01 00:00:00 UTC である。従って、@0 はその時刻を表し、@1 は 1970-01-01 00:00:01 UTC を表す、以下同様ということになる。GNU を始め、POSIX に準拠したほとんどのシステムでは、 POSIX に対する拡張として、こうした時間表記をマイナスの秒数を使うことも含めて、サポートしている。従って、@-1 は 1969-12-31 23:59:59 UTC を表すことになる。

旧来の Unix システムでは、秒を 32 ビットの 2 の補数である整数で数えており、 1901-12-13 20:45:52 から 2038-01-19 03:14:07 UTC まで表すことができる。もっと新しいシステムでは、64 ビットの秒数計算を、ナノセコンドのサブカウント付きで使用しており、宇宙の寿命として知られている時間のうちのあらゆる時刻を 1 ナノセコンドの精密さで表すことができる。

ほとんどのホストのこうした計算では、閏秒の存在が無視されている。たとえば、ほとんどのホストで @915148799 は 1998-12-31 23:59:59 UTC を表し、@915148800 は 1999-01-01 00:00:00 UTC を表している。従って、間にある 1998-12-31 23:59:60 UTC という閏秒を表現する方法は存在しない。

タイムゾーン・ルールの指定
=============

通常、日付の解釈は、現在のタイムゾーンのルールを使って行われる。そして、その現在のタイムゾーンのルールを指定しているのは、環境変数 TZ か、 TZ が設定されていなければ、システムのデフォルト設定である。今、ある一つの日付のみに別のタイムゾーンでデフォルトとして使われる一連のルールを適用したいとしよう。その場合は、日付を TZ="RULE" という文字列で始めればよい。日付中では、対になった二重引用符 (") で RULE を必ず囲わなければならない。また、RULE 中に引用符やバックスラッシュがあるときは、それをバックスラッシュでエスケープしなければならない。

一例を挙げると、GNU の date コマンドを使って、「パリで 2004 年 10 月 31 日 午前 6 時 30 分のとき、ニューヨークでは何時か？」という質問に答えることができる、以下で示すように、TZ="Europe/Paris"で始まる日付を使うのである。

     $ export TZ="America/New_York"
     $ date --date='TZ="Europe/Paris" 2004-10-31 06:30'
     Sun Oct 31 01:30:00 EDT 2004

この例では、--date のオペランドの最初の部分で、それ自身の TZ が設定されている。そこで、このオペランドの残りの部分は、Europe/Paris のルールに従って処理され、2004-10-31 06:30 という文字列が、パリの日時であるかのように扱われる。ところが、date コマンドの出力は、全体のタイムゾーンのルールに従って処理されるので、出力にはニューヨークの時刻が使用されるのである。 (2004 年には、パリは通常ニューヨークより 6 時間進んでいた。しかし、この例は、時差が 5 時間だったハロウィーンの日のほんの短い間のことを言っている。)

TZ の値はルールであり、ルールには通常、tz データベースの地域名が付けられている (http://www.twinsun.com/tz/tz-link.htm)。地域名の最新の一覧は、TWiki Date and Time Gateway で見ることができる (http://twiki.org/cgi-bin/xtra/tzdate)。なお、GNU 以外のホストの中には、TZ を設定するとき、TZ=":America/New_York" のように、地域名の前にコロンを置く必要があるものもある。

tz データベースには、Arctic/Longyearbyen から Antarctica/South_Pole に至るまで、実にさまざまな地域が含まれている。それでも、目下航海の最中でタイムゾーンが船特有のものである場合や、tz データベースに対応していない non-GNU のホストを使用している場合は、tz データベースの地域名で表されるルールの代わりに、POSIX 式のルールを使う必要があるかもしれない。UTC0 のような POSIX 式の単純なルールだと、夏時間なしのタイムゾーン指定になるが、簡単な夏時間制度なら指定できる他のルールも存在する。 See Specifying the Time Zone with TZ (The GNU C Library).

parse_datetime の作者
==================

parse_datetime は、getdate として誕生した。最初の実装を行ったのは、Steven M. Bellovin (smb@research.att.com) であり、ノースカロライナ大学チャペルヒル校に在学中のことだった。その後、Usenet 上で数人によって機能が追加され、1990 年 8 月に Rich $alz (rsalz@bbn.com) と Jim Berets (jberets@bbn.com) によって徹底的なオーバーホールがなされた。 GNU システムのための様々な改訂は、David MacKenzie, Jim Meyering, Paul Eggert などによって行われた。ほぼ同様の機能を持つ Posix の getdate 関数との衝突を避けるために get_date と名前を変えたのも、この改訂の一部であり、さらに後に、parse_datetime と改名した。Posix の getdate 関数は、strptime の使用によりロケール特有の日付の解析に優れているが、環境変数や外部のファイルに依存しており、 parse_datetime の持つスレッド安全性 (thread-safety) を欠いている。

この章は、François Pinard (pinard@iro.umontreal.ca) がソースコードの parse_datetime.y を元にして最初に作成し、その後、K. Berry (kb@cs.umb.edu) が増補改訂した。