web-dev-qa-db-fra.com

Java Convertir l'heure GMT / UTC en heure locale ne fonctionne pas comme prévu

Afin de montrer un scénario reproductible, je fais ce qui suit

  1. Obtenir l'heure système actuelle (heure locale)

  2. Conversion de l’heure locale en UTC // Fonctionne jusqu’ici

  3. Inverser l’heure UTC et revenir à l’heure locale. Trois approches différentes ont été suivies (répertoriées ci-dessous), mais les trois approches conservent l'heure uniquement en UTC.

    {

    long ts = System.currentTimeMillis();
    Date localTime = new Date(ts);
    String format = "yyyy/MM/dd HH:mm:ss";
    SimpleDateFormat sdf = new SimpleDateFormat (format);
    
    // Convert Local Time to UTC (Works Fine) 
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date gmtTime = new Date(sdf.format(localTime));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime());
    
    // Reverse Convert UTC Time to Locale time (Doesn't work) Approach 1
    sdf.setTimeZone(TimeZone.getDefault());        
    localTime = new Date(sdf.format(gmtTime));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime());
    
    // Reverse Convert UTC Time to Locale time (Doesn't work) Approach 2 using DateFormat
    DateFormat df = new SimpleDateFormat (format);
    df.setTimeZone(TimeZone.getDefault());
    localTime = df.parse((df.format(gmtTime)));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:" + gmtTime.toString() + "-" + gmtTime.getTime());
    
    // Approach 3
    Calendar c = new GregorianCalendar(TimeZone.getDefault());
    c.setTimeInMillis(gmtTime.getTime());
    System.out.println("Local Time " + c.toString());
    

    }

29
Vinod Jayachandran

Je recommande également d'utiliser Joda comme mentionné précédemment.

Résoudre votre problème à l’aide d’objets standard JavaDate peut être effectué comme suit:

    // **** YOUR CODE **** BEGIN ****
    long ts = System.currentTimeMillis();
    Date localTime = new Date(ts);
    String format = "yyyy/MM/dd HH:mm:ss";
    SimpleDateFormat sdf = new SimpleDateFormat(format);

    // Convert Local Time to UTC (Works Fine)
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    Date gmtTime = new Date(sdf.format(localTime));
    System.out.println("Local:" + localTime.toString() + "," + localTime.getTime() + " --> UTC time:"
            + gmtTime.toString() + "," + gmtTime.getTime());

    // **** YOUR CODE **** END ****

    // Convert UTC to Local Time
    Date fromGmt = new Date(gmtTime.getTime() + TimeZone.getDefault().getOffset(localTime.getTime()));
    System.out.println("UTC time:" + gmtTime.toString() + "," + gmtTime.getTime() + " --> Local:"
            + fromGmt.toString() + "-" + fromGmt.getTime());

Sortie:

Local:Tue Oct 15 12:19:40 CEST 2013,1381832380522 --> UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000
UTC time:Tue Oct 15 10:19:40 CEST 2013,1381825180000 --> Local:Tue Oct 15 12:19:40 CEST 2013-1381832380000
56
trylimits

Joda-Time


UPDATE: Le projet Joda-Time == est maintenant en mode de maintenance , avec l'équipe conseillant la migration vers Java.time classes. Voir Tutorial by Oracle .

Voir mon autre réponse en utilisant les classes de l'industrie Java.time.


Normalement, nous considérons que sur StackOverflow.com il est mauvais de répondre à une question spécifique en suggérant une technologie alternative. Mais dans le cas des classes de date, d’heure et de calendrier fournies avec Java 7 et versions antérieures, ces classes sont si notoirement mauvaises en conception et en exécution que je suis obligé de suggérer l’utilisation d’un tiers. bibliothèque du parti à la place: Joda-Time .

Joda-Time fonctionne en créant objets immuables . Ainsi, plutôt que de modifier le fuseau horaire d'un objet DateTime, nous instancions simplement un nouveau DateTime avec un fuseau horaire différent attribué.

Votre préoccupation centrale d’utiliser à la fois l’heure locale et l’heure UTC est très simple dans Joda-Time, ne prenant que 3 lignes de code.

    org.joda.time.DateTime now = new org.joda.time.DateTime();
    System.out.println( "Local time in ISO 8601 format: " + now + " in zone: " + now.getZone() );
    System.out.println( "UTC (Zulu) time zone: " + now.toDateTime( org.joda.time.DateTimeZone.UTC ) );

La sortie lorsqu'elle est exécutée sur la côte ouest de l'Amérique du Nord pourrait être:

Local time in ISO 8601 format: 2013-10-15T02:45:30.801-07:00

UTC (Zulu) time zone: 2013-10-15T09:45:30.801Z

Voici une classe avec plusieurs exemples et commentaires supplémentaires. Utiliser Joda-Time 2.5.

/**
 * Created by Basil Bourque on 2013-10-15.
 * © Basil Bourque 2013
 * This source code may be used freely forever by anyone taking full responsibility for doing so.
 */
public class TimeExample {
    public static void main(String[] args) {
        // Joda-Time - The popular alternative to Sun/Oracle's notoriously bad date, time, and calendar classes bundled with Java 8 and earlier.
        // http://www.joda.org/joda-time/

        // Joda-Time will become outmoded by the JSR 310 Date and Time API introduced in Java 8.
        // JSR 310 was inspired by Joda-Time but is not directly based on it.
        // http://jcp.org/en/jsr/detail?id=310

        // By default, Joda-Time produces strings in the standard ISO 8601 format.
        // https://en.wikipedia.org/wiki/ISO_8601
        // You may output to strings in other formats.

        // Capture one moment in time, to be used in all the examples to follow.
        org.joda.time.DateTime now = new org.joda.time.DateTime();

        System.out.println( "Local time in ISO 8601 format: " + now + " in zone: " + now.getZone() );
        System.out.println( "UTC (Zulu) time zone: " + now.toDateTime( org.joda.time.DateTimeZone.UTC ) );

        // You may specify a time zone in either of two ways:
        // • Using identifiers bundled with Joda-Time
        // • Using identifiers bundled with Java via its TimeZone class

        // ----|  Joda-Time Zones  |---------------------------------

        // Time zone identifiers defined by Joda-Time…
        System.out.println( "Time zones defined in Joda-Time : " + Java.util.Arrays.toString( org.joda.time.DateTimeZone.getAvailableIDs().toArray() ) );

        // Specify a time zone using DateTimeZone objects from Joda-Time.
        // http://joda-time.sourceforge.net/apidocs/org/joda/time/DateTimeZone.html
        org.joda.time.DateTimeZone parisDateTimeZone = org.joda.time.DateTimeZone.forID( "Europe/Paris" );
        System.out.println( "Paris France (Joda-Time zone): " + now.toDateTime( parisDateTimeZone ) );

        // ----|  Java Zones  |---------------------------------

        // Time zone identifiers defined by Java…
        System.out.println( "Time zones defined within Java : " + Java.util.Arrays.toString( Java.util.TimeZone.getAvailableIDs() ) );

        // Specify a time zone using TimeZone objects built into Java.
        // http://docs.Oracle.com/javase/8/docs/api/Java/util/TimeZone.html
        Java.util.TimeZone parisTimeZone = Java.util.TimeZone.getTimeZone( "Europe/Paris" );
        System.out.println( "Paris France (Java zone): " + now.toDateTime(org.joda.time.DateTimeZone.forTimeZone( parisTimeZone ) ) );

    }
}
4
Basil Bourque

tl; dr

Instant.now()                           // Capture the current moment in UTC.
.atZone( ZoneId.systemDefault() )       // Adjust into the JVM's current default time zone. Same moment, different wall-clock time. Produces a `ZonedDateTime` object.
.toInstant()                            // Extract a `Instant` (always in UTC) object from the `ZonedDateTime` object.
.atZone( ZoneId.of( "Europe/Paris" ) )  // Adjust the `Instant` into a specific time zone. Renders a `ZonedDateTime` object. Same moment, different wall-clock time.
.toInstant()                            // And back to UTC again.

Java.time

L’approche moderne utilise les classes Java.time qui ont supplanté les anciennes et anciennes classes problématiques (Date, Calendar, etc.).

Votre utilisation du mot "local" contredit l'utilisation de la classe Java.time . En Java.time , "local" signifie toute localité ou toutes localité , mais pas une localité en particulier. Les classes Java.time dont le nom commence par "Local ..." ne possèdent aucune notion de fuseau horaire ni de décalage par rapport à UTC. Ainsi, ils ne représentent pas un moment spécifique, ils ne représentent pas un point sur la timeline, alors que votre question concerne uniquement les moments, points de la chronologie visualisés à travers différentes heures.

Obtenir l'heure système actuelle (heure locale)

Si vous voulez capturer le moment actuel en UTC, utilisez Instant. La classe Instant représente un moment sur la ligne temporelle de UTC avec une résolution de nanosecondes (jusqu'à neuf (9) chiffres d'une fraction décimale).

Instant instant = Instant.now() ;  // Capture the current moment in UTC.

Ajustez-vous dans un fuseau horaire en appliquant un ZoneId pour obtenir un ZonedDateTime. Même moment, même point sur la timeline, heure différente de l'horloge murale.

Spécifiez un nom de fuseau horaire correct au format continent/region, tel que America/Montreal , Africa/Casablanca , ou Pacific/Auckland. N'utilisez jamais les abréviations de 3 ou 4 lettres telles que EST ou IST, car elles sont pas de vrais fuseaux horaires, ni normalisés ni uniques. (!).

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, different wall-clock time.

En tant que raccourci, vous pouvez ignorer l'utilisation de Instant pour obtenir un ZonedDateTime.

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

Conversion de l’heure locale en UTC // Fonctionne jusqu’ici

Vous pouvez ajuster le temps zoné à l'heure UTC en extrayant un Instant à partir d'un ZonedDateTime.

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
Instant instant = zdt.toInstant() ;

Inverser l’heure UTC et revenir à l’heure locale.

Comme indiqué ci-dessus, appliquez un ZoneId pour ajuster le même moment à une autre heure de l'horloge murale utilisée par les habitants d'une région donnée (un fuseau horaire).

Instant instant = Instant.now() ;  // Capture current moment in UTC.

ZoneId zDefault = ZoneId.systemDefault() ;  // The JVM's current default time zone.
ZonedDateTime zdtDefault = instant.atZone( zDefault ) ;

ZoneId zTunis = ZoneId.of( "Africa/Tunis" ) ;  // The JVM's current default time zone.
ZonedDateTime zdtTunis = instant.atZone( zTunis ) ;

ZoneId zAuckland = ZoneId.of( "Pacific/Auckland" ) ;  // The JVM's current default time zone.
ZonedDateTime zdtAuckland = instant.atZone( zAuckland ) ;

Pour revenir à l'heure UTC à partir d'une date et heure zonée, appelez ZonedDateTime::toInstant. Pensez-y conceptuellement comme suit: ZonedDateTime = Instant + ZoneId .

Instant instant = zdtAuckland.toInstant() ;

Tous ces objets, les objets Instant et les trois objets ZonedDateTime représentent tous le même moment simultané, le même point de l’histoire.

Trois approches différentes ont été suivies (répertoriées ci-dessous), mais les trois approches conservent l'heure uniquement en UTC.

Oubliez d'essayer de corriger le code en utilisant ces terribles classes Date, Calendar et GregorianCalendar. Ils sont un gâchis de mauvaise conception et de défauts. Vous ne devez plus jamais les toucher. Si vous devez vous connecter avec l'ancien code non encore mis à jour vers Java.time , vous pouvez convertir en arrière via de nouvelles méthodes de conversion ajoutées aux anciennes classes.


À propos de Java.time

Le cadre Java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent l'ancien problème - legacy classes date-heure telles que Java.util.Date , Calendar , & SimpleDateFormat .

Le projet Joda-Time , actuellement en mode de maintenance , conseille de migrer vers le Java.time = cours.

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

Vous pouvez échanger des objets Java.time directement avec votre base de données. Utilisez un pilote JDBC compatible avec JDBC 4.2 ou une version ultérieure. Pas besoin de chaînes, pas besoin de Java.sql.* Des classes.

Où obtenir les classes Java.time?

Le projet ThreeTen-Extra étend Java.time avec des classes supplémentaires. Ce projet est un terrain d’essai pour d’éventuels ajouts à Java.time. Vous pouvez trouver ici quelques classes utiles telles que Interval , YearWeek , YearQuarter , et plus .

2
Basil Bourque

Je rejoins la chorale en vous recommandant de ne pas utiliser les classes désormais obsolètes Date, Calendar, SimpleDateFormat et amis. En particulier, je vous déconseille d'utiliser les méthodes et les constructeurs obsolètes de la classe Date, comme le constructeur Date(String) que vous avez utilisé. Ils sont obsolètes car ils ne fonctionnent pas de manière fiable sur plusieurs fuseaux horaires. Ne les utilisez donc pas. Et oui, la plupart des constructeurs et des méthodes de cette classe sont déconseillés.

Alors qu'à l'époque où vous avez posé la question, Joda-Time était (d'après tout ce que je sais) une alternative nettement meilleure, le temps a encore passé. Aujourd'hui, Joda-Time est un projet en grande partie terminé et ses développeurs vous recommandent d'utiliser Java.time, L'API moderne Java date et heure. Je vous montrerai comment.

    ZonedDateTime localTime = ZonedDateTime.now(ZoneId.systemDefault());

    // Convert Local Time to UTC 
    OffsetDateTime gmtTime
            = localTime.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.UTC);
    System.out.println("Local:" + localTime.toString() 
            + " --> UTC time:" + gmtTime.toString());

    // Reverse Convert UTC Time to Local time
    localTime = gmtTime.atZoneSameInstant(ZoneId.systemDefault());
    System.out.println("Local Time " + localTime.toString());

Pour commencer, notez que le code n’est pas seulement deux fois plus long que le vôtre, il est également plus clair à lire.

Sur mon ordinateur, le code est imprimé:

Local:2017-09-02T07:25:46.211+02:00[Europe/Berlin] --> UTC time:2017-09-02T05:25:46.211Z
Local Time 2017-09-02T07:25:46.211+02:00[Europe/Berlin]

J'ai laissé de côté les millisecondes de l'époque. Vous pouvez toujours les obtenir à partir de System.currentTimeMillis(); comme dans votre question, et ils sont indépendants du fuseau horaire. Je ne les ai donc pas trouvés intéressants ici.

J’ai hésité à conserver votre nom de variable localTime. Je pense que c’est un bon nom. L'API moderne a une classe appelée LocalTime. Par conséquent, utiliser ce nom, non capitalisé uniquement, pour un objet qui n'a pas de type LocalTime pourrait en confondre (un LocalTime ne 'ne conserve pas les informations de fuseau horaire, que nous devons conserver ici pour pouvoir effectuer la conversion correcte; il ne contient également que l'heure, pas la date).

Votre conversion de l'heure locale en heure UTC était incorrecte et impossible

La classe obsolète Date ne contient aucune information de fuseau horaire (on peut dire qu’elle utilise toujours le temps UTC en interne); il n’existe donc pas de conversion de Date d’un fuseau horaire à un autre . Lorsque je viens d'exécuter votre code sur mon ordinateur, la première ligne imprimée était:

Local:Sat Sep 02 07:25:45 CEST 2017,1504329945967 --> UTC time:Sat Sep 02 05:25:45 CEST 2017-1504322745000

07:25:45 CEST Est correct, bien sûr. L'heure UTC correcte aurait été 05:25:45 UTC, Mais le message CEST apparaît à nouveau, ce qui est incorrect.

Maintenant, vous n'aurez plus jamais besoin de la classe Date, :-) mais si vous y allez, la lecture incontournable serait Tout sur Java.util.Date selon le codage de Jon Skeet Blog.

Question: Puis-je utiliser l'API moderne avec ma version Java version?

Si vous utilisez au moins Java 6 , vous pouvez.

  • Dans Java 8 et supérieur, la nouvelle API est intégrée.
  • Dans Java 6 et 7, obtenez le portage ThreeTen , le portage des nouvelles classes (il s’agit de ThreeTen pour JSR-310, où l’API moderne a été définie pour la première fois).
  • Sous Android, utilisez l’édition Android de ThreeTen Backport. Elle s’appelle ThreeTenABP, et je pense qu’il existe une merveilleuse explication dans cette question: Comment utiliser ThreeTenABP dans Android Projet .
2
Ole V.V.

Vous avez un rendez-vous avec un fuseau horaire connu (Ici Europe/Madrid) et un fuseau horaire cible (UTC)

Vous avez juste besoin de deux SimpleDateFormats:

 long ts = System.currentTimeMillis (); 
 Date heure locale = nouveau Date (ts); 
 
 SimpleDateFormat sdfLocal = nouveau SimpleDateFormat ("aaaa/MM/jj HH: mm: ss "); 
 SdfLocal.setTimeZone (TimeZone.getTimeZone (" Europe/Madrid ")); 
 
 SimpleDateFormat sdfUTC = new SimpleDateFormat (" aaaa/MM/dd HH: mm: ss "); 
 sdfUTC.setTimeZone (TimeZone.getTimeZone (" UTC ")); 
 
 // Convertir l'heure locale en UTC 
 Date utcTime = sdfLocal.parse (sdfUTC.format (localTime)); 
 System.out.println ("Local:" + localTime.toString () + "," + localTime.getTime () + "- > Heure UTC: "+ utcTime.toString () +" - "+ utcTime.getTime ()); 
 
 // Conversion inversée de l'heure UTC en heure locale 
 LocalTime = sdfUTC .parse (sdfLocal.format (utcTime)); 
 System.out.println ("UTC:" + utcTime.toString () + "," + utcTime.getTime () + "-> Heure locale: "+ localTime.toString () +" - "+ localTime .obtenir du temps());

Donc après l'avoir vu fonctionner, vous pouvez ajouter cette méthode à vos utilitaires:

 public Date convertDate (Date dateFrom, String fromTimeZone, String toTimeZone) lève ParseException {
 String modèle = "aaaa/MM/jj HH: mm: ss"; 
 SimpleDateFormat sdfFrom = new SimpleDateFormat (pattern); 
 sdfFrom.setTimeZone (TimeZone.getTimeZone (fromTimeZone)); 
 
 SimpleDateFormat sdfTo = new SimpleDateFormat (pattern); 
 sdfTo.setTimeZime (TimeZone.getTimeZone (toTimeZone)); 
 
 Date dateTo = sdfFrom.parse (sdfTo.format (dateFrom)); 
 Date de retourTo; 
} 
2
albfan

Je recommande fortement d'utiliser Joda Time http://joda-time.sourceforge.net/faq.html

1
Tomasz Waszczyk