/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.icu.text; import android.icu.text.TimeZoneNames; import android.icu.util.TimeZone; import android.icu.util.ULocale; import libcore.api.IntraCoreApi; import libcore.util.NonNull; import libcore.util.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Set; /** * Provide extra functionalities on top of {@link TimeZoneNames} public APIs. * * @hide */ @IntraCoreApi public class ExtendedTimeZoneNames { private static final Set DST_NAME_TYPES = Collections.unmodifiableSet( EnumSet.of( TimeZoneNames.NameType.LONG_DAYLIGHT, TimeZoneNames.NameType.SHORT_DAYLIGHT)); private static final EnumSet STANDARD_AND_DST_TYPES = EnumSet.of( TimeZoneNames.NameType.SHORT_STANDARD, TimeZoneNames.NameType.LONG_STANDARD, TimeZoneNames.NameType.SHORT_DAYLIGHT, TimeZoneNames.NameType.LONG_DAYLIGHT); private final ULocale locale; private final TimeZoneNames timeZoneNames; /** * A class representing the return result of {@link #matchName(CharSequence, int, String)} * * @hide */ @IntraCoreApi public static final class Match { private final int matchLength; private final @NonNull String tzId; private final boolean isDst; private Match(int matchLength, @NonNull String tzId, boolean isDst) { this.matchLength = matchLength; this.tzId = tzId; this.isDst = isDst; } /** * Returns the number of chars in the matched name. * * @hide */ @IntraCoreApi public int getMatchLength() { return matchLength; } /** * Returns the time zone id associated with the matched name. * * @hide */ @IntraCoreApi public @NonNull String getTzId() { return tzId; } /** * Returns true if the matched name is a display name for daylight saving time. For example, * returns true for "Pacific Daylight Time", but false for "Pacific Standard Time". * * @hide */ @IntraCoreApi public boolean isDst() { return isDst; } } private ExtendedTimeZoneNames(ULocale locale) { this.locale = locale; this.timeZoneNames = TimeZoneNames.getInstance(locale); } /** * Returns an instance of {@link ExtendedTimeZoneNames}. * * @hide */ @IntraCoreApi public static @NonNull ExtendedTimeZoneNames getInstance(@NonNull ULocale locale) { return new ExtendedTimeZoneNames(locale); } /** * Returns the underlying {@link TimeZoneNames} instance. * * @hide */ @IntraCoreApi public @NonNull TimeZoneNames getTimeZoneNames() { return timeZoneNames; } /** * Returns {@link Match} if a time zone name in ICU can be matched against the input * CharSequence {@code s}. * The best match is found by the following principles: * * * @param text input string to be matched against time zone names in ICU * @param start the begin index in the CharSequence {@code s} * @param currentTzId the time zone ID prioritized to be matched if multiple time zone IDs can * be matched and this is one of the matched IDs. * @return null if no match is found * * @hide */ @IntraCoreApi public @Nullable Match matchName(@NonNull CharSequence text, int start, @NonNull String currentTzId) { currentTzId = TimeZone.getCanonicalID(currentTzId); Collection matchedInfos = timeZoneNames.find(text, start, STANDARD_AND_DST_TYPES); if (matchedInfos.isEmpty()) { return null; } List maxLengthMatchInfos = new ArrayList<>(); int maxMatchedInfoLength = 0; for (TimeZoneNames.MatchInfo matchInfo : matchedInfos) { if (matchInfo.matchLength() > maxMatchedInfoLength) { maxMatchedInfoLength = matchInfo.matchLength(); maxLengthMatchInfos.clear(); } if (matchInfo.matchLength() >= maxMatchedInfoLength) { maxLengthMatchInfos.add(matchInfo); } } Set metaZonesInCurrentZone = timeZoneNames.getAvailableMetaZoneIDs(currentTzId); TimeZoneNames.MatchInfo tzMatchInfo = null; for (TimeZoneNames.MatchInfo matchInfo : maxLengthMatchInfos) { if (matchInfo.tzID() != null && matchInfo.tzID().equals(currentTzId)) { return matchedTimeZone(matchInfo.tzID(), matchInfo); } if (matchInfo.mzID() != null && metaZonesInCurrentZone.contains(matchInfo.mzID())) { return matchedTimeZone(currentTzId, matchInfo); } if (matchInfo.tzID() != null) { tzMatchInfo = matchInfo; } } if (tzMatchInfo != null) { return matchedTimeZone(tzMatchInfo.tzID(), tzMatchInfo); } String region = locale.getCountry(); if (region == null || region.isEmpty()) { // An UN M49 code to represent the world. See TimeZoneNames#getReferenceZoneID(). region = "001"; } for (TimeZoneNames.MatchInfo matchInfo : maxLengthMatchInfos) { if (matchInfo.mzID() != null) { String timeZoneId = timeZoneNames.getReferenceZoneID(matchInfo.mzID(), region); if (timeZoneId != null) { return matchedTimeZone(timeZoneId, matchInfo); } } } return null; } private static Match matchedTimeZone( String timeZoneId, TimeZoneNames.MatchInfo matchInfo) { return new Match( matchInfo.matchLength(), timeZoneId, DST_NAME_TYPES.contains(matchInfo.nameType())); } }