Darstellung und Manipulation von Datum- und Zeit-Angaben mit Java

Bei der Durchsicht der Java Datum Klassen beschleicht mich immer das Gefühl, die Entwickler dieser Klassen hatten den Ehrgeiz, eine Software zu entwickeln, die auch dann noch korrekte Angaben liefert, wenn sie auf dem Raumschiff Enterprise Bordcomputer im Moment einer katapultartigen Wurmloch-Passage ausgeführt wird.

Aber: alles halb so schlimm, denn zu 95 Prozent hat man es mit Formatierungsroutinen zu tun, entweder um ein Datum als String abzuspeichern, oder von einem abgespeicherten String einzulesen. Und hierfür gibt es eine einfache Klasse, die treffender Weise SimpleDateFormat heißt. Bei Verwendung dieser Klasse ist für jede spezifische Formatierung eine eigene Instanz unter Vorgabe des entsprechenden Musters zu erzeugen:

import java.util.*;
import java.text.*;

public class DateExample1 
{ private static SimpleDateFormat sdFormatter1 = new SimpleDateFormat("yyyy-MM-dd");
  private static SimpleDateFormat sdFormatter2 = new SimpleDateFormat("dd.MM.yyyy");
  
  public static void main(String[] args) throws ParseException
  { Date toDay = new Date();
    System.out.println(sdFormatter1.format(toDay));
    Date date = sdFormatter2.parse("29.02.2000");
    System.out.println(sdFormatter1.format(date));
  }
}
C:\temp>java DateExample1
2009-02-03
2000-02-29

Ebenso gut lässt sich die Klasse SimpleDateFormat verwenden, um Datum-Strings, die nicht in einer unmittelbar sortierbaren Form vorliegen, zu vergleichen. Dazu werden die Strings in Objekte des Typs Date konvertiert, die dann verglichen werden können:

Date date1 = sdFormatter.parse(dateString1);
Date date2 = sdFormatter.parse(dateString2);
return date1.compareTo(date2);

wobei als Ergebnis dieses Vergleichs
-1 wenn date1 < date2
 0 wenn date1 = date2
 1 wenn date1 > date2
geliefert wird.

Und um dieses Beispiel zu erweitern, können wir eine Sortierung implementieren, indem wir eine Comparator Klasse programmieren, in der diese Datum-Strings entsprechend aufbereitet werden:

import java.util.*;
import java.text.*;

public class DateExample2 
{ static SimpleDateFormat sdFormatter = new SimpleDateFormat("dd.MM.yyyy");
  static String[] dateStrings = { "25.03.2006", "12.07.2005", "18.12.2007" };
  
  public static void main(String[] args)
  { Arrays.sort(dateStrings, new DateStringComparator());
    for (String dateString: dateStrings)
    { System.out.println(dateString);
    }
  }
  
  static class DateStringComparator implements Comparator<String>
  { public int compare(String dateString1, String dateString2)
    { Date date1, date2;
      try
      { date1 = sdFormatter.parse(dateString1);
        date2 = sdFormatter.parse(dateString2);
      } catch (ParseException ex) { throw new RuntimeException(ex); }
      return date1.compareTo(date2);
    }
  }
}
C:\temp>java DateExample2
12.07.2005
25.03.2006
18.12.2007

Zugegebenermaßen ist das letzte Beispiel nicht sehr glücklich gewählt, da es bei der Sortierung von vielen Datum-Strings Unmengen an Ressourcen verbraucht, weil jedes Mal die Strings neu geparst werden müssen. Sinnvoller wäre hier sicherlich, die Datum-Strings in eine direkt vergleichbare Form (z.B. JJJJMMTT) umzuwandeln:

  ...
  static class DateStringComparator implements Comparator<String>
  { public int compare(String dateString1, String dateString2)
    { StringBuffer sb1 = new StringBuffer(dateString1.substring(6))
       .append(dateString1.substring(3, 5)).append(dateString1.substring(0, 2));
      StringBuffer sb2 = new StringBuffer(dateString2.substring(6))
       .append(dateString2.substring(3, 5)).append(dateString2.substring(0, 2));
      return sb1.toString().compareTo(sb2.toString());
    }
  }
  ...

Mit Datum-Angaben rechnen

Instanzen der Klasse Date selbst bieten keine Methoden an, mit denen man beispielsweise die Differenz in Anzahl Tagen zueinander berechnen könnte. Allerdings repräsentiert ein Date-Objekt die Anzahl Millisekunden, die seit dem 01.01.1970 0:00 Uhr vergangen sind, und mit diesen Anzahl Millisekunden können dann Berechnungen vorgenommen werden, indem man die Anzahl Millisekunden eines Tages als Konstante für Rechenoperationen heranzieht:

import java.util.*;
import java.text.*;

public class DateExample3 
{ public static void main(String[] args) throws ParseException
  { SimpleDateFormat sdFormatter = new SimpleDateFormat("dd.MM.yyyy");
    long dayMillis = 24 * 60 * 60 * 1000;
    Date date1 = sdFormatter.parse("15.01.2008");
    Date date2 = new Date(date1.getTime() + (30 * dayMillis));
    System.out.println(
      sdFormatter.format(date1) + " + 30 Tage = " + sdFormatter.format(date2));
    date1 = new Date(date2.getTime() - (60 * dayMillis));
    System.out.println(
      sdFormatter.format(date2) + " - 60 Tage = " + sdFormatter.format(date1));
  }
}
C:\temp>java DateExample3
15.01.2008 + 30 Tage = 14.02.2008
14.02.2008 - 60 Tage = 16.12.2007

Java Swing - JFormattedTextField

Falls Sie vorhaben, ein Datum über eine grafische Oberfläche erfassen zu lassen, bietet sich die Verwendung eines JFormattedTextField direkt an. Denn dieser GUI Komponente kann zum einen als unterliegendes Datenmodell eine SimpleDateFormat Instanz mit entsprechendem Eingabemuster mitgegeben werden und zum anderen sorgt das JFormattedTextField auch dafür, dass Eingaben nur basierend auf diesem Muster zulässig sind:

import java.awt.*;
import java.awt.event.*;
import java.text.*;
import java.util.*;
import javax.swing.*;

class DateExample4GUI extends JFrame
{ SimpleDateFormat sdF1 = new SimpleDateFormat("dd.MM.yyyy");
  SimpleDateFormat sdF2 = new SimpleDateFormat("yyyy-MM-dd");
  JFormattedTextField dateFTF;
  
  public DateExample4GUI()
  { getContentPane().setLayout(new BorderLayout());
    JLabel dateLB = new JLabel();
    dateLB.setText("Datum:");
    getContentPane().add(dateLB, BorderLayout.WEST);
    dateFTF = new JFormattedTextField(sdF1);
    dateFTF.setValue(new Date());
    getContentPane().add(dateFTF, BorderLayout.EAST);
    JButton saveBTN = new JButton();
    saveBTN.setText("Save");
    getContentPane().add(saveBTN, BorderLayout.SOUTH);
    saveBTN.addActionListener(new ActionListener() 
    { public void actionPerformed(ActionEvent e)
      { JOptionPane.showMessageDialog(null, sdF2.format(dateFTF.getValue()));
      }
    });
  }
}

public class DateExample4 extends JApplet
{ JFrame guiFrame = new DateExample4GUI();

  public void init()
  { setContentPane(guiFrame.getContentPane());
  }

  public static void main(String[] args)
  { DateExample4 ex4 = new DateExample4();
    ex4.guiFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    ex4.guiFrame.pack();
    ex4.guiFrame.setVisible(true);
  }
}

Java GregorianCalendar

Abschließend soll noch anhand eines selbsterklärenden Beispiels die Java GregorianCalendar Klasse vorgestellt werden, die einige nützliche Informationen wie z.B. den Wochentag oder die Jahreswoche, etc. zu einem Datum liefern kann:

import java.util.*;
import java.text.*;

public class DateExample5 
{ 
  public static void main(String[] args) throws ParseException
  { SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");
    String birthDay = "17.01.1978";
    String[] weekDays = 
    { "Sonntag", "Montag", "Dienstag", 
      "Mittwoch", "Donnerstag", "Freitag", "Samstag"
    };
    Calendar cal = new GregorianCalendar(2008, Calendar.DECEMBER, 25);
    log("Weihnachten 2008");
    log("Wochen Tag  : " + weekDays[cal.get(Calendar.DAY_OF_WEEK) - 1]);
    log("Jahres Woche: " + cal.get(Calendar.WEEK_OF_YEAR));
    log("Monats Woche: " + cal.get(Calendar.WEEK_OF_MONTH));
    cal.setTime(sdf.parse(birthDay));
    log("Geburtstag " + birthDay);
    log("Wochen Tag  : " + weekDays[cal.get(Calendar.DAY_OF_WEEK) - 1]);
    log("Jahres Woche: " + cal.get(Calendar.WEEK_OF_YEAR));
    log("Monats Woche: " + cal.get(Calendar.WEEK_OF_MONTH));
  }

  static void log(String logString)
  { System.out.println(logString);
  }
}
C:\temp>java DateExample5
Weihnachten 2008
Wochen Tag  : Donnerstag
Jahres Woche: 52
Monats Woche: 4
Geburtstag 17.01.1978
Wochen Tag  : Dienstag
Jahres Woche: 3
Monats Woche: 3