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.