it-swarm.com.de

Wie gehe ich mit Kalender-Zeitzonen mit Java um?

Ich habe einen Zeitstempelwert, der aus meiner Anwendung stammt. Der Benutzer kann sich in einer beliebigen lokalen Zeitzone befinden.

Da dieses Datum für einen WebService verwendet wird, der davon ausgeht, dass die angegebene Zeit immer in GMT angegeben ist, muss der Parameter des Benutzers von say (EST) in (GMT) konvertiert werden. Hier ist der Kicker: Der Benutzer ist sich seiner TZ nicht bewusst. Er gibt das Erstellungsdatum ein, das er an den WS senden möchte. Ich brauche also:

Benutzer gibt ein: 01.05.2008 6:12 PM (EST)
Der Parameter zum WS muss sein: 5/1/2008 6:12 PM (GMT)

Ich weiß, dass TimeStamps standardmäßig immer in GMT angezeigt werden sollen, aber wenn der Parameter gesendet wird, sind die Stunden immer deaktiviert, es sei denn, der Benutzer ist in GMT. Was vermisse ich?

Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
...
private static Java.util.Calendar convertTimestampToJavaCalendar(Timestamp ts_) {
  Java.util.Calendar cal = Java.util.Calendar.getInstance(
      GMT_TIMEZONE, EN_US_LOCALE);
  cal.setTimeInMillis(ts_.getTime());
  return cal;
}

Mit dem vorherigen Code erhalte ich das folgende Ergebnis (kurzes Format zum einfachen Lesen):

[1. Mai 2008, 23:12 Uhr]

91
Jorge Valois

Vielen Dank für Ihre Antwort. Nach einer weiteren Untersuchung kam ich zur richtigen Antwort. Wie von Skip Head erwähnt, wurde der von meiner Anwendung erhaltene TimeStamped an die TimeZone des Benutzers angepasst. Wenn der Benutzer 6:12 PM (EST)) eingibt, erhalte ich 2:12 PM (GMT). Was ich brauchte, war eine Möglichkeit, das rückgängig zu machen Konvertierung, sodass die vom Benutzer eingegebene Zeit die Zeit ist, die ich an die WebServer-Anforderung gesendet habe.

// Get TimeZone of user
TimeZone currentTimeZone = sc_.getTimeZone();
Calendar currentDt = new GregorianCalendar(currentTimeZone, EN_US_LOCALE);
// Get the Offset from GMT taking DST into account
int gmtOffset = currentTimeZone.getOffset(
    currentDt.get(Calendar.ERA), 
    currentDt.get(Calendar.YEAR), 
    currentDt.get(Calendar.MONTH), 
    currentDt.get(Calendar.DAY_OF_MONTH), 
    currentDt.get(Calendar.DAY_OF_WEEK), 
    currentDt.get(Calendar.MILLISECOND));
// convert to hours
gmtOffset = gmtOffset / (60*60*1000);
System.out.println("Current User's TimeZone: " + currentTimeZone.getID());
System.out.println("Current Offset from GMT (in hrs):" + gmtOffset);
// Get TS from User Input
Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
System.out.println("TS from ACP: " + issuedDate);
// Set TS into Calendar
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
// Adjust for GMT (note the offset negation)
issueDate.add(Calendar.HOUR_OF_DAY, -gmtOffset);
System.out.println("Calendar Date converted from TS using GMT and US_EN Locale: "
    + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
    .format(issueDate.getTime()));

Die Ausgabe des Codes ist: (Benutzer eingegeben am 01.05.2008 um 18:12 Uhr (EST)

Zeitzone des aktuellen Benutzers: EST
Aktueller Versatz von GMT (in Stunden): - 4 (Normalerweise -5, außer wenn die Sommerzeit eingestellt ist)
TS von ACP: 2008-05-01 14: 12: 00.0
Kalenderdatum mit GMT und US_EN von TS konvertiert Gebietsschema: 5/1/08 6:12 PM (GMT)

29
Jorge Valois
public static Calendar convertToGmt(Calendar cal) {

    Date date = cal.getTime();
    TimeZone tz = cal.getTimeZone();

    log.debug("input calendar has date [" + date + "]");

    //Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT 
    long msFromEpochGmt = date.getTime();

    //gives you the current offset in ms from GMT at the current date
    int offsetFromUTC = tz.getOffset(msFromEpochGmt);
    log.debug("offset is " + offsetFromUTC);

    //create a new calendar in GMT timezone, set to this date and add the offset
    Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.setTime(date);
    gmtCal.add(Calendar.MILLISECOND, offsetFromUTC);

    log.debug("Created GMT cal with date [" + gmtCal.getTime() + "]");

    return gmtCal;
}

Hier ist die Ausgabe, wenn ich die aktuelle Zeit ("12:09:05 EDT" von Calendar.getInstance()) übergebe:

DEBUG - Eingabekalender hat Datum [Do 23.10. 12.09.05 EDT 2008]
DEBUG - Offset ist -14400000
DEBUG - GMT-Cal mit Datum erstellt [Do., 23. Oktober, 08:09:05 EDT 2008]

12:09:05 GMT ist 8:09:05 EDT.

Der verwirrende Teil hier ist, dass Calendar.getTime() Ihnen ein Date in Ihrer aktuellen Zeitzone zurückgibt, und dass es keine Methode gibt, die Zeitzone eines Kalenders zu ändern und das zugrunde liegende Datum mitzurechnen. Abhängig von der Art der Parameter, die Ihr Web-Service verwendet, möchten Sie möglicherweise nur den WS-Deal in Millisekunden von Epoch haben.

61
matt b

Sie sagen, dass das Datum in Verbindung mit Webdiensten verwendet wird, also gehe ich davon aus, dass es irgendwann zu einer Zeichenfolge serialisiert wird.

In diesem Fall sollten Sie sich die setTimeZone-Methode der DateFormat-Klasse ansehen. Dies bestimmt, welche Zeitzone beim Drucken des Zeitstempels verwendet wird.

Ein einfaches Beispiel:

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));

Calendar cal = Calendar.getInstance();
String timestamp = formatter.format(cal.getTime());

Sie können es mit Joda Time lösen:

Date utcDate = new Date(timezoneFrom.convertLocalToUTC(date.getTime(), false));
Date localDate = new Date(timezoneTo.convertUTCToLocal(utcDate.getTime()));

Java 8:

LocalDateTime localDateTime = LocalDateTime.parse("2007-12-03T10:15:30");
ZonedDateTime fromDateTime = localDateTime.atZone(
    ZoneId.of("America/Toronto"));
ZonedDateTime toDateTime = fromDateTime.withZoneSameInstant(
    ZoneId.of("Canada/Newfoundland"));
12

Es sieht so aus, als würde Ihr TimeStamp auf die Zeitzone des Ursprungssystems eingestellt.

Dies ist veraltet, sollte aber funktionieren:

cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset());

Der nicht veraltete Weg ist zu verwenden

Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)

dies müsste jedoch auf der Clientseite erfolgen, da dieses System die Zeitzone kennt, in der es sich befindet.

8
Skip Head

Methode zum Konvertieren von einer Zeitzone in eine andere (wahrscheinlich funktioniert es :)).

/**
 * Adapt calendar to client time zone.
 * @param calendar - adapting calendar
 * @param timeZone - client time zone
 * @return adapt calendar to client time zone
 */
public static Calendar convertCalendar(final Calendar calendar, final TimeZone timeZone) {
    Calendar ret = new GregorianCalendar(timeZone);
    ret.setTimeInMillis(calendar.getTimeInMillis() +
            timeZone.getOffset(calendar.getTimeInMillis()) -
            TimeZone.getDefault().getOffset(calendar.getTimeInMillis()));
    ret.getTime();
    return ret;
}
7
Helpa

Datum und Zeitstempel Objekte sind zeitzonenunabhängig: Sie repräsentieren eine bestimmte Anzahl von Sekunden seit der Epoche, ohne sich auf eine bestimmte Interpretation dieses Zeitpunkts als Stunden und Tage festzulegen. Zeitzonen geben das Bild nur in GregorianCalendar (nicht direkt für diese Aufgabe benötigt) und SimpleDateFormat ein, die einen Zeitzonen-Offset benötigen, um zwischen separaten Feldern und Datum (oder long) Werte.

Das Problem des OP liegt direkt zu Beginn seiner Verarbeitung: Der Benutzer gibt mehrdeutige Stunden ein, die in der lokalen Zeitzone ohne GMT interpretiert werden. Zu diesem Zeitpunkt ist der Wert "6:12 EST", was leicht als "11.12 GMT" oder eine andere Zeitzone ausgedruckt werden kann, aber niemals als ändern Sie zu "6.12 GMT".

Es gibt keine Möglichkeit, das SimpleDateFormat zu erstellen, das "06:12" als "HH: MM" analysiert (standardmäßig die lokale Zeitzone) Standardeinstellung ist UTC. SimpleDateFormat ist etwas zu schlau für sich.

Sie können jedoch jede SimpleDateFormat - Instanz davon überzeugen, die richtige Zeitzone zu verwenden, wenn Sie sie explizit in die Eingabe einfügen: Fügen Sie einfach eine feste Zeichenfolge an die empfangene (und ausreichend validierte) "06: 12 " zu analysieren " 06:12 GMT " als " HH: MM z ".

Das explizite Setzen von GregorianCalendar -Feldern oder das Abrufen und Verwenden von Zeitzonen- und Sommerzeit-Offsets ist nicht erforderlich.

Das eigentliche Problem besteht darin, Eingaben, die standardmäßig der lokalen Zeitzone entsprechen, Eingaben, die standardmäßig UTC entsprechen, und Eingaben, für die eine explizite Zeitzonenangabe erforderlich ist, zu trennen.

6
user412090

In der Vergangenheit hat es sich für mich bewährt, den Versatz (in Millisekunden) zwischen der Zeitzone des Benutzers und GMT zu bestimmen. Sobald Sie den Versatz haben, können Sie einfach addieren/subtrahieren (abhängig davon, in welche Richtung die Konvertierung geht), um die entsprechende Zeit in einer der beiden Zeitzonen zu erhalten. Normalerweise würde ich dies durch Festlegen des Millisekundenfelds eines Kalenderobjekts erreichen, aber ich bin sicher, dass Sie es problemlos auf ein Zeitstempelobjekt anwenden können. Hier ist der Code, mit dem ich den Offset erhalte

int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();

timezoneId ist die ID der Zeitzone des Benutzers (z. B. EST).

4
Adam

Java.time

Der moderne Ansatz verwendet die Klassen Java.time, die die lästigen älteren Datum-Uhrzeit-Klassen ablösten, die mit den frühesten Versionen von Java gebündelt wurden.

Die Klasse Java.sql.Timestamp Ist eine dieser älteren Klassen. Nicht mehr gebraucht. Verwenden Sie stattdessen Instant oder andere Java.time-Klassen mit JDBC 4.2 und höher direkt in Ihrer Datenbank.

Die Klasse Instant repräsentiert einen Moment auf der Zeitachse in UTC mit einer Auflösung von Nanosekunden (bis zu neun (9) Stellen eines Dezimalbruchs).

Instant instant = myResultSet.getObject( … , Instant.class ) ; 

Wenn Sie mit einem vorhandenen Timestamp zusammenarbeiten müssen, konvertieren Sie sofort in Java.time über die neuen Konvertierungsmethoden, die den alten Klassen hinzugefügt wurden.

Instant instant = myTimestamp.toInstant() ;

Geben Sie zum Anpassen an eine andere Zeitzone die Zeitzone als ZoneId -Objekt an. Geben Sie ein richtiger Zeitzonenname im Format continent/region An, z. B. America/Montreal , Africa/Casablanca oder Pacific/Auckland. Verwenden Sie niemals Pseudozonen mit 3 bis 4 Buchstaben wie EST oder IST, da sie nicht echte Zeitzonen sind, die nicht standardisiert und nicht einmalig sind ( !).

ZoneId z = ZoneId.of( "America/Montreal" ) ;

Wenden Sie auf das Instant an, um ein ZonedDateTime -Objekt zu erstellen.

ZonedDateTime zdt = instant.atZone( z ) ;

Durchsuchen Sie Stack Overflow nach DateTimeFormatter, um eine Zeichenfolge zur Anzeige für den Benutzer zu generieren, und finden Sie viele Diskussionen und Beispiele.

Bei Ihrer Frage geht es wirklich darum, in die andere Richtung zu gehen, von der Benutzerdateneingabe zu den Datums-/Uhrzeitobjekten. Im Allgemeinen ist es am besten, Ihre Dateneingabe in zwei Teile zu unterteilen, ein Datum und eine Uhrzeit.

LocalDate ld = LocalDate.parse( dateInput , DateTimeFormatter.ofPattern( "M/d/uuuu" , Locale.US ) ) ;
LocalTime lt = LocalTime.parse( timeInput , DateTimeFormatter.ofPattern( "H:m a" , Locale.US ) ) ;

Ihre Frage ist nicht klar. Möchten Sie das vom Benutzer eingegebene Datum und die Uhrzeit in UTC interpretieren? Oder in einer anderen Zeitzone?

Wenn Sie UTC gemeint haben, erstellen Sie ein OffsetDateTime mit einem Versatz unter Verwendung der Konstante für UTC, ZoneOffset.UTC.

OffsetDateTime odt = OffsetDateTime.of( ld , lt , ZoneOffset.UTC ) ;

Wenn Sie eine andere Zeitzone gemeint haben, kombinieren Sie zusammen mit einem Zeitzonenobjekt ein ZoneId. Aber welche Zeitzone? Möglicherweise erkennen Sie eine Standardzeitzone. Wenn dies kritisch ist, müssen Sie dies mit dem Benutzer bestätigen, um dessen Absicht zu bestätigen.

ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;

Extrahieren Sie ein Instant, um ein einfacheres Objekt zu erhalten, das per Definition immer in UTC vorliegt.

Instant instant = odt.toInstant() ;

…oder…

Instant instant = zdt.toInstant() ; 

Senden Sie an Ihre Datenbank.

myPreparedStatement.setObject( … , instant ) ;

Über Java.time

Das Java.time Framework ist in Java 8 und höher. Diese Klassen ersetzen die lästigen alten Legacy Datum-Zeit-Klassen wie - Java.util.Date , Calendar , & SimpleDateFormat .

Das Joda-Time -Projekt, das sich jetzt im Wartungsmodus befindet, rät zur Migration in die Java.time -Klassen.

Weitere Informationen finden Sie im Oracle Tutorial . Durchsuchen Sie Stack Overflow nach vielen Beispielen und Erklärungen. Spezifikation ist JSR 31 .

Woher bekomme ich die Java.time-Klassen?

Das Projekt ThreeTen-Extra erweitert Java.time um zusätzliche Klassen. Dieses Projekt ist ein Testfeld für mögliche zukünftige Ergänzungen von Java.time. Hier finden Sie möglicherweise einige nützliche Klassen wie Interval , YearWeek , YearQuarter und mehr .

1
Basil Bourque