Good morning. It's twenty-five to five. (Japan Standard Time)

日付解析の手法

ここに parsedate の日付解析の手法についての覚書 (をまとめたもの) を 残しておきます。

歴史

その前に parsedate の歴史です。

元の parsedate (ここでは parsedate1 と呼びましょう) は、 まつもとさんが書かれたものだと思います。 僕が最初に見たときには、20行くらいのたいへん素朴なものでした。 parsedate は、少しづつ手を入れられて徐々にコードは膨れあがりました。 一度整理する必要があると感じて、 思いきって parsedate2 として書きなおすことにしました。 しかしこれも速度的に問題があり、再び、parsedate3 として書きなおしました。 これが現在の parsedate です。 ただし、date2 3.x では、parsedate の本体は、date/format.rb に移動しています。

日付の走査

parsedate2 は rbison で書かれていました。 ちゃんとした (?) 構文解析の手順を踏んでいるのです。 遅かったのはそのためです。 parsedate3 は、parsedate1 と同じです。 基本的に、正規表現との照合を必要なだけ繰り返すだけです。 これは速いのですが、安易にパターンを増してしまいがちで、 よく考えて整理しておかないと、すぐに訳がわからなくなります。 parsedate2 はボツになったのですが、 parsedate3 を書くためにたいへん役立ったと思います。

parsedate2 と parsedate3 は、手法の違いから、速度以外にも、 それぞれ長所と短所があります。

parsedate2 は、"09 AM" をちゃんと9時と認識します。 parsedate2 では、こういった表現を比較的素直に受けとれると感じました。 もちろん、parsedate3 でも、やろうと思えばできるはずですが、 わざわざ、そこまでする必要性は感じられないので、やっていません。 parsedate2 のやりかたでは、簡単にできると感じたので、 そのようにしてみただけです。 GNU date などでつかわれている get_date() は yacc で書かれています。 GNU date も、"09 AM" を受けつけます。 さらには、"09" も受けつけます。

parsedate3 は、ノイズに強いです。

Date: Mon, 20 Mar 2000 23:29:46 +0900
February 04, 2001 at 10:59 AM PST

といった入力も大丈夫です。 parsedate2 や get_date() では難しいです。

ちなみに、以下の日付は、parsedate2、parsedate3 ともに大丈夫ですが、 get_date() では受けつけてもらえません。 parsedate もなかなかがんばっています。

Sept. 23
October 23rd
2001-02-08 23:55:21+09:00
2001-02-08T23:55:21Z
H11.02.08T23:55:21Z
12-January-1990, 04:00 WET

一方、get_date() は、"yesterday" のような相対的な表現も受けつけます。

日付の分類

エンディアンネス

年月日の順番です。 以下のパターンがあります。

年月日 (日本、ISO 8601 など)

parsedate では、ISO 8601 のよくつかわれる形式と JIS X 0301 拡張の一部を 受けつけます。

「ISO 8601 のよくつかわれる形式」とは

ISO 8601 (1988、あるいは 2000) における、 暦日付 (calendar date) の完全表記 (Complete representation) の 基本形式 (Basic format) と拡張形式 (Extended format) の両方、 および上位省略表記 (Truncated representations) における、 ある世紀の特定の年月日 (A specific date in the current century)。 拡張形式では、拡大表記を許します。

CCYYMMDD
CCYY-MM-DD
YYMMDD
YY-MM-DD

地方時の時刻 (Local time of the day) の完全表記、 および下位省略表記 (Representations with reduced precision) の それぞれ基本形式と拡張形式の両方。 ただし、基本形式は日付との組み合せにおいてのみ有効である (単独では、日付の基本形式と区別がつかないので)。

最新版では、コンマ、およびピリオドにつづく秒の端数があってもよいです (たとえば、11:22:33,44 など)。 ただし、parsedate では、結果に反映されません。

hhmmss
hh:mm:ss
hhmm
hh:mm

協定世界時 (Coordinated Universal Time)、 および地方時と協定世界時との差 (Differences between local time and Coordinated Universal Time) の基本形式、 拡張形式の表記。

Z

+hhmm
+hh
-hhmm
-hh

+hh:mm
+hh
-hh:mm
-hh

上記の書式の範囲内における日付と時刻の組み合せ (Combinations of date and time of the day representations) の表記。

ただし、parsedate は、ISO 8601 の解釈を厳格には適用しません。 たとえば、ISO 8601 では、2000年の改訂版で、一切間隔 (空白) を含まない、 と明言していますが、parsedate は、 "1985-04-12 10:15:30" も "1985-04-12T10:15:30" も 同じように受けとります。

「JIS X0301 拡張の一部」とは

JIS X0301 (1992、あるいは 2002) における、 元号による日付の拡張形式 (元号を識別する記号があるもの)。

スラッシュで区切ったもの

スラッシュで数字を区切る形式は、基本的に米国式の月日年と解釈されますが、 "2003/02/18" のように あきらかに年が最初にあると判るものは年月日と解釈されます。

日月年 (欧州、RFC 822/850 など)

欧州といっても、今のところ英語の月名しか知りません。 parsedate は、RFC 822 や RFC 850 を受けつけます。 ダッシュで数字を区切る形式は、基本的に ISO 8601 と解釈されますが、 "18-02-2003" のように あきらかに年が最後にあると判るものは日月年と解釈されます。

月日年 (米国、asctime(3) や date(1) など)

parsedate は、asctime(3)、ctime(3)、date(1) などを受けつけます。

年のあつかい

年はしばしば省略されます。 もちろん、月の日、あるいは、月と日の両方が省略される場合もありますが、 parsedate の実際のつかわれかたからすれば、 それらはまったく重要ではないといえるでしょう。 年の省略は、どのようなパターンでも許されているわけではありません。 月が名前になっていて、年と月を並べただけのものは受けつけます。 parsedate1 は MM/DD を許していたことから、 このパターンも受けつけます (スラッシュで区切ったものだけです。 ダッシュで区切ったものはダメです)。 また、MMDD も受けつけます。 m4_dnl (これは、ISO 8601 の一部ですが、 m4_dnl これを許したのは、get_date() に対抗したためだったかも)。

その他

曜日、時刻およびタイムゾーン (時差) は付加的な存在といえます。

曜日は、省略月名などを含め一意な名前であれば、 とくに注意すべきことはないでしょう。 今現在は、曜日、月名ともに英語のみの受けつけなので問題ありませんが、 様々な地域の曜日、月名を受けつけようとすれば、その出現位置なども 考慮しなければならないかもしれません。

時刻は数字をコロンで区切った形式で、秒はどのような場合でも省略可能です。 午前午後を示す AM/PM を添えることもできます。

タイムゾーン (時差) は時刻の後に添えます。 ISO 8601 や RFC 822 でつかわれる形式などを受けつけます。

解析の実際

伝統的な date(1) の書式を考えてみましょう。 つぎのようなものです。

Tue Feb 18 00:03:05 JST 2003

まず、付加的な曜日をとり除きます。 するとつぎのようになります。

Feb 18 00:03:05 JST 2003

この日付を左から見てゆくと、 年が省略された日付のあとに時刻が現われているように見えます。 parsedate は、"Feb 18" を正しい日付として受けつけます。 しかし、その前に時刻とそれに伴なうタイムゾーンをとり除いてみましょう。 するとつぎのようになります。

Feb 18 2003

米国式の月日年のパターンをみつけました。 このように考えれば、 以下の微妙に違うふたつの日付も基本的に同じだとわかります。

Tue Feb 18 21:03:05 2003
Feb 18 2003 09:03:05 AM

このように整理すれば、むやみやたらとパターンを増さずに済みます。

課題

parsedate は、米国寄り、とみられる解釈をします。 たとえば、月名などは、英語のものしかありませんし、 日付の要素の順番もそうです。

月名や曜日名は、欧州のものだけでも結構あります。 parsedate では、月名や曜日名が一意であることは解析の手掛りです。 とくに略名のことを考えると、それらをひとまとめにして一意であるということは できません。

また、以下の日付は、2002年3月4日を、 日本式、欧州式、米国式で表現したものですが、 もちろん区別はつきません。

02/03/04
04/03/02
03/04/02

範囲検査を一切しません。 2002年13月32日、といった日付も有効です。 今のところ、必要ないと考えています。

漢字を含む日付も解釈できません。 ロケールなどを考慮すべきなのでしょうか。

対応が簡単そうでも、実装していないものもあります。 たとえば、ISO 8601 の 年間通算日 (年日付) や 暦週日付の拡張形式は、 特徴的で、対応は簡単そうですが、あまり需要はなさそうです。


目次

ふなばただよし 2002,2003
更新日時: 2014-06-14T17:54:24+09:00