Java Ist System.currentTimeMillis() systemzeitzonenabhängig?

CyborgBeta

Lt. Commander
Registriert
Jan. 2021
Beiträge
1.834
Hallo, ich hab folgendes Problem:

Ich speichere einen Zeitstempel in einer Datenbank auf einem Linux-System:

Code:
$ timedatectl
               Local time: Sat 2024-03-30 15:11:14 CET
           Universal time: Sat 2024-03-30 14:11:14 UTC
                 RTC time: Sat 2024-03-30 14:11:14
                Time zone: Europe/Berlin (CET, +0100)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

(^-- Edit: Leider falsch gewesen, das bezog sich auf ein anderes System. --^)

Java:
      try (PreparedStatement ps =
          c.prepareStatement("INSERT INTO chat (frage, antwort, zeit) VALUES (?, ?, ?);")) {
        ps.setString(1, frage);
        ps.setString(2, antwort);
        ps.setString(3, String.valueOf(System.currentTimeMillis()));
        ps.executeUpdate();
      }

Das Problem ist nun, wenn ich den Zeitstempel wieder heraushole, ist er um 1 Stunde verschoben:

Java:
  /**
   * @param s timestamp in millis from database
   * @return formatted timestamp
   */
  private static String formatDate(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(new Date(Long.parseLong(s)));
  }

Das ist mit vorher noch nicht passiert, was mache ich falsch?

Danke & Gruß
 
Zuletzt bearbeitet:
Ich bin grad unterwegs und hab es mir deshalb jetzt nicht ganz genau ansehen können, aber ich vermute die Ursache hier.

System.currentTimeMillis() returns the number of milliseconds since epoch, i.e. since midnight UTC on the 1st January 1970.
Quelle: https://stackoverflow.com/a/9149289

Du machst ein Date draus. Und für Date gilt:
The date itself doesn't have any time zone. Its toString() method uses the current default time zone to return a String representing this date
Quelle: https://stackoverflow.com/a/11357789

Vielleicht hilft dir das bereits weiter.
 
  • Gefällt mir
Reaktionen: CyborgBeta
@ReignInBlo0d Danke für die Antwort. Ich habs gelöst, indem ich einfach beidesmal die Zeitzone mit angegeben habe ...

Java:
        ps.setString(
            3,
            String.valueOf(
                Calendar.getInstance(TimeZone.getTimeZone("Europe/Berlin")).getTimeInMillis()));


    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    sdf.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
    return sdf.format(new Date(Long.parseLong(s)));

Damit wäre das auch vom Tisch. :)
 
CyborgBeta schrieb:
Damit wäre das auch vom Tisch. :)

Der Vollständigkeit halber:
  • du brauchst beim Speichern keine Zeitzone, der Timestamp ist absolut. Lediglich beim Auslesen ist er notwendig, weil dort der absolute Timestamp in ein lokales Datum konvertiert werden muss. Wenn du die Zeitzone auf dem ausführenden System nutzen möchtest und diese richtig eingestellt ist, musst du die Zone nicht angeben, aber möglicherweise ist bei dir da etwas falsch konfiguriert (siehe unten).
  • Verwende doch java.util.time.* und nicht mehr die alten Klassen.
Java:
// Speichern
// es reicht nach wie vor der "nackte" Timestamp
String timestampAsString = String.valueOf(System.currentTimeMillis());

// alternativ:
String timestampAsString = String.valueOf(Instant.now().toEpochMilli());


// Auslesen mittels Java 8 java.time.*
LocalDateTime dateTime = LocalDateTime
                .ofInstant(Instant.ofEpochMilli(Long.parseLong(timeStampAsMillisFromDb)), ZoneId.of("Europe/Berlin"));
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        System.out.println(dateTime.format(formatter));

Wenn du das ZoneId.of("Europe/Berlin") testweise durch ZoneId.systemDefault() ersetzt und das Ergebnis abweicht, dann ist die Zeitzone auf deinem System nicht die, welche du denkst. Ist der Aufruf von timedatectl oben von deinem DB-Server oder der Maschine, welche den Code ausführt? Die Zeitzone der DB ist völlig egal in diesem Fall. Die Maschine, die den Java-Code ausführt, ist wichtig.
(Zusätzlich könntest du überlegen, ob du den Wert in der DB nicht als Ganzzahl speicherst, oder gleich als Datumstyp, je nachdem, was deine DB unterstützt. String ist nicht so gut, auch wenn es für deinen Fall ausreichen mag)
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: DefconDev, CyborgBeta und ReignInBlo0d
Ach, ich Dummerchen. Verzeihung, ich hatte oben die falsche Zeitzone gepostet.

Das Java-Backend läuft in einem eigenen Docker-Container, dessen Zeitzone auf UTC eingestellt ist.

Die MariaDB läuft auch in einem eigenen Docker-Container, dessen Zeitzone auf "Berlin" eingestellt ist. Das hat aber mit meiner Frage nix zu tun, weil ich die Zeit als Text in der DB speichere. (Ob das so super-schlau ist, sei mal dahingestellt.)

Die Zeitabweichung resultierte also aus der UTC-Systemzeit. Mit der expliziten Angabe der Zeitzone "Berlin" in beiden Fällen kann ich das umgehen.
Ergänzung ()

CyborgBeta schrieb:
Mit der expliziten Angabe der Zeitzone "Berlin" in beiden Fällen

Hm, wahrscheinlich würde es auch genügen, die Zeitzone nur beim "Herausholen" anzugeben ... das hab ich noch nicht ausprobiert.
Ergänzung ()

Nachtrag:

Code:
user@name:~$ docker exec -ti proxy-mariadb-1 bash -c "date"
Sat Mar 30 15:41:16 UTC 2024
user@name:~$ docker exec -ti proxy-java-tagebuch-1 bash -c "date"
Sat Mar 30 15:41:23 UTC 2024

user@name:~$ date
Sat Mar 30 04:43:06 PM CET 2024
 
Zuletzt bearbeitet:
crvn075 schrieb:
Verwende doch java.util.time.* und nicht mehr die alten Klassen.

Ich bin an die alte API gewöhnt, und die neue enthält viel Boilerplate, ist unintuitiv und macht es nicht unbedingt besser. Außerdem: Falls man viel mit Zeiten rechnen muss, sollte man ohnehin auf Joda-Time o. Ä. zurückgreifen.

Siehe auch:


Cheers!
 
CyborgBeta schrieb:
Falls man viel mit Zeiten rechnen muss, sollte man ohnehin auf Joda-Time o. Ä. zurückgreifen.
Joda-Time wurde im Prinzip zu java.time und ist seitdem deprecated.
 
  • Gefällt mir
Reaktionen: codengine, Marco01_809 und CyborgBeta
CyborgBeta schrieb:
Ich bin an die alte API gewöhnt, und die neue enthält viel Boilerplate, ist unintuitiv und macht es nicht unbedingt besser. Außerdem: Falls man viel mit Zeiten rechnen muss, sollte man ohnehin auf Joda-Time o. Ä. zurückgreifen.

Siehe auch:


Cheers!

Das Gegenteil ist der Fall. Die neue API ist aus einem Guss und viel intuitiver, vielmehr sind Date und Calendar schlecht designed und in der Verwendung von vorn bis hinten fehleranfällig. Joda-Time spielt seit dem Release von Java 8 keine Rolle mehr, vielmehr hat der Maintainer von Joda-Time an der "neuen" Java Date & Time API mitgewirkt und die Konzepte sind sehr ähnlich.

Und am Rande, hast du deine beiden Links überhaupt angeschaut?
  • Beim ersten Link hat jemand generelle Verständnisprobleme und die Leute erklären ihm in den Kommentaren, warum die neue API gerade in diesem Punkt viel klarer ist
  • Beim zweiten geht es explizit um java.util.Date, die ist tatsächlich nicht gut. Das ist aber genau die Klasse, die du im ersten Post verwendet hast und nichts mit der neuen API zu tun hat
Du kannst persönlich natürlich machen was du willst, aber kein neues Projekt setzt mehr auf etwas anderes als java.time. Es gibt keinerlei Nachteile, nur viele Vorteile. Alleine schon, dass die Typen immutable sind. Und: Hättest du von vornherein darauf zugegriffen, wäre dir das Problem oben vermutlich sofort aufgefallen, weil man von einem Instant (also quasi einem Unix-Timestamp) gar nicht zu LocalDateTime oder ZonedDateTime kommt, ohne eine Information über die Zone angeben zu müssen.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: codengine und Marco01_809
Nein, das stimmt so nicht. Die neue Api ist un-intuitiv, nicht gut durchdacht und viel behäbiger als die alte. Sie wurde quasi nur wegen des Stream-Gedöns eingeführt, und viele Teile der alten Api wurden infolgedessen als deprecated gekennzeichnet ... Aber das Übel liegt noch tiefer. An für sich sind die gesamten Stream-Features eher übergestülpte Konstrukte, denn eine sinnvolle Ergänzung. Warum? Nun, da sie neuer und in waren. Sogar Brain Goetz stimmt da in mancher Hinsicht zu, wenn ich seine Beiträge richtig interpretiere ...

Ich vertrete da eher einen konservativeren Standpunkt. Sprachfeatures sollten gut durchdacht sein und nicht einfach aus einer Laune heraus durch etwas anderes ersetzt werden, was keinen Mehrwert bietet.

Aber das ist nun wurscht, ich komme mit beiden "Geschmacksrichtungen" gut zurecht. Es würde mich aber nicht wundern, wenn es in der Zukunft noch eine dritte Zeit-Api geben würde.
 
CyborgBeta schrieb:
Nein, das stimmt so nicht. Die neue Api ist un-intuitiv, nicht gut durchdacht und viel behäbiger als die alte. Sie wurde quasi nur wegen des Stream-Gedöns eingeführt, und viele Teile der alten Api wurden infolgedessen als deprecated gekennzeichnet ...

Da bringst du jetzt aber etwas gewaltig durcheinander. Die Java Time API hat mit Streams mal so rein gar nichts zu tun, das sind zwei so völlig verschiedene Dinge wie Tag und Nacht. Der Rest von deinem Post macht in diesem Kontext dann auch keinen Sinn mehr. Die Date/Time API ist völlig lösgelöst von jeglichen anderen Sprachfeatures und hätte somit genau so gut auch in Java 5 oder 17 kommen können.

Du wärst gut beraten, die von dir selbst geposteten Links genauer anzuschauen, insbesondere die Kommentare. Von der Nutzung von Calendar und Date kann nur abgeraten werden. Auch das von dir genannte Joda-Time beschreibt es auf der Projektseite prägnant:

The standard date and time classes prior to Java SE 8 are poor. By tackling this problem head-on, Joda-Time became the de facto standard date and time library for Java prior to Java SE 8. Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.

Ansonsten gibt es genug Lesematerial, wie das Oracle Tutorial dazu oder auch hier auf Baeldung etwas kürzer.
 
"ist schlecht" ist keine fundierte Begründung, sondern eine Meinung. ;) Aber ich les' mir mal die Links durch.
Ergänzung ()

Kurze Frage noch ... was findest du kürzer, besser lesbar und auch schneller? ;)

https://tio.run/##ZZBNT8MwDIbv/Aqrp.../HgFGEGX7Xr56NLWgPm33K2glsswhUmK0fbcWncIBD9wM

oder

https://tio.run/##ZU87T8QwDN7vV1idU...jCJi0fPYToVIk6VOEuczjmWkL4n2TFIxok/3jSDIxzHHw

Edit:

Java:
LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(timestampAsString)), ZoneId.of("Europe/Berlin"));
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = dateTime.format(formatter);

// vs.

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
String formatted = sdf.format(new Date(Long.parseLong(timestampAsString)));
 
Zuletzt bearbeitet:
Ich würde folgende Variante vorschlagen:
Java:
Instant.ofEpochMilli(Long.parseLong(s)).atZone(ZoneId.of("Europe/Berlin"));

So erhält man ZonedDateTime, was im gegebenen Fall den Job erledigt. Wenn es unbedingt LocalDateTime sein soll, kann man mit .toLocalDateTime() dahinter die Zeitzonen-Information wieder wegschmeißen.
 
Zuletzt bearbeitet: (+m 🤣)
  • Gefällt mir
Reaktionen: CyborgBeta
  • Gefällt mir
Reaktionen: H4110
CyborgBeta schrieb:
Kurze Frage noch ... was findest du kürzer, besser lesbar und auch schneller? ;)

Ich denke nicht, dass du verstanden hast, was eigentlich das Problem ist. Und auch nicht, dass du gerade zwei APIs in Kombination verwendest (java.util.Date und das später eingeführte java.util.Calendar sind verschiedene APIs, entgegen deiner Frage oben IST java.time bereits die dritte). Hier der JSR 310 von vor zig Jahren, Punkt 2.5 reicht.

Was ich "kürzer und besser" finde? Ich glaube, du hast vergessen, warum du diesen Thread hier eröffnet hattest. Hättest du java.time verwendet, wäre er jedenfalls nicht notwendig gewesen, weil die API eben klar vorgibt, wie du von einem Timestamp zu einem formatierten Datum kommst. Und genau das ist das Problem mit den alten APIs, einfach mal machen und schnell ins Bein schießen.

Und um deine Frage noch zu beantworten, von irgendwelchen Hello-World 3 Zeilern abgesehen ist java.time deutlich kompakter zu schreiben.

Der Weg über ZonedDateTime ist nicht falsch, aber für diesen Fall komplett unnötig. Genau dafür hat die Factory-Methode von LocalDateTime direkt die ZoneId als Pflichtargument.

public static LocalDateTime ofInstant(Instant instant, ZoneId zone)
 
crvn075 schrieb:
Und um deine Frage noch zu beantworten, von irgendwelchen Hello-World 3 Zeilern abgesehen ist java.time deutlich kompakter zu schreiben.

Nein, ja eben nicht (zumindest dieser Punkt). Die neue Time-Api ist viel länger und umständlicher zu schreiben (egal bei welchem Use-Case) => un-intuitiv.

Aber ich muss hier niemanden überzeugen, der fest davon ausgeht, dass alles Neue "besser" sei als das Bestehende. Es soll bitte jeder das nehmen, was er möchte.
Ergänzung ()

Edit: ... also, insofern es nicht deprecated ist.
 
CyborgBeta schrieb:
Nein, ja eben nicht (zumindest dieser Punkt). Die neue Time-Api ist viel länger und umständlicher zu schreiben (egal bei welchem Use-Case) => un-intuitiv.

Du scheinst mir bei beide APIs nicht besonders gut zu kennen und triffst solche Aussagen, aber das Problem bin natürlich ich.

Kleine Aufgabe:

Am 14.06.2024 21 Uhr deutscher Zeit beginnt die Fussball-EM gegen Schottland. Berechne den Countdown in ganzen Stunden ab jetzt.

(Der Countdown in Tagen und Stunden wäre zwar sinnvoller, aber das ist mit Period und Duration in java.time fast schon geschummelt.)

Code:
ZoneId zoneId = ZoneId.of("Europe/Berlin");
ZonedDateTime targetDateTime = ZonedDateTime.of(2024, 6, 14, 21, 0, 0, 0, zoneId);
long hoursUntilTarget = ChronoUnit.HOURS.between(ZonedDateTime.now(zoneId), targetDateTime);
System.out.println(hoursUntilTarget);

Und jetzt du mit Calendar/Date.

Achja: als Ausgangsdatum bitte den heutigen Tag nehmen. Heute Nacht ist ja interessanterweise Zeitumstellung, der morgige Tag hat also nur 23 Stunden statt 24. Wäre ja schade, dieses Schmankerl wegzulassen.
 
crvn075 schrieb:
Du scheinst mir bei beide APIs nicht besonders gut zu kennen und triffst solche Aussagen,

Können wir diese Unterstellungen nicht sein lassen?

crvn075 schrieb:

Das ist eine andere Use-Case-Ebene. Vorhin ging es noch darum, zum nächsten Bahnhof zu kommen, jetzt willst zum Mars fliegen. :D

Im Übrigen, fordere mich besser nicht heraus.
 
crvn075 schrieb:
Der Countdown in Tagen und Stunden wäre zwar sinnvoller, aber das ist mit Period und Duration in java.time fast schon geschummelt.
Will ich sehen, aber ohne ThreeTen-Extra. 😉
 
Der größte No-No ist hier, dass mit JDBC API gearbeitet wird.
JPA ist seit Ewigkeiten Standard.
 
LencoX2 schrieb:
JPA ist seit Ewigkeiten Standard.

Und worauf basiert JPA? ;)

Noch mal zurück zum Countdown. Ich möchte in meinem Programm keine Zeitspannen berechnen. Insofern kann mir so etwas wie die https://en.wikipedia.org/wiki/Central_European_Summer_Time auch egal sein.

Es ist auch nicht absehbar, dass in mittelbarer Zeit solch eine Anforderung hinzukommt.

Btw. interessantes Video zum Thema

 
Zurück
Oben