2017-09-11 3 views
1

avec Spring ZoneId, le code est:La liste déroulante obtenir en utilisant ZoneId est trop long, comment simplifier la liste?

private static Map<String, String> getAllZoneIds() { 
    final List<String> zoneList = new ArrayList<>(ZoneId.getAvailableZoneIds()); 
    final Map<String, String> zones = new HashMap<>(); 

    final LocalDateTime dt = LocalDateTime.now(); 

    for (final String zoneId : zoneList) { 

     final ZoneId zone = ZoneId.of(zoneId); 
     final ZonedDateTime zdt = dt.atZone(zone); 
     final ZoneOffset zos = zdt.getOffset(); 

     //replace Z to +00:00 
     final String offset = zos.getId().replaceAll("Z", "+00:00"); 

     zones.put(zone.toString(), offset); 

    } 

    final Map<String, String> sortZones = new LinkedHashMap<>(); 
    zones.entrySet().stream().sorted((left, right) -> { 
     final String leftValue = left.getValue(); 
     final String leftKey = left.getKey(); 
     final String rightValue = right.getValue(); 
     final String rightKey = right.getKey(); 

     if (leftValue.equalsIgnoreCase(rightValue)) { 
      return leftKey.compareTo(rightKey); 
     } else { 
      if (leftValue.charAt(0) == '+' && leftValue.charAt(0) == rightValue.charAt(0)) { 
       return leftValue.compareTo(rightValue); 
      } else { 
       return rightValue.compareTo(leftValue); 
      } 
     } 
    }).forEachOrdered(e -> { 
     sortZones.put(e.getKey(), e.getKey() + " (UTC" + e.getValue() + ")"); 
    }); 
    System.out.println(sortZones); 
    return sortZones; 
} 

sortie est:

.... 
Antarctica/Casey->Antarctica/Casey (UTC+08:00) 
Asia/Brunei->Asia/Brunei (UTC+08:00) 
Asia/Chongqing->Asia/Chongqing (UTC+08:00) 
Asia/Chungking->Asia/Chungking (UTC+08:00) 
Asia/Harbin->Asia/Harbin (UTC+08:00) 
Asia/Hong_Kong->Asia/Hong_Kong (UTC+08:00) 
Asia/Hovd->Asia/Hovd (UTC+08:00) 
Asia/Irkutsk->Asia/Irkutsk (UTC+08:00) 
Asia/Kuala_Lumpur->Asia/Kuala_Lumpur (UTC+08:00) 
Asia/Kuching->Asia/Kuching (UTC+08:00) 
Asia/Macao->Asia/Macao (UTC+08:00) 
Asia/Macau->Asia/Macau (UTC+08:00) 
Asia/Makassar->Asia/Makassar (UTC+08:00) 
Asia/Manila->Asia/Manila (UTC+08:00) 
Asia/Shanghai->Asia/Shanghai (UTC+08:00) 
Asia/Singapore->Asia/Singapore (UTC+08:00) 
Asia/Taipei->Asia/Taipei (UTC+08:00) 
Asia/Ujung_Pandang->Asia/Ujung_Pandang (UTC+08:00) 
Australia/Perth->Australia/Perth (UTC+08:00) 
Australia/West->Australia/West (UTC+08:00) 
Etc/GMT-8->Etc/GMT-8 (UTC+08:00) 
Hongkong->Hongkong (UTC+08:00) 
PRC->PRC (UTC+08:00) 
Singapore->Singapore (UTC+08:00) 
Asia/Pyongyang->Asia/Pyongyang (UTC+08:30) 
.... 

Mais la liste est trop longue, il contient plus de 500 options et il est plein de répétition. Par exemple, ChongQing existe deux fois:

Asia/Chongqing->Asia/Chongqing (UTC+08:00) 
Asia/Chungking->Asia/Chungking (UTC+08:00) 

Comment utiliser cette liste pour la rendre plus courte?

+0

et Singapour aussi – daxue

Répondre

2

Certaines zones apparaissent deux fois en raison de la backward file: ce fichier de cartes TimeZones noms qui ont été modifiés, mais les anciens noms ont été conservés comme synonymes, probablement pour des raisons de rétrocompatibilité. Si vous jetez un oeil au dossier, vous trouverez ces entrées:

Link Asia/Shanghai  Asia/Chongqing 
Link Asia/Shanghai  Asia/Chungking 

Ce qui signifie que Asia/Shanghai est le nom du fuseau horaire actuel pour Asia/Chungking et Asia/Chongqing timezones. Une façon de vérifier si deux zones sont les mêmes est de comparer leurs respectifs ZoneRules:

ZoneId chungking = ZoneId.of("Asia/Chungking"); 
ZoneId chongqing = ZoneId.of("Asia/Chongqing"); 
ZoneId shanghai = ZoneId.of("Asia/Shanghai"); 
System.out.println(chungking.getRules().equals(chongqing.getRules())); 
System.out.println(chungking.getRules().equals(shanghai.getRules())); 

Les deux comparaisons imprimer true, ce qui signifie que tous les objets ZoneId ci-dessus représentent le même fuseau horaire. Malheureusement, la comparaison des objets ZoneId ne fonctionne pas directement, mais en comparant le ZoneRules, nous pouvons savoir si deux zones sont identiques.

Donc une façon de réduire votre liste est d'ignorer les synonymes. Si deux zones ou plus ont la même ZoneRules, vous pouvez considérer qu'une seule d'entre elles figure dans la liste.

Pour cela, vous pouvez créer une classe auxiliaire qui enveloppe un ZoneId, et a une méthode equals qui compare la ZoneRules (et aussi une méthode hashcode, car il est un good practice to properly implement both).

public class UniqueZone { 

    private ZoneId zone; 

    public UniqueZone(ZoneId zone) { 
     this.zone = zone; 
    } 

    public ZoneId getZone() { 
     return zone; 
    } 

    // hashcode and equals use the ZoneRules 
    @Override 
    public int hashCode() { 
     final int prime = 31; 
     int result = 1; 
     result = prime * result + ((zone == null) ? 0 : zone.getRules().hashCode()); 
     return result; 
    } 

    @Override 
    public boolean equals(Object obj) { 
     if (this == obj) 
      return true; 
     if (!(obj instanceof UniqueZone)) 
      return false; 
     UniqueZone other = (UniqueZone) obj; 
     if (zone == null) { 
      if (other.zone != null) 
       return false; 
     // two zones are equal if the ZoneRules are the same 
     } else if (!zone.getRules().equals(other.zone.getRules())) 
      return false; 
     return true; 
    } 
} 

Puis-je créer un Set de UniqueZone instances, en utilisant toute la zone ID disponibles:

Set<UniqueZone> uniqueZones = ZoneId 
    // get all IDs 
    .getAvailableZoneIds().stream() 
    // map to a UniqueZone (so zones with the same ZoneRules won't be duplicated) 
    .map(zoneName -> new UniqueZone(ZoneId.of(zoneName))) 
    // create Set 
    .collect(Collectors.toSet()); 

Cela va créer un Set avec des instances uniques de UniqueZone, en utilisant les critères définis dans la méthode equals (si deux instances ZoneId ont le même ZoneRules, elles sont considérées comme identiques et une seule est insérée dans le Set).

Maintenant, nous avons juste besoin de mapper ce Set retour à un List:

List<String> zoneList = uniqueZones.stream() 
    // map back to the zoneId 
    .map(u -> u.getZone().getId()) 
    // create list 
    .collect(Collectors.toList()); 

Et maintenant, vous pouvez utiliser le zoneList de la même façon que vous utilisez déjà.


Avec le code ci-dessus, dans le JDK 1.8.0_144, la liste a 385 éléments, et seulement Asia/Chungking est sur la liste.Comme j'utilise les flux, vous ne pouvez pas vraiment prédire lequel des trois (Asia/Chungking, Asia/Chongqing ou Asia/Shanghai) sera dans la liste.

Si vous souhaitez avoir un certain contrôle sur celui-ci, vous pouvez explicitement filtrer les noms que vous ne voulez pas, en créant une liste de noms à exclure. Supposons que je veux exclure Asia/Chungking et Asia/Chongqing (donc seulement Asia/Shanghai sera dans la liste). Je pouvais faire:

// zone names to be excluded 
Set<String> excludedNames = new HashSet<>(); 
excludedNames.add("Asia/Chungking"); 
excludedNames.add("Asia/Chongqing"); 

Set<UniqueZone> uniqueZones = ZoneId 
    // get all IDs 
    .getAvailableZoneIds().stream() 
    // filter names I don't want 
    .filter(zoneName -> ! excludedNames.contains(zoneName)) 
    // map to a UniqueZone (so zones with the same ZoneRules won't be duplicated) 
    .map(zoneName -> new UniqueZone(ZoneId.of(zoneName))) 
    // create Set 
    .collect(Collectors.toSet()); 

Avec cela, seulement Asia/Shanghai sera dans la liste.

PS: Si cette liste de noms exclus est assez long pour couvrir tous les cas de synonymes, vous ne même pas besoin de la classe UniqueZone (le filtre fera tout le travail). Mais vous devrez d'abord connaître tous les synonymes à exclure.