こんばんは。 時刻は午後十時三十五分をまわったところです。(日本中央標準時)
この文書はまだまだ未完成です。 間違いを見つけた方は、おしらせください。 提案もどうぞ。
ここでの対象は、プログラミングにおける暦や時刻のあつかいについてです。 基本的に暦や時刻そのものの話題は含まれません。 また、天文学的な計算もあつかいません。
cal(1)| calendar(1)| date(1)| ctime(3)、asctime(3)| getdate(3)| get_date(3)| localtime(3)、gmtime(3)| mktime(3)| strftime(3)| strptime(3)| time(3)| xcalendar 4.0| xcal 4.1| zic(8)
こよみ計算入門| 超こよみ計算入門 (オススメのライブラリ)| 祝日法| 復活祭| ISO 8601 と JIS X0301 ありがちな間違い
計算機の2000年問題| 協定世界時 (UTC)| グレゴリオ暦| ユリウス暦| ユリウス日 (JD)| 修正ユリウス日 (MJD)| 切詰ユリウス日 (TJD)| リリウス日 (Lilian day)| スウォッチビート| ビッグエンディアン| リトルエンディアン
cal(1) は英国の改暦にのみ対応している (FreeBSD の ncal、 gcal、ruby 添付の cal.rb などはそうではない)、 やや風変りなコマンドです。
もともとの cal(1) 4.4BSD-Lite など 最近、ありがちなやつ
February 4 February 4 February 4
S M Tu W Th F S S M Tu W Th F S Su Mo Tu We Th Fr Sa
1 2 1 2 1 2
3 4 5 6 7 8 9 3 4 5 6 7 8 9 3 4 5 6 7 8 9
10 11 12 13 14 15 16 10 11 12 13 14 15 16 10 11 12 13 14 15 16
17 18 19 20 21 22 23 17 18 19 20 21 22 23 17 18 19 20 21 22 23
24 25 26 27 28 29 24 25 26 27 28 29 24 25 26 27 28 29
MINIX のもの gcal 2.40
Feb 4
February 0004
S M Tu W Th F S Su Mo Tu We Th Fr Sa
1 2 1 2
3 4 5 6 7 8 9 3 4 5 6 7 8 9
10 11 12 13 14 15 16 10 11 12 13 14 15 16
17 18 19 20 21 22 23 17 18 19 20 21 22 23
24 25 26 27 28 29 24 25 26 27 28
cal(1) は西暦4年を閏年としていますが、これは歴史的には正しくありません。 なぜなら、カエサルの死後にユリウス暦は間違って運用され、 その間違いをとり戻すために、この年まで変則的な運用していたからです。
歴史的には間違いですが、 ユリウス暦には、ユリウス日と同じように、 ただ単に、ある特定の日を表現するための方便としての利用法もあるので、 一概にダメともいえないでしょう。 実際のところ、このあたりをちゃんと運用通りに実装したコードというのは 殆どないと思われます。
gcal は、西暦4年を平年としています。 ここまでは、それほど難しくなさそうです。 一般に、cal(1) は紀元前に対応していないものですが、 もし、対応しようと思えば、それは簡単ではないかもしれません。 紀元前における閏年ははっきりしないところがあるようです。
初期のマニュアルでは、 VI. User-maintained programs (ユーザ保守のプログラム) に記述があります。 cal(1) を書いたのは、Ken "ken" Thompson なのでしょうか。 もともとの cal(1) は初期のころから、あまり変化していないように思われます。
現在の FreeBSD では、cal(1) は ncal(1) へのリンクになているようですが、 個人的には以前のほうが好きでした。
calendar(1) は予定を知らせてくれるプログラムです。
もともとの calendar は、 日付をみつけるための egrep(1) のパターンを生成するプログラムと それを呼び出す簡単なシェルスクリプトから成っていました。
date(1) の書式については、 ctime(3) と strfimte(3) も 参照してください。
GNU date では、日付の解析に get_date() を つかっています。
ctime(3)、asctime(3) の結果は 26 文字で、
書式も決まっています (初期のころは 16 文字だったようだ)。
ANSI C でもちゃんと定められています。
UNIX では、ls(1) などで ctime(3) の結果を切り出してつかっていることもあります。
GNU Emacs の (current-time-string) も ctime(3) です。
date(1) も同じことで、本来、あっさり書式を変えてはいけないものだったと思います。
ちなみに、ctime(3) と asctime(3) の書式と、 典型的な date(1) の書式は似ていますが、ちょっと違います。
"Mon Dec 31 23:59:59 2001\n\0"
Mon Dec 31 23:59:59 JST 2001
システムによっては、getdate(3) というものがあるようです (glibc にはある)。
これは、strptime(3) に似ています。
strptime(3) には書式を直接わたしますが、
getdate(3) は環境変数 DATEMSK に
雛形ファイルのパスを設定しておきます。
あつかう書式が複数あったり、
書式を「外」に置いておきたい場合には便利なのかもしれません。
getdate() は曜日のみを受けとると、年月日などを補完します。 その際「今あるいは今より後」になるように解釈します。
パブリックドメインの get_date() というものがあります。 これは、yacc で書かれた解析器で、 与えられた日付や時刻の絶対、相対表現を解釈します。 get_date() は、GNU date、GNU tar、CVS などでつかわれています。
get_date() は タイムゾーンの略名を解釈するための表を自前で持っています。 どのような根拠で、数多くある略名のなかから選らばれたかはよくわかりません (zoneinfo にある略名を解釈しようとしても、 ちゃんとした標準があるわけでもなく、一意でもないので、 仮令、zoneinfo の情報をまるごと持っていても完璧な解釈など提供できません。 だから、何かを捨てないといけません。 でも、どのように決めればいいのでしょう?)。
get_date() は "friday" のような表現を
「『今』あるいは『今』より後」に解釈します。
つまり、天気予報など、未来について参照したいときは、
ただ、"friday" とすればよいのです。
ログなど、過去について参照したいときは、
"last friday" としましょう。
get_date() は time_t を返します。
get_date() は内部で mktime(3) を呼びだしますが (旧いのは違ったと思う)、
その返値が負になった場合でも、
すぐにエラーとは判断せずに、それが正しい結果かどうか確認しようとします。
つまり、エポック以前の日付も、あつかおうとしています。
これらは、time_t から struct tm へ変換する関数です。
gmtime(3) は協定世界時に、localtime(3) は地方時に変換します。
標準ライブラリで、任意のタイムゾーンを選択してつかうことはできません。
あつかえるのは、協定世界時と地方時のふたつだけなのです。
ところで、struct tm では、1月は 0 であらわされます。
このことについて、欧米人は、月を名前で呼ぶから、0 だろうと、1 だろうと、
構わないのだろう、という意見もあるようです。
たしかに、そういう事もあると思われます。
しかし、Common Lisp の Decoded Time では
1 です。
Dershowitz と Reingold、Meeus、そして Scott E. Lee のコードでも同じでした。
反対に、1月を 0 であらわすというのは、
あまり他では見ないように思います。そうなっているのは、
たいていは struct tm の流儀をただ踏襲したものだと思われるのです
(例: Perl、Java、JavaScript など)。
そもそも、cal(1) や strftime(3) でも、
1月は 1 なのです。
やはり、struct tm のほうが少し変っているのだと思います。
一方、曜日は、0 から始まるのが多そうです。
でも、どうしてそうなのかは、よくわかりません。
欧州なら月曜から、米国なら日曜から、週が始まるという感覚があるはずですし、
序数を割り当てていけない理由はないと思います。
たいていは、0 は日曜ですが、Python では、
0 は月曜です。
0 から始まらないものとして、
ISO 8601 の暦週が挙げられます。
Python や ISO 8601 で、週の始まりは月曜であるのは、
欧州の慣習に従ったものだからではないかと思います。
localtime(3) は 他人に公開することを想定して仕様を決めていないのだと思います。 おそらく、localtime(3)、もしくはその前身は ctime(3) の 下請けとして書かれたか、ctime(3) の部分だったのではないでしょうか。
mktime(3) は struct tm から time_t へ変換します。
mktime(3) は、与えられた struct tm を地方時としてあつかいます。
mktime(3) は localtime(3) の逆と考えられます。
協定世界時としてあつかうことはできないし、
標準ライブラリで、それを可能にする版があるわけではありません。
また、任意のタイムゾーンを解釈することもできません。
わたしたちは、なにかタイムゾーンを易々とあつかっているように錯覚しがちですが、
実はそうでもありません。
ANSI C にもある strftime(3) ですが、 ロケールや標準にはない指定子も多く、 どこでも、同じ結果が得られると思っていると失敗するかもしれません。
ANSI C の strfime(3) では asctime(3) や
典型的な date(1) の書式をつくることはできません。
何故なら、"%d" は 01-31 であるからです。
BSD、glibc、Arnold Robbins の strftime(3) など
では "%e" をつかうことができます。
こちらは 1-31 です。
strftime(3) の値は結果の長さです。
エラーの際には0を返すことになっています。
でも、これってヘンじゃないですか? 書式が空の場合だってあるわけです。
書式が空でなくても、"%Z" は空を
生成する可能性があります。これにキチンと対処するのはちょっと面倒です。
エラーの0とそうじゃない0をどう見分けたらよいのでしょう。
たいていは strftime(3) が0を返したらバッファ不足であると考えて、
少し大きめにバッファをアロケートし直すでしょう。
GAWK (3.0.3) では
書式の1024倍までアロケートし直しても0だったら、
それが正しい結果だと判断しています。
書式が空だったら、いきなり空を返すようにしています。
Python、Ruby もこれに倣っています。
ちなみに、田中哲さんは、書式の先頭ないしは最後に空白を付け加えることで
判断できるのではないかとおっしゃています。
date(1) や strftime(3) の "%j" の j は
おそらく Julian に由来すると思われます。
一般に、Julian day はユリウス日の意味でつかわれていますが、
計算機の世界では、
年初からの通日の意味でつかわれることも多いようです (たとえば、
Python のマニュアルなど)。
暦に関係して Julian はユリウス暦 (Julian calendar) と
ユリウス日 (Julian day number) につかわれる。
これらに同じ名前がつかわれているのは偶然です。
これだけでも混乱します。
これ以上混乱させてどうするのでしょう。
以下は、計算機でよくつかわれる書式です (書式は、 locale が "C" 等における一般的なものとします)。
"%a %b %e %H:%M:%S %Z %Y"
"%e" は ANSI C にはありません。
"%a %b %e %H:%M:%S %Y\n"
いくつかの実装では、"%c\n" で得られます。
"%Y-%m-%dT%H:%M:%S"
あるいは、"%FT%T" がつかえるかもしれません。
"%F" と "%T" は ANSI C には
ありません。
これはムリ。残念でした。
"%a, %d %b %Y %H:%M:%S %Z"
"%A, %d-%b-%y %H:%M:%S %Z"
strftime(3) のフリーな実装がいくつかあります。
strptime(3) は strftime(3) の逆の機能と考えられます。 この関数は、ANSI C に含まれていません。
かつて、time(3) はシステムコール time(2) でした (今でも変らないものもあるそうです)。 今は gettimeofday(2) がその役をつとめます (ftime(2) というものもありました)。 でも、time(3) は、今でもよくつかわれます。
time(3) があつかう time_t は
協定世界時1970年1月1日午前0時からの経過秒数です。
閏秒が含まれているかどうかはシステムによります、
としかいえませんが、
実際には滅多に勘定しているシステムはないでしょう。
POSIX は勘定されないものと考えられているようです。
[閏秒について]
ANSI C では time_t がどんな型でもよいことになっています。
ですが、ほとんどの環境で time_t は long です
(一度だけ long じゃない処理系をみたことがあります)。
ちょっと前までは time_t なんてなくて、
実際 long と書いていました。
これはそんなに古い話ではありません。
たいてい long は32ビット符号付き整数です。
これは決して十分な大きさとはいえないでしょう。
[UNIX の2038年問題について]
協定世界時1970年1月1日午前0時以前の時刻をあつかうことはできるのでしょうか。
つかえると考える人達もいると思いますが、本当にそうなのでしょうか。
実装によるのでしょうが、
FreeBSD 2.2.8 では gmtime(3)、
localtime(3)、
mktime(3) などは
一見、不平をいわずに機能しているようです。
しかし、考えてみると time_t に
値を与えるのは time(3) や mktime(3) です。
これらはエラーとして (time_t)-1 を返してきます。
というわけで、一般には、やはりつかえないと考えるべきだと思います。
DEC (Compaq) の2000年問題のページ によると、
DEC C V5.6 の time_t は、
符号なし整数 (typedef unsigned long int time_t) です。
time_t は、2106 年まで表現できるらしいですが、
負数はあつかえません。
また、Windows の C コンパイラでも、
localtime() が負の time_t を受けつけないものがあるようです。
以下は GNU date (GNU sh-utils) 1.16 によります。
$ date -d 'Wed Dec 31 23:59:58 UTC 1969' +'%s' -2 $ date -d 'Wed Dec 31 23:59:59 UTC 1969' +'%s' date: invalid date `Wed Dec 31 23:59:59 UTC 1969'
ところで、time(3) の界面ってヘンだと思いませんか?
time_t time(void); でよいではありませんか。
time(3) へポインタを与えるようになっているのは、
以前は long 型がつかえなくて、
要素がふたつの short 型の配列で受けとる必要が
あったからだといいます。
xcalendar はメモ帳つきの暦です。 cal(1) とは違い、年に 1-9999 という制限はありません。 西暦4年は閏年としています。 改暦は1752年10月1日にあったという解釈のようです (なんかバグっぽい)。
xcalendar は、添付されている
XCal-uj.sed (XCal-uj.uu) をつかうと、
少し日本語をつかってくれます。しかし、これはかなりヘンです。
前月であるべきところが先月になっています。
来月は翌月、昨日は前日、明日は翌日でなければならないと思います。
xcalendar がタイムマシンならこれでもよかったのでしょうが。
似たもので、xcal というものもあり、 こちらのほうが実用的かもしれません。
xcal はメモ帳つきの暦です。 xcalendar よりも実用的のように思います。 アラーム機能はなかなか便利です。
閏秒を勘定している UNIX マシンはあまりないと思いますが、
たいていの UNIX にはそのための機能がちゃんとあります。
ためしに、FreeBSD で閏秒を勘定した場合と、そうでない場合とで、
time_t の解釈がどう違ってくるのか、確認してみましょう。
$ su # cd /usr/src/share/zoneinfo # cat > ljst Zone Asia/LTokyo 9:00 - LJST ^D # zic -L leapseconds ljst # ls /usr/share/zoneinfo/Asia/LTokyo /usr/share/zoneinfo/Asia/LTokyo # exit $ date; TZ=Asia/LTokyo date Tue Feb 16 22:50:35 JST 1999 Tue Feb 16 22:50:13 LJST 1999
正しく閏秒を勘定するには、make LEAPSECONDS= install して、
/etc/localtime も置き換え、
時刻を設定しなおせばよいのだと思います (実際にためしてません)。
閏秒をちゃんとあつかうのは、なかなか大変でしょう。 早目に閏秒の挿入の情報を仕入れ、 zoneinfo を更新しつづける必要があります。 閏秒を考慮していないソフトウェアが問題を起す可能性もあります。
計算機の世界では閏秒は一度に2秒挿入されることがあるという見方があります。 しかし、これは閏秒挿入の規則からは考えられないことです。 閏秒が一度に2秒挿入されることを前堤に書かれた文書やコードが 散見されましたが急速に訂正されつつあるように思います。
Olson さんたちの zic(8) を
調べると閏秒が2秒まであることを考慮していたことが判かります
(zic.8 にコメントアウトされた記述があります。
コードのほうはそのままで、機能としては残っています。7.18 で確認)。
また、旧い glibc のマニュアルをみると、
tm_sec は 00-61 となっていたことが判ります。
ちなみに、POSIX では閏秒は勘定しないものとされているようです。 time2posix.3 7.7 より引用:
IEEE Standard 1003.1 (POSIX) legislates that a time_t value of 536457599 shall correspond to "Wed Dec 31 23:59:59 UTC 1986." This effectively implies that POSIX time_t's cannot include leap seconds and, therefore, that the system time must be adjusted as each leap occurs.
ところで、
タイムゾーンの省略名は3、4文字くらいのものだと思いこんでいないでしょうか。
たしかに普通はそうです。
でも、50文字くらいは、つかおうと思えばつかえるようです。
また、タイムゾーンの省略名はアルファベットに限らないし、
空白だって含めることができるのです。
普通は、そんなつかわれかたはされませんが、
タイムゾーン Factory (?) では見ることができます。
$ TZ=Factory date Sun Apr 1 10:27:21 Local time zone must be set--use tzsetup 2001
この zoneinfo には UNIX エポック以前のデータもあるようですが、 わたしはあまり信用できないような気がします (これに限らず、 この手のものは、間違いがあって当然だと思ってますが)。 Asia/Tokyo では、1896 年から 1937 までは、 略名は JST でなく、CJT になっています。 この CJT は「中央標準時」のことを言っているのだと思います (それなら CST でいいように思いますが)。 この中央標準時は、西部標準時が誕生したときに、できた言いかたです。 その後、西部標準時は廃止されましたが、中央標準時という名前は残ったのです。 だから、今でも中央標準時が正式な言いかたです (日本標準時とか、JST とかいう言いかたは、どこからでてきたのでしょうか? なんとなく外国からの輸入くさいですけど)。 この JST と CJT のつかいわけは、正しくないし、あまり意味もないと思います。 また、日本にも 1948 年から 1951 年までは夏時間があったのですが、zoneinfo では、 この夏時間は生きていません (コメントアウトしてあります)。
自国のことでも過去のことを調べるのは面倒なものです。 外国のことなら尚更でしょう。 日本人でも、South Ryukyu Islands の疑問 (くわしくは UNIX USER 1999年5月号100頁を参照) に即答できる人は、 ほとんどいなかったようです。この手のものをまとめるのは本当に大変です。 ちなみに、South Ryukyu Islands (Asia/Ishigaki) はなくなったようですが、 正確はデータベースをつくるなら、なくしちゃいけなかったと思いますが、 どうなんでしょう。
タイムゾーンなど、UNIX における時刻関連の議論と実装は ftp://elsie.nci.nih.gov/pub/ にあります。
Emacs カレンダーモードは、Edward M. Reingold によって書かれました。 Reingold は、Nachum Dershowitz、Stewart M. Clamen とともに、 こよみ計算の論文を2つ書いています。また、それを基に本も書いています。 [これらのコードについて]
カレンダーモードは、ちゃんとしたアプリケイションになっていますが、 カレンダーモードのこよみ計算部分、論文のコード、本のコードは、 本質的には同じものであると思います (論文と本のは、 Common Lisp で書かれています)。 たしかに、本のコードは、より洗練されているようですが (Reingold のコードは とても素直なものだと思うが、ときどき、ちょっとうますぎるのでは、 と思わせるものがあります)。 論文と本のコードは入手可能です。 論文のはパブリックドメインですが、本のはフリーではありません。
QT (Query Time) とは、英語で、時刻を告げてくれるプログラムです。 QT のオリジナルというのは、どこから来たのか、知らないのですが、 わたしは 「REXX 言語入門」(Mike F. Cowlishaw THE REXX LANGUAGE) という本で知りました。
ちなみに、NetRexx に qtime.nrx として添付されていたものは、 たぶん、Cowlishaw のものそのままかと思います (あまり詳しくみてない)。
Icon のパッケージにも、QT が含まれています。 8.x のころは、saytime.icn とう名前でした。 9.x では、主要部分がライブラリコードとして datetime.icn に入り、 プログラムとしては qt.icn になりました。これらは、パブリックドメインです
Klaus Alexander Seistrup さんは、いくつかの言語で、 QT を書いて、 QTime Collection としてまとめています。 これらの QT は、上記の QT とは、ほんの少し振舞いが違うものです。
日本語版 QT に挑戦したりしてみましが、 日本語ではあまり面白くならないようです。
プログラミング言語 Common Lisp での 時刻の表現は Decoded Time と Universal Time です。 Internal Time は時刻ではないので、ここでは触れません。
Decoded Time は 秒 (0-59)、分 (0-59)、時 (0-23)、日 (1-31)、月 (1-12)、年、 曜日 (0-6)、夏時間をあらわすフラグ、タイムゾーンの9つの組です。
年は西暦をあらわす整数ですが、もし、この整数が 0-99 であれば、 明白な年と解釈されます (現在の年-50≦その年≦現在の年-49)。 月曜日は0であらわされます。
Universal Time は 正の整数で、1900年1月1日の午前0時からの経過秒数です。 Universal Time は閏秒を含みません。
gcl 2.2.2 の (encode-universal-time) は引数の検査をしていません (閏秒を勘定しないシステムで実行)。 また、負の Universal Time を返えすこともあります。 (decode-universal-time) も負の Universal Time を受けつけます。
(multiple-value-list
(decode-universal-time (encode-universal-time 60 59 23 29 2 99 -9) -9))
=> (0 0 0 2 3 1999 1 NIL -9)
(encode-universal-time 0 0 0 1 1 1900 -12)
=> -43200
(multiple-value-list (decode-universal-time -1 0))
=> (59 59 23 31 12 1899 6 NIL 0)
clisp 1999-01-08 の (encode-universal-time) では引数の検査をしています。 閏秒も、平年の2月28日も弾いてくれます。 だが、負の Universal Time を返えすことはあります。 (decode-universal-time) も負の Universal Time を受けつけます。
(encode-universal-time 0 0 0 1 1 1900 -12)
=> -43200
(multiple-value-list (decode-universal-time -1 0))
=> (59 59 23 31 12 1899 6 NIL 0)
Common Lisp の仕様書は、何らかの移動できる車輛の中で使用できることを 考えるべきだといっています。たしかに、 使用中にタイムゾーンは変らないということはできません。
注意: ここでの記述は、GAWK 3.1.4 に基いて改訂しました。
もともとの AWK は今にしてみると簡単なものでした。 NAWK で機能が増えて、 GAWK ではさらに増えました。
AWK にはもともと時刻に関係する機能はありませんでした。 GAWK にしても、比較的最近のことだという印象があります。 GAWK で暦や時刻に関連しているのは、つぎのものです。
注意: 3.1.x では、組み込み関数として mktime がつけ加えられました。 そして、添付ライブラリとしてあった mktime.awk は削除されたようです。
mktime()、srand()、
strftime()、systime()
awklib/eg/lib/ctime.awk、
awklib/eg/lib/gettime.awk
systime()
systime() は time(3) そのままです。
マニュアルでは、
POSIX では協定世界時の1970年1月1日午前0時からの経過秒数であるとし、
べつのシステムでは異なるかもしれないということを認めています。
strftime()
strftime() も基本的
に strftime(3) そのままです。
strftime() は書式にナル文字が含まれていると、
そこを終端としてしまいます。
GAWK の strftime() には結果が空になったときの備えがあります。
もとの書式の1024倍までアロケートし直しても空であれば、
それが正しい結果だと判断しています。書式が空だったら、いきなり空を返します。
現在 の GAWK のメンテナ Arnold Robbins は 以前の missing/strftime.c の作者でもありました。 Ruby に添付されているものも基本的に同じものです。 しかし、現在、missing_d/strftime.c は、glibc のものに替えられたようです。
srand()もともと、GAWK は時刻を得る手段を提供していませんでしたが、 ウラワザがありました。まず、引数なしで srand() を呼びます。 すると、種として time(3) をつかいます。もう一度 srand() を呼ぶと、 前の種を返すので、これで時刻を得られるのです。
awklib/eg/lib/ctime.awk
ctime() が提供するのは ctime(3) の
書式ではありません。
どちらかというと、
典型的な date(1) の書式に近いですが、それとも違います。
"%d" を "%e" にすれば (これは ANSI C 標準にはない)、
date(1) になるでしょう。ctime(3) にするには、
さらに、"%Z" をとり除けばよいでしょう。
ctime() の設計はよくないと思います。
時刻として0を与えると、systime() を呼びだしてしまいます。
これではエポックがあつかえません。
awklib/eg/lib/gettime.awk
gettimeofday() は strftime() の結果を配列
に詰めるとともに、date(1) に似た (少し違う) 書式を返えします。
今ひとつ用途がわからず、名前もあまりよくないかもしれません。
コメントに、秒は 0-59、年の日は 0-365 とありますが、これは間違いでしょう。
わたしの Perl についての知識はヴァージョン4で止っています。 したがって、ここであつかうのもヴァージョン4についてです。
スクリプト言語 Perl で 暦や時刻に関連しているのは、つぎのものです。
gmtime()、
localtime()、time()
ctime.pl、timelocal.pl
この他にも、Perl には非標準のライブラリがいろいろあります。
時間 (time interval) についての関数 times() は除外します。
gmtime()、localtime()、time() は、
それぞれ UNIX における同名のライブラリ関数と同様の機能を提供するものです。
殆どそのままであると考えてよいと思います。Perl らしいやり方だと思います。
Perl が関数として提供しているのは時刻の参照についてだけです。
Perl では time() が
閏秒を勘定しないことになっています。
ですが、Perl は、time(3) が、
そうしていることを保証してくれるわけではないはずです。
[time(3) について]
ctime.plラクダ本では、&ctime は UNIX の C ライブラリ関数 ctime と同様に、 タイムスタンプから人間が読むことのできる日付を生成する、とありますが、 これは ctime(3) の書式ではないのです。 どちらかといえば典型的な date(1) の書式です。
timelocal.pl
&timelocal、&timegm は、
それぞれ組み込み関数 localtime()、gmtime() の
逆の仕事をします。
ヴァージョン4の &timelocal は
引数について検査らしい検査はしていません。
ヴァージョン5では文句をいわれるようです
(閏秒を勘定しないシステムで実行)。
print &ctime(&timelocal(60,59,23,29,1,99));
=> Mon Mar 1 23:00:00 JST 1999
注意: ここでの記述は、Python 2.4.1 に基いて改訂しました。
スクリプト言語 Python で 暦や時刻に関連しているのは、つぎのものです。
time、 datetime
Lib/calendar.py、
Lib/rfc822.py、
Lib/tzparse.py
Demo/classes/Dates.py
Demo/scripts/unbirthday.py
この他にも、Python には非標準のライブラリがいろいろとあります。 もっとも有名なのは、 mxDateTime ではないかと思います。
timeこのモジュールには時刻と時間 (time interval) が同居していますが、 ここであつかうのは前者のみです。
このモジュールは、UNIX における伝統的な システムコール time(2) (あるいは time(3)) を 基本にしたもので、多くはUNIX ライブラリに依存しています。
実際には Python は gettimeofday(2)、ftime(3)、time(3) のいずれかを利用します。
Python では、 asctime(3)、 ctime(3)、 gmtime(3)、 localtime(3)、 mktime(3)、 strftime(3)、 strptime(3) (2.4.1 では、Lib/_strptime.py をつかているようです) などが つかわれています。これらの殆どは ANSI C に含まれています。
time.asctime と time.ctime は
"\n\0" がとり除かれている他は
UNIX ライブラリのそれと同じです。
time.gmtime、
time.localtime は UNIX ライブラリのそれと同じ機能です。
ただし、struct tm とは要素の順番が違います
(年、月、日、時、分、秒、曜日、年初からの通日、夏時間のフラグ)。
また、年は -=1900 ではないし、月も 0-11 ではありません。
秒は 0-59 として、暗に閏秒を勘定しないことを示しているようです。
また、曜日が 0-6 であるのは珍しくありませんが、
月曜日が 0 なので気をつける必要があるでしょう。
Python では年初からの通日の意味で、Julian day をつかっています。
time.mktime が提供されます。
time.mktime も UNIX ライブラリのそれと同じ機能です。
引数の順は time.gmtime や time.localtime と
同じです。
年は下2桁 (69-99 => 1969-1999, 00-68 => 2000-2068 となる) でもよいです。
time.mktime は引数について検査らしい検査をしていません
(閏秒を勘定しないシステムで実行)。
localtime(mktime((99,2,29,23,59,60, -1, -1, -1)))
=> (1999, 3, 2, 0, 0, 0, 1, 61, 0)
time.mktime によって地方時の時刻を生成することはできますが、
協定世界時や、ほかの地方時で時刻を生成する手段は提供されていません。
しかし、calendar モジュールには、どういうわけか、
calendar.timegm があります (注意: time.mktime とは
違い、accept2dyear は参照しません)。
また、多くのシステムではつぎのような手段がつかえるかもしれません。
これは移植性がないし、スレッドについて考慮する必要があるかもしれません。
>>> from time import *
>>> import posix
>>> posix.putenv('TZ', 'Europe/Amsterdam')
>>> strftime('%+', localtime(mktime((1999,5,23,0,0,0,-1,-1,-1)))) # %+ は標準にはない
'Sun May 23 00:00:00 CEST 1999'
>>> posix.putenv('TZ', 'Asia/Tokyo')
>>> strftime('%+', localtime(mktime((1999,5,23,0,0,0,-1,-1,-1))))
'Sun May 23 00:00:00 JST 1999'
time.strftime が提供されます。
time.strftime は UNIX ライブラリのそれと同じ機能です。
time.strftime は書式にナル文字が含まれていると文句をいいます。
>>> from time import *
>>> strftime("%c\0", localtime(time()))
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: strftime() argument 1 must be string without null bytes, not str
ドキュメントでは "%S" が61秒まであるように書いてあります。 また、コードでも、61秒まで許していますが、これは間違いでしょう。
Python では基本的に月曜日は0ですが、
time.strftime の "%w" では月曜日は1になるので、
注意が必要です。
Python は
time.strftime の結果が空になったときの備えがあります。
対処のしかたは GAWK と同じです。 [strftime(3) について]
time.strptime が提供されます。
これは、Python で書かれていて、strptime(3) に依存していません。
time.accept2dyear を用意しました。
これが 0 なら、年の下2桁入力は拒否されます。
環境変数 $PYTHONY2K になにか値を設定してやれば
time.accept2dyear を 0 にすることができます。
>>> from time import * >>> accept2dyear 0 >>> mktime((1,1,1,0,0,0,-1,-1,-1)) # 年の下2桁入力をためしてみる Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: year >= 1900 required # 親切に注意してくれる >>> mktime((1900,1,1,0,0,0,-1,-1,-1)) # 注意に従って入力しなおしてみる Traceback (most recent call last): File "<stdin>", line 1, in ? OverflowError: mktime argument out of range
datetime
Python 2.3 以降では、datetime がつかえます。
このモジュールには、以下のクラスが含まれています。
このモジュールは、比較的最近できたもので、 様々なものに影響されていると思われますが、 とりわけ、mxDateTime の影響が大きいように思われます。
このモジュールでは、naive と aware という言葉で、 タイムゾーン、夏時間、その他の調整についての 異る姿勢を区別することにしているようです。 このモジュールでは、閏秒については、一切関知せず、 タイムゾーン、夏時間についても、枠組だけ用意したので、 あとは利用者が好きにしてください、ということのようです。
Lib/calendar.pyこのモジュールは cal(1) の ような結果を得るためのものです (ただし、こちちらはデフォルトでは、 週の始まりは月曜になります)。 モジュールとして、このようなものが用意されているのは珍しいように思います。 以前は、time モジュールに依存していたため、 つかえる範囲がたいへんに狭かったのですが、 Python 2.3 以降では、datetime を利用するようになり、改善されました。
>>> from calendar import *
>>> prmonth(1752,9)
September 1752
Mo Tu We Th Fr Sa Su
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30
Lib/rfc822.pyこのモジュールには RFC822 の日付を咀嚼するための関数があるようです。
Lib/tzparse.pyタイムゾーンを解釈してくれるようです。
Demo/classes/Dates.py
このクラスは make install してもコピーされないものなので、
議論してもしかたないような気もします。
とくに注目すべき点もないようです。
Demo/scripts/unbirthday.py年齢など計算してくれます。
In which year were you born? 1888 And in which month? (1-12) 7 And on what day of that month? (1-31) 10 You were born on 10 July 1888 Today is 27 May 2000 You have lived 40863 days You are 111 years old Today is your 40752th unbirthday
注意: ここでの記述は、mxDateTime 2.0.6 に基いて改訂しました。
mxDateTime は Marc-Andre Lemburg さんが作成しました。
このパッケージには3つのデータ構造があります。
また、サブモジュール ISO、ARPA、Feasts、Parser、NIST をもっています。
>>> from mx.DateTime import * >>> a = DateTime(1998,8,29) >>> b = DateTime(1998,8,28) >>> a <DateTime object for '1998-08-29 00:00:00.00' at 81ded08> >>> b <DateTime object for '1998-08-28 00:00:00.00' at 81de1e0> >>> a - b <DateTimeDelta object for '1:00:00:00.00' at 81e15e8> >>> r = RelativeDateTime(days=1) >>> r <RelativeDateTime instance for 'YYYY-MM-(+01) HH:MM:SS' at 0x81eb3ec> >>> a - r <DateTime object for '1998-08-28 00:00:00.00' at 81ed058>
DateTime の内部書式は Absolute date と Absolute time です。 前者は C の long、後者は C の double です。 前者は 0001-01-01 (グレゴリオ暦) からの経過日数で、 後者は 0:00:00.00 からの経過秒です。
DateTime はタイムゾーン、夏時間、閏秒などは基本的に考慮しません。
注意: ここでの記述は、ruby 1.8.2 に基いて改訂しました。
オブジェクト指向スクリプト言語 Ruby で 暦や時刻に関連しているのは、つぎのものです。
Time
Date (lib/date.rb、
lib/date2.rb、lib/date/format.rb)、
ParseDate (lib/parsedate.rb)、
lib/time
sample/biorhythm.rb、
sample/cal.rb、
sample/goodfriday.rb
この他にも、Ruby には非標準の暦計算モジュール calendar-1.11.4r.tar.gz があります。
Timeこのクラスには時刻と時間 (time interval) が同居していますが、 ここであつかうのは前者のみです (後者とは Time.times です。 ruby 1.7 以降、Process に移動しています)。
このクラスは、UNIX における伝統的な システムコール time(2) (あるいは time(3)) を 基本にしたもので、多くをUNIX ライブラリに依存しています。
実際には Ruby は gettimeofday(2) を利用しています。 これがない環境では ftime(3)、time(3) 相当の分解能しか得られないかもしれません。
Ruby では、 asctime(3)、 gmtime(3)、 localtime(3)、 strftime(3) などがつかわれています。 これらはみな ANSI C に含まれています。 ちなみに Ruby では mktime(3) はつかわれていません。
Time#ctime はそうではありません。
協定世界時でも、地方時でもつかわれます。
Time#strftime の
結果が空になったときの備えがすでにあります。
対処のしかたは GAWK と同じです。 [strftime(3) について]
Time.mktime に代表されるように、
このクラスでは基本的に、年月日時分秒の並びが想定されていると思われます。
しかし、Time#to_a では逆順になっています。
(これは Perl の gmtime()、
localtime() の影響かもしれません)。
現在、Time#mktime は引数の数が 10 であれば、
Time#to_a と同じ並びだと解釈します。
Time.mktime に代表されるいくつかのクラスメソッドは、
引数の厳密な検査はしません。
たとえば、つぎのようなことがあります (閏秒を勘定しないシステムで実行)。
Time.mktime(1999,2,29,23,59,60)
=> Tue Mar 02 00:00:00 JST 1999
Time.mktime に代表されるいくつかのクラスメソッドは、
X/Open がいうように、69-99 を 1969-1999 に、
00-68 を 2000-2068 に写像します。
ただし、冗長モードでは警告されます。
Time.mktime によって、地方時の時刻を生成することはできますが、
ほかの地方時で時刻を生成する手段は提供されていません。
しかし、多くのシステムではつぎのような手段がつかえるかもしれません。 これは移植性がないし、スレッドについて考慮する必要があるかもしれません。
rbc0> OTZ = ENV['TZ'] nil rbc0> ENV['TZ'] = 'Europe/Amsterdam' true rbc0> Time.mktime(1999,5,23) Sun May 23 00:00:00 CEST 1999 rbc0> ENV['TZ'] = OTZ nil rbc0> Time.mktime(1999,5,23) Sun May 23 00:00:00 JST 1999
Time#year は -=1900 ではありません。
Time#mon は 1-12、
Time#yday は 1-366 です。
ruby 1.0 のころは Time#year は -=1900、
Time#mon は 0-11、
Time#isdst は整数でした。
ruby 1.2 までは Time#yday は 0-365 でした。
Time#sec は 0-60 です。
Ruby は閏秒が勘定される可能性を考慮しています。
つぎのコードは、閏秒が勘定されているか調べます。
if Time.at(78796800).sec == 60 then puts '閏秒を勘定している' else puts '閏秒を勘定してない' end
Marshal.dump について
ruby 1.2 までは
Marshal.dump は時刻の内部表現をそのまま書きだすだけでした。
しかも、time_t 相当部分は32ビットだと仮定していました。
ruby 1.3 からは協定世界時での
年月日時分秒とマイクロ秒として書きだしています。
このあたらしい書式では、132971年12月31日23時59分59秒 (あるいは60秒) まで
表現できます。
この方法には閏秒の影響を受けにくいという利点がありますが、 Time オブジェクトの本質が、少し判り難くなったかもしれません。
ここでモードとは、Time オブジェクトでいくつかのメソッドが協定世界時と 地方時のつかいわけをするために便宜的に設けられた (と思われる) 区別のことです。 Time は、基本的にはイミュータブルなのですが、 このモードがあるために、完全なものになっていません。
以前の Ruby では、Time オブジェクトに加減算を適用しても、 それで生成される Time オブジェクトは、いつも地方時になり、 元のオブジェクトのモードが伝播することはありませんでした。 しかし、ruby 1.3.2 からは伝播するようになりました。 このモードは、Marshal で保存されないし、 もし、保存したとしても、意味があるかどうか疑問です。 わたしたちは、タイムゾーンをあつかっているように錯覚してしまうのですが、 与えられた協定世界時と地方時の解釈をつかい分けているだけなのです。
time.rb
time.rb は、組み込みクラス Time に
補助するメソッドをつけ加えます。
これにより、Time.parse 等のメソッドが利用可能になります。
date.rbRuby にはふたつの Date クラスがありました。
最初の Date クラスは
大庭康生さんによって書かれました。
英国における改暦に対応しているなど、ちょっと面白い仕様になっていました。
現在の Date クラスは、
先ず、date2.rb として現れ、
後に (ruby 1.5)、date.rb に上書きされたものです。
以前の版と区別したい場合や、ruby とは別に
配布されているパッケージを指すために、今でも date2 という言いかたをします。
date.rb は、Date と DateTime の
ふたつのクラスを提供します。
これらはともに、日を単位に勘定しています。
date (date2) については、以下の文書を御覧下さい。
parsedate.rbParseDate の仕様と実装については、 parsedate の日付解析の手法 を御覧下さい。
Python の datetime では、naive と aware という概念を用いていました。 なるほど、と思いました。 たとえば、ruby においては、 何故、Time と Date (あるいは DateTime) は別々にあるのかと、 しばしば、文句をいわれます。 理由のひとつとして、naive と aware の違い、ということもあるわけです。
多くの人が、時刻とは高度に抽象化された完全な概念であり、 誰もが簡単にあつかえるものだと考えているようです。 しかし、現実は、非常に複雑です。 たとえば、日や秒が示す概念は、過去に何度も変更になっています。 タイムゾーンや夏時間は、政治の問題でもあります (もとから暦は 為政者や宗教的指導者の権力や権威の象徴として重要な意味を持っている、 ということもあるわけですが)。 閏秒の挿入については、常に極近い将来について決定されているにすぎません。
ruby についていえば、Time は、aware です。 背後には、しばしば zoneinfo のような データベースの存在があるわけです。 タイムゾーン、夏時間、閏秒の情報を含みます。 ただし、仮令、それらの情報が完璧だとしても、高々、数十年のものです。
Time と Date (および DateTime) を一緒にしてしまえ、という場合、 ふたつの考えがありそうです。 ひとつ目は、Date がもっとがんばって、Time を置き換える、という発想。 ふたつ目は、Date の実装で、Time が賄えない範囲を外挿する、という発想です。
ひとつ目の方法は、実装が非常に大変です。 また、この問題については完璧な答えなどないので、 システムが提供する時刻の解釈と異るものになる可能性が極めて高い、 ということになります。 いずれにしても、現実的な問題を解決するためには、 システムが提供する時刻の解釈は必要です。 Time を消すことはできません。
ふたつ目の方法は、乱暴だと思います。
そうまでしても、嬉しくないと思います。
問題は、器の幅だけではないわけです。
time_t のラッパーとして設計された Time と、
専ら日を勘定することを基本に設計された Date や DateTime では、
本質的に相容れないものがあります。
たとえば、Time はタイムゾーンを立派にあつかっているように
見えるかもしれませんが、
それは、ただ与えられただけのものです。
Time では、協定世界時と (利用者にとって自明なはずの) 地方時を
あつかっているだけです。
自由に、タイムゾーンをあつかっているわけではないのです。
また、閏秒を勘定するかどうかは、システムに依存することで、
利用者がどうこうできる問題ではありません。
事務的な処理では、むしろ、タイムゾーンや閏秒など、
最初から無視ししたほうが便利な場合が多くあります。
一般に、UNIX/C の時刻サービスの限界に起因する課題が多いように思います。
Common Lisp の仕様書は、何らかの移動できる車輛の中で使用できることを 考えるべきだといっています。 このことついてはあまり議論されていいないと思いますが、 今後は重要になってくるのではないでしょうか。
暦計算をするには、通日に変換するのが普通です。 そうすれば暦の相互変換が簡単におこなえるからです。 また、加減算、比較演算も簡単になります。 起算日は適当に選んでもいいのですが、 どうせならユリウス日のような よくつかわれているものがいいでしょう。
たとえば、旧暦の日付がほしいなら、 グレゴリオ暦から一旦ユリウス日にして、それから旧暦に変換するようにします。 7日前の日付がほしいなら、グレゴリオ暦から一旦ユリウス日にして、 7を引いてからグレゴリオ暦に戻せばいいのです。
割り算 (と剰余) には注意しましょう。 床を求めていることがあります。
さて、 ここでは Meeus の Astronomical Algorithms (Willmann-Bell,Inc., 1991) にある グレゴリオ暦と ユリウス暦の簡単な計算方法を紹介します。
まず、Y に年、M に月、D に日を代入します。
これは、1月と2月を前年の13月、14月とみなすためです。
A = INT(Y / 100) B = 2 - A + INT(A / 4)
ユリウス暦では B = 0 とします。
JD = INT(365.25(Y + 4716)) + INT(30.6001(M + 1)) + D + B - 1524.5
ここではユリウス暦からグレゴリオ暦やの改暦は1582年10月15日としています。
まず、JD にユリウス日を代入します。 JD に 0.5 足して Z に整数部を代入し、F に残りの端数を代入します。
X = INT((Z - 1867216.25) / 36524.25)
A = Z + 1 + X - INT(X / 4)
B = A + 1524
C = INT((B - 122.1) / 365.25)
D = INT(365.25C)
E = INT((B - D) / 30.6001)
B - D - INT(30.6001E) + F
もし E < 14 なら E - 1
もし E = 14 あるいは 15 なら E - 13
もし m > 2 なら C - 4716
もし M = 1 あるいは 2 なら C - 4715
Ruby の date2.rb では このアルゴリズムがつかわれています。
こよみの計算では相互に変換する関数を用意することが大事です。 まず、コードを書いたら、 変換と逆変換をして結果が一致するか念を入れて調べてみましょう。 また、アプリケイションでもこれを利用することができます。 たとえば、1999年2月29日を変換、逆変換すれば1999年3月1日となって、 入力が正しくないことが判るでしょう (あまり効率はよくないですけど)。
曜日を簡単に求める方法としては zeller の公式が有名ですが、 ユリウス日のような通日を求めることができれば、それも必要ありません。 ユリウス暦とグレゴリオ暦の違いも気にする必要もないので便利です。 ただ7で割った余りを求めればいいのです。 日曜日を0に当てるなら、 ユリウス日に1足してから7で割って余りを求めればいいでしょう。
もっともですね。
Nachum Dershowitz と Edward M. Reingold の ``Calendrical Calculations'' とその続篇である ``Calendrical Calculations, II: Three Historical Calendars'' では様々な暦をできるだけ汎用的な手法であつかおうとしています。 このコードは素直なもので、Meeus のものよりずっとわかり易いと思います。 コードはパブリックドメインとして提供されています。 また、わたしが C に翻訳したものもあります。 その後、著者たちは ``Calendridal Calculations'' (Cambridge University press, 1997) と いう本を執筆し (すでに改訂版もでています)、 そこではさらに多くの暦をあつかっています。 そのコードは入手可能ですがフリーではありません。
Dershowitz と Reingold は、通日として、彼らが絶対日付 (Absolute Day Number) と 呼ぶものをつかっています (本では、Rata die, RD です)。 この絶対日付とはグレゴリオ暦の1年1月1日から数え上げた日数です ちなみに、Reingold は Emacs Lisp ハッカーでもあり、 GNU Emacs のカレンダーモードの作者です。
あと、Mathematica はよく知りませんが、 I.ヴァルディの「Mathematica 計算の愉しみ」(トッパン, 1991)の第3章は こよみ計算の入門になかなかよさそうです。
これは以下の暦をあつかいます。また、祝祭日についてもあつかいます。
プログラミング言語 Common Lisp で書かれた、 このコードはパブリックドメインとして提供されています。 calendar.l [ 33k]
II のコードは含まれませんが、C++ 版もあります。 calendar.C [ 14k]
わたしが C に翻訳したパッケージがあります。 プログラミング言語 Scheme に翻訳したものや、 オブジェクト指向スクリプト言語 Ruby、 スクリプト言語 Python の インターフェイスも含まれています。 また、旧暦などいくつかの暦のためのコードを追加しています。 calendar-1.11.4.tar.gz [ 165k]
Ruby のためのコードだけを抜きだしたパッケージもあります。 calendar-1.11.4r.tar.gz [ 132k]
Python のためのコードだけを抜きだしたパッケージもあります。 calendar-1.11.4p.tar.gz [ 123k]
Ozan S. Yigit による Scheme 版もあります。 これには II のコードは含まれていません。 また、ちょっとした間違いがあるので、注意してください。 calend.scm [ 19k] Yigit のコードをわたしが修正したものもあります。 calend.scm.fixed [ 20k]
くわしくは以下を参照してください。
その後、著者らは ``Calendridal Calculations'' (Cambridge University press, 1997) という本を執筆し、 そこではさらに多くの暦をあつかっています。 そのコードは入手可能ですがフリーではありません。 すでに出版されている改訂版では日本の暦もあつかっていると聞いていますが、 まだ見てません。
このパッケージは以下の暦をあつかいます。
この ANSI C で書かれたパッケージは、たいへん質がよいと思います。 sdncal20.zip [ 26k]
わたしが書いた、 オブジェクト指向スクリプト言語 Ruby、 スクリプト言語 Python の インターフェイスもあります。 calendar-1.11.4.tar.gz [ 165k]
Ruby のためのコードだけを抜きだしたパッケージもあります。 calendar-1.11.4r.tar.gz [ 132k]
Python のためのコードだけを抜きだしたパッケージもあります。 calendar-1.11.4p.tar.gz [ 123k]
くわしくは Scott E. Lee's Home Page を参照してください。
Ruby で日付をあつかうための、わたしが書いた Date クラスがあります。 このクラスでは改暦の日を任意に設定できます。 また、祝祭日についての手続きもあります。 date2-4.0.19.tar.gz [ 47k]
ここで祝日とは「国民の祝日に関する法律」の第2条で定められたもので、 休日とは、第3条で定められたものです。 休日には、祝日による休日、その振替休日、「国民の休日」があります。 なお、この他にも、特別に休日が設けられることがあります。
祝日には月日が固定なもの、不定なものがあります。 政令で定められた建国記念の日も含め、殆どの祝日は固定です。
不定なものには春分の日、秋分の日のように春分日、秋分日が当てられるもの、 ある月の第NK曜日のように定められているものがあります。 春分の日、秋分の日も含めて、正式な祝日は前年の2月最初の官報で発表されます。
第3条2項により「国民の祝日」が日曜日であれば、 その日後において、その日に最も近い「国民の祝日」でない日は休日になります。 これは平成17年5月法律第43号により修正されました。
第3条3項により前日および翌日が「国民の祝日」である日は休日になります (ただし、「国民の祝日」でない日に限る)。 これは平成17年5月法律第43号により修正されました。
判定はそんなに難しくないと思われます。 2005年5月現在までに、祝日法は8度改正されています。 過去についても正しい判定を必要とするならば、このことを考慮する必要があります。 また、将来について備えるなら、官報などの確認を怠らないことです。
わたしは、休日の判定のためのコードをふたつ書いています。 ひとつは自作の専用言語のために、もうひとつは オブジェクト指向スクリプト言語 Ruby のためにです。 date2-4.0.19.tar.gz [ 47k]
乗りかかった船ということで、旧祝祭日についても調べてみましたが、 断片的な情報ばかりで、いまひとつスッキリしない、と思っていました。 が、日外アソシエーツの「20世紀暦」でだいたい確認できました。 祝日の判定そのものはこれで困らないでしょう。 でも、できれば施行の日まで詳しく知りたいです。 くわしい資料をお持ちのかたはゼヒ御一報ください。
日本の祝日はわりと簡単ですが、米国ではこうはいかないようです。 以下は、Lance Latham さんから聞いた話をもとに、わたしがまとめたものです。
連邦政府は、国定休日を宣言する権限をもっていない。 休日の宣言の権利はそれぞれの州がもっている。 しかし、州によって、休日がまったく違っているわけではない。 連邦政府は、連邦政府職員とコロンビア特別区のために、休日を宣言できる。 実際は、多くの州の、多くの休日は、連邦政府の宣言に従ったものである。 だが、50もある州のすべての休日を追いかけることは、たいへんなことなのだ。
ということで、米国において休日の判定を完全にやるのは、かなり大変のようです。
日本では復活祭 (イースター) をあつかうこともほとんどないように思いますが、 とりあえず。
復活祭の日曜日を求めるアルゴリズムはいくつかあると思いますが、 どれも比較的簡単です。 簡単といってもパっとみて意味がわかるというわけではないです。 つぎのコードは Knuth の本に載ってた、グレゴリオ暦における復活祭の アルゴリズムを Ruby で書いたものです。
g = (y % 19) + 1 c = (y / 100) + 1 x = (3 * c / 4) - 12 z = ((8 * c + 5) / 25) - 5 d = (5 * y / 4) - x - 10 e = (11 * g + 20 + z - x) % 30 e += 1 if e == 25 and g > 11 or e == 24 n = 44 - e n += 30 if n < 21 n = n + 7 - ((d + n) % 7) if n <= 31 then [y, 3, n] else [y, 4, n - 31] end
一般に復活祭は、 「春分もしくはその直後の満月の後の最初の日曜日」 というふうに 説明されます。普通に考えれば、春分の日は観測によって決定されるものであるし、 計算で予想するにも、それなりに複雑な計算せねばならないはずです。 そう考えると、ここで挙げたコードはあまりにも簡単すぎます。どうしてでしょう? じつは、ここでいう「春分の日」は 3月21日に固定されているのです。 さらに満月の日 (月齢) はエパクトで決定します。 それで天文学的な難しい計算はありません。
わたしもこのアルゴリズムを完全に理解しているとはいえませんが、 変数 g は黄金数 (メトン周期の何年目か)で、e がエパクト、 n が3月からの日をあらわしているようです。
Ruby に添付されている sample/goodfriday.rb は
上記のアルゴリズムをつかっています。
date2 パッケージにある lib/date/holiday.rb では、
別のアルゴリズムをつかっています。
注意: ここでの記述は、 ISO 8601-2000 (Second Edition) と JIS X 0301-2002 に基いて改訂しました。
ISO 8601 (Data elements and interchange formats --- Information interchange --- Representation of dates and times) は 日付や時刻の表記についての規格です。 この規格ではつぎのようなものをあつかいます。
例: 1985-04-12
例: 1985-102
例: 1985-W15-5
例: 23:20:50
これらのそれぞれに完全表記と様々な省略表記があって、 さらにそれぞれに基本形式と拡張形式があります。 また、年が 0000-9999 の範囲外の場合、拡大表記を用いることもできます。
例: 1985-04-12
例: ---12
例: 19850412
例: 1985-04-12
+ ないし - に
4桁以上の年をつづけたものです。
例: +001985-04-12
注意: 1582年から9999年の範囲外については、 情報交換当事者間の合意によってのみ用いることができます。 0001年の前は、0000年になります。
時刻は時差も表現できます。 日付と時刻の組み合わせもあって、時間間隔、反復時間間隔も表現できます。
Z をおきます。
例: 23:20:30Z
+、
遅れていれば - を前置きします。
例: 15:27:46+01:00
T を時刻の指示記号としてつけます。
ただし、情報交換当事者間の合意によって省略可能です。
例: 1985-04-12T10:15:30
特定の始点、終点のいずれか、
あるいは両方を含む表現において、
ふたつの構成要素は / で分離します。
継続期間は P を前置きし、
要素とその要素の指示記号の組の羅列によってあらわします。
時刻の構成要素を含む部分には T を前置きします。
例: 19850412T232050/P1Y2M15DT12H30M
1985年4月12日23時20分50秒に始まる1年2ヶ月15日と12時間30分の期間
指示記号 R で始まり、空白なしで反復回数、/、
時間間隔がつづきます。
例: R12/19850412T232050/19850625T103000
JIS X 0301-2002 (日付及び時刻の表記) は ISO 8601-2000 の 翻訳版ですが、元号のための表現が追加されています。 JIS X 0301:2002 の ISO 8601:2000 との整合の度合は、 ISO/IEC Guide 21:1999 の MOD (修正) です。
例: S60.04.12
かつては JIS X 0301-1977 (日付の表示)、 JIS X 302-1977 (時刻の表示) がありました。 JIS X 0301-1992 は それらとよく似ていますが、完全上位互換というわけではないようです。
よく見る間違いなどを紹介します (というか、自分でやってたりしますが)。
しばしば、翌日を求めるために、以下のようなコードを書いてしまいます。
t = Time.mktime(2005, 7, 9)
t += 86400 # 60*60*24
t.strftime('翌日は、%Y年%m月%d日')
しかし、もし、システムで閏秒を勘定していたら、 求めるような結果は得られないかもしれません。 また、夏時間などの影響を受けてしまうかもしれません。 ruby なら、Date をつかうのが確実ですが、 もし、Time、あるいは Time のようなものをつかう場合には、
t = Time.utc(2005, 7, 9, 12)
t += 86400 # 60*60*24
t.strftime('翌日は、%Y年%m月%d日')
のように、協定世界時とし、 深夜のかわりに正午をつかったほうがいいと思います。
time(3) や gettimeofday(2) を繰り返し呼んでしまいます。
year = Time.now.year mon = Time.now.mon mday = Time.now.mday
わかりますよね。こうでないといけません。
now = Time.now year = now.year mon = now.mon mday = now.mday
たとえば、ユリウス日から曜日を求めるのは簡単です。
(jd + 1) % 7
もし、負のユリウス日が与えられたらどうでしょう
(あまり意味がありませんし、いい例ではなかったかも)。
ruby では、このままで大丈夫です。
なぜなら、% は、modulo だからです。
remainder では、求める結果は得られないでしょう。
12時間制の時刻では、12 は、零と同じなのですが、 どういうわけか、よく忘れてしまいます。
hour += 12 if pm
おそらく、以下のようにすべきです。
hour %= 12 if am or pm hour += 12 if pm
コードの間違いではないですが、 よく、英語で書いたつもりで、"AM 10:00" や "2005 July" などと書いたりします。 いろいろなスタイルが混っていたりします。 これらは、中途半端に日本語化した date(1) と 同様のある種の異国情緒を醸しだすものと思われます。 少し魅力的だったりするかもしれません。
土 7 9 01:32:12 JST 2005
一見して判るものを "Sat." のように律儀にピリオドを置いたり、 暦の土曜日を青くしておくと、さらに日本風味が引き立つような気がします。
9 Jul.,A.D.2005 A.M.11:00(Sat.)
いまさら説明する必要もないと思いますが、 西暦2000年に起るとされる様々な問題のことです。
今から10年くらい前にみた表現ですが、
そこでは、1987年を 187 とあらわしていました。
最初の 1 は1900年代の意味です。
西暦そのままだと冗長になる。かといって2桁にしてしまってはダメです。
そこで、このような表現になったようです。
べつにこんなことを好き好んでやっていたわけではないでしょうが、
こういうものがプログラミングの作法として求められていたのでしょう (というか、
今でもあるのかも)。
IBMの西暦2000年 では、2000年問題対応で、みつけた面白いコードを紹介しています。 YYMMDD を MMDDYY に簡単に変換する方法です。 そこにあるのは COBOL ならではの手法のようですが、 もっと一般的に、たとえば Ruby ならつぎのようにできるでしょう。
'%06d' % (yymmdd.to_i * 100.0001 % 1000000)
また、逆変換はつぎのようにできるでしょう。
'%06d' % (mmddyy.to_i * 10000.01 % 1000000)
たとえば、date(1) で、年を下2桁で与える、ということがあります。 年を4桁で与える手段を用意することは重要ですが、 これまでの互換性についても考慮する必要があるでしょう。 X/Open では下2桁による表現について 69-99 を 1969-1999 に、 00-68 を 2000-2068 に写像する解釈を勧めています。
多くの UNIX では time_t は32ビット符号付き整数で、
協定世界時の2038年1月19日3時14分7秒までしか勘定できません
(閏秒は勘定しないとして)。
しかしまあ、おそらく、そのころには32ビットマシンは
つかわれなくなっているだろう (ことを祈る)。
ちなみに、time_t が64ビット符号付き整数だとすると、
292277026596年12月4日15時30分7秒まで勘定できることになります。
そのころ人類はどうなっちゃってんの?
なーんてこというと、考え込んでしまう人もいるでしょうが、
おそらく、滅亡しているだろうと思われます。
理科年表より:
協定世界時 (UTC) は原子振動に基づく世界時の時刻との差が一定範囲内に あるように人工的に管理された人工的時系である. 1971年末まで使われた旧 UTC では,周波数オフセット (標準値より一定値ずらせる操作) と 0.1 秒の秒信号のステップ調整で, (UT2-UTC) を ±0.1秒の範囲内に収めるように管理されていた. 1972年1月1日から実施されている新 UTC では, 周波数オフセットは全廃され,1秒単位のステップ調整だけで (UT1-UTC) が ±0.9秒 (1975年1月1日改訂) を超えないように管理されている. 現在,UTC と TAI の差は常に整数秒である.
1秒のステップ調整は,12月か6月の末日の最終秒 (UTC) の後へ1秒を挿入するか, または最終秒を引抜くことによって行われる. 1秒のステップ調整の時期は IERS の中央局が決定している. この際挿入される (引抜かれる) 1秒を正 (負) のうるう秒と呼ぶ. DUT1 は (UT1-UTC) の予想値を 0.1 秒精度で表わす記号で, この数値 (-0.8〜+0.8秒) は,標準電波報時にのせて毎分コード形式で通報される. (以下略)
以前は1秒は「平均太陽日の86400分の1」でした。 しかし、それだと秒はどんどん変化するのです。 1967年、秒は不変 (その測定が完璧かどうかしりませんが) になり、 秒の挿入で積ったズレを調整するようになったのです。
ISO 8601-2000 (JIS X0301-2002) には、 「協定世界時 (UTC) 国際度量衡局 (BIPM) 及び国際地球回転観測事業 (IERS) によって 維持管理されている時間尺度」、 「ITU-R は協定世界時に対して UTC という省略形を決めている」とあります。 UTC という略称は英語の Coordinated Universal Time と 仏語の Temps universal coordonn\'{e} との混合です。
多くの地方時は UTC と整数時間の差で定義されています (とくに決まりはないらしい)。 たとえば、日本の中央標準時は UTC+9h です (といっても、法律に協定世界時云々ということはでてきません)。 ちなみに、かつては西部標準時というものがあって、 それと区別するために中央標準時という表現ができました。 これが現在でも法的に生きています。
一般には、協定世界時よりも グリニッジ平均時 (Greenwich Mean Time, GMT) のほうが まだ馴染みがあるのではないかと思います。 UTC は 世界標準時としての GMT に代るものであるから、 その意味では、UTC をつかうべきでしょう。 現在、GMT は、ある地方時の伝統的な名称と考えるべきと思います。 ところで、GMT はいつも英国での時刻をあらわしているわけではありません。 英国には夏時間があり、それは GMT ではありません。
多くの計算機では閏秒を無視しています。UTC は閏秒を勘定するものだから、 それを UTC と呼ぶのは、本当はヘンなのかもしれません。 閏秒を勘定しない UTC、としかいえないのかも。
協定世界時をあらわすのに、Z や Zule がつかわれることがあります。 これは、軍の慣習に由来するようです (たぶん、零の意味だと思います)。 Zule は、聞き間違いを防ぐために、Z に当てられた言葉のようです。 たとえば、映画「クリムゾンタイド」では、 これが時刻や指令の確認の場面などでつかわれていました。
いわゆる新暦です。 グレゴリオ暦 (Gregorian Calendar) では一年は平均365.2425日で、 この半端をなんとかするために、閏年を設けます。
グレゴリオ暦では、西暦で4で割切れる年は閏年です。 ただし、100 で割切れる年は除外します。でも、400 で割切れる年は閏年です。 少し複雑なようですが、グレゴリオ暦はかなり洗練されたわかりやすい暦だと思います。 グレゴリオ暦は一周400年(146097日)だと考えればよいです。 7で割切れるので、曜日まで同じになります。
グレゴリオ暦がつかわれ始めたのは1582年10月15日です。 ユリウス暦の 1582年10月4日の翌日をグレゴリオ暦の1582年10月15日としたのです。 日付は飛んでいますが、曜日は連続しています。 改暦の大きな理由は復活祭が季節とズレてきたからです。 欧州でもグレゴリオ暦はすぐに受けいれられたわけではありません。 カトリックの国は比較的早く、プロテスタントの国は遅れました。 英国では1752年9月14日、ロシアは20世紀になってからです。 我が国では明治6年 (1873) の正月からです。
我々にとって旧暦といえば太陰太陽暦である天保暦のことですが、 欧州ではユリウス暦 (Julian Calendar) のことです。
グレゴリオ暦との違いは 閏年の挿入の規則 (置閏法) です。 ユリウス暦では一年は平均365.25日で、 この半端をなんとかするために、閏年を設けます。
ユリウス暦では、西暦の4で割切れる年は閏年です。 ユリウス暦は一周4年(1461日)だと考えればよいです。
カエサルの死後、ユリウス暦は間違って運用されました。 それをとり戻すために、 アウグストゥスは紀元前6年から西暦4年まで閏年を省くことにしました。 また、アウグストゥスは8月に自分の名前をつけて、 もとは30日であったものを31日にしました。 おかげで2月は28日間になりました。 ユリウス暦移行の前後はかなり混乱しています。 そのようなわけで、歴史的には cal(1) は 正しくないところがあります。
ユリウス日、あるいはユリウス通日 (Julian period, Julian Day Number, JD) は 紀元前4713年1月1日 (ユリウス暦) 正午 (グリニッジ平均時) を暦元 (0.0) とした通日 (経過日数) です。 1999年2月16日はユリウス日で2,451,226です。
ヨセフス・スカリゲルは、ユリウス周期というものを考案しました。 これは、7980年の周期です。 その後、この周期の最初の日の正午を暦元とする日の数えかたが 考案されました。これがユリウス日です。 よく、スカリゲルがユリウス日を考案したといわれますが、 おそらくそれは、正確ではないでしょう。
よく、ユリウス周期という名前は、 考案者の父の名前に由来するといわれますが (よくそのように説明されていますし、 わたしもそう信じていました)、 それは間違いで、やはりユリウス暦に由来するのだ、という説があります。
計算機の世界では年初からの通日を Julian day と表現することがありますが、 これは紛らわしいので、つかわないほうがいいと思います。
こよみ計算をするにはユリウス日のような通日が必要です。 べつに、ユリウス日でなくてもよいのですが、 やはり、一般によくつかわれているもののほうが、なにかと便利です。
ユリウス日の暦元は正午ですが、時刻がいらないなら、 正午の勘定をもってその日のユリウス日としてあつかえばいいです。 一般的かどうか知りませんが、 わたしは、世界時の正午を一日の始まりとし、 しばしば、ともに時刻をも表現するものを 「天文学的なユリウス日」、地方時における零時を一日の始まりとし、 概ね時刻については頓着しないものを 「年代学的なユリウス日」と呼んでいます。
修正ユリウス日 (Modified Julian Day, MJD) は 西暦1858年11月17日 (グレゴリオ暦) 零時 (協定世界時) を暦元 (0.0) とした 通日 (経過日数) です。 つまり、MJD = JD - 2400000.5 です (半端な0.5があるのは、 ユリウス日と違い、深夜に暦元があるからです)。
切詰ユリウス日 (Truncated Julian Day, TJD) は よくわからない存在です。 ちなみに、「切詰ユリウス日」という言いかたは、 一般的な訳でなく、わたしが勝手にそう言っているにすぎません。
この切詰ユリウス日の定義ははっきりしませんが、 わたしがみたところ、ふたつの定義、あるいは解釈が存在しているようです。
ひとつは、 西暦1968年5月24日 (グレゴリオ暦) 零時 (協定世界時) を暦元 (0.0) とした 通日 (経過日数) です。 つまり、JD - 2440000.5 です。
もうひとつは、ひとつ目のものの下4桁だけを残したものです (もちろん10進法)。 つまり、(JD - 2440000.5) % 10000 です。
根拠はありませんが、ふたつ目のほうが正解かもしれない。 CCSDS 301.0-B-2 (他に WORD、PDF, PS 版もある) を参照してください。
リリウス日 (Lilian day) は 西暦1582年10月15日 (グレゴリオ暦) を暦元 (1) とした 通日です。 ちなみに、「リリウス日」という言いかたは、 一般的な訳でなく、わたしが勝手にそう言っているにすぎません。
"Lilian day" の名前の由来は、 西暦1582年10月15日が、グレゴリオ暦をつかい始めた日で、 そのグレゴリオ暦の考案者が、Aloysius Lilius であるからでしょう。
スウォッチビートはスイスの時計屋スウォッチ社が デッチあげた時刻表現です。 「インターネット時間」とかいってるらしいです。 よくわかりません。
一日のはじまりは、スウォッチ本社のあるスイスのビールでの深夜で (これを Biel Mean Time (BMT) と呼んでいるらしい)、 一日を1000に分割します。1ビートは 1分26.4秒です。 たぶん、閏秒は勘定しないのだと思います。
ビートには時差がないと、 いばっているようですが (いばっていないのかもしれませんが)、 ただ単に、スウォッチ本社の地方時を全世界でつかえといっているに すぎないように思います。 一日をただ1000に分割しただけですよ。
日付を「年月日」のように大きいものから書くやりかたです。 我が国では一般的です。 また、ISO 8601-2000 もビッグエンディアンです。
例: 1999年7月20日
日付を「日月年」のように小さいものから書くやりかたです。 欧州では一般的です。
例: 20 juillet 1999
よさげな本です (読んでないのもありますが)。
目にとまった雑誌記事の一覧です。
ふなばただよし 1999-2005
更新日時: 2011-01-09T23:47:17+09:00