連載
» 2007年03月28日 00時00分 公開

プログラマーの常識をJavaで身につける(4):OSとプログラミングの「時間」に関する常識 (1/3)

本連載は、Java言語やその文法は一通り理解しているが、「プログラマー」としては初心者、という方を対象とします。Javaコアパッケージを掘り下げることにより「プログラマーの常識」を身に付けられるように話を進めていきます。今回は、「時間」について。OSの時間はどこから取得されるか、グレゴリオ暦とユリウス暦の境目などの常識と時間に関するクラスについて解説します。

[伊賀敏樹,NTTデータ ビジネスブレインズ]

編集部注

Java言語の基礎を学びたい読者は、連載「【改訂版】Eclipseではじめるプログラミング」をご参照ください。


 今回の記事では、「時間」に関する常識を Javaで身に付けていきます。時間の扱い方は、前回説明した「」の扱い方と同様で、プログラミング言語によって特徴が出やすいものです。Java言語を通じて、時間の取り扱い方を見ていくことにより、プログラマーとしての常識を身に付けていきましょう。

デスクトップなど身近なところに現れる日付・時刻

 プログラミングの話題に入る前に、まず私たちの身近なところに現れる日付や時刻を見ていきましょう。あなたがお使いのOSがWindows XPでしたら、デフォルトの設定では、タスクバーの通知領域時計が表示されていると思います。

図1 通知領域に表示される時計 図1 通知領域に表示される時計

 時計の部分をダブルクリックすると、 「日付と時刻のプロパティ」画面を開くことができます。

図2 日付と時刻のプロパティ(日付と時刻) 図2 日付と時刻のプロパティ(日付と時刻)

リアルタイムクロックとシステムクロックとは?

 この画面には、日付と時刻が表示されます。ここで表示されている日付および時刻は、OSが内部的に保持している日時となります。OSが刻んでいる時計は システムクロックシステム時計)と呼ばれます。

 一方で、OSが起動していない間にも時刻を刻んでいる必要があります。これを実現するために、コンピュータにはリアルタイムクロックハードウェアクロック)と呼ばれる時計が内蔵されています。

 Windows XPなどのOSは、起動時にリアルタイムクロックから時刻を取得して、その後、OSとしてのシステム時計の時刻を刻むようになっています。私たちが普段何げなく利用しているOSの時計ですが、二重構造の時計として実装されているのは、意外なことです。

コラム 「OSの時計は不正確」  

原稿執筆時点において、私たちの身近なパソコンのOS上で表示される時計は、意外なほどに不正確です。プログラムから日時を扱うプログラミングをする場合にも、この点に注意を払う必要があります。


時差を表すタイムゾーン

 先ほどの「日付と時刻のプロパティ」画面の「タイムゾーン」タブを選択すると、タイムゾーンを選択する画面が表示されます。

図3 日付と時刻のプロパティ(タイムゾーン) 図3 日付と時刻のプロパティ(タイムゾーン)

 最近のOSの多くはGMTグリニッジ標準時)によって定義された標準にのっとっています。そして、java.util.CalendarなどJava言語の日時についても、GMTを基準とした仕組みになっています。この画面に表示があるように、グリニッジと日本では、9時間の時差があります。

Java言語における日時を表すクラス・型

 それでは、Java言語における日付・時刻を見ていきましょう。Java言語には、図4のように主に3つの日付・時刻を表す型や数値があります。そして、それらは日時を表すようになっています(※注釈:これ以外にもいくつかの型があります)。歴史的な経緯もあり、これら型や数値の間には、いくぶん癖のある関係があります。

図4 Java言語における日付・時刻 図4 Java言語における日付・時刻

 それぞれについて見ていきましょう。

「エポックからのミリ秒」というlong値による日時の表現

 コンピュータ関連技術の多くでは、日時を整数で表す手法が採用されています。整数はコンピュータ処理しやすいからでしょう。Java言語も日時を整数で表す方法を採用しています。

 具体的には、1970年1月1日 00:00:00 GMTからのミリ秒数を表すlong値として表現されています。この原稿では、これを便宜的に「エポックからのミリ秒」と呼ぶことにします。なお、1970年1月1日 00:00:00 GMTについてJava言語APIでは、「エポック」と呼んでいます。

図5 エポックから現在へ 図5 「エポック」から現在へ

[java.util.Date]による日時の表現

 Java言語には、日時を表すクラスとしてjava.util.Dateクラスが提供されています。クラス名も、それが日付を表す重要なクラスであることを示唆しています。しかし、java.util.Dateクラスの大半のメソッドは推奨されない(deprecated)メソッドとしてマークされています。

 java.util.DateのAPIドキュメントに記載があるように、歴史的な経緯から「日付と時間フィールドの間の変換には、Calendarクラスを、日付文字列のフォーマットと構文解析には、DateFormatクラスを使用する」(ここはAPIドキュメントから引用しています)ことになっているのです。とはいえ、日時を表現するクラスとして、java.util.Dateはよく利用されます。

注意 Dateクラスは別パッケージにも存在する[java.sql.Date]

Java言語APIには、java.util.Dateクラスに似た名前のjava.sql.Dateクラスがあります。パッケージ名を除くと、まったく同じ名称なので混同しないよう注意が必要です。例えば、Eclipseなどの統合開発環境において、入力補完インポートの編成などの機能を利用すると、以下のような選択肢が表示されます。ここでどちらのクラスを利用するのか、適切に選択する必要があります。

図6 Eclipseの場合のインポートの選択 図6 Eclipseの場合のインポートの選択
編集部注:2つのDateクラスについて詳しく知りたい読者は、Java TIPSの「SQLを使うプログラムではDateクラスに要注意」をご参照ください


[java.util.Calendar]による日時の表現

 Java言語では、日付や時間の各種変換にはjava.util.Calendarクラスが利用されます。特にget/setメソッドがよく利用されるようです。なお、Calendarクラスのget/setメソッドの呼び出しには、Calendarクラスの定数の利用が必要になるので、主立ったものを以下に紹介します。

定数 説明
YEAR 年を示す 2007
MONTH 月を示す(0オリジンになっている点に注意が必要。例えば、1月は1ではなく、0として表現される 0
DATE 月の日を示す 31
HOUR_OF_DAY 時刻を示す(HOUR_OF_DAYは24時間制 15
MINUTE 分を示す 46
SECOND 秒を示す 52
MILLISECOND ミリ秒を示す 251
表1 java.util.Calendarクラスの主な定数

 特に、月が0オリジンである点に注意する必要があります。

[java.util.GregorianCalendar]はハイブリッドカレンダー

 java.util.Calendarクラスは抽象クラスです。Calendarクラスのインスタンスとして実際にはjava.util.GregorianCalendarクラスが利用されます。下記のソースコードを実行してください。

CalendarSample.java
import java.util.Calendar;

public class CalendarSample {
    public static void main(final String[] args) {
        final Calendar cal = Calendar.getInstance();
        System.out.println(
            "java.util.Calendarクラスの実行時の具象クラス: "
            + cal.getClass().toString());
    }
}

実行結果

java.util.Calendarクラスの実行時の具象クラス: class java.util.GregorianCalendar


 getInstanceしてgetClass→toStringすると、GregorianCalendarクラスであることが確認できます。

 また、java.util.GregorianCalendarのAPIドキュメントには、以下のような記載があります。

GregorianCalendarは、Calendarの具象サブクラスであり、世界のほとんどの地域で使用される標準的なカレンダシステムを提供します。GregorianCalendar は、グレゴリオ暦ユリウス暦をサポートするハイブリッドカレンダシステムで、単一の変わり目を処理します。

…中略…

歴史的に、グレゴリオ暦を最初に採用した国々では、1582年10月4日(ユリウス歴)の後に 1582年10月15日(グレゴリオ歴)が続きました。


 本当にそうなっているのか、実際に試してみましょう。下記のソースコードを実行してください。SimpleDateFormatクラスについては、後述します。

GregorianCalendarSample.java
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class GregorianCalendarSample {
    public static void main(final String[] args) {
        final Calendar cal = Calendar.getInstance();
        final SimpleDateFormat sdf
            = new SimpleDateFormat("yyyy/MM/dd");

        cal.set(Calendar.YEAR, 1582);
        cal.set(Calendar.MONTH, 9); // 10月のことです
        cal.set(Calendar.DATE, 17);
        System.out.println(sdf.format(cal.getTime()));

        cal.add(Calendar.DATE, -1);
        System.out.println(sdf.format(cal.getTime()));

        cal.add(Calendar.DATE, -1);
        System.out.println(sdf.format(cal.getTime()));

        cal.add(Calendar.DATE, -1);
        System.out.println(sdf.format(cal.getTime()));

        cal.add(Calendar.DATE, -1);
        System.out.println(sdf.format(cal.getTime()));

        cal.add(Calendar.DATE, -1);
        System.out.println(sdf.format(cal.getTime()));
    }
}

実行結果

1582/10/17

1582/10/16

1582/10/15

1582/10/04

1582/10/03

1582/10/02


 本当に日付が飛ぶところが確認できました。紫色の日付と赤色の日付の間に、グレゴリオ暦とユリウス暦の境界があります。

ほかのクラスにおける日時

 ほかのクラスで日時を表す際には、いままで紹介した日時を表す型が利用されることが多いです。

 例えば、java.io.Fileクラスにおいて、ファイル最終更新日時はFile.lastModified()メソッドで取得できます。個々で得られるlong値は、「エポックからのミリ秒」なのです。

       1|2|3 次のページへ

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。