blob: 2afbd333049d3a09dbe4a3e9b5bb747083369ebe [file] [log] [blame]
/*
* 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<TimeZoneNames.NameType> DST_NAME_TYPES =
Collections.unmodifiableSet(
EnumSet.of(
TimeZoneNames.NameType.LONG_DAYLIGHT,
TimeZoneNames.NameType.SHORT_DAYLIGHT));
private static final EnumSet<TimeZoneNames.NameType> 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:
* <ul>
* <li>Length of the matched name. Longest name matched to the given {@code s} has the
* highest priority.</li>
* <li>The current time zone and meta zones possible in the current country have higher
* priority than other zones.</li>
* <li>If only meta zones are matched, the country/region in the locale is used to select
* a reference time zone. For example, if the name is "Pacific Standard Time" and the country
* is US, America/Los_Angeles is returned.</li>
* </ul>
*
* @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<TimeZoneNames.MatchInfo> matchedInfos =
timeZoneNames.find(text, start, STANDARD_AND_DST_TYPES);
if (matchedInfos.isEmpty()) {
return null;
}
List<TimeZoneNames.MatchInfo> 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<String> 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()));
}
}