2012/12/16

文系のための「日付型」

我々は、無意識に様々な種類の情報を使い分けている。
例えば、「数値」と「数字」の区別は、何も考えずに出来ている。
人の認識能力は、非常に柔軟に、当たり前のことをこなしてしまうのである。

しかしながら、コンピュータは、人の認識能力にははるかに及ばず
多種多様な情報の種類を柔軟に分離する能力を持っていない
人に違和感を感じさせないためには、一つ一つ、定義しなければならない。

そういった定義の中に「データ型」がある。
データ型」は、非常に多くの種類が存在していて、
このブログで全てを紹介することは不可能に近いそれくらい多い

本ブログでは、すでに、「文字列型」と「数値型」について整理した。
この二つは、最も基本的なデータ型ではあるが、これだけではない。
ということで、今回は「日付型」と呼ばれる「データ型」に注目したい。

まず、一つの疑問として、なぜ、「日付型」が必要なのか?
一見すると、その型を定義することに重要な意味を見いだせないかもしれない。
しかしながら、この型を定義しなければ少々厄介なことが起きるのである。

12月31日の『1』日後の日付を教えてください。」という質問にどのように答えるか?
おそらく、普通の人は、即座に「1月1日」と答えるであろう。常識的に。
では、この質問の内容を、データ型の考え方を含めて整理してみるとどうなるか?

1日後」の日付ということは、これは日付の「足し算をしていることになる。
したがって、この問い合わせに適したデータ型は文字列型ではなく、数値型であろう。
月と日は、「整数値」で表すことができるので、仮に、そのように定義したとする。

すると、困ったことになっていまう。「31+1=32」になってしまうので、
12月31日」の次の日が「12月32日」になってしまい、「1月1日」にはならない。
年末の忙しい時期、「32日」があったら嬉しいかもしれないが…システムとしては困る

また、日付の表し方の問題もある。「○○月××日」という表し方、
○○-××」、「○○/××」あるいは「××/○○」といった表現方法もある。
これらの問題を解決しないと、コンピュータ上で予定表が作れない

国や地域、文化圏によって異なる表記さえも考慮しなくてはならない。
要するに、「日付型」というのは、これらの問題を解決するための「データ型」である。
つまり、日付型の主要な役目というのは、次の二つに分けることができる。
  • 日付や時間の演算を可能にする。
  • 要求に応じて適切な日付表記を提供する。
さてさて、かなり前置きが長くなったが、まずは、PostgreSQL 9.1 のデータ型を確認。

型名サイズ説明最遠の過去最遠の未来精度
timestamp8 バイト
日付と時刻両方
(時間帯なし)
4713 BC294276 AD1μ秒、14桁
timestamp8バイト
日付と時刻両方、
時間帯付き
4713 BC294276 AD1μ秒、14桁
date4バイト日付(時刻なし)4713 BC5874897 AD1日
time 8バイト時刻(日付なし)00:00:0024:00:001μ秒、14桁
time12バイト
その日の時刻のみ、
時間帯付き
00:00:00+145924:00:00-14591μ秒、14桁
interval12バイト時間間隔-178000000年178000000年1μ秒、14桁

大きく分けて4種類、全部で6種類の「日付型」が定義されていることが解る。
このデータ型に関しても、表現できる日付の区間には上限」と「下限」があるので、
この範囲を超えた日付は、これらの型では扱えない。これは、他のデータ型と同じ。

また、「精度」に「14桁」と書いてある型は「浮動小数点型」が使われている。
date型は「整数値型」のようである。いずれも、どこかを基準に「0」とおいていて、
そこからの日数で日付を計算しているらしい。

実は、この「基準となる日」が重要。
PostgreSQL の場合、その基準日は「2000年1月1日 00:00」となっていて、
基本的に、時間に関しては、小数点以下の部分で表すようになっている。

とりあえず、timestamp型を見てみる。これは、日付と時間の両方を表すことができ、
さらに、時間帯(タイムゾーン)の指定できる。
date型time型は、timestamp型で表記できる部分を、日付時間に分けたようなもの。

時間帯というのは、世界協定時間(UTC)からの時差。
例えば、「2012-12-16 12:58:23 -9:00」のように表すことができる。
通常は、timestamp型、date型、time型の3つのいずれかを使うことになろう。

コンピュータ上で日付を扱う際には、気を付けるべきことがいくつかある。
日付を記述する際の、「」と「」と「」の書き方である。
この表記方法は、国や地域によって異なり、間違えると年と日が入れ替わる

例えば、YMD方式と言われる方法では、Year/Month/Dayの順番で表記し、
一方、MDY方式と言われる方法では、Month/Day/Year の順番で表記する。
例えば、「12/12/10」の場合、どちらかを区別することは可能であろうか?

  • YMD方式:2012年12月10
  • MDY方式:2010年12月12

となり、全く異なる日付になってしまう。この問題を回避するのは簡単である。
要するに、年を省略せずに「4桁」で記述する癖をつけておくこと。
かつては、データの容量を節約するために「2桁」表記をしていたが、今は関係無い。

最も安全な記述方法は、年と月と日を「-(ハイフン)」で区切った方法
例えば、「2012-12-16」と記述する。間違い無く、特定の日付を指定できる
実は、ISO 8601という国際標準で定められた方式

少し、話が逸れるが、コンピュータ上で日付を扱う際に「和暦」を用いている人がいる
確かに、ソフトウェアによっては、これを上手く操作している場合もあるが、
計算プロセスが増えてしまうので良い方法とは言えない。西暦表記に直した方が良い

これらの問題が、なぜ重要かは、データ型のことが分かっていれば解るハズ。

また、いわゆる文系分野では、歴史を扱う場合もあるが、
それぞれの日付型の「上限」と「下限」についても考慮した方が良い。
 timestamp型date型time型の3つの型では、「縄文時代」は扱えない

そもそも、現在のデータベースシステムは、それほどまでに古い時間を扱う想定が無い
日付型で古い時代を扱うためには、interval型を拡張すれば良いのかもしれないが、
正直なところ、私は、interval型を上手く使いこなせていない。色々と検証が必要

さて、今回は、「日付型」について整理した。
最も重要なことは、ある基準となる日があって、そこからの日数で計算すること
したがって、その実体は数値型であり、timestamp型などでは浮動小数点型を使っている。

ただし、注意しないといけない。

文字列型」と「数値型」と同様に、システムやソフトウェアによって定義が異なる
面倒なことに、基準日の設定そのものが異なるので、
自分が使っているシステムやソフトウェアの仕様を確認する必要がある。

特に、Excelは色々と厄介で、知っていないと困ることがある。
基本的には、1900年1月1日が基準であるが、1904年1月2日を基準とする方法もある
1904年1月2日を基準というのは、何とも半端な気もするが閏年が関係しているらしい。

ちなみに、この方法で表された時間のことを「シリアル時間」と呼ぶ。

Windows版のExcelの場合には、1900年が基本であるが「負の値」を扱えない
一方、Mac版Excelの場合には、1904年が基本であり「負の値」も扱える
もちろん、どちらの版も、相互に変換できるが、4年と1日ほどずれる
この話、執筆時にUbuntuを使っていたので未検証。

この問題が理解できていれば、Excelのあの不思議な現象の原因も容易に理解できる。
つまり、ID番号のつもりで「12-12-16」などと入力すると「2012年12月16日」になり
慌てて、文字列型に変換すると「42716」となる問題…これは1900年1月1日からの日数である。

日付では無いのであれば、日付型に判断されないようにする工夫が必要で、
最も簡単な方法は、先頭に何らかの文字を置いて日付型に見えないようにするか、
あるいは、「"」や「'」で囲んで文字列型にするという手段がある。

ところで、当然のことながら、Python にも日付型が定義されているが、
その基準日は、1970年1月1日 00:00 である。PostgreSQL ともExcelとも異なる…。
ということで、ここからは、Python で実験してみる。

試しに、Python コンソールを開いて、以下の二行を実行してみる。

import time
time.gmtime(0)

ここでは、一行目で「時間」を扱うライブラリ「time」を読み込んで
二行目では、そのライブラリが持っている「gmtime()」という機能を使っている。
0」とすることで、基準となる「0」の日時を取得できる。結果は以下の通り。

>>> time.gmtime(0)
time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=1, tm_isdst=0)

この結果から、1970年1月1日 00:00 が基準であることが解る。
また、「tm_wday=3」となっているので、月曜日を「0」として、
この日が「木曜日」であることが解る。「tm_yday」は、365日換算した時の日数

最後に、tm_isdstと言うのは、年を2桁で表示した時の問題を回避するためのものらしい。
いわゆる「2000年問題(Y2K問題)」の対策のためのもの。
ついでに、以下のコマンドも実行してみる。

time.time()

これは、現在時刻を浮動小数点型で教えてくれる。実行結果は以下の通り。

>>> time.time()
1355635704.049219

これが、基準日からの現在の日付と時刻を表している。
さらに、これを、我々が見慣れた表記にするためには、
最初の「time.gmtime」を使って以下のようにする。

time.gmtime(time.time())

そして、この実行結果は以下の通り。

>>> time.gmtime(time.time())
time.struct_time(tm_year=2012, tm_mon=12, tm_mday=16, tm_hour=5, tm_min=30, tm_sec=7, tm_wday=6, tm_yday=351, tm_isdst=0)

このように、日付型は内部的には数値型で計算され、
必要に応じて、適切な表示がなされるのである。

日付型」は、我々が、コンピュータ上で頻繁に扱っているにも関わらず、
あまり意識することが無いデータ型であり、意外に、奥が深い。
扱いを間違えると、全く異なる日時を示すことがあるので注意が必要である

0 件のコメント:

コメントを投稿