Tit   Inh   Ind   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   31   32   <<   <   >   >> 

12.7 Die Klassen Date, Calendar und GregorianCalendar



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:

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.

12.7.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 Kontruktoren 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.

12.7.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 12.1 gibt eine Übersicht der in GregorianCalendar vorgesehenen Feldbezeichner und ihrer Wertegrenzen:

Feldbezeichner Minimalwert Maximalwert
Calendar.ERA 0 1
Calendar.YEAR 1 5,000,000
Calendar.MONTH 0 11
Calendar.WEEK_OF_YEAR 1 54
Calendar.WEEK_OF_MONTH 1 6
Calendar.DAY_OF_MONTH 1 31
Calendar.DAY_OF_YEAR 1 366
Calendar.DAY_OF_WEEK 1 7
Calendar.DAY_OF_WEEK_IN_MONTH -1 6
Calendar.AM_PM 0 1
Calendar.HOUR 0 12
Calendar.HOUR_OF_DAY 0 23
Calendar.MINUTE 0 59
Calendar.SECOND 0 59
Calendar.MILLISECOND 0 999
Calendar.ZONE_OFFSET -12*60*60*1000 12*60*60*1000
Calendar.DST_OFFSET 0 1*60*60*1000

Tabelle 12.1: Feldbezeichner der Klasse GregorianCalendar

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

 Beispiel 

Das Programm erzeugt zunächst ein GregorianCalendar-Objekt für das aktuelle Tagesdatum und gibt die internen Feldwerte aus. Anschließend ändert es per 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......: 2.1.1998
Zeit.......: 18:12:53 (+560 ms)
Am.Zeit....: 6:12:53 PM
Woche......: 53. im Jahr
             0. im Monat
Tag........: 2. im Jahr
             2. im Monat
             1. in der Woche
Wochentag..: Samstag
Zeitzone...: 1 Stunden
Sommerzeit.: 0 Stunden
---
Aera.......: Anno Domini
Datum......: 22.6.1910
Zeit.......: 12:14:41 (+880 ms)
Am.Zeit....: 0:14:41 PM
Woche......: 25. im Jahr
             4. im Monat
Tag........: 173. im Jahr
             22. im Monat
             4. in der Woche
Wochentag..: Mittwoch
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 könnten 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 023 hinter dem Kommentar angedeutet. Wie gesagt, auch das scheint im JDK 1.2 nicht mehr nötig zu sein.
 JDK1.1/1.2 

12.7.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 /* Listing1208.java */
002 
003 import java.util.*;
004 
005 public class Listing1208
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 cal.SUNDAY:
040 	  ret += "Sonntag";
041 	  break;
042 	case cal.MONDAY:
043 	  ret += "Montag";
044 	  break;
045 	case cal.TUESDAY:
046 	  ret += "Dienstag";
047 	  break;
048 	case cal.WEDNESDAY:
049 	  ret += "Mittwoch";
050 	  break;
051 	case cal.THURSDAY:
052 	  ret += "Donnerstag";
053 	  break;
054 	case cal.FRIDAY:
055 	  ret += "Freitag";
056 	  break;
057 	case cal.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 }
Listing1208.java
Listing 12.8: Datumsarithmetik

 Beispiel 

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

12.7.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 30) 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.


 Tit   Inh   Ind   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   31   32   <<   <   >   >> 
Go To Java 2, Addison Wesley, Version 1.0.2, © 1999 Guido Krüger, http://www.gkrueger.com