Berechnung der Zeit funktioniert nicht wie erwartet: Date, GregorianCalendar

Zrebna

Bekanntes Mitglied
Hi!

Die Ausgangslage ist, dass ich einen Time_stamp aus einem JSON in ein XML schreiben will.

JSON:
JSON:
"VALID_FROM": "2023-05-15T06:10:04.4200000Z"

Im Json ist die UTC-Zeit (z.B. England) gegeben, daher hole ich mir erstmal das Date-Objekt.
An bestimmten Stelle nicht wundern, denn SimpleDateFormat kann nicht Mikrosekunden handeln, daher muss man den Time-String auf dem json an entsprechenden Stellen verarbeiten und die Microsekunden manuell aus dem String extrahieren, in Milliesekunden umrechnen und runden, weil ich im xml nur drei Stellen als Millisekunden haben will.
Java:
Date date = getDateFromJson(jsonFile);
  private Date getDateFromJson(JsonObject ciJo) throws ParseException {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        String validFrom = ciJo.get("VALID_FROM").getAsString();

        String processedValidFrom = getProcessedValidFromString(validFrom);

        return sdf.parse(processedValidFrom);
    }

   private static String getProcessedValidFromString(String validFrom) {
        // Extrahiere Mikrosekunden und wandle sie in Millisekunden um
        int microSeconds = Integer.parseInt(validFrom.substring(20, 26)); // Nimm sechs Stellen für Mikrosekunden
        int milliSeconds = Math.round(microSeconds / 1000f); // Konvertiere Mikrosekunden zu Millisekunden durch Rundung
        // Erstelle das neue Datum mit den gerundeten Millisekunden
        String processedValidFrom = validFrom.substring(0, 20) + String.format("%03d", milliSeconds) + "Z";
        return processedValidFrom;
    }

Hier ist die erste Irritation gegeben:
Die Zeit des Date-Objekts ist eine Stunde größer, als im Json, obwohl wir gemacht haben
Java:
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

Die Zeit ist dort bzgl. Tagen und Jahr korrekt, aber statt:
06:10:04.420, steht im Debugger im Date-Objekt:
07:10:04.420 UND beim Datum steht CET dabei, wobei CET doch nicht UTC ist, sondern meist UTC+1.


Danach baue ich mir ein XMLGregorianCalendar-Objekt je nach übergebener Zeitzone mit folgender Methode:
Java:
private XMLGregorianCalendar createXmlCalendarByTimezone(Date date, String timezone) throws DatatypeConfigurationException {
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone(timezone));
cal.setTime(date);
XMLGregorianCalendar xCal = DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);
cal.setTimezone(DatatypeConstants.FIELD_UNDEFINED);

return xCal;
}



Ich übergebe da vorher erstellte Date-Objekt und die Zeitzone als String-ID - für folgende Zeitzonen als 2ter Übergabe-Parameter erhalte ich teils gewollte und teils verwirrende Ergebnisse:

1. "UTC" // 06:10:04.420 -> gewollt
2. "Europe/Berlin" // 07:10:04.420 -> gewollt

3. "UTC+01:00" // 06:10:04.420 -> nicht gewollt -> warum wird da nicht die Stunde um Eins erhöht
4. "UTC-1" // 06:10:04.420 -> nicht gewollt.


Interessant ist, wenn ich mir im Debugger die GregorianCalendar-Objekte ansehe, die zur Erzeugung der XMLGregorianCalendar-Objekte nötig sind, dann gibt es dort ein Feld 'zoneOffsets'.
Dieses ist nur bei timezone "Europ/Berlin": [36000000, 0],
sonst immer [0,0].
Das Unix-timestamp-Feld bei allen 4 GregorianCalendar-Objekten ergbit bei Umrechnung immer die Zeit 06:10:04.420 mit dem korrenten Datum.

Also
1. Scheint die Umrechnung bzgl. dem Offset bei dem GregorianCalendar-Objekten nicht zu stimmen, außer bei der übergebenen Zeitzone "Europ/Berlin".
2. Ich bin verwirrt, warum das Date-Objekt, war wir zu Beginn holen in CET ist, also 1 Stunde mehr, als im Json, aus dem wir lesen.

Evtl. kennt sich Jemand ja bei den Klassen SimpleDateFormat, GregorianCalendar, etc. aus und kann weiterhelfen.

Lg
Zrebna
 
Zuletzt bearbeitet:
Die Zeitzone ist nicht eingestellt, sondern nur ein Z am Ende des Datums/der Uhrzeit angehängt, so dass es wie ein GMT-Datum/eine GMT-Zeit aussieht, was aber den Wert nicht ändert.

Setze die Zeitzone auf GMT, dann ist sie korrekt.
 
Wenn du irgendwie kannst wirf Date & alles was mit GregorianCalender zu tun hat ganz weit weg und nimm die neue Date/Time API mit LocalDate, ZonedDate & Co: https://www.baeldung.com/java-format-zoned-datetime-string

Da kannst du auch eine DateFormatter reingeben für dein Datumsformat. Das Verfahren hat ca. unendlich viel weniger Fallstricke als alles, was mit GregorianCalender zu tun hat.
Ok, das ist verm. eine gute Idee - ich werde das absprechen.
 
Die Zeit ist dort bzgl. Tagen und Jahr korrekt, aber statt:
06:10:04.420, steht im Debugger im Date-Objekt:
07:10:04.420 UND beim Datum steht CET dabei, wobei CET doch nicht UTC ist, sondern meist UTC+1.
Das wuerde doch passen. Es ist 06:10 in UTC und 07:10 in CET.

Ich übergebe da vorher erstellte Date-Objekt und die Zeitzone als String-ID
Das Date selbst ist ein vorgeschaedigter Wert, naemlich Die Uhrzeit/Datum sind von der Zeitzone abhaengig welche dauz gehoert. Wenn du jetzt diesern wert nimmst, in das XML setzt, und dann die Zeitzone aenderst, hast du definitiv nicht mehr zusammen passende Daten. Du musst das Date vorher auf die korrekte Zeitzone bringen.
 
Ich kann dir gerade auch nur zeigen wie ich das gemacht habe (nach viel herumprobieren bis die Werte/Zeitstempel schlieszlich stimmten), ob das genau der richtige Weg ist weisz ich aber nicht.

Java:
public static final OffsetDateTime toOffsetDateTime(XMLGregorianCalendar xmlGregorianCalendar) {
    return OffsetDateTime.of(
            xmlGregorianCalendar.getYear(),
            xmlGregorianCalendar.getMonth(),
            xmlGregorianCalendar.getDay(),
            Math.max(0, xmlGregorianCalendar.getHour()),
            Math.max(0, xmlGregorianCalendar.getMinute()),
            Math.max(0, xmlGregorianCalendar.getSecond()),
            0,
            ZoneOffset.ofTotalSeconds(xmlGregorianCalendar.getTimezone() * 60));
}

public static final XMLGregorianCalendar toXmlGregorianCalendar(Date date) {
    return toXmlGregorianCalendar(OffsetDateTime.ofInstant(
            date.toInstant(),
            ZoneOffset.UTC));
}

public static final XMLGregorianCalendar toXmlGregorianCalendar(OffsetDateTime offsetDateTime) {
    return getDatatypeFactory().newXMLGregorianCalendar(
            offsetDateTime.getYear(),
            offsetDateTime.getMonthValue(),
            offsetDateTime.getDayOfMonth(),
            offsetDateTime.getHour(),
            offsetDateTime.getMinute(),
            offsetDateTime.getSecond(),
            offsetDateTime.get(ChronoField.MILLI_OF_SECOND),
            offsetDateTime.getOffset().getTotalSeconds() / 60);
}

Auf jeden Fall wirst du dir in Zukunft leichter tun wenn du die neue Java Time API verwendest. Dort ist sehr klar definiert wie sich die Typen verhalten und was was ist. getDatatypeFactory ist eine einfache Hilfsmethode die eben die DataTypeFactory zwischenspeichert nach Bedarf.
 
Faellt mir gerade auf dass ich von XMLGregorianCalendar auf OffsetDateTime die Millisekunden verwerfe, weisz ich aber gar nicht mehr wieso. Ich bilde mir ein die haette es nicht gegeben, aber ehrlich gesagt schreibe ich immer solche Klassen damit ich mir genau solche Details nicht merken muss (ja, ein Kommentar waere gut gewesen). Die Uhrzeit habe ich auf jeden Fall als Optional gesehen, daran kann ich mich wieder erinnern (also im XML stand nur das Datum mit Zeitzone).
 
nach viel herumprobieren bis die Werte/Zeitstempel schlieszlich stimmten
Hm... ich hätte angenommen, xmlGregorianCalendar.toZonedDateTime().toOffsetDateTime() bzw. getDatatypeFactory().newXMLGregorianCalendar(GregorianCalendar.from(offsetDateTime.toZonedDateTime())) würden reichen. Gibts da irgendwelche Gemeinheiten bzw. übersehe ich etwas?
 
Du meinst xmlGregorianCalendar.toGregorianCalendar().toZonedDateTime().toOffsetDateTime()? Mh, ja, stimmt, koennte ich dir gerade nicht beantworten ob ich das uebersehen habe, oder gesonderte Anforderungen hatte welche spaeter dann vielleicht auch weggefallen sind.
 
Das wuerde doch passen. Es ist 06:10 in UTC und 07:10 in CET.
Ja, ich bin nur irritiert, woher der Code "wusste", dass die Systemzeit im JSON CET ist und das dann so für das Date-Objekt übernommen hat, obwohl man auch dort "UTC" gesetzt hat - evtl. Systemzeit der JVM?

Jedenfalls klappt es jetzt mit 'ZonedDateTime', außer mit den String-IDs für timezone "Europe/Berlin" und "UTC".
Mit "UTC+01:00" nicht - ist das evtl. die falsche String-ID.

Notfalls kann man evtl. sowas machen:


Java:
// Manuelle Anpassung für Zeitzonen-Offsets wie "UTC+01:00"
        if ("UTC+01:00".equals(timezone)) {
            cal.add(GregorianCalendar.HOUR_OF_DAY, 1);
        }

Scheint zwar "dirty", aber evtl. riecht mir das, wenn es klappt.

PS.:
Was schon mal cool ist, ist dass ZoneDateTime wohl kein Problem hat die mikrosekunden korrekt zu handeln und ich nicht mehr den timestamp-string vom Json vorab bearbeiten muss - siehe Eingangspost.
 
Zuletzt bearbeitet:
Ja, ich bin nur irritiert, woher der Code "wusste", dass die Systemzeit im JSON CET ist und das dann so für das Date-Objekt übernommen hat, obwohl man auch dort "UTC" gesetzt hat - evtl. Systemzeit der JVM?

Jedenfalls klappt es jetzt mit 'ZonedDateTime', außer mit den String-IDs für timezone "Europe/Berlin" und "UTC".
Mit "UTC+01:00" nicht - ist das evtl. die falsche String-ID.

Notfalls kann man evtl. sowas machen:


Java:
// Manuelle Anpassung für Zeitzonen-Offsets wie "UTC+01:00"
        if ("UTC+01:00".equals(timezone)) {
            cal.add(GregorianCalendar.HOUR_OF_DAY, 1);
        }

Scheint zwar "dirty", aber evtl. riecht mir das, wenn es klappt.

PS.:
Was schon mal cool ist, ist dass ZoneDateTime wohl kein Problem hat die mikrosekunden korrekt zu handeln und ich nicht mehr den timestamp-string vom Json vorab bearbeiten muss - siehe Eingangspost.
PS 2 zum obigem Post (Editier-Zeit abgelaufen) :

"UTC+01:00" ist wohl die korrekte ID und nachdem ich nun wie Robert Zenz das XMLGregorianCalendar-Objekt direkt über das zoneDateTime-Objekt zusammenbaue und nun gar nicht mehr GregorianCalendar verwende, stimmt es nun auch - ich gehe da nun so vor:

Java:
    private static XMLGregorianCalendar createXmlCalendarByTimezone(ZonedDateTime zdt, String timezone) throws Exception {
        ZonedDateTime zonedDateTime = zdt.withZoneSameInstant(ZoneId.of(timezone));
        return DatatypeFactory.newInstance().newXMLGregorianCalendar(
            zonedDateTime.getYear(),
            zonedDateTime.getMonthValue(),
            zonedDateTime.getDayOfMonth(),
            zonedDateTime.getHour(),
            zonedDateTime.getMinute(),
            zonedDateTime.getSecond(),
            zonedDateTime.getNano() / 1000000,  // Convert nanoseconds to milliseconds
            zonedDateTime.getOffset().getTotalSeconds() / 60  // Convert offset in seconds to minutes
        );
    }


Jedenfalls danke für die Hilfe!
 
Das Date/Time Format ist ISO 8601-Standard und das Z steht für Zulu - Also Zulu Time, was UTC meint.

Edit: Noch den Wikipedia Link dazu: https://de.wikipedia.org/wiki/ISO_8601#Zeitzonen
Ja genau das habe ich auch noch in Erfahrung bringen können, aber wenn das "Z" am Ende eben für UTC-Zeit steht, dann sollte das Date-Objekt, so wie es noch im Eingangspost erstellt wurde erst recht nicht im Debugger "CET" dabei stehen haben und eben die CET Zeit inne haben?
 
Ja genau das habe ich auch noch in Erfahrung bringen können, aber wenn das "Z" am Ende eben für UTC-Zeit steht, dann sollte das Date-Objekt, so wie es noch im Eingangspost erstellt wurde erst recht nicht im Debugger "CET" dabei stehen haben und eben die CET Zeit inne haben?
Also ich habe den Thread jetzt nicht wirklich im Detail verfolgt und der ganz wichtige Hinweis ist bereits gegeben worden:
Wenn du irgendwie kannst wirf Date & alles was mit GregorianCalender zu tun hat ganz weit weg und nimm die neue Date/Time API mit LocalDate, ZonedDate & Co: https://www.baeldung.com/java-format-zoned-datetime-string

Und der Hinweis mit dem Z ist ja auch schon gegeben worden direkt im Anschluss.

Aber betrachten wir einfach einmal, was Du im ersten Post geschrieben hast:
06:10:04.420, steht im Debugger im Date-Objekt:
07:10:04.420 UND beim Datum steht CET dabei, wobei CET doch nicht UTC ist, sondern meist UTC+1.
06:10:04.420 UTC ist doch 07:10:04.420 +01:00

Das Problem ist doch vermutlich schlicht ein Verständnisproblem was Du da überhaupt siehst. Erst einmal erstellst Du eine Date Instanz. Und die ist korrekt. Wenn Du Die Dokumentation anschaust, dann ist das ein Zeitpunkt und das wird intern als UTC abgebildet:
The class Date represents a specific instant in time, with millisecond precision.
Although the Date class is intended to reflect coordinated universal time (UTC), .... (Hier irrelevante Dinge zu leap seconds und UTC vs. GMT - so genau betrachten wir hier das alles nicht...)

Aber jetzt bekommst du da etwas vom Debugger angezeigt. Was zeigt denn der Debugger an? Das ist eine String Representation der Instanz, also das, was toString zurück gibt. Und was macht toString?
Code:
 423:   public String toString()
 424:   {
 425:     Calendar cal = Calendar.getInstance();
 426:     cal.setTimeInMillis(time);
 427:     String day = "0" + cal.get(Calendar.DATE);
 428:     String hour = "0" + cal.get(Calendar.HOUR_OF_DAY);
 429:     String min = "0" + cal.get(Calendar.MINUTE);
 430:     String sec = "0" + cal.get(Calendar.SECOND);
 431:     String year = "000" + cal.get(Calendar.YEAR);
 432:     return weekNames[cal.get(Calendar.DAY_OF_WEEK) - 1] + " "
 433:       + monthNames[cal.get(Calendar.MONTH)] + " "
 434:       + day.substring(day.length() - 2) + " "
 435:       + hour.substring(hour.length() - 2) + ":"
 436:       + min.substring(min.length() - 2) + ":"
 437:       + sec.substring(sec.length() - 2) + " "
 438:       +
 439:       cal.getTimeZone().getDisplayName(cal.getTimeZone().inDaylightTime(this),
 440:                        TimeZone.SHORT) + " " +
 441:       year.substring(year.length() - 4);
 442:   }

Es wird also die Timezone genutzt, die Calendar.getInstace() verwendet. Die default Zeitzone, die bei Dir gesetzt ist, ist offensichtlich CET. Daher siehst Du eine CET Angabe. Das ist aber doch nur eine reine Ansicht auf diese Date Instanz.

Daher bin ich hier doch auch bei @Robert Zenz:
Das wuerde doch passen. Es ist 06:10 in UTC und 07:10 in CET.

Das also nur, um ein kleines bisschen um zu erläutern, was Du da siehst bzw. was da passiert, da es Dich ja etwas bewegt und Du die bisherigen Hinweise wohl nicht direkt richtig einordnen konntest. Ich hoffe, dass ich Deine Fragestellung zu der Anzeige richtig verstanden habe und ich mit den Hinweisen (die prinzipiell alle schon da waren) etwas geholfen habe.
 
@Konrad
Danke, jetzt kann ich es nachvollziehen - das hat mich vorher gejuckt.

Ich glaube das ist auch keine zulaessige Bezeichnung fuer eine Zeitzone. Entweder ist es "UTC" oder es ist "+01:00".
Habe es mit dem altem approach versucht (siehe Eingangspost) - klappt auch nicht.

Mit dem neuem Approach, also keine Verwendung mehr von SimpleDateFormat und GregorianCalendar, klappt es sowohl mit "UTC+01:00" als auch mit "+01:00".
Ich hab mich reingedebuggt und kurz gesagt, in der Klasse 'ZoneId' ist Logik, die beide String IDs korrekt interpretiert.
Meinen Recherchen nach entspricht "+01:00" eher den Anforderungen der ISO-8601-Norm.
"UTC+01:00" ist dann gut, wenn man explizit klar machen will, dass man +1 relativ zur UTC-Zeit haben will.
Hier wohl egal, weil ich es an einer Stelle verwende, an der ich bereits mit der UTC-Zeit immer "reingehe":

Java:
ZonedDateTime zonedDateTime = zdt.withZoneSameInstant(ZoneId.of(timezone));
'zdt' ist dor UTC-based.

Ok, dann glaube ich wars das und mir ist sehr geholfen. Nehme auf jeden Fall u.a. mit, dass java.time bei sowas sehr hilfreich ist und SimpleDateFormat als auch GregorianCalendar eher zu vermeiden ist, wo es nur geht.

Danke für den Support und die Erklärungen 🙂

Lg
Zrebna
 

Zurück
Oben