Question Pourquoi soustraire ces deux temps (en 1927) en donnant un résultat étrange?


Si j'exécute le programme suivant, qui analyse deux chaînes de date faisant référence à des temps d'une seconde 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 est-ce ld4-ld3 ne pas 1 (comme je m'attendrais à la différence d'une seconde dans le temps), mais 353?

Si je change les dates aux heures 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

6015
2017-07-27 08:15


origine


Réponses:


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

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

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

MODIFIER: Arrêtez la presse! L'histoire change ...

La question originale ne montrerait plus tout à fait le même comportement, si reconstruit 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é changé, mais il va juste à montrer - pas même les données historiques sont en sécurité.

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

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

MODIFIER: Pour répondre à une question autour d'une transition à 1900 ... on dirait que les ficelles d'implémentation de timezone Java tout les fuseaux horaires étant simplement dans leur heure normale pour n'importe quel instant 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 mon ordinateur Windows. Donc, tout fuseau horaire qui a un décalage autre que son standard au début de 1900 le comptera comme une transition. TZDB lui-même a quelques données qui remontent plus tôt que cela, et ne repose sur aucune idée d'un temps standard "fixe" (qui est ce que getRawOffset suppose d'être un concept valable), d'autres bibliothèques n'ont donc pas besoin d'introduire cette transition artificielle.


9719
2017-07-27 08:31



Vous avez rencontré un discontinuité temporelle locale:

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

Ce n'est pas particulièrement étrange et cela s'est passé à peu près partout à un moment ou à un autre car les fuseaux horaires ont été changés ou changés en raison d'actions politiques ou administratives.


1462
2017-07-27 08:38



La morale de cette étrangeté est:

  • Utilisez les dates et les heures en UTC dans la mesure du possible.
  • Si vous ne pouvez pas afficher une date ou une heure en UTC, indiquez toujours le fuseau horaire.
  • Si vous ne pouvez pas demander une date / heure d'entrée en UTC, demandez un fuseau horaire explicitement indiqué.

579
2017-07-28 11:50



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

De cette façon, vous serez en mesure de marcher à travers les périodes où les heures ou les minutes se produisent deux fois.

Si vous avez converti en UTC, ajoutez chaque seconde et convertissez en heure locale pour l'affichage. Vous passeriez à travers 23:54:08 pm. LMT - 23:59:59 LMT puis 23h54: 08h CST - 23:59:59 CST.


319
2017-07-30 16:55



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

264
2018-05-16 05:31



Je suis désolé de le dire, mais la discontinuité du temps a bougé un peu

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

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


187
2018-02-17 22:44



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

Ce léger décalage des compensations, au lieu de l'heure habituelle d'une heure (l'heure d'été) à laquelle nous sommes habitués, obscurcit un peu le problème.

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

Le nouveau java.time package sur Java 8 laissez voir voir plus clairement, et 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 durationAtLaterOffset sera de cinq minutes et 53 secondes.

En outre, ces deux offsets 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:00Cependant, 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 d'aborder cela est de vérifier s'il y a une transition en cours. Nous pouvons le faire comme ceci:

// 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 intervalle - auquel cas cette date / heure n'est pas valide pour cet identifiant de zone - en utilisant le isOverlap() et isGap() méthodes sur zot4.

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


167
2018-01-03 14:43