2009年4月18日土曜日

時刻を扱う - 特定の日付に属さない時間

例えば、「いつも朝の 6:30 から 6:40 までラジオ体操をする」という事柄を表現したいとする。「いつも」とあるように、この表現は特定の日付と関連したものではない。過去のある期間においてそういう事実があったことを記録したいわけでもなく、未来の予定について述べているわけでもない。「日常」という言葉通り、典型的な「生活パターン」というような抽象的なレベルの事象をどのように表現して扱うかという問題。

 

Access の日付/時刻型

さて、Access でデータを管理しようと思うと、上記の「時刻」をどのデータ型で扱えばいいのだろうか?最初に思い付くが「日付/時刻型」。(cf.  Access で使用できるフィールドのデータ型 (MDB) - Access - Microsoft Office Online)

「日常生活」と命名したテーブルを以下のように定義し、

090417-001

データを入力したとする。

090417-002

 

これに対して、次のように「年月日」を表示させるクエリを実行すると、

SELECT 日常生活.id, 日常生活.内容, Format([開始時刻],"yyyy/mm/dd hh:nn") AS 式1, Format([終了時刻],"yyyy/mm/dd hh:nn") AS 式2
FROM 日常生活;

時刻の前に `1899/12/30’ が表示される。これでは表現したいデータを素直に表現しているとは言い難い。 (+_+)

090417-003

ちなみにこの日付となる理由は、「フィールドのデータ型の設定を修正または変更する - Access - Microsoft Office Online」によると、

Access では、1899 年 12 月 30 日が日付 0 として使用されます。

 

時間について

プログラマのためのSQL 第2版 (P64) によると、

時間に関する重要な問題の 1 つに、時間には 3 種あるというのがあります。固定された出来事「彼は、13:00 に到着した」、期間「その旅は 3 時間かかる」、時間間隔「その列車は 10:00 に出発して 13:00 に到着する」は、それぞれ依存関係を持っています。 SQL-92 は、 INTERVAL データ型を導入していますが、ほとんどの現実の実装 (…) では、明白に持っていません。

(太文字は引用者による)

SQL – Wikipedia を見ると、SQL-92 で INTERVAL が追加されているのがわかる。

データ型の拡張 (可変長文字列、ビット、文字集合、日付・時刻・時間間隔 (DATE, TIME, TIMESTAMP, INTERVAL))

どうやらこの「時間間隔」というのが考える手がかりになりそうだ。

ちなみに Access と SQL のデータ型を比較した表を見ると、Access では INTERVAL 型を直接表現した型がないことがわかる。

 

ところで、Access ではユーザ定義型を作成することができない。(型とその型に適用できる演算子の定義がまともにできる RDB ってどれくらいあるのだろう?) よって、上記の要求を満たすドメインの値を素直に保持することはできない。そのため、結果として機能し得る型で代用することになる。

 

MySQL の Time 型

MySQL :: MySQL 5.1 Reference Manual :: 10.3.2 The TIME Type によると、

MySQL retrieves and displays TIME values in 'HH:MM:SS' format (or 'HHH:MM:SS' format for large hours values). TIME values may range from '-838:59:59' to '838:59:59'. The hours part may be so large because the TIME type can be used not only to represent a time of day (which must be less than 24 hours), but also elapsed time or a time interval between two events (which may be much greater than 24 hours, or even negative).

MySQL 5.1 では「時間間隔」と「一日の時刻」を同じ Time 型で表わすようだ。というより、「一日の時刻」を「時間間隔」で代用しているということかな。

 

Postgres の interval 型と time 型

Postgres は、「時間間隔」 (Interval) と「その日の時刻」(time) が分かれている。

 

Python の time オブジェクト

ところで、以前に Python で指定した日数後の日付を得るのに timedelta オブジェクト を使ったことがある。timedelta 周りを見ると time オブジェクト があるのに気がついた。(@_@)

time クラスは、

理想化された時刻表現で、あらゆる特定の日における影響から独立しており、毎日厳密に 24*60*60 秒であると仮定します ("うるう秒: leap seconds" の概念はありません)。 属性: hourminutesecondmicrosecond、 および tzinfo

(5.1.1 利用可能なデータ型 より)

まさにこれ。ただし、

time オブジェクト間の四則演算はサポートされていないので注意してください。

(time オブジェクト より)

「年月日」がなく「時・分・秒」だけので、上記の例を表現するには概念的にちょうどいい。

 

Visual Basic の日付

VB にも、似たような見かけの TimeSerial 関数 があったが、

    Debug.Print TimeSerial(6, 30, 0)
    Debug.Print Format(TimeSerial(6, 30, 0), "yyyy/mm/dd hh:nn")

実行すると、

6:30:00 
1899/12/30 06:30

あ~、内部では日付型かぁ…

@IT:連載:プロフェッショナルVB.NETプログラミング 第5回 日付時刻の取得とフォーマット によると、

日付と時刻の扱いは、Microsoft系BASICの歴史に限ってもすでに激変が起きている。初期のBASICでは、日付と時刻はそれぞれ文字列として扱われており、日付と時刻は別個の情報として扱われていた。(…)

Timeの場合日付は1899年12月30日となっているが、これはVB 6の日付を扱う起点となる日付である。(…)

VB.NETでは日付時刻の起点は0001/01/01 12:00:00である

うーむ… (@_@;)

 

Haskell の TimeOfDay 型

では、Haskell ではどうかな?と思い調べてみると、Time of day に、

data TimeOfDay

Time of day as represented in hour, minute and second (with picoseconds), typically used to express local time of day.

「時・分・秒」のみを値に持つ型がある。

先ほどの Python の Time オブジェクトと同様に、TimeOfDay 型は Num クラスのインスタンスではないので四則演算はできない。ただし、DiffTime 型という「時間間隔」を表わす型を利用すると計算を行うことができる。

最初に timeOfDayToTime 関数で TimeOfDay 型を DiffTime 型に変換。この関数は午前 0 時からの時間間隔を返してくれる。

timeOfDayToTime :: TimeOfDay -> DiffTime

090417-001

DiffTime 型は Num クラスのインスタンスなので、この型の値を足したり引いたりできる。それを上記の関数とは逆の timeToTimeOfDay で元の型に戻す。例えば、

import Data.Time
main = do
  let s = TimeOfDay 6 30 0
  print s
  let ds = timeOfDayToTime s
  print ds
  print $ timeToTimeOfDay $ ds + (secondsToDiffTime (10*60))

結果は、

06:30:00
23400s
06:40:00

 

対応する型がない場合の妥協

さて、話を元に戻して Access で時刻を扱うにはどうしよう。

一つは、年月日の情報は無視して、つまり ‘1899/12/30’ はないものとして時刻だけを「日付/時刻型」で扱う。とりあえず楽な方法。

自前でどうにかしたいなら、「時・分・秒」をそれぞれ別のフィールドで数値として扱い、それを一塊のものとして自分で思い込み、「時・分・秒」間での計算が必要なら関数を定義。ちょっと面倒だし、それに見合ういいことがあるのかわからない。

また別の方法として、NULL撲滅委員会 にあるように、

日付の列は文字型で宣言しておくべきです。(…) ほとんどの DBMS が日付型を用意していますが、私は個人的に使うメリットを感じません。暦日計算の場合など、必要なときだけキャストしています。

時刻を文字列として保持し、必要なときだけキャストする。ただし、年月日の情報は無視して日付型として計算を行う。この場合、最低でも文字列として時刻を保存するとき、時刻へとキャスト可能な形式であるか確認する必要となる。