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




date get (8)

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 ld4-ld3 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

Answers

Comme d'autres l'ont expliqué, il y a une discontinuité dans le temps. Il y a deux décalages possibles du 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 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());

Ensuite, durationAtEarlierOffset durera une seconde, tandis que durationAtLaterOffset durera 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 le 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. Nous pouvons faire ceci 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 espace vide - auquel cas cette date / heure n'est pas valide pour cet identifiant de zone) en utilisant la isOverlap() et les méthodes isGap() 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.


IMHO, la localisation implicite et 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 pour lesquels 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 votre horloge système sur UTC
  • n'utilisez jamais d'implémentations localisées sauf en cas d'absolue nécessité (par exemple, pour l'affichage uniquement)

Aux membres du Java Community Process , 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 plutôt 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 modèle anti-OO? Rien d'autre n'est ces défauts omniprésents donnés par certaines variables d'environnement rudimentaires .......


Vous avez rencontré une discontinuité de l'heure locale :

Lorsque l'heure standard locale était sur le point d'atteindre le dimanche 1er janvier 1928, les horloges étaient remises en arrière de 0:05:52 heures au samedi 31 décembre, à 19h54 (heure locale).

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.


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. Vous passeriez par 11:54:08 pm LMT - 11:59:59 pm LMT et ensuite 23:54:08 pm CST - 23:59:59 pm CST.


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é.

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

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 le plus tard possible 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: Arrêtez appuyez! 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 collecte des questions comme celle-ci dans Noda Time, sous la forme de TZDB ... Le test a maintenant été modifié, mais il va sans dire: 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 vers 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 tous les fuseaux horaires comme étant simplement à leur heure normale pour tout instant avant le début de l’heure 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 qui remontent plus tôt que cela et ne se fie à aucune idée d'heure standard "fixe" (ce que getRawOffset suppose être un concept valide), les autres bibliothèques n'ont donc pas à introduire cette transition artificielle.


Certaines alternatives incluent: -

Cependant, l'un des principaux avantages de l'utilisation de JAXB est qu'il est livré avec la distribution Java.







java date timezone