Inselraus: Zeitdauern und der XML-Datentyp Duration

Eine der Schwachstellen in der Datumsverarbeitung ist das Fehlen eines Typs für Dauern (wenn wir von TimeUnit einmal absehen). Ein eigenständiger Typ bringt Vorteile, wenn es zum Beispiel darum geht, Dauern zu addieren (»Was ergibt 1 Woche plus 2 Monate?«) oder zu vergleichen (»Ist 1 Stunde mehr als 123456789 Millisekunden?«). Zwar lassen sich mit der add(…)-Methode von Calendar einzelne Segmente ändern, und dadurch lässt sich ein früherer oder späterer Zeitpunkt ansteuern, aber das ist wenig objektorientiert. Besser ist ein eigener Datentyp, der auch Operationen anbietet, um die Dauer auf ein Calendar- oder Date-Objekt zu setzen.

DatatypeFactory als Fabrik

Im Rahmen der W3C-XML-Schema-Unterstützung gibt es Klassen, unter anderem den Typ Duration für Dauern nach der gregorianischen Zeit. Die Klasse liegt jedoch nicht im java.util-Paket, sondern wegen ihres XML-Bezugs im Paket javax.xml.datatype (wo es neben XMLGregorianCalendar nahezu alleine liegt). Exemplare von Duration werden auch von keinem Konstruktor angelegt, sondern von einer Fabrikklasse DatatypeFactory. Beispiel: Lege ein Duration-Objekt mit der Dauer von 1 Tag und 2 Stunden an:

Duration d = DatatypeFactory.newInstance().newDuration(true, 0, 0, 1, 2, 0, 0 );
 System.out.println( d );  // P0Y0M1DT1H0M0S

Die DatatypeFactory bietet diverse Fabrikmethoden zum Anlegen der Duration-Objekte. Die Parameterlisten sind wie im Beispiel mitunter recht lang – in der längsten Variante gibt es einen Indikator für ein Vorzeichen, Jahr, Monat, Tag, Stunden, Minuten, Sekunden (keine Millisekunden oder genauer), also mit sieben Parametern. Ein Builder-Pattern zum Aufbau der Objekte wäre nett gewesen … abstract class javax.xml.datatype.DatatypeFactory

  • staticDatatypeFactorynewInstance()throwsDatatypeConfigurationException
  • DurationnewDuration(booleanisPositive,intyears,intmonths,intdays,inthours, intminutes,intseconds)
  • abstractDurationnewDuration(booleanisPositive,BigIntegeryears,BigIntegermonths, BigIntegerdays,BigIntegerhours,BigIntegerminutes,BigDecimalseconds)
  • abstractDurationnewDuration(longdurationInMilliSeconds)
  • abstractDurationnewDuration(StringlexicalRepresentation)
  • DurationnewDurationDayTime(booleanisPositive,BigIntegerday,BigIntegerhour,BigInteger   minute,BigIntegersecond)
  • DurationnewDurationDayTime(booleanisPositive,intday,inthour,intminute,intsecond)
  • DurationnewDurationDayTime(longdurationInMilliseconds)
  • DurationnewDurationDayTime(StringlexicalRepresentation)
  • DurationnewDurationYearMonth(booleanisPositive,BigIntegeryear,BigIntegermonth)
  • DurationnewDurationYearMonth(booleanisPositive,intyear,intmonth)
  • DurationnewDurationYearMonth(longdurationInMilliseconds)
  • DurationnewDurationYearMonth(StringlexicalRepresentation)

Die Duration-Klasse und ihre Methoden

Die Duration-Ausgabe im Beispiel über toString() liefert eine besondere String-Repräsentation, die für XML-Dokumente interessant ist, aber andere Methoden sind interessanter. Eine grobe Einteilung ergibt:

  • Anfragemethoden für die Segmente wie getYear(), getDay(), …
  • Vergleichsmethoden wie compare(Duration) oder isLongerThan(Duration)
  • Duration-Objekte sind immutable, doch gibt es Methoden wie add(Duration) oder multiply(Duration), die neue Duration-Objekte mit veränderten Segmenten zurückgeben, oder die Methode normalizeWith(Calendar), die Calendar-Felder zur Initialisierung nutzt.
  • Anwenden der Duration-Objekte auf Calendar oder Date mit addTo(Calendar)/ addTo(Date).

Beispiel: Addiere die Dauer von 2 Monaten und 3 Tagen, und berechne, wo wir dann relativ zu heute stehen:

DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
 Duration d1 = datatypeFactory.newDurationYearMonth( true, 0, 2 );
 Duration d2 = datatypeFactory.newDuration( true, 0, 0, 3, 0, 0, 0 );
 Duration sum = d1.add( d2 );
 Date date = new Date();
 System.out.printf( "%tF%n", date ); // 2011-06-21
 sum.addTo( date );
 System.out.printf( "%tF%n", date ); // 2011-08-24

Mögliche und unmögliche Operationen

Intern speichert die Duration-Implementierung jedes einzelne Segment und legt es nicht zu einer Zahl, etwa Sekunden zusammen. Das wäre auch nicht möglich, da die Anzahl Tage im Monat und im Jahr nicht immer gleich sind (dass zeigen uns der Februar und Schaltjahre). Wenn wir auf dem 1.1. einen Monat addieren, wollen wir beim 1.2. auskommen, und wenn wir bei 1.2. beginnen und einen Monat addieren, soll der 1.3. das Ergebnis sein. Beispiel: Additionen eines Monats auf einen Kalender:

DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
 Duration month = datatypeFactory.newDurationYearMonth( true, 0, 1 );
 Calendar cal = new GregorianCalendar( 2012, Calendar.JANUARY, 1 );
 month.addTo( cal );
 System.out.printf( "%tF%n", cal); // 2012-02-01
 month.addTo( cal );
 System.out.printf( "%tF%n", cal); // 2012-03-01

Da die Anzahl der Tage im Monat und im Jahr beweglich ist, sind bei add(…) und substract(…) nur gewisse Kombinationen möglich. Es gibt Operationen, die Duration nicht ausführen kann und mit einer IllegalStateException bestraft. Während innerhalb der Gruppe Sekunden, Minuten, Stunden und Tage beliebig addiert und subtrahiert werden können, ist der Übergang nach Monat und Jahr problematisch, insbesondere bei Subtraktionen. Beispiel: 1 Monat minus 1 Tag ist genauso wenig möglich wie 1 Jahr minus 1 Tag:

DatatypeFactory factory = DatatypeFactory.newInstance();
 Duration year  = factory.newDuration( true, 1, 0, 0, 0, 0, 0 );
 Duration month = factory.newDuration( true, 0, 1, 0, 0, 0, 0 );
 Duration day   = factory.newDuration( true, 0, 0, 1, 0, 0, 0 );
 year.subtract( day );  // N  IllegalStateException
 month.subtract( day ); // N  IllegalStateException

Duration-Vergleiche

Beim Vergleichen zweier Dauern gibt es vier unterschiedliche Ergebnisse, und daher implementiert Duration auch nicht die bekannte Comparable-Schnittstelle. Der Grund ist, dass einige Vergleiche nicht endscheidbar sind. So sind 30 Tage sind nicht automatisch 1 Monat, 365 Tage nicht automatisch 1 Jahr. Die compare(…)-Methode liefert Ganzahlen, die den jeweiligen Ausgang dokumentieren:

Vergleichsergebnis Beispiel Konstante
Dauer1 ist kürzer als Dauer2. 1 Minute ist kürzer als 100 Sekunden DatatypeConstants.LESSER
Dauer1 ist länger als Dauer2. 1 Tag ist länger als 1 Minute DatatypeConstants.GREATER
Dauer1 ist gleichlang Dauer2. 1 Minute ist gleich 60 Sekunden DatatypeConstants.EQUAL
Dauer1 ist unvergleichbar mit Dauer2. 30 Tage sind nicht automatisch 1 Monat DatatypeConstants.INDETERMINATE

Ausgang von Vergleichen zwischen Duration-Objekten Beispiel: Vergleiche 30 Tage mit 1 Monat:

DatatypeFactory factory = DatatypeFactory.newInstance();
 Duration month       = factory.newDurationYearMonth( true, 0, 1 );
 Duration thirtyDays  = factory.newDuration( true, 0, 0, 30, 0, 0, 0 );
 System.out.println( month.compare( thirtyDays ) ==
                     DatatypeConstants.INDETERMINATE );   // true
 System.out.println( month.isLongerThan( thirtyDays ) );  // false
 System.out.println( thirtyDays.isLongerThan( month ) );  // false

Wir sprechen bei Duration daher auch nur von einer partiellen Ordnung statt von einer vollständigen Ordnung.

Zusammenfassung der Duration-Methoden

abstract class javax.xml.datatype.DatatypeFactory

  • abstractintgetSign()
  • intgetYears()
  • intgetMonths()
  • intgetDays()
  • intgetHours()
  • intgetMinutes()
  • intgetSeconds()
  • abstractDurationadd(Durationrhs)
  • Durationsubtract(Durationrhs)
  • Durationmultiply(intfactor)
  • abstractDurationmultiply(BigDecimalfactor)
  • abstractDurationnegate()
  • abstractintcompare(Durationduration)
  • booleanisLongerThan(Durationduration)
  • booleanisShorterThan(Durationduration)
  • abstractvoidaddTo(Calendarcalendar)
  • voidaddTo(Datedate)
  • abstractDurationnormalizeWith(CalendarstartTimeInstant)
  • longgetTimeInMillis(CalendarstartInstant)
  • longgetTimeInMillis(DatestartInstant)
  • abstractbooleanisSet(DatatypeConstants.Fieldfield)
  • abstractNumbergetField(DatatypeConstants.Fieldfield)
  • QNamegetXMLSchemaType()
  • abstractinthashCode()
  • booleanequals(Objectduration)
  • StringtoString()

Seit dem in Java 8 die Java Date & Time API eingezogen ist, muss man nicht mehr zu diesen Typen greifen, es sei denn, mann arbeitet direkt mit der XML-API.

Über Christian Ullenboom

Ich bin Christian Ullenboom und Autor der Bücher ›Java ist auch eine Insel. Einführung, Ausbildung, Praxis‹ und ›Java SE 8 Standard-Bibliothek. Das Handbuch für Java-Entwickler‹. Seit 1997 berate ich Unternehmen im Einsatz von Java. Sun ernannte mich 2005 zum ›Java-Champion‹.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.