简体   繁体   中英

The pulldown list get using ZoneId is too long, how to simplify the list?

Using Spring ZoneId , the code is:

 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;
}

Output is:

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

But the list is too long, it contains more than 500 options, and it is full of repetition. For example, ChongQing exists twice:

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

How to use this to be a shorter list?

Some zones appear twice due to the backward file : this file maps timezones names that were changed, but the old names were kept as synonyms, probably due to retrocompatibility reasons. If you take a look at the file, you'll find these entries:

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

Which means that Asia/Shanghai is the current timezone name for Asia/Chungking and Asia/Chongqing timezones. One way to check if two zones are the same is to compare their respective 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()));

Both comparisons print true , which means all the ZoneId objects above represent the same timezone. Unfortunately, comparing the ZoneId objects directly doesn' work, only by comparing the ZoneRules we can know if two zones are the same.

So one way to reduce your list is to ignore the synonyms. If two or more zones have the same ZoneRules , you can consider just one of them to be in the list.

For that, you could create an auxiliary class that wraps a ZoneId , and has an equals method that compares the ZoneRules (and also a hashcode method, as it's a 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;
    }
}

Then I create a Set of UniqueZone instances, using all the zone IDs available:

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());

This will create a Set with unique instances of UniqueZone , using the criteria defined in the equals method (if two ZoneId instances have the same ZoneRules , they are considered the same, and only one is inserted in the Set ).

Now we just need to map this Set back to a List :

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

And now you can use the zoneList the same way you're already using.


With the code above, in JDK 1.8.0_144, the list has 385 elements, and only Asia/Chungking is on the list. As I'm using streams, you can't really predict which one of the three ( Asia/Chungking , Asia/Chongqing or Asia/Shanghai ) will be in the list.

If you want to have some control over it, you can explicity filter the names you don't want, by creating a list of names to be excluded. Suppose that I want to exclude Asia/Chungking and Asia/Chongqing (so only Asia/Shanghai will be in the list). I could do:

// 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());

With this, only Asia/Shanghai will be in the list.

PS: If this list of excluded names is long enough to cover all synonym cases, you don't even need the UniqueZone class (the filter will do all the job). But you'll need to previously know all the synonyms to be excluded.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM