メッセージ。 - 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での標準的な時刻の扱いに近い。

*Main> :m System.Time
Prelude System.Time> getClockTime
Sun Jul 12 23:38:45 JST 2009
ClockTimeを使ってみる

ところが一方で、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
Data.Time.Formatモジュールに含まれている関数(抜粋)

ParseTime tというのがよく分からなかったのだけど、どうやら次の内部表現に対応しているということらしい。とくに、UTCTimeというのはClockTimeと同じようなもので、年月日+時刻を扱うのに良さそう。

ParseTime Day
ParseTime LocalTime
ParseTime TimeOfDay
ParseTime TimeZone
ParseTime UTCTime
ParseTime ZonedTime
Data.Time.Formatモジュールで扱う日付・時刻の内部表現

今回は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
RSS 2.0のpubDate要素から日付・時刻情報を内部表現に変換する

内部表現を文字列に変換する場合は、次のようになる。

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"
内部表現のUTCTimeを文字列に変換する場合

と、うまくいけば格好良いのだけど、厳密にはこの実行結果は正しくない(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
西暦が2桁の場合のコード(?)

1909年と解釈してしまうのだ。これも、ちょっと解決策は見つかっていない。ということで、parseTimeとformatTimeを使う方法も、ちょっと足りないようだ…。まぁ、この程度なら大きな問題ではないので、独自実装するほうがよいのかもしれない。いつも思うことながら、日付の問題は案外面倒くさいですね。
2009-07-13 00:50:35 / ふじさわ / Comment: 0 / Trackback: 0

Comment

コメント投稿機能は無効化されています。

Trackback

TrackBack投稿機能は無効化されています。