Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel - Zum Katalog
gp Kapitel 10 Raum und Zeit
  gp 10.1 Greenwich Mean Time (GMT)
  gp 10.2 Wichtige Datum-Klassen im Überblick
    gp 10.2.1 Zeitzonen durch die Klasse TimeZone repräsentiert
  gp 10.3 Sprachen der Länder
    gp 10.3.1 Sprachen in Java über Locale-Objekte
  gp 10.4 Einfache Übersetzung durch ResourceBundle-Objekte
  gp 10.5 Die Klasse Date
    gp 10.5.1 Die Date-Klasse
    gp 10.5.2 Zeitmessung und Profiling
  gp 10.6 Die abstrakte Klasse Calendar
  gp 10.7 Der gregorianische Kalender
  gp 10.8 Formatieren der Datumsangaben
    gp 10.8.1 Mit DateFormat und SimpleDateFormat formatieren
    gp 10.8.2 Parsen von Datumswerten
    gp 10.8.3 Parsen und Formatieren ab bestimmten Positionen

Kapitel 10 Raum und Zeit

Schurken verschiedener Nationalität
verstehen sich einander wortlos.
– Halldór Laxness


Galileo Computing

10.1 Greenwich Mean Time (GMT)  downtop

Zeitmessung und Kalenderwesen zählen zu den wichtigsten Aufgaben der frühen Astronomie. Astronomen beobachteten Sterne, um etwa Prognosen über kommende Ernteperioden abzugeben. Bis zur atomgenauen Zeitmessung war die Zeit durch Beobachtung der Sonne und des Planetenlaufs gegeben. Leider sind die Daten von den Planeten nicht eindeutig. Mehrdeutigkeiten gibt es zum Beispiel bei der Definition einer Umdrehung. Ein bekanntes Problem ist die Inkommensurabilität von Tag, Monat und Jahr. Ein Jahr lässt sich nicht präzise durch eine ganze Anzahl von Monaten oder Tagen ausdrücken. Auch ein Monat besteht nicht immer aus einer ganzen Zahl von Tagen. In den unterschiedlichen Lösungen wurden in der Vergangenheit Schaltsekunden und Schaltjahre nach bestimmten mathematischen Formeln eingeführt. Das führte zu einer Vielzahl von Zeitskalen und Kalendern. Eine bekannte Zeitskala ist die Sternzeit, die sich aus der Umdrehung der Erde gegenüber dem Hintergrund der in großer Entfernung stehenden Sterne ableitet. Ein wichtiger Begriff für Astronomen ist der Meridian. Er ist der vom Erdmittelpunkt aus auf die Himmelskugel projizierte Längenkreis des Beobachtungsorts. Die dadurch gebildete Zeit ist ortsabhängig. Um auf eine weltweit einheitliche Sternzeit zu kommen, erklärte eine Kommission den Meridian von Greenwich zum Nullpunkt und definierte die so erhaltene Zeitskala als Greenwich Mean Siderial Time (GMST). Alle anderen Zeiten berechnen sich durch eine relativ einfache Formel aus der Greenwich-Zeit.

Abbildung

Mit der Zeit ergaben sich unterschiedliche Bedeutungen der GMT und 1926 wurde die Universal Time (UT), auch als Weltzeit bezeichnet, als Ersatz für die Greenwich Mean Time eingeführt. Wir wollen im Folgenden jedoch immer noch von der GMT sprechen, da dies die alltägliche Sprechweise ist. Die Zeit nach UT ist meistens mit dem mittleren Sonnentag, bezogen auf den Nullmeridian von Greenwich, gleichzusetzen. Dieser basiert auf astronomischen Beobachtungen. Aufgrund der Erdrotation, die sich ständig verlangsamt, ist UT keine Konstante.

Die Datum-Klasse in der Java-Standardbibliothek spiegelt – so wie es die meisten Computer-Uhren auch tun – die UTC (Coordinated Universal Time) wieder. In UTC besteht ein Tag aus genau 24 * 60 * 60 = 86400 Sekunden. UTC basiert auf einer Atomuhr und somit sind die erdbedingten Verschiebungen nicht berücksichtigt. Aus diesem Grund muss in regelmäßigen Abständen die Zeit korrigiert werden. So werden Schaltsekunden in bestimmten Jahresabständen am 31. Dezember oder 30. Juni eingeführt. Die letzte zusätzliche Schaltsekunde gab es im Jahr 1995, sodass dann die letzte Minute des Jahres 61 Sekunden dauerte. Kaum eine Computeruhr ist in der Lage, diese Sekunden zuzuzählen. Um UTC und UT einander anzugleichen, gibt es eine Zwischengröße UT1. Dies ist die um die Schalteinheiten korrigierte Größe UT. Eine sehr schöne und präzise Zusammenfassung findet der Leser unter http://www.maa.mhn.de/Scholar/dt_times.html.

Die folgende Tabelle ordnet einigen ausgewählten Ländern die jeweilige Zeitzone zu. Wir sehen, dass Deutschland dem Greenwich-Meridian um eine Stunde voraus ist. Da die GMT-Zeit keinen Wechsel zwischen Sommer- und Winterzeit kennt, ist mitunter im Sommer zu unserer Zeit eine weitere Stunde zu addieren.

Tabelle 10.1   Die Zeitverschiebungen gegenüber GMT
Gegend Relative Zeit
Midway Island, Samoa GMT -11:00
Hawaii GMT -10:00
Alaska GMT -08:00
Pacific Time (US & Canada), Tijuana GMT -09:00
Mountain Time (US & Canada) GMT -07:00
Central Time (US & Canada), Mexico City GMT -06:00
Eastern Time (US & Canada), Bogota, Lima GMT -05:00
Atlantic Time (Canada), Caraca, La Paz GMT -04:00
New Foundland GMT -03:30
Brasila, Buenos Aires, Georgetown GMT -03:00
Mid-Atlantic GMT -02:00
Azores GMT -01:00
Greenwich Mean Time, Monrovia, Casablanca GMT -00:00
Central European Time, Central Africa GMT +01:00
Athens, Helsinki, Istanbul, Eastern Europe, Cairo, Harare, Pretoria GMT +02:00
Moscow, St. Petersburg, Baghdad, Kuwait, Nairobi, Riyadh GMT +03:00
Abu Dhabi, Muscat, Volgograd GMT +04:00
Karachi GMT +04:30
Islamabad, Tashkent GMT +05:00
Bombay, Calcutta, Colombo GMT +05:30
Almaty, Dhaka GMT +06:00
Bankok, Jakarta, Hanoi GMT +07:00
Hong Kong, Singapore, Taipei, Beijing GMT +08:00
Tokyo, Seoul GMT +09:00
Darwin GMT +09:30
Guam, Hobart, Port Moresby, Vladivostok, Brisbane, Melbourne, Sydney GMT +10:00
Magadan, Solomon Is., New Caledonia GMT +11:00
Wellington, Auckland, Fiji GMT +12:00


Galileo Computing

10.2 Wichtige Datum-Klassen im Überblick  downtop

Da Datumsberechnungen verschlungene Gebilde sind, können wir den Entwicklern von Java dankbar sein, uns drei Klassen zur Datumsberechnung beigelegt zu haben. Diese Klassen wurden von den Entwicklern so abstrakt gestaltet, dass lokale Besonderheiten wie Ausgabeformatierung, Zeitzonen, Schaltjahre und Schaltsekunden unter verschiedenen Kalendern und Zeitmessungssystemen möglich sind.

Bis zur Java-Version 1.1 stand zur Darstellung und Manipulation von Datumswerten nur die Klasse Date zur Verfügung. Diese hatte drei Aufgaben:

gp  Verwaltung von Datum-Objekten mit der Genauigkeit von Millisekunden
gp  Erzeugung eines Datum-Objekts aus Jahr, Monat, Tag, Minute und Sekunde
gp  Ausgabe und Verarbeitung von Datum-Zeichenketten

Da die Date-Klasse nicht ganz fehlerfrei und auch nicht internationalisiert war, wurden zwei neue Klassen, DateFormat und Calendar eingeführt. Calendar nimmt sich nun der Aufgabe von Date an, zwischen verschiedenen Datumsrepräsentationen und Zeitskalen zu konvertieren; DateFormat wird nun benutzt, um Datum-Zeichenketten zu zerlegen und für die Ausgabe zu formatieren. Datum-Formate sind ebenfalls abhängig vom Land, das in Java durch Locale-Objekte dargestellt wird, und von einer Zeitzone, die durch die Exemplare der Klasse TimeZone repräsentiert ist.


Galileo Computing

10.2.1 Zeitzonen durch die Klasse TimeZone repräsentiert  downtop

Ein Exemplar der Klasse TimeZone repräsentiert eine Zeitzone inklusive Zeitverschiebung. Mit der statischen Methode getDefault() lässt sich immer ein Zeitzonen-Objekt passend zu den lokalen Gegebenheiten ermitteln. So weit die Theorie! Eigentlich sollte getDefault() immer die Zeitzone liefern, in der sich das Programm befindet, doch leider kommt es immer wieder zu Fehlern. So sollte das Ergebnis für Deutschland immer ein TimeZone-Exemplar für die ECT sein, doch das ist nicht immer so. Aufgrund dieser Unzulänglichkeit bleibt uns nichts anderes übrig, als ECT fest an die Datum-Klasse des Objekts zu binden.

Beispiel Nutzen wir für die Zeitrepräsentation Calendar-Objekte, so können wir mit setTimeZone() eine Zone fest zuweisen.
calendar.setTimeZone( 
TimeZone.getTimeZone("ECT") );

abstract class java.util.TimeZone
implements Serializable, Cloneable

gp  static TimeZone getDefault()
Gibt die Zeitzone für die aktuelle (geographische) Umgebung zurück.
gp  static String[] getAvailableIDs()
Liefert alle der Bibliothek bekannten Abkürzungen für Zeitzonen.
gp  static TimeZone getTimeZone( String ID )
Liefert die Zeitzone für eine gegebene Abkürzung.
Beispiel Um eine Aufzählung aller unterstützten Zeitzonen zu erhalten, geben wir alle Zeichenketten aus, die uns die statische Methode getAvailableIDs() liefert.

Listing 10.1   AllZones.java
import java.util.*;
public class AllZones
{
  public static void main( String args[] )
  {
    String s[] = TimeZone.getAvailableIDs();

Arrays.sort( s ); for ( int i = 0; i < s.length; i++ ) System.out.println( s[i] ); } }

Arrays.sort(String[]) ist die statische Methode einer speziellen Array-Klasse, die das übergebene Feld sortiert. Unter Windows mit dem SDK Version 1.4 liefert das Programm 327 Zeitzonen von

ACT
AET
AGT
ART
AST
Africa/Abidjan
Africa/Accra

bis

Pacific/Wake
Pacific/Wallis
SST
UTC
VST
WET

Die Unterklasse SimpleTimeZone

Eine spezielle Unterklasse von TimeZone ist SimpleTimeZone. Um ein Exemplar für die mitteleuropäische Zeit zu konfigurieren, initialisieren wir das Objekt mit der gewünschten Zeitverschiebung gegenüber der GMT. Zusätzlich wird ein Kürzel für die Zeitzone als String übergeben. Danach stellen wir noch passend die Regeln für Anfang und Ende der Sommerzeit ein. Der Wechsel erfolgt am letzten Sonntag im März bzw. Oktober jeweils um 2 Uhr nachts.

Beispiel Eine SimpleTimeZone für Deutschland
SimpleTimeZone mez = new SimpleTimeZone( +1*60*60*1000, 
"ECT" );
mez.setStartRule( Calendar.MARCH, -1, Calendar.SUNDAY,
  2*60*60*1000 );
mez.setEndRule( Calendar.OCTOBER, -1, Calendar.SUNDAY,
  2*60*60*1000 );

Wenn wir wiederum ein Calendar-Objekt mit der mitteleuropäischen Zeit als Grundlage versehen wollen, schreiben wir:

Calendar cal = GregorianCalendar.getInstance( mez 
);

Galileo Computing

10.3 Sprachen der Länder  downtop

Programme der ersten Generation konnten nur mit fest verdrahteten Landessprachen und landesüblichen Bezeichnungen umgehen. Daraus ergaben sich natürlich vielfältige Probleme. Mehrsprachige Programme mussten aufwendig programmiert werden, damit sie unter mehreren Sprachen liefen. Es ergaben sich bereits Probleme dadurch, dass unterschiedliche Zeichenkodierungen verwendet wurden. Dies wurde aber durch Unicode umgangen. Es bleibt das Problem, dass sprachabhängige Zeichenketten, wie alle anderen Zeichenketten auch, überall im Programmtext verteilt sind. Eine nachträgliche Sprachanpassung wird schwer. Java bietet hier eine Lösung an. Einmal durch die Definition einer Sprache und einmal durch die Möglichkeit, sprachenabhängige Teile in Ressourcen-Dateien auszulagern.


Galileo Computing

10.3.1 Sprachen in Java über Locale-Objekte  downtop

Eine Sprache (Locale) ist in Java eine Verbindung aus gesprochener Sprache und geografischer Region. Jede dieser sprachspezifischen Eigenschaften ist in einem speziellen Objekt gekapselt, das als Exemplar der Klasse Locale realisiert ist. Die Sprache und die Region müssen getrennt werden, denn nicht immer gibt eine Region oder ein Land die Sprache eindeutig vor. Für die Umgebung in dem französisch sprechenden Teil von Kanada ist etwa der Kalender von Quebec relevant, und der unterscheidet sich von einem englischen Kalender.

Beispiel Sprach-Objekte werden immer mit dem Namen der Sprache und optional mit dem Namen des Landes bzw. einer Region erzeugt. Im Konstruktor der Klasse Locale werden dann Länderabkürzungen angegeben. Etwa für ein Sprach-Objekt für England oder Frankreich.
Locale greatBritain = new Locale( "en", "GB" );
Locale french = new Locale( "fr" );

Im zweiten Beispiel ist uns das Land egal. Wir haben einfach nur die Sprache Französisch ausgewählt, egal auf welchem Teil der Welt. Die Sprachen sind durch Zwei-Buchstabenkürzel aus dem ISO-639 Code identifiziert und die Ländernamen sind Zwei-Buchstabenkürzel, die im ISO-3166 beschrieben sind.

final class java.util.Locale
implements Cloneable, Serializable

gp  Locale( String language )

Erzeugt ein neues Locale-Objekt für die Sprache language.

gp  Locale( String language, String country )

Erzeugt ein Locale-Objekt für language und das Land country.

gp  public Locale( String language, String country, String variant )

Erzeugt ein Locale-Objekt für language und das Land country in der Variante variant. variant ist eine herstellerabhängiges Angabe, etwa WIN für Windows, MAC für Macintosh und POSIX für POSIX.

Konstanten für die wichtigen Länder

Da die Codes nicht immer so intuitiv sind, besitzt die Locale-Klasse ein paar Konstanten für häufig auftretende Länder und Sprachen. Für ein Locale-Objekt für Great Britain kann anstelle von new Locale("en","GB") auch Locale.UK Verwendung finden. Unter den Konstanten sind: CANADA, CANADA_FRENCH, CHINA, CHINESE, ENGLISH, FRANCE, FRENCH, GERMAN, GERMANY, ITALIAN, ITALY, JAPAN, JAPANESE, KOREA, KOREAN, PRC, SIMPLIFIED_CHINESE, TAIWAN, TRADITIONAL_CHINESE, UK, US. Im Quellcode der Klasse Locale sind dazu einfach die entsprechenden Länderkürzel als Namen von Konstanten definiert.

static public final Locale FRANCE = new Locale("fr","FR","");
static public final Locale GERMANY = new Locale("de","DE","");

Sinnvolle Methoden

Locale-Objekte lassen sich fragen, wie ihre Sprache heißt, die sie repräsentieren. Dazu bietet das Locale-Objekt eine Reihe von Methoden an:

final class Locale
implements Cloneable, Serializable

gp  String getCountry()

Liefert das Länderkürzel nach dem ISO 3166 Zwei-Buchstaben-Code.

gp  String getLanguage()

Liefert das Kürzel der Sprache im ISO 639 Code.

gp  String getVariant()

Liefert das Kürzel der Variante.

gp  final String getDisplayCountry()

Liefert ein Kürzel des Landes für Bildschirmausgaben.

gp  final String getDisplayLanguage()

Liefert ein Kürzel der Sprache für Bildschirmausgaben.

gp  final String getDisplayName()

Liefert den Namen der Einstellungen.

gp  final String getDisplayVariant()

Liefert den Namen der Variante.

gp  String getISO3Country()

Liefert die ISO-Abkürzung des Landes dieser Einstellungen und löst eine ExMissingResourceException aus, wenn die ISO-Abkürzung nicht verfügbar ist.

gp  String getISO3Language()

Liefert die ISO-Abkürzung der Sprache dieser Einstellungen und löst eine MissingResourceException aus, wenn die ISO-Abkürzung nicht verfügbar ist.

Beispiel Die folgende Applikation gibt alle gefundenen Locale-Objekte aus. Wir erkennen an der Ausgabe auch die Unterschiede der Funktionen.

Listing 10.2   AllLocales.java
import java.util.*;

public class AllLocales
{
  public static void main( String args[] )
  {
    Locale locs[] = Locale.getAvailableLocales();

    for ( int i = 0; i < locs.length; i++ )
    {
      Locale l = locs[i];
      System.out.print( l.getCountry() + ", " );
      System.out.print( l.getLanguage() + ", " );
      System.out.print( l.getVariant() + ", " );

      System.out.print( l.getDisplayName() + ", " );
      System.out.print( l.getDisplayCountry() + ", " );
      System.out.print( l.getDisplayLanguage() + ", " );
      System.out.print( l.getDisplayName() + ", " );
      System.out.print( l.getDisplayVariant() + ", " );

      System.out.print( l.getISO3Country() + ", " );
      System.out.println( l.getISO3Language() );
    }
  }
}

Ein Ausschnitt der Ausgabe. Bemerkenswert ist, dass einige Angaben nicht gefüllt sind.

, en, , Englisch, , Englisch, Englisch, , , eng
US, en, , Englisch (Vereinigte Staaten von Amerika), Vereinigte Staaten 
von Amerika, Englisch, Englisch (Vereinigte Staaten von Amerika), , 
USA, eng
, ar, , Arabisch, , Arabisch, Arabisch, , , ara
AE, ar, , Arabisch (Vereinigte Arabische Emirate), Vereinigte Arabische 
Emirate, Arabisch, Arabisch (Vereinigte Arabische Emirate), , ARE, ara
BH, ar, , Arabisch (Bahrain), Bahrain, Arabisch, Arabisch (Bahrain), 
, BHR, ara
...
, zh, , Chinesisch, , Chinesisch, Chinesisch, , , zho
CN, zh, , Chinesisch (China), China, Chinesisch, Chinesisch (China), 
, CHN, zho
HK, zh, , Chinesisch (Hongkong), Hongkong, Chinesisch, Chinesisch (Hongkong), 
, HKG, zho
TW, zh, , Chinesisch (Taiwan), Taiwan, Chinesisch, Chinesisch (Taiwan), 
, TWN, zho

Galileo Computing

10.4 Einfache Übersetzung durch ResourceBundle-Objekte  downtop

Sollen Java-Programme sprachunabhängig gestaltet werden, müssen alle Zeichenketten einer Landessprache durch symbolische Namen, wie Variablen, ersetzt werden. Wenn das Programm später eine Zeichenkette nutzen will, greift es auf die Variable zu, und diese soll dann die entsprechende Übersetzung enthalten. Die Verbindung zwischen den symbolischen Namen und den landessprachlichen Texten erfolgt in Java mithilfe der Klasse ResourceBundle. Dazu speichert ein ResourceBundle alle Programm-relevanten Texte und Informationen für ein spezielles Land. In einer Datei werden die Variablennamen als Schlüssel und die landesabhängigen Zeichenketten als Werte gehalten. Der Aufbau eines ResourceBundle erinnert uns dabei stark an eine Property-Datei. Wir wollen daher mit dem Aufbau dieser Property-Datei beginnen und definieren eine Variable Hello. Da wir die Sprachen Englisch und Deutsch unterstützen wollen, legen wir zwei Dateien an. Die Dateinamen haben dabei einen speziellen Aufbau. Sie setzen sich aus dem Namen des ResourceBundle, einem _, einer Landeskennung und der Dateiendung properties zusammen. Da wir unser ResourceBundle »HelloWorld« nennen wollen und die Ländercodes für England en und für Deutschland de sind, ergeben sich die folgenden Dateien:

Listing 10.3   HelloWorld_en.properties
# HelloWorld_en.properties
Hello=Hello World.
Listing 10.4   HelloWorld_de.properties
# HelloWorld_de.properties
Hello=Hallo Welt.

Kommentare können mit einer Raute beginnen.

Als Nächstes kann das Programm folgen, welches die ResourceBundle-Dateien verwendet. Wir benötigen dazu eine Objekt der Klasse ResourceBundle, welches wir über eine Fabrik-Methode ResourceBundle.getBundle(name) bekommen. name ist dabei der Name des ResourceBundle. Die Methode wählt dabei automatisch auf Grund der eingestellten Landessprache die passende Datei. Mit einer Referenz auf das aktuelle ResourceBundle-Objekte können wir mit Hilfe der Methode getString() auf die landesspezifische Meldung über einem Schlüssel zugreifen.

Beispiel Bezüglich der beiden obigen Ressource-Dateien mit der Variablen Hello setzt sich das Programm einfach zusammen.

Listing 10.5   InternationalHelloWorld.java
import java.util.*;

//java -Duser.language=en InternationalHelloWorld
//Hello World.

public class InternationalHelloWorld
{
  public static void main( String args[] )
  {
    String baseName = "HelloWorld";

    try
    {
      ResourceBundle bundle = ResourceBundle.getBundle( 
baseName );
      System.out.println( bundle.getString("Hello") 
);
    }
    catch ( MissingResourceException e ) {
      System.err.println( e );
    }
  }
}

Die Dateinamen für die jeweiligen ResourceBundle-Objekte können sehr variabel zusammengesetzt werden. Die nachfolgenden Bildungsgesetze verwendet die Java-Bibliothek:

bundleName_localeLanguage_localeCountry_localeVariant
bundleName_localeLanguage_localeCountry
bundleName_localeLanguage
bundleName_defaultLanguage_defaultCountry_defaultVariant
bundleName_defaultLanguage_defaultCountry
bundleName_defaultLanguage
bundleName

Die Suchreihenfolge ist dabei wie angegeben. Es können auch mehrere Resource-Dateien angegeben werden, die dann der Reihe nach durchlaufen werden, sodass dadurch Werte überschrieben werden können.

Abbildung


Galileo Computing

10.5 Die Klasse Date  downtop


Galileo Computing

10.5.1 Die Date-Klasse  downtop

Die ältere Klasse Date ist durch die Aufgabenverteilung auf die Klassen DateFormat und Calendar sehr schlank. Ein Exemplar der Klasse Date verwaltet ein besonderes Datum oder eine bestimmte Zeit; die Zeitgenauigkeit beträgt eine Millisekunde.

Mit der Date-Klasse können keine Datum-Objekte für Zeitpunkte vor dem 1.1.1970 erzeugt werden. Zwei Konstruktoren der Klasse bleiben uns:

gp  Der Standard-Konstruktor, welcher ein Datum-Objekt mit der augenblicklichen Zeit erzeugt. Die gegenwärtige Zeit erfragt dieser Konstruktor mittels System.currentTimeMillis().
gp  Der zweite Konstruktor bekommt eine Zeitangabe in Millisekunden seit dem Stichtag 1.1.1970 und initialisiert daraufhin das Objekt. Mit verschiedenen Methoden kann nun die Zeit verändert, ausgelesen oder in der zeitlichen Abfolge verglichen werden.
class java.util.Date
implements Serializable, Cloneable, Comparable

gp  Date()
Erzeugt ein Datum-Objekt und initialisiert es mit der Zeit, die bei der Erzeugung gelesen wurde.
gp  Date( long )
Erzeugt ein Datum-Objekt und initialisiert es mit der übergebenen Anzahl von Millisekunden seit dem 1. Januar 1970, 00:00:00 GMT.
gp  long getTime()
Liefert die Anzahl der Millisekunden nach dem 1. Januar 1970, 00:00:00 GMT zurück.
gp  void setTime( long )
Setzt wie der Konstruktor die Anzahl Millisekunden des Datum-Objekts neu. Wurde ein Datum-Objekt einmal erzeugt, dann ändert sich dies durch das Objekt repräsentierte Datum natürlich nicht mehr automatisch. Um also die Zeit wieder aktuell zu setzen, kann today.setTime( new java.util.Date() ) verwendet werden.
gp  boolean before( Date )
boolean after( Date )
Testet, ob das eigene Datum vor oder nach dem Datum des Parameters liegt. true, wenn davor oder danach, false sonst.
gp  boolean equals( Object )
Testet die Datum-Objekte auf Gleichheit. true, wenn getTime() für beide den gleichen Wert ergibt und der Parameter nicht null ist.
gp  int compareTo( Date )
Vergleicht zwei Datum-Objekte und gibt null zurück, falls beide die gleiche Zeit repräsentieren. Der Rückgabewert ist kleiner Null, falls das Datum des aufrufenden Exemplars vor dem Datum von anotherDate ist, und größer Null sonst.
gp  int compareTo( Object )
Ist das übergebene Objekt vom Typ Date, dann verhält sich die Funktion wie compareTo(). Andernfalls wirft die Methode, falls der Parameter sich nicht als Date verhalten kann, eine ClassCastException.
gp  toString()
Gibt eine Repräsentation des Datums aus.
Beispiel Mit der toString()-Funktion können wir ein minimales Zeit-Anzeige-Programm schreiben. Wir rufen den Standard-Konstruktor auf und geben dann die Zeit aus. Natürlich wird von der println()-Funktion toString() aufgerufen.

Listing 10.6   MiniClock.java
import java.util.Date;

class MiniClock
{
  public static void main( String args[] )
  {
    System.out.println( new Date() );
  }
}

Galileo Computing

10.5.2 Zeitmessung und Profiling  downtop

Die Methode getTime() der Klasse Date gibt einen Zeitpunkt als vergangene Millisekunden ab einem festen Stichtag zurück. Damit kann die Differenz zweier Datum-Objekte auf Millisekunden-Basis errechnet werden und diese kann dann zur groben Abschätzung von Ausführungszeiten für Programme dienen.

Listing 10.7   Profiling.java
import java.util.Date;

final class Loop
{
 static int loop1( int i )
 {
  return i * i;
 }

 int loop2( int i )
 {
  return i * i;
 }

}

class Profiling
{
 public synchronized static void main( String args[] 
)
 {
  Loop loop = new Loop();

  int MAX = 100000000;

  // use static function

  Date start1 = new Date();

  for ( int i = MAX; i-- > 0; )
   Loop.loop1( i );

  long time1 = new Date().getTime() – start1.getTime();


  // use dynamic function

  Date start2 = new Date();

  for ( int i = MAX; i-- > 0; )
   loop.loop2( i );

  long time2 = new Date().getTime() – start1.getTime();


  System.out.println( "Used time static: " + time1 );

  System.out.println( "Used time dynamic: " + time2 );
 }
}

Wird das Programm gestartet, so bekomme ich auf meinem Rechner (AMD Athlon 1,2 GHz, Java SDK 1.4b2) die nachstehende Ausgabe.

Used time static: 641
Used time dynamic: 1232
Tipp Dieses Beispiel zeigt, an welchen Stellen ein Programmierer optimieren kann. Wenn es auf Geschwindigkeit ankommt, sollte der Einsatz von statischen Methoden in Erwägung gezogen werden und dynamische Bindungen vermieden werden. Leider macht das unter Umständen den Entwurf kaputt bzw. das Programm später schwerer änderbar.


Galileo Computing

10.6 Die abstrakte Klasse Calendar  downtop

Calendar ist eine abstrakte Klasse, die zur Konvertierung zwischen verschiedenen Datum-Formaten eingesetzt wird. Über Methoden von Calendar ist es möglich, Datum und Uhrzeit in den einzelnen Komponenten wie Jahr, Monat, Tag, Minute, Sekunde zu verändern. Da Calendar eine abstrakte Basisklasse ist, können erst Unterklassen die Attribute und Methoden richtig benutzen. Unter dem JDK ist bisher nur die Unterklasse GregorianC alendar implementiert, deren Exemplare Daten und Zeitpunkte gemäß dem gregorianischen Kalender verkörpern.

Die Klasse Calendar besitzt nur wenige statische Funktionen, die sofort genutzt werden können. Eine Klassen- und Fabrik-Methode ist getInstance(), um ein benutzbares Objekt zu bekommen. Die Methode gibt ein Objekt vom Typ GregorianCalendar zurück und die Werte sind mit der aktuellen Zeit gesetzt.

Abbildung

abstract class java.util.Calendar
implements Serializable, Cloneable

gp  static Calendar getInstance()
Liefert einen Calendar in der Ausprägung von GregorianCalendar mit der eingestellten Zeitzone und Lokalisierung zurück.

Neben der parameterlosen Variante von getInstance() bestehen drei weitere Varianten, denen ein TimeZone-Objekt und Locale-Objekt mit übergeben werden können. Damit kann dann der Kalender auf eine spezielle Zeitzone und einen Landstrich zugeschnitten werden. Da ein Kalender unter verschiedenen Orten installiert sein kann, gibt getAvailableLocales() eine Aufzählung von Locale-Objekten zurück.


Galileo Computing

10.7 Der gregorianische Kalender  downtop

Die abstrakte Klasse Calendar wird durch die Klasse GregorianCalendar implementiert. In der jetzigen Implementierung deckt diese Klasse vom Jahr 4716 vor unserer Zeitrechnung bis zum Jahre 5.000.000 alles korrekt ab. Sieben Konstruktoren stehen zur Verfügung, und vier schauen wir uns an:

class java.util.GregorianCalendar
extends Calendar

gp  GregorianCalendar()
Erzeugt ein standardmäßiges GregorianCalendar-Objekt mit der aktuellen Zeit in der voreingestellten Zeitzone und Lokalisierung.
gp  GregorianCalendar( int year, int month, int date )
Erzeugt ein GregorianCalendar-Objekt in der voreingestellten Zeitzone und Lokalisierung. Das Datum wird durch Jahr, Monat (der zwischen 0 und 11 und nicht zwischen 1 und 12 liegt) und Tag festgelegt.
gp  GregorianCalendar( int year, int month, int date, int hour, int minute )
Erzeugt ein GregorianCalendar-Objekt in der voreingestellten Zeitzone und Lokalisierung. Das Datum wird durch Jahr, Monat (0 <= month <= 11 ), Tag, Stunde und Minute festgelegt.
gp  GregorianCalendar( int year, int month, int date,
int hour, int minute, int second )
Erzeugt ein GregorianCalendar-Objekt in der voreingestellten Zeitzone und Lokalisierung. Das Datum wird durch Jahr, Monat (0 <= month <= 11), Tag, Stunde, Minute und Sekunde festgelegt.

Neben den hier aufgeführten Konstruktoren gibt es noch weitere, die es erlauben, die Zeitzone und Lokalisierung zu ändern. Standardmäßig eingestellt ist die lokale Zeitzone und die aktuelle Lokalisierung. Ist einer der Parameter im falschen Bereich, so wird eine IllegalArgumentException ausgelöst. Damit bei der 0 und 1 Anfrageprobleme vermieden werden, sollten die Konstanten JANUARY (0), FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER (11) verwendet werden. Die spezielle Variable UNDECIMBER (12) steht für den dreizehnten Monat, der etwa bei einem Mondkalender anzutreffen ist.

Probleme beim Standard-Konstruktor

Leider haben wir mit dem Standard-Konstruktor von GregorianCalendar ein kleines Problem. Zur Klärung schauen wir uns die Implementierung einmal genauer an:

public GregorianCalendar()
{
  this( TimeZone.getDefault(), Locale.getDefault() );
}

Wird der gregorianische Kalender ohne Zeitzonenangabe konstruiert, wird die statische Methode getDefault() von der Klasse TimeZone aufgerufen. In der Implementierung von getDefault() finden wir dann Folgendes:

ID = System.getProperty( "user.timezone", "GMT" );

In den System-Eigenschaften wird also nach user.timezone gefragt und falls dieser Eintrag dann nicht existiert, wird die Zeitzone GMT angenommen. Da diese Einstellung bei den meisten Rechnern nicht auf (European Central Time, kurz ECT) steht – wie es bei uns in Deutschland sein sollte – gehen alle Zeitangaben eine Stunde nach. Um dieses Problem zu beheben, sollte die korrekte Zeitzone mit dem Konstruktor GregorianCalendar(TimeZone) oder mit der Methode setTimeZone()aus der Klasse Calendar eingestellt werden.

calendar = new GregorianCalendar();
calendar.setTimeZone( TimeZone.getTimeZone("ECT") );
Hinweis Natürlich verfehlt dies total seinen Zweck, da wir die Zeitzone nicht von Hand setzen sollten. Aber leider funktioniert es ohne explizites Setzen auf einigen Computern nicht.

Abfragen und Setzen von Datumselementen

Das Abfragen und Setzen von Elementen des Datums vom gregorianischen Kalender erfolgt mit den überladenen Methoden set() und get(). Beide erwarten als ersten Parameter einen Feldbezeichner – eine Konstante aus der Klasse Calendar –, der angibt, auf welches Datum- bzw. Zeit-Feld zugegriffen werden soll. Die get-Methode liefert den Inhalt des angegebenen Feldes und set schreibt den als zweiten Parameter übergebenen Wert in das Feld. Die nachfolgende Tabelle gibt eine Übersicht der Feldbezeichner und ihrer Wertbereiche.

abstract class java.util.Calendar
implements Serializable, Cloneable

Tabelle 10.2   Konstanten aus der Klasse Calendar
Feldbezeichner
Calendar.*
Minimalwert Maximalwert Erklärung

ERA

0 (BC) 1 (AD) Datum vor oder nach Chr.

YEAR

1 5000000 Jahr

MONTH

0 11 MonatMonat (nicht von 1-12!)
DAY_OF_MONTH alternativ DATE 1 31 Tag

WEEK_OF_YEAR

1 54 Woche

WEEK_OF_MONTH

1 6 Woche des Monats

DAY_OF_YEAR

1 366 Tag des Jahres

DAY_OF_WEEK

1 7 Tag der Woche
(1= Sonntag,
7 = Samstag)

DAY_OF_WEEK_IN_MONTH

-1 6 Tag der Woche im Monat

HOUR

0 12 Stunde von 12

HOUR_OF_DAY

0 23 Stunde von 24

MINUTE

0 59 Minute

SECOND

0 59 Sekunden

MILLISECOND

0 999 Millisekunden

AM_PM

0 1 vor 12, nach 12

ZONE_OFFSET

-12*60*60*1000 12*60*60*1000 Zeitzonenabweichung in Millisekunden

DST_OFFSET

0 1*60*60*1000 Sommerzeitabweichung in Millisekunden

Nun können wir mit den Varianten von set() die Felder setzen und mit get() wieder reinholen. Beachtenswert ist der Anfang der Monate mit 0 und der Anfang der Wochentage mit 1 (SUNDAY), 2 (MONDAY), ..., 7 (SATURDAY) -Konstanten der Klasse Calendar stehen in Klammern.

abstract class java.util.Calendar
implements Serializable, Cloneable

gp  final void set( int field, int value )
Setzt das Feld field mit dem Wert value.
gp  final void set( int year, int month, int date )
Setzt die Werte für Jahr, Monat und Tag.
gp  final void set( int year, int month, int date, int hour,
                int minute )
Setzt die Werte für Jahr, Monat, Tag, Stunde und Minute.
gp  final void set( int year, int month, int date, int hour,
                int minute, int second)
Setzt die Werte für Jahr, Monat, Tag, Stunde und Minute und Sekunde.
gp  final int get( int field )
Liefert den Wert für field.

Die Methoden zum gleichzeitigen Setzen von Jahr, Monat und Tag usw. sind nur Hilfsfunktionen und ihre Implementierung ist einfach:

public final void set(int year, int month, int date)
{
  set(YEAR, year);
  set(MONTH, month);
  set(DATE, date);
}

Leider ist es mit einem einfachen set() zum Setzen der Feldwerte nicht getan.

Ein weiteres Problem betrifft die Konsistenz zwischen den verschiedenen internen Datumsdarstellungen eines Calendar-Exemplars. Hier müssen wir etwas tricksen und durch den Aufruf von setTime(getTime()) die Darstellung in Millisekunden und die verschiedenen anderen Feldwerte abgleichen. Bevor wir also mit get() die Werte wieder abfragen, schreiben wir die Zeile:

calendar.setTime( calendar.getTime() );

Ein gregorianischer Kalender mit eigenen Werten

Wir wollen im folgenden Beispiel ein GregorianCalendar-Objekt erzeugen und mit einer selbst geschriebenen Funktion printCalendar() wichtige Felder ausgeben. Da die Daten nicht bearbeitet werden, handelt es sich um das aktuelle Tagesdatum. Anschließend manipulieren wir mit den set()-Methoden das Objekt und setzen es auf das Geburtsdatum des Autors.

Listing 10.8   DateDemo.java
import java.util.*;

public class DateDemo
{
  public static void main( String args[] )
  {
    GregorianCalendar cal = new GregorianCalendar();
    cal.setTimeZone(TimeZone.getTimeZone("ECT"));
    cal.setTime(cal.getTime());

    printCalendar( cal );

    cal.set( Calendar.DATE, 12 );
    cal.set( Calendar.MONTH, Calendar.MARCH );
    cal.set( Calendar.YEAR, 1973 );

    printCalendar( cal );
  }

  public static void printCalendar( Calendar cal )
  {
    String dayOfWeek = (new String[]{ "Sonntag", "Montag",
      "Dienstag", "Mittwoch", "Donnertag", "Freitag", "Samstag"})
      [cal.get(Calendar.DAY_OF_WEEK)-1];  // Sonntag = 1

    System.out.println( dayOfWeek + ", " +
                        cal.get(Calendar.DATE) + "." +
                        (cal.get(Calendar.MONTH)+1) + "." +
                        cal.get(Calendar.YEAR) + ", " +
                        cal.get(Calendar.HOUR_OF_DAY) + ":" +
                        out(cal.get(Calendar.MINUTE)) + ":" +
                        out(cal.get(Calendar.SECOND)) + " und " +
                        cal.get(Calendar.MILLISECOND) + " ms" );

    System.out.print( "Es ist die "+
                      cal.get(Calendar.WEEK_OF_YEAR) +
                      ". Woche im Jahr und " );

    System.out.println( cal.get(Calendar.WEEK_OF_MONTH) +
                        ". Woche im Monat\n" );
  }

  public static String out( int i ) {
    return ( i >= 10 ) ? Integer.toString(i) : "0"+i;
  }
}

Die Ausgabe des Programms lautet etwa so:

Dienstag, 19.6.2001, 21:52:13 und 816 ms
Es ist die 25. Woche im Jahr und 3. Woche im Monat

Montag, 12.3.1973, 21:52:13 und 816 ms
Es ist die 11. Woche im Jahr und 3. Woche im Monat

Da die Ausgabe auf diese Art und Weise nicht besonders komfortabel ist, werden wir mit DateFormat eine Klasse kennen lernen, die die Formatierung der Ausgabe vereinfacht.

Wie viele Tage hat der Monat?

Diese Frage lässt sich einfach mit getActualMaximum() herausfinden. Als Parameter wird der Methode ein Feldbezeichner aus der Klasse Calendar übermittelt, dessen Maximum sie dann bestimmt.

Das folgende Programm listet alle Monate des gregorianischen Kalenders auf. Da dieser genau 12 Monate umfasst, können wir eine feste Schleife programmieren. Für einen Mondkalender sähe dies etwas anders aus. Den Beweis, dass das Programm korrekt funktioniert, sollten wir mit einem Abzählen der Fingerknochen führen.

Listing 10.9   Fingerknochen.java
import java.util.*;

class Fingerknochen
{
  public static void main( String args[] )
  {
    GregorianCalendar cal = new GregorianCalendar();

    System.out.println( cal.getTime() );

    for ( int month=Calendar.JANUARY;
          month<=Calendar.DECEMBER;
          month++ )
    {
      cal.set( Calendar.MONTH, month );

      System.out.println( (month+1) + " : " +
        cal.getActualMaximum(Calendar.DAY_OF_MONTH) 
);
    }
  }
}

Galileo Computing

10.8 Formatieren der Datumsangaben  downtop

Nachdem wir nun eine Unterklasse von Calendar, nämlich GregorianCalendar, dazu benutzt haben, Datumswerte zu verwalten, wollen wir nun untersuchen, wie eine Formatierungsklasse dazu landestypische Ausgaben erzeugt.


Galileo Computing

10.8.1 Mit DateFormat und SimpleDateFormat formatieren  downtop

Bevor wir mit den Methoden der Calendar- bzw. GregorianCalendar-Klasse weitermachen, wollen wir eine neue Klasse kennen lernen, die die Ausgabe und das Einlesen der Datum-Felder übernimmt. Es handelt sich dabei um die Klasse DateFormat. Da