Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 16 - Utility-Klassen I

## 16.2 Die Klassen Date, Calendar und GregorianCalendar

- 16.2 Die Klassen Date, Calendar und GregorianCalendar
  - 16.2.1 Konstruktoren
  - 16.2.2 Abfragen und Setzen von Datumsbestandteilen
  - 16.2.3 Vergleiche und Datums-/Zeitarithmetik
  - 16.2.4 Umwandlung zwischen Date und Calendar

Bis zum JDK 1.0.2 war die Klasse `Date` zur Darstellung und Manipulation von Datumswerten vorgesehen. Leider war sie nicht ganz fehlerfrei und aufgrund diverser Einschränkungen nur sehr bedingt zu gebrauchen. Ab der Version 1.1 des JDK gibt es neben `Date` die Klasse `Calendar` zur Verarbeitung von Datumswerten. Obgleich der Name den Anschein erwecken mag, daß ein Objekt vom Typ `Calendar` ein visueller Kalender ist, der als Komponente in GUI-basierten Programmen verwendet werden kann, ist dies nicht richtig. Statt dessen stellt `Calendar` eine Kapselung für `Date` dar, deren Aufgabe es ist, ein Datum-/Uhrzeitobjekt zu realisieren und Methoden zur Konstruktion, zum Verändern und Auslesen von Datums-/Uhrzeitbestandteilen und für die Zeit- und Datumsarithmetik zur Verfügung zu stellen.

Zunächst ist `Calendar` nichts weiter als eine abstrakte Basisklasse. Sie enthält die Methoden, mit denen auf die einzelnen Elemente konkreter Kalenderklassen zugegriffen werden kann bzw. mit denen diese Klassen manipuliert werden können. Als einzige konkrete Ableitung von `Calendar` steht die Klasse `GregorianCalendar` zur Verfügung, die ein Datum nach dem hierzulande verwendeten gregorianischen Kalender implementiert. Die Komplexität der Klassen `Calendar` und `GregorianCalendar` kommt vor allem durch folgende Ursachen zustande:

- Datumsarithmetik ist grundsätzlich eine nicht ganz triviale Angelegenheit
- Lokale Besonderheiten sind zu berücksichtigen (Ausgabeformatierung, an welchem Tag beginnt die Woche, 12/24-Stunden-Darstellung, wie viele Tage muß die erste Woche des Jahres mindestens haben usw.)
- Zeitzonen sind zu berücksichtigen
- Schaltjahre oder -sekunden und ähnliche Abweichungen zu astronomischen Kalendern müssen berücksichtigt werden
- Weltweit kommen viele unterschiedliche Kalender zum Einsatz

In der Tat ist die Implementierung der Kalenderklassen des JDK komplex und war lange Zeit fehlerbehaftet. Sie wird insbesondere dadurch erschwert, daß ein Datumsobjekt nicht nur aus den einzelnen Feldern für Tag, Monat, Jahr usw. besteht, sondern zusätzlich einen ganzzahligen Wert des Typs `long` enthält, der das Datum als Anzahl der Millisekunden seit dem 1.1.1970 speichert. Beide Werte müssen auch nach Veränderungen einzelner Bestandteile des Datumsobjekts konsistent gehalten werden.

Auch die Bedienung der Kalenderklassen ist nicht so eingängig wie in vielen anderen Programmiersprachen. Hinderlich ist dabei oft die Tatsache, daß neben Datum und Uhrzeit grundsätzlich auch die Zeitzone mit betrachtet wird. Wir wollen in diesem Abschnitt einen pragmatischen Ansatz wählen und nur die wesentlichen Eigenschaften der beiden Klassen vorstellen. Fortgeschrittenere Themen wie Zeitzonenkalkulation oder Lokalisierung werden außen vor bleiben.

### 16.2.1 Konstruktoren

Da die Klasse `Calendar` abstrakt ist, müssen konkrete Datumsobjekte aus der Klasse `GregorianCalendar` erzeugt werden. Dazu stehen folgende Konstruktoren zur Verfügung:

    public GregorianCalendar()

    public GregorianCalendar(int year, int month, int date)

    public GregorianCalendar(
       int year, int month, int date,
       int hour, int minute
    )

    public GregorianCalendar(
       int year, int month, int date,
       int hour, int minute, int second
    )
| java.util.GregorianCalendar

Der parameterlose Konstruktor initialisiert das Datumsobjekt mit dem aktuellen Datum und der aktuellen Uhrzeit. Die übrigen Konstruktoren weisen die als Parameter übergebenen Werte zu. Neben den hier vorgestellten Konstruktoren gibt es noch weitere, die es erlauben, die Zeitzone und Lokalisierung zu verändern. Standardmäßig werden die lokale Zeitzone und die aktuelle Lokalisierung verwendet.

### 16.2.2 Abfragen und Setzen von Datumsbestandteilen

Das Abfragen und Setzen von Datumsbestandteilen erfolgt mit den Methoden `set` und `get`:

    public final int get(int field)

    public final void set(int field, int value)
| java.util.Calendar

`get` und `set` erwarten als erstes Argument einen Feldbezeichner, der angibt, auf welches der diversen Datums-/Zeitfelder des Objektes zugegriffen werden soll. Als Rückgabewert liefert `get` den Inhalt des angegebenen Feldes; `set` schreibt den als zweiten Parameter `value` übergebenen Wert in das Feld hinein. Tabelle 16.1 gibt eine Übersicht der in `Calendar` vorgesehenen Feldbezeichner, ihrer Wertegrenzen und Bedeutung:

Feldbezeichner | Minimalwert | Maximalwert | Bedeutung
`Calendar.ERA` | 0 | 1 | Ära
`Calendar.YEAR` | 1 | 5,000,000 | Jahr
`Calendar.MONTH` | 0 | 11 | Monat - 1
`Calendar.WEEK_OF_YEAR` | 1 | 54 | Woche im Jahr
`Calendar.WEEK_OF_MONTH` | 1 | 6 | Woche im Monat
`Calendar.DAY_OF_MONTH` | 1 | 31 | Tag im Monat
`Calendar.DAY_OF_YEAR` | 1 | 366 | Tag im Jahr
`Calendar.DAY_OF_WEEK` | 1 | 7 | Wochentag
`Calendar.DAY_OF_WEEK_IN_MONTH` | -1 | 6 | Wochentagswiederholung im Monat
`Calendar.AM_PM` | 0 | 1 | Vor-/nachmittags
`Calendar.HOUR` | 0 | 12 | Amerik. Stunde
`Calendar.HOUR_OF_DAY` | 0 | 23 | Stunde
`Calendar.MINUTE` | 0 | 59 | Minute
`Calendar.SECOND` | 0 | 59 | Sekunde
`Calendar.MILLISECOND` | 0 | 999 | Millisekunde
`Calendar.ZONE_OFFSET` | -12*60*60*1000 | 12*60*60*1000 | Zeitzonenoffset
`Calendar.DST_OFFSET` | 0 | 1*60*60*1000 | Sommerzeitoffset

Tabelle 16.1: Feldbezeichner der Klasse Calendar

|  | Hierbei gibt es einige Besonderheiten zu beachten. So wird beispielsweise der Monat nicht von 1 bis 12 gemessen, sondern von 0 bis 11. Das Feld `ERA` gibt an, ob das Datum vor Christi Geburt oder danach liegt. `DAY_OF_WEEK` geht von 1 = Sonntag bis 7 = Samstag, es stehen aber auch symbolische Konstanten zur Verfügung. `ZONE_OFFSET` und `DST_OFFSET` sind die Zeitzonen- und Sommerzeitabweichungen, die in Millisekunden gemessen werden. |  |
Hinweis

Wir wollen uns die Verwendung der verschiedenen Felder an einem einfachen Beispiel ansehen. Das Programm zeigt auch die Verwendung einiger symbolischer Konstanten zur Darstellung von Wochentagen und der Ära (vor/nach Christi Geburt):

    001 /* Listing1602.java */
    002
    003 import java.util.*;
    004
    005 public class Listing1602
    006 {
    007   public static void main(String[] args)
    008   {
    009     //Zuerst Ausgabe des aktuellen Datums
    010     GregorianCalendar cal = new GregorianCalendar();
    011     //cal.setTimeZone(TimeZone.getTimeZone("ECT"));
    012     printCalendarInfo(cal);
    013     System.out.println("---");
    014
    015     //Nun Ausgabe der Informationen zum 22.6.1910,
    016     //dem Geburtstag von Konrad Zuse
    017     cal.set(Calendar.DATE, 22);
    018     cal.set(Calendar.MONTH, 6 - 1);
    019     cal.set(Calendar.YEAR, 1910);
    020     printCalendarInfo(cal);
    021     //cal.setTime(cal.getTime());
    022   }
    023
    024   public static void printCalendarInfo(GregorianCalendar cal)
    025   {
    026     //Aera
    027     int value = cal.get(Calendar.ERA);
    028     if (value == cal.BC) {
    029       System.out.println("Aera.......: vor Christi Geburt");
    030     } else if (value == cal.AD) {
    031       System.out.println("Aera.......: Anno Domini");
    032     } else {
    033       System.out.println("Aera.......: unbekannt");
    034     }
    035     //Datum
    036     System.out.println(
    037       "Datum......: " +
    038       cal.get(Calendar.DATE) + "." +
    039       (cal.get(Calendar.MONTH)+1) + "." +
    040       cal.get(Calendar.YEAR)
    041     );
    042     //Zeit
    043     System.out.println(
    044       "Zeit.......: " +
    045       cal.get(Calendar.HOUR_OF_DAY) + ":" +
    046       cal.get(Calendar.MINUTE) + ":" +
    047       cal.get(Calendar.SECOND) + " (+" +
    048       cal.get(Calendar.MILLISECOND) + " ms)"
    049     );
    050     //Zeit, amerikanisch
    051     System.out.print(
    052       "Am.Zeit....: " +
    053       cal.get(Calendar.HOUR) + ":" +
    054       cal.get(Calendar.MINUTE) + ":" +
    055       cal.get(Calendar.SECOND)
    056     );
    057     value = cal.get(Calendar.AM_PM);
    058     if (value == cal.AM) {
    059       System.out.println(" AM");
    060     } else if (value == cal.PM) {
    061       System.out.println(" PM");
    062     }
    063     //Tag
    064     System.out.println(
    065       "Tag........: " +
    066       cal.get(Calendar.DAY_OF_YEAR) + ". im Jahr"
    067     );
    068     System.out.println(
    069       "             " +
    070       cal.get(Calendar.DAY_OF_MONTH) + ". im Monat"
    071     );
    072     //Woche
    073     System.out.println(
    074       "Woche......: " +
    075       cal.get(Calendar.WEEK_OF_YEAR) + ". im Jahr"
    076     );
    077     System.out.println(
    078       "             " +
    079       cal.get(Calendar.WEEK_OF_MONTH) + ". im Monat"
    080     );
    081     //Wochentag
    082     System.out.print(
    083       "Wochentag..: " +
    084       cal.get(Calendar.DAY_OF_WEEK_IN_MONTH) +
    085       ". "
    086     );
    087     value = cal.get(Calendar.DAY_OF_WEEK);
    088     if (value == cal.SUNDAY) {
    089       System.out.print("Sonntag");
    090     } else if (value == cal.MONDAY) {
    091       System.out.print("Montag");
    092     } else if (value == cal.TUESDAY) {
    093       System.out.print("Dienstag");
    094     } else if (value == cal.WEDNESDAY) {
    095       System.out.print("Mittwoch");
    096     } else if (value == cal.THURSDAY) {
    097       System.out.print("Donnerstag");
    098     } else if (value == cal.FRIDAY) {
    099       System.out.print("Freitag");
    100     } else if (value == cal.SATURDAY) {
    101       System.out.print("Samstag");
    102     } else {
    103       System.out.print("unbekannt");
    104     }
    105     System.out.println(" im Monat");
    106     //Zeitzone
    107     System.out.println(
    108       "Zeitzone...: " +
    109       cal.get(Calendar.ZONE_OFFSET)/3600000 +
    110       " Stunden"
    111     );
    112     System.out.println(
    113       "Sommerzeit.: " +
    114       cal.get(Calendar.DST_OFFSET)/3600000 +
    115       " Stunden"
    116     );
    117   }
    118 }
| Listing1602.java

Listing 16.2: Die Felder der Klasse Calendar

Das Programm erzeugt zunächst ein `GregorianCalendar`-Objekt für das aktuelle Tagesdatum und gibt die internen Feldwerte aus. Anschließend ändert es durch mehrfachen Aufruf von `set` das Datum in den 22.6.1910 ab und gibt die Felder erneut aus. Die Ausgabe des Programms lautet:

    Aera.......: Anno Domini
    Datum......: 28.4.2000
    Zeit.......: 17:55:0 (+590 ms)
    Am.Zeit....: 5:55:0 PM
    Tag........: 119. im Jahr
                 28. im Monat
    Woche......: 17. im Jahr
                 4. im Monat
    Wochentag..: 4. Freitag im Monat
    Zeitzone...: 1 Stunden
    Sommerzeit.: 1 Stunden
    ---
    Aera.......: Anno Domini
    Datum......: 22.6.1910
    Zeit.......: 17:55:0 (+590 ms)
    Am.Zeit....: 5:55:0 PM
    Tag........: 173. im Jahr
                 22. im Monat
    Woche......: 25. im Jahr
                 4. im Monat
    Wochentag..: 4. Mittwoch im Monat
    Zeitzone...: 1 Stunden
    Sommerzeit.: 1 Stunden

|  |  |  | Wie man sieht, werden sowohl Datums- als auch Zeitwerte korrekt ausgegeben. Damit dieses und vergleichbare Programme auch in den JDK-1.1-Versionen korrekt laufen, sind einige Besonderheiten zu beachten: Wird das Datumsobjekt ohne explizite Zeitzonenangabe konstruiert, so verwendet der Konstruktor die von der Methode `TimeZone.getDefault` gelieferte Zeitzone, die ihrerseits aus dem System-Property `user.timezone` generiert wird. Unter dem JDK 1.1 wurde dieses Property aber nicht immer gefüllt und es war erforderlich, die Zeitzone per Hand zu setzen. Im JDK 1.2 und späteren Versionen können wir dagegen auf den Aufruf von `setTimeZone` in Zeile 011 verzichten. Das zweite Problem rührt aus der oben erwähnten Schwierigkeit, den internen Zeitwert und die Feldwerte korrekt zu synchronisieren. Nach der Zuweisung eines neuen Datums im Beispielprogramm werden zwar die Felder für Tag, Monat und Jahr korrekt gesetzt, die übrigen aber leider nicht. Nach dieser Zuweisung wäre also die Ausgabe des Wochentags fehlerhaft gewesen. Als Workaround könnte das Beispielprogramm nach dem Aufruf der `set`-Methoden einen Aufruf von `setTime(getTime())` verwenden, der interne Uhrzeit und Feldwerte abgleicht. Wir haben das im Listing in der Zeile 021 hinter dem Kommentar angedeutet. Auch das scheint seit dem JDK 1.2 nicht mehr nötig zu sein. |  |
JDK1.1-1.4

### 16.2.3 Vergleiche und Datums-/Zeitarithmetik

Die Methoden `equals`, `before` und `after` erlauben es, zwei Datumswerte auf ihre relative zeitliche Lage zueinander zu vergleichen:

    public boolean equals(Object obj)

    public boolean before(Object obj)

    public boolean after(Object obj)
| java.util.Calendar

Mit Hilfe der Methode `add` kann zu einem beliebigen Feld eines `Calendar`- oder `GregorianCalendar`-Objekts ein beliebiger positiver oder negativer Wert hinzugezählt werden:

    public abstract void add(int field, int amount)
| java.util.Calendar

Dabei ist es auch erlaubt, daß die Summe den für dieses Feld erlaubten Grenzwert über- bzw. unterschreitet. In diesem Fall wird der nächsthöherwertige Datums- bzw. Zeitbestandteil entsprechend angepaßt.

Das folgende Programm konstruiert zunächst ein Datum für den 30.10.1908 und gibt es aus. Anschließend wird zunächst der Tag, dann der Monat und schließlich das Jahr je zweimal um 1 erhöht. Nach erfolgter Ausgabe wird die Änderung schrittweise wieder rückgängig gemacht und der ursprüngliche Wert wieder erzeugt:

    001 /* Listing1603.java */
    002
    003 import java.util.*;
    004
    005 public class Listing1603
    006 {
    007   public static void main(String[] args)
    008   {
    009     GregorianCalendar cal   = new GregorianCalendar();
    010     cal.set(Calendar.DATE, 30);
    011     cal.set(Calendar.MONTH, 10 - 1);
    012     cal.set(Calendar.YEAR, 1908);
    013     showDate(cal);
    014     addOne(cal, Calendar.DATE);
    015     addOne(cal, Calendar.DATE);
    016     addOne(cal, Calendar.MONTH);
    017     addOne(cal, Calendar.MONTH);
    018     addOne(cal, Calendar.YEAR);
    019     addOne(cal, Calendar.YEAR);
    020
    021     cal.add(Calendar.DATE, -2);
    022     cal.add(Calendar.MONTH, -2);
    023     cal.add(Calendar.YEAR, -2);
    024     showDate(cal);
    025   }
    026
    027   public static void addOne(Calendar cal, int field)
    028   {
    029     cal.add(field,1);
    030     showDate(cal);
    031   }
    032
    033   public static void showDate(Calendar cal)
    034   {
    035     String ret = "";
    036     int    value = cal.get(Calendar.DAY_OF_WEEK);
    037
    038     switch (value) {
    039     case Calendar.SUNDAY:
    040       ret += "Sonntag";
    041       break;
    042     case Calendar.MONDAY:
    043       ret += "Montag";
    044       break;
    045     case Calendar.TUESDAY:
    046       ret += "Dienstag";
    047       break;
    048     case Calendar.WEDNESDAY:
    049       ret += "Mittwoch";
    050       break;
    051     case Calendar.THURSDAY:
    052       ret += "Donnerstag";
    053       break;
    054     case Calendar.FRIDAY:
    055       ret += "Freitag";
    056       break;
    057     case Calendar.SATURDAY:
    058       ret += "Samstag";
    059       break;
    060     }
    061     ret += ", den ";
    062     ret += cal.get(Calendar.DATE) + ".";
    063     ret += (cal.get(Calendar.MONTH)+1) + ".";
    064     ret += cal.get(Calendar.YEAR);
    065     System.out.println(ret);
    066   }
    067 }
| Listing1603.java

Listing 16.3: Datumsarithmetik

Die Ausgabe des Programms lautet:

    Freitag, den 30.10.1908
    Samstag, den 31.10.1908
    Sonntag, den 1.11.1908
    Dienstag, den 1.12.1908
    Freitag, den 1.1.1909
    Samstag, den 1.1.1910
    Sonntag, den 1.1.1911
    Freitag, den 30.10.1908

### 16.2.4 Umwandlung zwischen Date und Calendar

In der Praxis ist es mitunter erforderlich, zwischen den beiden konkurrierenden Zeitdarstellungen der Klassen `Date` und `Calendar` hin- und herzuschalten. So ist beispielsweise der in JDBC (siehe Kapitel 42) häufig verwendete SQL-Datentyp `java.sql.Date` aus `java.util.Date` abgeleitet und repräsentiert ein Datum als Anzahl der Millisekunden seit dem 1.1.1970. Mit Hilfe der Methoden `setTime` und `getTime` können beide Darstellungen ineinander überführt werden:

    public final Date getTime()

    public final void setTime(Date date)
| java.util.Calendar

Ein Aufruf von `getTime` liefert das Datum als Objekt des Typs `Date`. Soll das Datum aus einem vorhandenen `Date`-Objekt in ein `Calendar`-Objekt übertragen werden, kann dazu die Methode `setTime` aufgerufen werden. Die Klasse `Date` kann weiterhin dazu verwendet werden, auf die Anzahl der Millisekunden seit dem 1.1.1970 zuzugreifen:

    public Date(long date)

    public long getTime()
| java.util.Date

Der Konstruktor erzeugt aus dem `long` ein `Date`-Objekt, und die Methode `getTime` kann dazu verwendet werden, zu einem gegebenen `Date`-Objekt die Anzahl der Millisekunden seit dem 1.1.1970 zu ermitteln.

Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de