web-dev-qa-db-fra.com

SimpleDateFormat parse perd le fuseau horaire

Code:

 SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
    sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
    System.out.println(new Date());
    try {
        String d = sdf.format(new Date());
        System.out.println(d);
        System.out.println(sdf.parse(d));
    } catch (Exception e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
    }

Sortie:

Thu Aug 08 17:26:32 GMT+08:00 2013
2013.08.08 09:26:32 GMT
Thu Aug 08 17:26:32 GMT+08:00 2013

Notez que format() formate correctement Date en GMT, mais parse() a perdu les détails GMT. Je sais que je peux utiliser substring() et contourner ce problème, mais quelle est la raison de ce phénomène?

Voici une question en double qui n'a pas de réponse.

Edit: Permettez-moi de poser la question d’une autre manière: quel est le moyen de récupérer un objet Date afin qu’il soit toujours à l’heure GMT?

53
Achow

Tout ce dont j'avais besoin était ceci:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));

SimpleDateFormat sdfLocal = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");

try {
    String d = sdf.format(new Date());
    System.out.println(d);
    System.out.println(sdfLocal.parse(d));
} catch (Exception e) {
    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
}

Sortie: légèrement douteuse, mais je veux que la date soit cohérente

2013.08.08 11:01:08
Thu Aug 08 11:01:08 GMT+08:00 2013
51
Achow

La solution de l'OP à son problème, dit-il, produit des résultats douteux. Ce code montre encore de la confusion à propos des représentations du temps. Pour dissiper cette confusion et créer un code qui ne conduira pas à de mauvais moments, considérons cette extension de ce qu'il a fait:

public static void _testDateFormatting() {
    SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT"));
    SimpleDateFormat sdfGMT2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
    sdfGMT2.setTimeZone(TimeZone.getTimeZone("GMT"));

    SimpleDateFormat sdfLocal1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    SimpleDateFormat sdfLocal2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");

    try {
        Date d = new Date();
        String s1 = d.toString();
        String s2 = sdfLocal1.format(d);
        // Store s3 or s4 in database.
        String s3 = sdfGMT1.format(d);
        String s4 = sdfGMT2.format(d);
        // Retrieve s3 or s4 from database, using LOCAL sdf.
        String s5 = sdfLocal1.parse(s3).toString();
        //EXCEPTION String s6 = sdfLocal2.parse(s3).toString();
        String s7 = sdfLocal1.parse(s4).toString();
        String s8 = sdfLocal2.parse(s4).toString();
        // Retrieve s3 from database, using GMT sdf.
        // Note that this is the SAME sdf that created s3.
        Date d2 = sdfGMT1.parse(s3);
        String s9 = d2.toString();
        String s10 = sdfGMT1.format(d2);
        String s11 = sdfLocal2.format(d2);
    } catch (Exception e) {
        e.printStackTrace();
    }       
}

examiner les valeurs dans un débogueur:

s1  "Mon Sep 07 06:11:53 EDT 2015" (id=831698113128)    
s2  "2015.09.07 06:11:53" (id=831698114048) 
s3  "2015.09.07 10:11:53" (id=831698114968) 
s4  "2015.09.07 10:11:53 GMT+00:00" (id=831698116112)   
s5  "Mon Sep 07 10:11:53 EDT 2015" (id=831698116944)    
s6  -- omitted, gave parse exception    
s7  "Mon Sep 07 10:11:53 EDT 2015" (id=831698118680)    
s8  "Mon Sep 07 06:11:53 EDT 2015" (id=831698119584)    
s9  "Mon Sep 07 06:11:53 EDT 2015" (id=831698120392)    
s10 "2015.09.07 10:11:53" (id=831698121312) 
s11 "2015.09.07 06:11:53 EDT" (id=831698122256) 

sdf2 et sdfLocal2 incluent le fuseau horaire pour que nous puissions voir ce qui se passe réellement. s1 et s2 sont à 06:11:53 dans la zone EDT. s3 et s4 sont à 10:11:53 dans la zone GMT - équivalent à l'heure EDT d'origine. Imaginons que nous sauvegardions s3 ou s4 dans une base de données, où nous utilisons l’heure GMT par souci de cohérence, afin que nous puissions avoir des heures de n’importe où dans le monde, sans stocker de fuseaux horaires différents.

s5 analyse l'heure GMT, mais la traite comme une heure locale. Donc, il est écrit "10:11:53" - l'heure GMT - mais pense qu'il est 10:11:53 à heure locale. Pas bon.

s7 analyse l’heure GMT, mais ignore l’heure GMT dans la chaîne et la considère donc toujours comme une heure locale.

s8 fonctionne, car nous incluons maintenant GMT dans la chaîne, et l’analyseur de zone locale l’utilise pour convertir un fuseau horaire en un autre.

Supposons maintenant que vous ne voulez pas stocker la zone, vous voulez pouvoir analyser s3, mais l'afficher en tant qu'heure locale. La réponse est tilisez le même fuseau horaire que celui utilisé pour l'enregistrement - utilisez donc le même sdf que celui dans lequel il a été créé, sdfGMT1. S9, S10 et S11 sont toutes des représentations de l'heure originale. Ils sont tous "corrects". C'est-à-dire, d2 == d1. Ensuite, la question de savoir comment vous voulez l’afficher est prise en compte. Si vous souhaitez afficher ce qui est stocké dans la base de données - Heure GMT - vous devez le formater à l'aide d'un fichier sdf GMT. C'est s10.

Voici donc la solution finale, si vous ne souhaitez pas stocker explicitement avec "GMT" dans la chaîne, et souhaitez afficher au format GMT:

public static void _testDateFormatting() {
    SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT"));

    try {
        Date d = new Date();
        String s3 = sdfGMT1.format(d);
        // Store s3 in DB.
        // ...
        // Retrieve s3 from database, using GMT sdf.
        Date d2 = sdfGMT1.parse(s3);
        String s10 = sdfGMT1.format(d2);
    } catch (Exception e) {
        e.printStackTrace();
    }       
}
8
ToolmakerSteve

tl; dr

quel est le moyen de récupérer un objet Date pour qu'il soit toujours à l'heure GMT?

Instant.now() 

Détails

Vous utilisez d'anciennes classes de date-heure qui déroutent et qui sont maintenant remplacées par les classes Java.time.

Instant = UTC

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() ; // Current moment in UTC.

ISO 8601

Pour échanger ces données sous forme de texte, utilisez exclusivement les formats standard ISO 8601 . Ces formats sont raisonnablement conçus pour être sans ambiguïté, faciles à traiter par la machine et faciles à lire par de nombreuses cultures.

Les classes Java.time utilisent les formats standard par défaut lors de l'analyse et de la génération de chaînes.

String output = instant.toString() ;  

2017-01-23T12: 34: 56.123456789Z

Fuseau horaire

Si vous voulez voir le même moment que celui présenté dans l'heure de l'horloge murale d'une région particulière, appliquez un ZoneId pour obtenir un ZonedDateTime.

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-4 lettres telles que EST ou IST telles qu'elles sont pas vrais fuseaux horaires, non standardisés, et même pas uniques (!) .

ZoneId z = ZoneId.of( "Asia/Singapore" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same simultaneous moment, same point on the timeline.

Voir ceci code en direct sur IdeOne.com .

Notez la différence de huit heures, comme le fuseau horaire de Asia/Singapore a actuellement un décalage par rapport à l'UTC de +08: 00. Même moment, heure différente.

instant.toString (): 2017-01-23T12: 34: 56.123456789Z

zdt.toString (): 2017-01-23T20: 34: 56.123456789 + 08: 00 [Asie/Singapour]

Convertir

Évitez l'héritage Java.util.Date classe. Mais si vous devez, vous pouvez convertir. Rechercher de nouvelles méthodes ajoutées aux anciennes classes.

Java.util.Date date = Date.fromInstant( instant ) ;

… Aller dans l'autre sens…

Instant instant = myJavaUtilDate.toInstant() ;

Date seulement

Pour la date uniquement, utilisez LocalDate.

LocalDate ld = zdt.toLocalDate() ;

À propos de Java.time

Le cadre Java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent les anciennes classes ennuyeuses héritées telles que - Java.util.Date , Calendar , & SimpleDateFormat .

Le projet Joda-Time , actuellement en mode maintenance , conseille de migrer vers les classes Java.time .

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

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 .

6
Basil Bourque