メッセージ。 - Haskellでの日付・時刻の処理
# Haskellでの日付・時刻の処理
Haskellの習作として、RSSのパーサーを作ってみた。その際、日付・時刻情報の取り扱い、とくにRSS中の時刻情報(文字列)をパースして内部表現に変換する方法を模索した。Webを検索もしてみたのだが、そういった情報はあまり見つからなかったので、ここにメモをしておく(この模索では、処理系としてGHCを使用したため、ここに書いた情報ではほかの処理系に適合しない場合もあるかと思います)。
『Real World Haskell』の20章を見たところ、Haskellでは基本的にClockTimeというデータ型を使うのが便利らしい。GHCのマニュアルでいうとSystem.Timeの項に説明がある。Eq、Ord、Showクラスのインスタンスなので、日付どうしを比較したり、デバッグ用にshowで文字列変換したりするのも簡単だ。ClockTimeは、内部的に1970年1月1日からの秒数を持っていて、UNIXでの標準的な時刻の扱いに近い。
ところが一方で、ClockTime型の値を任意フォーマットの文字列に変換したり、逆に時刻情報を記述した文字列を読み込んでClockTime型に変換したりする方法は、標準では用意されていないようだ。GHCマニュアルの中では見つけられなかった。そこで、そういった機能は拡張パッケージで提供されているのかもしれないと、パッケージを探してみた。
ぼくは、GHCの開発環境としてDebian lennyを使っているので、apt-cache search ghcとしてみる。すると、次のパッケージが怪しそうだ。インストールしてGHCのマニュアル(file:///usr/share/doc/ghc6-doc/index.html)を再読み込みしてみる。
「Data.Time.Format」というモジュールができていることに気付いた。parseTimeやformatTimeのように、文字列と内部表現の橋渡しをしてくれそうな関数も見える。
ParseTime tというのがよく分からなかったのだけど、どうやら次の内部表現に対応しているということらしい。とくに、UTCTimeというのはClockTimeと同じようなもので、年月日+時刻を扱うのに良さそう。
今回はRSS 2.0から日付・時刻情報をパースするので、次のようにすれば良い。
内部表現を文字列に変換する場合は、次のようになる。
と、うまくいけば格好良いのだけど、厳密にはこの実行結果は正しくない(W3Cの日付フォーマットに従っていない)。正しくは「"2009-06-29T14:11:08+00:00"」と、最後のタイムゾーンにコロンが入っているのだ。ここをうまく解決する方法は、まだ見つけていない。
あと、RFC822の文字列をパースするとき、西暦が「2009」のような4桁ならばうまく動くのだけど、ここが2桁だった場合も期待したように動かない。この場合、コードと実行結果は次のようになる。
1909年と解釈してしまうのだ。これも、ちょっと解決策は見つかっていない。ということで、parseTimeとformatTimeを使う方法も、ちょっと足りないようだ…。まぁ、この程度なら大きな問題ではないので、独自実装するほうがよいのかもしれない。いつも思うことながら、日付の問題は案外面倒くさいですね。
『Real World Haskell』の20章を見たところ、Haskellでは基本的にClockTimeというデータ型を使うのが便利らしい。GHCのマニュアルでいうとSystem.Timeの項に説明がある。Eq、Ord、Showクラスのインスタンスなので、日付どうしを比較したり、デバッグ用にshowで文字列変換したりするのも簡単だ。ClockTimeは、内部的に1970年1月1日からの秒数を持っていて、UNIXでの標準的な時刻の扱いに近い。
*Main> :m System.Time Prelude System.Time> getClockTime Sun Jul 12 23:38:45 JST 2009
ところが一方で、ClockTime型の値を任意フォーマットの文字列に変換したり、逆に時刻情報を記述した文字列を読み込んでClockTime型に変換したりする方法は、標準では用意されていないようだ。GHCマニュアルの中では見つけられなかった。そこで、そういった機能は拡張パッケージで提供されているのかもしれないと、パッケージを探してみた。
ぼくは、GHCの開発環境としてDebian lennyを使っているので、apt-cache search ghcとしてみる。すると、次のパッケージが怪しそうだ。インストールしてGHCのマニュアル(file:///usr/share/doc/ghc6-doc/index.html)を再読み込みしてみる。
libghc6-time-dev - Haskell time library for GHC libghc6-time-doc - Haskell time library for GHC; documentation libghc6-time-prof - Haskell time library for GHC; profiling libraries
「Data.Time.Format」というモジュールができていることに気付いた。parseTimeやformatTimeのように、文字列と内部表現の橋渡しをしてくれそうな関数も見える。
formatTime :: FormatTime t => TimeLocale -> String -> t -> String parseTime :: ParseTime t => TimeLocale -> String -> String -> Maybe t
ParseTime tというのがよく分からなかったのだけど、どうやら次の内部表現に対応しているということらしい。とくに、UTCTimeというのはClockTimeと同じようなもので、年月日+時刻を扱うのに良さそう。
ParseTime Day ParseTime LocalTime ParseTime TimeOfDay ParseTime TimeZone ParseTime UTCTime ParseTime ZonedTime
今回はRSS 2.0から日付・時刻情報をパースするので、次のようにすれば良い。
import Data.Time import Data.Time.Format import System.Locale import Maybe parseRFC822 :: String -> UTCTime parseRFC822 s = fromJust $ parseTime defaultTimeLocale "%a, %d %b %Y %T %z" s main = print $ parseRFC822 "Mon, 29 Jun 2009 14:11:08 +0000" -- 実行結果 2009-06-29 14:11:08 UTC
内部表現を文字列に変換する場合は、次のようになる。
import Data.Time import Data.Time.Format import System.Locale import Maybe parseRFC822 :: String -> UTCTime parseRFC822 s = fromJust $ parseTime defaultTimeLocale "%a, %d %b %Y %T %z" s showW3CDate :: UTCTime -> String showW3CDate = formatTime defaultTimeLocale "%Y-%m-%dT%T%z" main = do t <- return $ parseRFC822 "Mon, 29 Jun 2009 14:11:08 +0000" print $ showW3CDate t -- 実行結果 "2009-06-29T14:11:08+0000"
と、うまくいけば格好良いのだけど、厳密にはこの実行結果は正しくない(W3Cの日付フォーマットに従っていない)。正しくは「"2009-06-29T14:11:08+00:00"」と、最後のタイムゾーンにコロンが入っているのだ。ここをうまく解決する方法は、まだ見つけていない。
あと、RFC822の文字列をパースするとき、西暦が「2009」のような4桁ならばうまく動くのだけど、ここが2桁だった場合も期待したように動かない。この場合、コードと実行結果は次のようになる。
parseRFC822 :: String -> UTCTime parseRFC822 s = fromJust $ parseTime defaultTimeLocale "%a, %d %b %y %T %z" s main = print $ parseRFC822 "Mon, 29 Jun 09 14:11:08 +0000" -- 実行結果 1909-06-29 14:11:08 UTC
1909年と解釈してしまうのだ。これも、ちょっと解決策は見つかっていない。ということで、parseTimeとformatTimeを使う方法も、ちょっと足りないようだ…。まぁ、この程度なら大きな問題ではないので、独自実装するほうがよいのかもしれない。いつも思うことながら、日付の問題は案外面倒くさいですね。
Comment
Trackback