Comment gérer les fuseaux horaires du calendrier en utilisant Java?


J'ai une valeur d'horodatage qui provient de mon application. L'utilisateur peut être dans n'importe quel fuseau horaire local donné.

Puisque cette date est utilisée pour un service Web qui suppose que l'heure donnée est toujours en GMT, j'ai besoin de convertir le paramètre de l'utilisateur de say (EST) en (GMT). Voici le kicker: L'utilisateur est inconscient de son TZ. Il entre la date de création qu'il veut envoyer au WS, donc ce dont j'ai besoin, c'est:

Utilisateur entre: 5/1/2008 6:12 H (HNE)
Le paramètre de le WS doit être: 5/1/2008 6:12 PM (GMT)

Je sais que les horodatages sont toujours censés être en GMT par défaut, mais lors de l'envoi du paramètre, même si j'ai créé mon calendrier à partir du TS (qui est censé être en GMT), les heures sont toujours éteintes sauf si l'utilisateur est en GMT. Ce qui me manque?

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;
}

Avec le code précédent, voici ce que j'obtiens en conséquence (Format court pour une lecture facile):

[1 mai 2008 11:12 PM]

Author: John Topley, 2008-10-23

9 answers

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;
}

Voici la sortie si je passe l'heure actuelle ("12:09:05 EDT" de Calendar.getInstance()) dans:

Le calendrier d'entrée de DÉBOGAGE a la date [Jeu Oct 23 12:09:05 EDT 2008]
DEBUG-offset est -14400000
DEBUG - Créé GMT cal avec date [Jeu Oct 23 08: 09: 05 EDT 2008]

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

La partie déroutante ici est que Calendar.getTime() vous renvoie un Date dans votre fuseau horaire actuel, et aussi qu'il n'y a pas de méthode pour modifier le fuseau horaire d'un calendrier et faites rouler la date sous-jacente également. Selon le type de paramètre que votre service Web prend, vous voudrez peut-être simplement que le WS traite en termes de millisecondes à partir de l'époque.

 60
Author: matt b, 2008-10-23 16:23:12

Merci à tous d'avoir répondu. Après une enquête plus approfondie, je suis arrivé à la bonne réponse. Comme mentionné par Skip Head, l'horodatage que je recevais de mon application était ajusté au fuseau horaire de l'utilisateur. Donc, si l'utilisateur entrait à 18h12 (HNE), j'obtiendrais 14h12 (GMT). Ce dont j'avais besoin était un moyen d'annuler la conversion afin que l'heure saisie par l'utilisateur soit l'heure que j'ai envoyée à la demande du serveur Web. Voici comment j'ai accompli cela:

// 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()));

La sortie du code est: (Utilisateur entré 5/1/2008 6:12 H (HNE)

Fuseau horaire de l'utilisateur actuel: EST
Décalage actuel par rapport à GMT (en heures):-4 (normalement -5, sauf si l'heure d'été est ajustée)
TS de ACP: 2008-05-01 14:12:00.0
Date du calendrier convertie à partir de TS en utilisant GMT et US_FR Locale: 5/1/08 6: 12 PM (GMT)

 29
Author: Jorge Valois, 2008-10-23 18:03:18

Vous dites que la date est utilisée en relation avec les services Web, donc je suppose qu'elle est sérialisée dans une chaîne à un moment donné.

Si c'est le cas, vous devriez jeter un oeil à la setTimeZone méthode de la classe DateFormat. Cela dicte quel fuseau horaire qui sera utilisé lors de l'impression de l'horodatage.

Un exemple simple:

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());
 20
Author: Henrik Aasted, 2009-02-02 12:41:36

Vous pouvez le résoudre avec Joda Time:

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
Author: Vitalii Fedorenko, 2014-04-21 00:22:27

Méthode de conversion d'un fuseau horaire à un autre(cela fonctionne probablement :) ).

/**
 * 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
Author: Helpa, 2009-04-06 09:42:02

Il semble que votre horodatage soit défini sur le fuseau horaire du système d'origine.

Ceci est obsolète, mais cela devrait fonctionner:

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

La méthode non obsolète consiste à utiliser

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

Mais cela devrait être fait du côté client, car ce système sait dans quel fuseau horaire il se trouve.

 7
Author: Skip Head, 2014-12-24 14:08:52

Les objets Date et Timestamp sont inconscients du fuseau horaire: ils représentent un certain nombre de secondes depuis l'époque, sans s'engager dans une interprétation particulière de cet instant en heures et en jours. Fuseaux horaires entrez l'image uniquement dans GregorianCalendar (pas directement nécessaire pour cette tâche) et SimpleDateFormat, qui ont besoin d'un décalage de fuseau horaire pour convertir entre des champs séparés et des valeurs de Date (ou long).

Le problème de l'OP est juste à le début de son traitement: l'utilisateur entre les heures, qui sont ambiguës, et elles sont interprétées dans le fuseau horaire local, non GMT; à ce stade, la valeur est "6:12 EST", qui peut être facilement imprimé comme "11.12 GMT" ou tout autre fuseau horaire mais ne va jamais changer en "6.12 GMT".

Il n'y a aucun moyen de faire le SimpleDateFormat qui analyse "06:12" comme "HH: MM" (par défaut au fuseau horaire local) par défaut à UTC au lieu de cela; SimpleDateFormat est un peu trop intelligent pour son propre bien.

Cependant, vous pouvez convaincre les SimpleDateFormat exemple d'utiliser le bon fuseau horaire si vous le mettez explicitement dans l'entrée: il suffit d'ajouter une chaîne de caractères fixe à l' (et validées) "06:12" pour analyser "06:12 GMT", comme "HH:MM z".

Il n'est pas nécessaire de définir explicitement les champs GregorianCalendar ou de récupérer et d'utiliser le fuseau horaire et l'heure d'été décalages.

Le vrai problème est de séparer les entrées par défaut sur le fuseau horaire local, les entrées par défaut sur UTC et les entrées qui nécessitent réellement une indication explicite du fuseau horaire.

 6
Author: user412090, 2012-02-06 11:28:47

Quelque chose qui a fonctionné pour moi dans le passé était de déterminer le décalage (en millisecondes) entre le fuseau horaire de l'utilisateur et GMT. Une fois que vous avez le décalage, vous pouvez simplement ajouter/soustraire (selon la façon dont la conversion va) pour obtenir l'heure appropriée dans l'un ou l'autre fuseau horaire. J'accomplirais généralement cela en définissant le champ millisecondes d'un objet Calendrier, mais je suis sûr que vous pourriez facilement l'appliquer à un objet horodatage. Voici le code que j'utilise pour obtenir le décalage

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

TimezoneId est l'id du fuseau horaire de l'utilisateur (tel que EST).

 4
Author: Adam, 2008-10-23 15:59:25

Java.temps

L'approche moderne utilise le java.classes time qui ont supplanté les classes date-heure héritées gênantes fournies avec les premières versions de Java.

La classe java.sql.Timestamp est l'une de ces classes héritées. N'est plus nécessaire. Utilisez plutôt Instant ou un autre java.classes de temps directement avec votre base de données en utilisant JDBC 4.2 et versions ultérieures.

Le Instant la classe représente un moment dans le scénario de UTC, avec une résolution de nanosecondes (jusqu'à neuf (9) chiffres d'une fraction décimale).

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

Si vous devez interopérer avec un Timestamp existant, convertissez-le immédiatement en java.temps via les nouvelles méthodes de conversion ajoutées aux anciennes classes.

Instant instant = myTimestamp.toInstant() ;

Pour ajuster dans un autre fuseau horaire, spécifiez le fuseau horaire en tant qu'objet ZoneId. Spécifier un fuseau horaire nom dans le format continent/region, tels que America/Montreal, Africa/Casablanca, ou Pacific/Auckland. N'utilisez jamais les pseudo-zones à 3-4 lettres tels que EST ou ISTcar ils sont pas vrais fuseaux horaires, non standardisés, et même pas uniques(!).

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

Appliquer au Instant pour produire un ZonedDateTime objet.

ZonedDateTime zdt = instant.atZone( z ) ;

Pour générer une chaîne à afficher à l'utilisateur, recherchez Stack Overflow pour DateTimeFormatter pour trouver de nombreuses discussions et exemples.

Votre question consiste vraiment à aller dans l'autre sens, de la saisie des données utilisateur aux objets date-heure. Généralement, il est préférable de diviser votre saisie de données en deux parties, une date et une heure de la journée.

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

Votre question n'est pas claire. Voulez-vous interpréter la date et l'heure saisies par l'utilisateur pour être en UTC? Ou dans un autre fuseau horaire?

Si vous vouliez dire UTC, créez un OffsetDateTime avec un décalage en utilisant la constante pour UTC, ZoneOffset.UTC.

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

Si vous vouliez dire un autre fuseau horaire, combinez avec un objet de fuseau horaire, un ZoneId. Mais quel fuseau horaire? Vous pouvez détecter un fuseau horaire par défaut. Ou, si critique, vous devez confirmer avec l'utilisateur pour être certain de leur intention.

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

Pour obtenir un objet plus simple qui est toujours en UTC par définition, extrayez un Instant.

Instant instant = odt.toInstant() ;

...ou...

Instant instant = zdt.toInstant() ; 

Envoyer à votre base de données.

myPreparedStatement.setObject( … , instant ) ;

À propos de java.temps

Le java.time framework est intégré à Java 8 et versions ultérieures. Ces classes permettent d'éviter la pénible vieux héritage date-heure classes telles que java.util.Date, Calendar, & SimpleDateFormat.

Le Joda-Temps project, maintenant en mode de maintenance, conseille la migration vers le java.temps classes.

Pour en savoir plus, consultez le tutoriel Oracle. Et rechercher Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 310.

Où obtenir le java.les classes de temps?

  • Java SE 8, Java SE 9, et plus tard
    • Intégré.
    • Partie du standard Java API avec une implémentation groupée.
    • Java 9 ajoute quelques fonctionnalités et corrections mineures.
  • Java SE 6 et Java SE 7
    • Une grande partie du java.fonction temps est de retour, porté à Java 6 et 7ThreeTen-Backport.
  • Android
    • Versions ultérieures des implémentations Android bundle de java.temps classe.
    • Pour Android plus tôt, le Troisabp le projet adapteThreeTen-Backport (mentionné ci-dessus). Voir Comment utiliser ThreeTenABP....

Le Trois-Extra le projet étend java.temps avec des cours supplémentaires. Ce projet est un terrain d'essai pour d'éventuels ajouts futurs à java.temps. Vous pouvez trouver quelques classes utiles ici comme Interval, YearWeek, YearQuarter, et plus.

 1
Author: Basil Bourque, 2018-01-26 06:43:50