web-dev-qa-db-fra.com

Pourquoi soustraire ces deux fois (en 1927) donne un résultat étrange?

Si j'exécute le programme suivant, qui analyse deux chaînes de date faisant référence à une seconde d'intervalle et les compare:

public static void main(String[] args) throws ParseException {
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    String str3 = "1927-12-31 23:54:07";  
    String str4 = "1927-12-31 23:54:08";  
    Date sDt3 = sf.parse(str3);  
    Date sDt4 = sf.parse(str4);  
    long ld3 = sDt3.getTime() /1000;  
    long ld4 = sDt4.getTime() /1000;
    System.out.println(ld4-ld3);
}

La sortie est:

353

Pourquoi ld4-ld3 n'est-il pas 1 (comme je l'attendrais de la différence d'une seconde dans les temps), mais 353?

Si je change les dates en fois 1 seconde plus tard:

String str3 = "1927-12-31 23:54:08";  
String str4 = "1927-12-31 23:54:09";  

Alors ld4-ld3 sera 1.


Version Java:

Java version "1.6.0_22"
Java(TM) SE Runtime Environment (build 1.6.0_22-b04)
Dynamic Code Evolution Client VM (build 0.2-b02-internal, 19.0-b04-internal, mixed mode)

Timezone(`TimeZone.getDefault()`):

Sun.util.calendar.ZoneInfo[id="Asia/Shanghai",
offset=28800000,dstSavings=0,
useDaylight=false,
transitions=19,
lastRule=null]

Locale(Locale.getDefault()): zh_CN
6294
Freewind

C'est un changement de fuseau horaire le 31 décembre à Shanghai.

Voir cette page pour plus de détails sur 1927 à Shanghai. À la fin de 1927, à minuit, les horloges remontaient à 5 minutes et 52 secondes. Donc, "1927-12-31 23:54:08" est en fait arrivé deux fois et il semble que Java l’analyse comme l’instant possible plus tard pour cette date/heure locale - d’où la différence.

Juste un autre épisode dans le monde souvent étrange et merveilleux des fuseaux horaires.

EDIT: Stop, appuyez sur! L'histoire change ...

La question initiale ne démontrerait plus le même comportement si elle était reconstruite avec la version 2013a de TZDB . En 2013a, le résultat serait de 358 secondes, avec un temps de transition de 23:54:03 au lieu de 23:54:08.

Je l’ai seulement remarqué parce que je collectionne des questions comme celle-ci dans Noda Time, sous la forme de tests unitaires ... Le test a maintenant été modifié, mais il est évident que même les données historiques ne sont pas sûres.

EDIT: L'histoire a encore changé ...

Dans TZDB 2014f, l'heure du changement a été déplacée à 1900-12-31, et il ne s'agit maintenant que d'un changement de 343 secondes (le temps entre t et t+1 est donc de 344 secondes, si vous voyez ce que je veux dire).

EDIT: Pour répondre à une question sur une transition à 1900 ... il semblerait que l'implémentation du fuseau horaire Java traite les fuseaux horaires tous comme s'ils étaient simplement à l'heure standard avant le début de 1900 UTC:

import Java.util.TimeZone;

public class Test {
    public static void main(String[] args) throws Exception {
        long startOf1900Utc = -2208988800000L;
        for (String id : TimeZone.getAvailableIDs()) {
            TimeZone zone = TimeZone.getTimeZone(id);
            if (zone.getRawOffset() != zone.getOffset(startOf1900Utc - 1)) {
                System.out.println(id);
            }
        }
    }
}

Le code ci-dessus ne produit aucune sortie sur ma machine Windows. Ainsi, tout fuseau horaire qui a un décalage autre que celui standard au début de 1900 comptera cela comme une transition. TZDB elle-même a des données remontant plus tôt que cela, et ne repose sur aucune idée d'un temps standard "fixe" (ce que getRawOffset suppose être un concept valide), les autres bibliothèques n'ont donc pas à introduire cette transition artificielle.

10176
Jon Skeet

Vous avez rencontré une heure locale :

Lorsque l'heure standard locale était sur le point d'atteindre le dimanche 1er janvier 1928, 00:00:00 les horloges ont été tournées en arrière de 0:05:52 au samedi 31 . Décembre 1927, 23:54:08 heure locale standard à la place

Cela n’est pas particulièrement étrange et s’est produit un peu partout à un moment ou à un autre, car les fuseaux horaires ont été modifiés ou modifiés en raison d’actes politiques ou administratifs.

1523
Michael Borgwardt

La morale de cette étrangeté est:

  • Utilisez les dates et heures en UTC chaque fois que possible.
  • Si vous ne pouvez pas afficher une date ou une heure au format UTC, indiquez toujours le fuseau horaire.
  • Si vous ne pouvez pas exiger de date/heure d'entrée en UTC, indiquez un fuseau horaire explicitement indiqué.
611
Raedwald

Lorsque vous incrémentez le temps, vous devez reconvertir en UTC, puis ajouter ou soustraire. Utilisez l'heure locale uniquement pour l'affichage.

De cette façon, vous serez capable de traverser toutes les périodes où des heures ou des minutes se répètent deux fois.

Si vous avez converti en UTC, ajoutez chaque seconde et convertissez l'heure locale en affichage. Tu passerais par 11:54:08 pm LMT - 11:59:59 pm LMT et ensuite 23h54:08 CST - 23h59:59 CST.

338
PatrickO

Au lieu de convertir chaque date, vous utilisez le code suivant 

long difference = (sDt4.getTime() - sDt3.getTime()) / 1000;
System.out.println(difference);

Et voir le résultat est:

1
284
Rajshri

Je suis désolé de le dire, mais la discontinuité temporelle a un peu évolué

JDK 6 il y a deux ans et dans JDK 7 tout récemment dans mise à jour 25 .

Leçon à apprendre: évitez les temps non-UTC à tout prix, sauf peut-être pour l'affichage.

202
user1050755

Comme d'autres l'ont expliqué, il y a une discontinuité dans le temps. Il existe deux décalages possibles dans le fuseau horaire pour 1927-12-31 23:54:08 à Asia/Shanghai, mais un seul décalage pour 1927-12-31 23:54:07. Ainsi, en fonction du décalage utilisé, il existe une différence d'une seconde ou d'une différence de 5 minutes et 53 secondes.

Ce léger décalage des décalages, au lieu des habituelles économies d’heure avancée d’une heure (heure d’été) auxquelles nous sommes habitués, masque un peu le problème.

Notez que la mise à jour 2013a de la base de données de fuseaux horaires a déplacé cette discontinuité quelques secondes plus tôt, mais l'effet serait toujours observable.

Le nouveau package Java.time sur Java 8 permet à l’utilisateur de voir cela plus clairement et de fournir des outils pour le gérer. Donné:

DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.append(DateTimeFormatter.ISO_LOCAL_DATE);
dtfb.appendLiteral(' ');
dtfb.append(DateTimeFormatter.ISO_LOCAL_TIME);
DateTimeFormatter dtf = dtfb.toFormatter();
ZoneId shanghai = ZoneId.of("Asia/Shanghai");

String str3 = "1927-12-31 23:54:07";  
String str4 = "1927-12-31 23:54:08";  

ZonedDateTime zdt3 = LocalDateTime.parse(str3, dtf).atZone(shanghai);
ZonedDateTime zdt4 = LocalDateTime.parse(str4, dtf).atZone(shanghai);

Duration durationAtEarlierOffset = Duration.between(zdt3.withEarlierOffsetAtOverlap(), zdt4.withEarlierOffsetAtOverlap());

Duration durationAtLaterOffset = Duration.between(zdt3.withLaterOffsetAtOverlap(), zdt4.withLaterOffsetAtOverlap());

Alors durationAtEarlierOffset sera une seconde, alors que durationAtLaterOffset sera cinq minutes et 53 secondes.

En outre, ces deux décalages sont les mêmes:

// Both have offsets +08:05:52
ZoneOffset zo3Earlier = zdt3.withEarlierOffsetAtOverlap().getOffset();
ZoneOffset zo3Later = zdt3.withLaterOffsetAtOverlap().getOffset();

Mais ces deux sont différents:

// +08:05:52
ZoneOffset zo4Earlier = zdt4.withEarlierOffsetAtOverlap().getOffset();

// +08:00
ZoneOffset zo4Later = zdt4.withLaterOffsetAtOverlap().getOffset();

Vous pouvez voir le même problème en comparant 1927-12-31 23:59:59 avec 1928-01-01 00:00:00, bien que, dans ce cas, c’est le décalage antérieur qui produit la divergence la plus longue, et c’est la date antérieure qui a deux décalages possibles.

Une autre façon de procéder consiste à vérifier si une transition est en cours. On peut faire ça comme ça:

// Null
ZoneOffsetTransition zot3 = shanghai.getRules().getTransition(ld3.toLocalDateTime);

// An overlap transition
ZoneOffsetTransition zot4 = shanghai.getRules().getTransition(ld3.toLocalDateTime);

Vous pouvez vérifier si la transition est un chevauchement (auquel cas il existe plusieurs décalages valides pour cette date/heure - ou un espace vide), auquel cas cette date/heure n'est pas valide pour cet identifiant de zone - en utilisant les fonctions isOverlap() et isGap(). méthodes sur zot4.

J'espère que cela aidera les gens à gérer ce type de problème une fois Java 8 largement disponible, ou à ceux qui utilisent Java 7 qui adoptent le backport JSR 310.

180
Daniel C. Sobral

IMHO, la localisation implicite omniprésente en Java est son principal défaut de conception. C’est peut-être destiné aux interfaces utilisateur, mais franchement, qui utilise réellement Java pour les interfaces utilisateur aujourd’hui, à l’exception de certains IDE où vous pouvez ignorer la localisation, car les programmeurs ne sont pas exactement le public cible. Vous pouvez le réparer (en particulier sur les serveurs Linux) en:

  • export LC_ALL = C TZ = UTC
  • régler l'horloge de votre système sur UTC
  • n'utilisez jamais d'implémentations localisées sauf en cas d'absolue nécessité (c.-à-d. pour l'affichage uniquement)

Pour le Java Community Process members, je recommande:

  • les méthodes localisées ne sont pas les méthodes par défaut, mais nécessitent que l'utilisateur demande explicitement la localisation.
  • utilisez UTF-8/UTC comme valeur par défaut FIXED car il s'agit simplement de la valeur par défaut aujourd'hui. Il n'y a aucune raison de faire autre chose, sauf si vous voulez produire des threads comme celui-ci.

Je veux dire, allez, les variables statiques globales ne sont-elles pas un motif anti-OO? Rien d'autre n'est ces défauts omniprésents donnés par certaines variables d'environnement rudimentaires .......

148
user1050755

C'est un changement d'heure en 1927 à Shanghai, comme d'autres l'ont mentionné. 

Fondamentalement, ce 23:54:07 à Shanghai, l’heure locale, a pris un peu de temps puis il est passé au lendemain à 00:00:00. Mais ensuite, il est revenu à 23:54:08 pour l'heure standard locale. Donc, c'est pourquoi la différence est de 343 secondes et non d'une seconde.

Cela peut aussi nuire à d'autres endroits comme les États-Unis. Aux États-Unis, ils ont l'heure d'été. Lorsque l'heure d'été commence, l'heure avance d'une heure. Mais au bout d’un moment, l’heure d’été se termine, elle recule d’une heure dans le fuseau horaire standard. Ainsi, parfois, lorsque l'on compare les temps aux États-Unis, la différence est d'environ 3600 secondes et non d'une seconde.

Il est préférable d'utiliser UTC où le temps ne change pas, sauf si nécessaire d'utiliser un temps autre que UTC, comme dans l'affichage.

0
zixuan