mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
805dd78c93
Bug 924839 - Remove a patch already part of ICU 52.1. See http://bugs.icu-project.org/trac/ticket/10283 but also note the relevant code was removed completely upstream. r=glandium * * * Bug 924839 - Remove another patch already part of ICU 52.1. See http://bugs.icu-project.org/trac/ticket/10290 for that. r=gaston * * * Bug 924839 - Remove another patch already in ICU 52.1. See http://bugs.icu-project.org/trac/ticket/10045 for more. r=Norbert * * * Bug 924839 - Remove another patch already applied upstream. See http://bugs.icu-project.org/trac/changeset/32937 for more. r=gaston * * * Bug 924839 - Update the ICU update script to update to 52.1, *without* applying any of our local patches. r=glandium * * * Bug 924839 - Make the ICU update script only do updating within intl/icu/source and nowhere else. r=glandium * * * Bug 924839 - Implement the changes that would be made by |cd intl/; ./update-icu.sh http://source.icu-project.org/repos/icu/icu/tags/release-52-1/;|, run with the prior changesets' changes made (thus not applying any of our local patches). These changes don't actually work without subsequent adjustments, but this provides a codebase upon which those adjustments can be made, for the purpose of generating local patches to be kept in intl/icu-patches/. rs=the-usual-suspects * * * Bug 924839 - Update the bug 899722 local patch to make runConfigureICU not override CC/CXX on BSD systems. r=gaston * * * Bug 924839 - Update the bug 724533 patch that makes ICU builds with MozillaBuild on Windows. r=glandium * * * Bug 924839 - Import an upstream patch fixing the genrb tool to properly handle the -R (--omitCollationRules) option. See http://bugs.icu-project.org/trac/ticket/10043 for the original bug report and a link to the ultimate upstream landing. r=Norbert * * * Bug 924839 - Import the upstream fix for http://bugs.icu-project.org/trac/ticket/10486 so that ICU with -DU_USING_ICU_NAMESPACE=0 will compile on Windows. r=Norbert * * * Bug 924839 - Adjust the update script to update ICU, then to apply all local patches (rather than skipping the second step). Thus if the update script is properly run, now, the final result should be no changes at all to the tree. NOT REVIEWED YET * * * Bug 924839 - Update jstests that depend on CLDR locale data to match CLDR 24. r=Norbert
2778 lines
90 KiB
C++
2778 lines
90 KiB
C++
/*
|
|
*******************************************************************************
|
|
* Copyright (C) 2011-2013, International Business Machines Corporation and
|
|
* others. All Rights Reserved.
|
|
*******************************************************************************
|
|
*/
|
|
|
|
#include "unicode/utypes.h"
|
|
|
|
#if !UCONFIG_NO_FORMATTING
|
|
|
|
#include "unicode/calendar.h"
|
|
#include "unicode/tzfmt.h"
|
|
#include "unicode/numsys.h"
|
|
#include "unicode/uchar.h"
|
|
#include "unicode/udat.h"
|
|
#include "tzgnames.h"
|
|
#include "cmemory.h"
|
|
#include "cstring.h"
|
|
#include "putilimp.h"
|
|
#include "uassert.h"
|
|
#include "ucln_in.h"
|
|
#include "umutex.h"
|
|
#include "uresimp.h"
|
|
#include "ureslocs.h"
|
|
#include "uvector.h"
|
|
#include "zonemeta.h"
|
|
#include "tznames_impl.h" // TextTrieMap
|
|
|
|
U_NAMESPACE_BEGIN
|
|
|
|
// Bit flags used by the parse method.
|
|
// The order must match UTimeZoneFormatStyle enum.
|
|
#define ISO_Z_STYLE_FLAG 0x0080
|
|
#define ISO_LOCAL_STYLE_FLAG 0x0100
|
|
static const int16_t STYLE_PARSE_FLAGS[] = {
|
|
0x0001, // UTZFMT_STYLE_GENERIC_LOCATION,
|
|
0x0002, // UTZFMT_STYLE_GENERIC_LONG,
|
|
0x0004, // UTZFMT_STYLE_GENERIC_SHORT,
|
|
0x0008, // UTZFMT_STYLE_SPECIFIC_LONG,
|
|
0x0010, // UTZFMT_STYLE_SPECIFIC_SHORT,
|
|
0x0020, // UTZFMT_STYLE_LOCALIZED_GMT,
|
|
0x0040, // UTZFMT_STYLE_LOCALIZED_GMT_SHORT,
|
|
ISO_Z_STYLE_FLAG, // UTZFMT_STYLE_ISO_BASIC_SHORT,
|
|
ISO_LOCAL_STYLE_FLAG, // UTZFMT_STYLE_ISO_BASIC_LOCAL_SHORT,
|
|
ISO_Z_STYLE_FLAG, // UTZFMT_STYLE_ISO_BASIC_FIXED,
|
|
ISO_LOCAL_STYLE_FLAG, // UTZFMT_STYLE_ISO_BASIC_LOCAL_FIXED,
|
|
ISO_Z_STYLE_FLAG, // UTZFMT_STYLE_ISO_BASIC_FULL,
|
|
ISO_LOCAL_STYLE_FLAG, // UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL,
|
|
ISO_Z_STYLE_FLAG, // UTZFMT_STYLE_ISO_EXTENDED_FIXED,
|
|
ISO_LOCAL_STYLE_FLAG, // UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FIXED,
|
|
ISO_Z_STYLE_FLAG, // UTZFMT_STYLE_ISO_EXTENDED_FULL,
|
|
ISO_LOCAL_STYLE_FLAG, // UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL,
|
|
0x0200, // UTZFMT_STYLE_ZONE_ID,
|
|
0x0400, // UTZFMT_STYLE_ZONE_ID_SHORT,
|
|
0x0800 // UTZFMT_STYLE_EXEMPLAR_LOCATION
|
|
};
|
|
|
|
static const char gZoneStringsTag[] = "zoneStrings";
|
|
static const char gGmtFormatTag[]= "gmtFormat";
|
|
static const char gGmtZeroFormatTag[] = "gmtZeroFormat";
|
|
static const char gHourFormatTag[]= "hourFormat";
|
|
|
|
static const UChar TZID_GMT[] = {0x0045, 0x0074, 0x0063, 0x002F, 0x0047, 0x004D, 0x0054, 0}; // Etc/GMT
|
|
static const UChar UNKNOWN_ZONE_ID[] = {
|
|
0x0045, 0x0074, 0x0063, 0x002F, 0x0055, 0x006E, 0x006B, 0x006E, 0x006F, 0x0077, 0x006E, 0}; // Etc/Unknown
|
|
static const UChar UNKNOWN_SHORT_ZONE_ID[] = {0x0075, 0x006E, 0x006B, 0}; // unk
|
|
static const UChar UNKNOWN_LOCATION[] = {0x0055, 0x006E, 0x006B, 0x006E, 0x006F, 0x0077, 0x006E, 0}; // Unknown
|
|
|
|
static const UChar DEFAULT_GMT_PATTERN[] = {0x0047, 0x004D, 0x0054, 0x007B, 0x0030, 0x007D, 0}; // GMT{0}
|
|
//static const UChar DEFAULT_GMT_ZERO[] = {0x0047, 0x004D, 0x0054, 0}; // GMT
|
|
static const UChar DEFAULT_GMT_POSITIVE_HM[] = {0x002B, 0x0048, 0x003A, 0x006D, 0x006D, 0}; // +H:mm
|
|
static const UChar DEFAULT_GMT_POSITIVE_HMS[] = {0x002B, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0}; // +H:mm:ss
|
|
static const UChar DEFAULT_GMT_NEGATIVE_HM[] = {0x002D, 0x0048, 0x003A, 0x006D, 0x006D, 0}; // -H:mm
|
|
static const UChar DEFAULT_GMT_NEGATIVE_HMS[] = {0x002D, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0}; // -H:mm:ss
|
|
static const UChar DEFAULT_GMT_POSITIVE_H[] = {0x002B, 0x0048, 0}; // +H
|
|
static const UChar DEFAULT_GMT_NEGATIVE_H[] = {0x002D, 0x0048, 0}; // -H
|
|
|
|
static const UChar32 DEFAULT_GMT_DIGITS[] = {
|
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034,
|
|
0x0035, 0x0036, 0x0037, 0x0038, 0x0039
|
|
};
|
|
|
|
static const UChar DEFAULT_GMT_OFFSET_SEP = 0x003A; // ':'
|
|
|
|
static const UChar ARG0[] = {0x007B, 0x0030, 0x007D}; // "{0}"
|
|
static const int32_t ARG0_LEN = 3;
|
|
|
|
static const UChar DEFAULT_GMT_OFFSET_MINUTE_PATTERN[] = {0x006D, 0x006D, 0}; // "mm"
|
|
static const UChar DEFAULT_GMT_OFFSET_SECOND_PATTERN[] = {0x0073, 0x0073, 0}; // "ss"
|
|
|
|
static const UChar ALT_GMT_STRINGS[][4] = {
|
|
{0x0047, 0x004D, 0x0054, 0}, // GMT
|
|
{0x0055, 0x0054, 0x0043, 0}, // UTC
|
|
{0x0055, 0x0054, 0, 0}, // UT
|
|
{0, 0, 0, 0}
|
|
};
|
|
|
|
// Order of GMT offset pattern parsing, *_HMS must be evaluated first
|
|
// because *_HM is most likely a substring of *_HMS
|
|
static const int32_t PARSE_GMT_OFFSET_TYPES[] = {
|
|
UTZFMT_PAT_POSITIVE_HMS,
|
|
UTZFMT_PAT_NEGATIVE_HMS,
|
|
UTZFMT_PAT_POSITIVE_HM,
|
|
UTZFMT_PAT_NEGATIVE_HM,
|
|
UTZFMT_PAT_POSITIVE_H,
|
|
UTZFMT_PAT_NEGATIVE_H,
|
|
-1
|
|
};
|
|
|
|
static const UChar SINGLEQUOTE = 0x0027;
|
|
static const UChar PLUS = 0x002B;
|
|
static const UChar MINUS = 0x002D;
|
|
static const UChar ISO8601_UTC = 0x005A; // 'Z'
|
|
static const UChar ISO8601_SEP = 0x003A; // ':'
|
|
|
|
static const int32_t MILLIS_PER_HOUR = 60 * 60 * 1000;
|
|
static const int32_t MILLIS_PER_MINUTE = 60 * 1000;
|
|
static const int32_t MILLIS_PER_SECOND = 1000;
|
|
|
|
// Maximum offset (exclusive) in millisecond supported by offset formats
|
|
static int32_t MAX_OFFSET = 24 * MILLIS_PER_HOUR;
|
|
|
|
// Maximum values for GMT offset fields
|
|
static const int32_t MAX_OFFSET_HOUR = 23;
|
|
static const int32_t MAX_OFFSET_MINUTE = 59;
|
|
static const int32_t MAX_OFFSET_SECOND = 59;
|
|
|
|
static const int32_t UNKNOWN_OFFSET = 0x7FFFFFFF;
|
|
|
|
static const int32_t ALL_SIMPLE_NAME_TYPES = UTZNM_LONG_STANDARD | UTZNM_LONG_DAYLIGHT | UTZNM_SHORT_STANDARD | UTZNM_SHORT_DAYLIGHT | UTZNM_EXEMPLAR_LOCATION;
|
|
static const int32_t ALL_GENERIC_NAME_TYPES = UTZGNM_LOCATION | UTZGNM_LONG | UTZGNM_SHORT;
|
|
|
|
#define DIGIT_VAL(c) (0x0030 <= (c) && (c) <= 0x0039 ? (c) - 0x0030 : -1)
|
|
#define MAX_OFFSET_DIGITS 6
|
|
|
|
// Time Zone ID/Short ID trie
|
|
static TextTrieMap *gZoneIdTrie = NULL;
|
|
static icu::UInitOnce gZoneIdTrieInitOnce = U_INITONCE_INITIALIZER;
|
|
|
|
static TextTrieMap *gShortZoneIdTrie = NULL;
|
|
static icu::UInitOnce gShortZoneIdTrieInitOnce = U_INITONCE_INITIALIZER;
|
|
|
|
static UMutex gLock = U_MUTEX_INITIALIZER;
|
|
|
|
U_CDECL_BEGIN
|
|
/**
|
|
* Cleanup callback func
|
|
*/
|
|
static UBool U_CALLCONV tzfmt_cleanup(void)
|
|
{
|
|
if (gZoneIdTrie != NULL) {
|
|
delete gZoneIdTrie;
|
|
}
|
|
gZoneIdTrie = NULL;
|
|
gZoneIdTrieInitOnce.reset();
|
|
|
|
if (gShortZoneIdTrie != NULL) {
|
|
delete gShortZoneIdTrie;
|
|
}
|
|
gShortZoneIdTrie = NULL;
|
|
gShortZoneIdTrieInitOnce.reset();
|
|
|
|
return TRUE;
|
|
}
|
|
U_CDECL_END
|
|
|
|
// ------------------------------------------------------------------
|
|
// GMTOffsetField
|
|
//
|
|
// This class represents a localized GMT offset pattern
|
|
// item and used by TimeZoneFormat
|
|
// ------------------------------------------------------------------
|
|
class GMTOffsetField : public UMemory {
|
|
public:
|
|
enum FieldType {
|
|
TEXT = 0,
|
|
HOUR = 1,
|
|
MINUTE = 2,
|
|
SECOND = 4
|
|
};
|
|
|
|
virtual ~GMTOffsetField();
|
|
|
|
static GMTOffsetField* createText(const UnicodeString& text, UErrorCode& status);
|
|
static GMTOffsetField* createTimeField(FieldType type, uint8_t width, UErrorCode& status);
|
|
static UBool isValid(FieldType type, int32_t width);
|
|
static FieldType getTypeByLetter(UChar ch);
|
|
|
|
FieldType getType() const;
|
|
uint8_t getWidth() const;
|
|
const UChar* getPatternText(void) const;
|
|
|
|
private:
|
|
UChar* fText;
|
|
FieldType fType;
|
|
uint8_t fWidth;
|
|
|
|
GMTOffsetField();
|
|
};
|
|
|
|
GMTOffsetField::GMTOffsetField()
|
|
: fText(NULL), fType(TEXT), fWidth(0) {
|
|
}
|
|
|
|
GMTOffsetField::~GMTOffsetField() {
|
|
if (fText) {
|
|
uprv_free(fText);
|
|
}
|
|
}
|
|
|
|
GMTOffsetField*
|
|
GMTOffsetField::createText(const UnicodeString& text, UErrorCode& status) {
|
|
if (U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
GMTOffsetField* result = new GMTOffsetField();
|
|
if (result == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
|
|
int32_t len = text.length();
|
|
result->fText = (UChar*)uprv_malloc((len + 1) * sizeof(UChar));
|
|
if (result->fText == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
delete result;
|
|
return NULL;
|
|
}
|
|
u_strncpy(result->fText, text.getBuffer(), len);
|
|
result->fText[len] = 0;
|
|
result->fType = TEXT;
|
|
|
|
return result;
|
|
}
|
|
|
|
GMTOffsetField*
|
|
GMTOffsetField::createTimeField(FieldType type, uint8_t width, UErrorCode& status) {
|
|
U_ASSERT(type != TEXT);
|
|
if (U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
GMTOffsetField* result = new GMTOffsetField();
|
|
if (result == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
|
|
result->fType = type;
|
|
result->fWidth = width;
|
|
|
|
return result;
|
|
}
|
|
|
|
UBool
|
|
GMTOffsetField::isValid(FieldType type, int32_t width) {
|
|
switch (type) {
|
|
case HOUR:
|
|
return (width == 1 || width == 2);
|
|
case MINUTE:
|
|
case SECOND:
|
|
return (width == 2);
|
|
default:
|
|
U_ASSERT(FALSE);
|
|
}
|
|
return (width > 0);
|
|
}
|
|
|
|
GMTOffsetField::FieldType
|
|
GMTOffsetField::getTypeByLetter(UChar ch) {
|
|
if (ch == 0x0048 /* H */) {
|
|
return HOUR;
|
|
} else if (ch == 0x006D /* m */) {
|
|
return MINUTE;
|
|
} else if (ch == 0x0073 /* s */) {
|
|
return SECOND;
|
|
}
|
|
return TEXT;
|
|
}
|
|
|
|
inline GMTOffsetField::FieldType
|
|
GMTOffsetField::getType() const {
|
|
return fType;
|
|
}
|
|
|
|
inline uint8_t
|
|
GMTOffsetField::getWidth() const {
|
|
return fWidth;
|
|
}
|
|
|
|
inline const UChar*
|
|
GMTOffsetField::getPatternText(void) const {
|
|
return fText;
|
|
}
|
|
|
|
|
|
U_CDECL_BEGIN
|
|
static void U_CALLCONV
|
|
deleteGMTOffsetField(void *obj) {
|
|
delete static_cast<GMTOffsetField *>(obj);
|
|
}
|
|
U_CDECL_END
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
// TimeZoneFormat
|
|
// ------------------------------------------------------------------
|
|
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(TimeZoneFormat)
|
|
|
|
TimeZoneFormat::TimeZoneFormat(const Locale& locale, UErrorCode& status)
|
|
: fLocale(locale), fTimeZoneNames(NULL), fTimeZoneGenericNames(NULL), fDefParseOptionFlags(0) {
|
|
|
|
for (int32_t i = 0; i < UTZFMT_PAT_COUNT; i++) {
|
|
fGMTOffsetPatternItems[i] = NULL;
|
|
}
|
|
|
|
const char* region = fLocale.getCountry();
|
|
int32_t regionLen = uprv_strlen(region);
|
|
if (regionLen == 0) {
|
|
char loc[ULOC_FULLNAME_CAPACITY];
|
|
uloc_addLikelySubtags(fLocale.getName(), loc, sizeof(loc), &status);
|
|
|
|
regionLen = uloc_getCountry(loc, fTargetRegion, sizeof(fTargetRegion), &status);
|
|
if (U_SUCCESS(status)) {
|
|
fTargetRegion[regionLen] = 0;
|
|
} else {
|
|
return;
|
|
}
|
|
} else if (regionLen < (int32_t)sizeof(fTargetRegion)) {
|
|
uprv_strcpy(fTargetRegion, region);
|
|
} else {
|
|
fTargetRegion[0] = 0;
|
|
}
|
|
|
|
fTimeZoneNames = TimeZoneNames::createInstance(locale, status);
|
|
// fTimeZoneGenericNames is lazily instantiated
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
|
|
const UChar* gmtPattern = NULL;
|
|
const UChar* hourFormats = NULL;
|
|
|
|
UResourceBundle *zoneBundle = ures_open(U_ICUDATA_ZONE, locale.getName(), &status);
|
|
UResourceBundle *zoneStringsArray = ures_getByKeyWithFallback(zoneBundle, gZoneStringsTag, NULL, &status);
|
|
if (U_SUCCESS(status)) {
|
|
const UChar* resStr;
|
|
int32_t len;
|
|
resStr = ures_getStringByKeyWithFallback(zoneStringsArray, gGmtFormatTag, &len, &status);
|
|
if (len > 0) {
|
|
gmtPattern = resStr;
|
|
}
|
|
resStr = ures_getStringByKeyWithFallback(zoneStringsArray, gGmtZeroFormatTag, &len, &status);
|
|
if (len > 0) {
|
|
fGMTZeroFormat.setTo(TRUE, resStr, len);
|
|
}
|
|
resStr = ures_getStringByKeyWithFallback(zoneStringsArray, gHourFormatTag, &len, &status);
|
|
if (len > 0) {
|
|
hourFormats = resStr;
|
|
}
|
|
ures_close(zoneStringsArray);
|
|
ures_close(zoneBundle);
|
|
}
|
|
|
|
if (gmtPattern == NULL) {
|
|
gmtPattern = DEFAULT_GMT_PATTERN;
|
|
}
|
|
initGMTPattern(UnicodeString(gmtPattern, -1), status);
|
|
|
|
UBool useDefaultOffsetPatterns = TRUE;
|
|
if (hourFormats) {
|
|
UChar *sep = u_strchr(hourFormats, (UChar)0x003B /* ';' */);
|
|
if (sep != NULL) {
|
|
UErrorCode tmpStatus = U_ZERO_ERROR;
|
|
fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_HM].setTo(FALSE, hourFormats, (int32_t)(sep - hourFormats));
|
|
fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HM].setTo(TRUE, sep + 1, -1);
|
|
expandOffsetPattern(fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_HM], fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_HMS], tmpStatus);
|
|
expandOffsetPattern(fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HM], fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HMS], tmpStatus);
|
|
truncateOffsetPattern(fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_HM], fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_H], tmpStatus);
|
|
truncateOffsetPattern(fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HM], fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_H], tmpStatus);
|
|
if (U_SUCCESS(tmpStatus)) {
|
|
useDefaultOffsetPatterns = FALSE;
|
|
}
|
|
}
|
|
}
|
|
if (useDefaultOffsetPatterns) {
|
|
fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_H].setTo(TRUE, DEFAULT_GMT_POSITIVE_H, -1);
|
|
fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_HM].setTo(TRUE, DEFAULT_GMT_POSITIVE_HM, -1);
|
|
fGMTOffsetPatterns[UTZFMT_PAT_POSITIVE_HMS].setTo(TRUE, DEFAULT_GMT_POSITIVE_HMS, -1);
|
|
fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_H].setTo(TRUE, DEFAULT_GMT_NEGATIVE_H, -1);
|
|
fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HM].setTo(TRUE, DEFAULT_GMT_NEGATIVE_HM, -1);
|
|
fGMTOffsetPatterns[UTZFMT_PAT_NEGATIVE_HMS].setTo(TRUE, DEFAULT_GMT_NEGATIVE_HMS, -1);
|
|
}
|
|
initGMTOffsetPatterns(status);
|
|
|
|
NumberingSystem* ns = NumberingSystem::createInstance(locale, status);
|
|
UBool useDefDigits = TRUE;
|
|
if (ns && !ns->isAlgorithmic()) {
|
|
UnicodeString digits = ns->getDescription();
|
|
useDefDigits = !toCodePoints(digits, fGMTOffsetDigits, 10);
|
|
}
|
|
if (useDefDigits) {
|
|
uprv_memcpy(fGMTOffsetDigits, DEFAULT_GMT_DIGITS, sizeof(UChar32) * 10);
|
|
}
|
|
delete ns;
|
|
}
|
|
|
|
TimeZoneFormat::TimeZoneFormat(const TimeZoneFormat& other)
|
|
: Format(other), fTimeZoneNames(NULL), fTimeZoneGenericNames(NULL) {
|
|
|
|
for (int32_t i = 0; i < UTZFMT_PAT_COUNT; i++) {
|
|
fGMTOffsetPatternItems[i] = NULL;
|
|
}
|
|
*this = other;
|
|
}
|
|
|
|
|
|
TimeZoneFormat::~TimeZoneFormat() {
|
|
delete fTimeZoneNames;
|
|
delete fTimeZoneGenericNames;
|
|
for (int32_t i = 0; i < UTZFMT_PAT_COUNT; i++) {
|
|
delete fGMTOffsetPatternItems[i];
|
|
}
|
|
}
|
|
|
|
TimeZoneFormat&
|
|
TimeZoneFormat::operator=(const TimeZoneFormat& other) {
|
|
if (this == &other) {
|
|
return *this;
|
|
}
|
|
|
|
delete fTimeZoneNames;
|
|
delete fTimeZoneGenericNames;
|
|
fTimeZoneGenericNames = NULL;
|
|
|
|
fLocale = other.fLocale;
|
|
uprv_memcpy(fTargetRegion, other.fTargetRegion, sizeof(fTargetRegion));
|
|
|
|
fTimeZoneNames = other.fTimeZoneNames->clone();
|
|
if (other.fTimeZoneGenericNames) {
|
|
// TODO: this test has dubious thread safety.
|
|
fTimeZoneGenericNames = other.fTimeZoneGenericNames->clone();
|
|
}
|
|
|
|
fGMTPattern = other.fGMTPattern;
|
|
fGMTPatternPrefix = other.fGMTPatternPrefix;
|
|
fGMTPatternSuffix = other.fGMTPatternSuffix;
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
for (int32_t i = 0; i < UTZFMT_PAT_COUNT; i++) {
|
|
fGMTOffsetPatterns[i] = other.fGMTOffsetPatterns[i];
|
|
delete fGMTOffsetPatternItems[i];
|
|
}
|
|
initGMTOffsetPatterns(status);
|
|
U_ASSERT(U_SUCCESS(status));
|
|
|
|
fGMTZeroFormat = other.fGMTZeroFormat;
|
|
|
|
uprv_memcpy(fGMTOffsetDigits, other.fGMTOffsetDigits, sizeof(fGMTOffsetDigits));
|
|
|
|
fDefParseOptionFlags = other.fDefParseOptionFlags;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
UBool
|
|
TimeZoneFormat::operator==(const Format& other) const {
|
|
TimeZoneFormat* tzfmt = (TimeZoneFormat*)&other;
|
|
|
|
UBool isEqual =
|
|
fLocale == tzfmt->fLocale
|
|
&& fGMTPattern == tzfmt->fGMTPattern
|
|
&& fGMTZeroFormat == tzfmt->fGMTZeroFormat
|
|
&& *fTimeZoneNames == *tzfmt->fTimeZoneNames;
|
|
|
|
for (int32_t i = 0; i < UTZFMT_PAT_COUNT && isEqual; i++) {
|
|
isEqual = fGMTOffsetPatterns[i] == tzfmt->fGMTOffsetPatterns[i];
|
|
}
|
|
for (int32_t i = 0; i < 10 && isEqual; i++) {
|
|
isEqual = fGMTOffsetDigits[i] == tzfmt->fGMTOffsetDigits[i];
|
|
}
|
|
// TODO
|
|
// Check fTimeZoneGenericNames. For now,
|
|
// if fTimeZoneNames is same, fTimeZoneGenericNames should
|
|
// be also equivalent.
|
|
return isEqual;
|
|
}
|
|
|
|
Format*
|
|
TimeZoneFormat::clone() const {
|
|
return new TimeZoneFormat(*this);
|
|
}
|
|
|
|
TimeZoneFormat* U_EXPORT2
|
|
TimeZoneFormat::createInstance(const Locale& locale, UErrorCode& status) {
|
|
TimeZoneFormat* tzfmt = new TimeZoneFormat(locale, status);
|
|
if (U_SUCCESS(status)) {
|
|
return tzfmt;
|
|
}
|
|
delete tzfmt;
|
|
return NULL;
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Setter and Getter
|
|
|
|
const TimeZoneNames*
|
|
TimeZoneFormat::getTimeZoneNames() const {
|
|
return (const TimeZoneNames*)fTimeZoneNames;
|
|
}
|
|
|
|
void
|
|
TimeZoneFormat::adoptTimeZoneNames(TimeZoneNames *tznames) {
|
|
delete fTimeZoneNames;
|
|
fTimeZoneNames = tznames;
|
|
|
|
// TODO - We should also update fTimeZoneGenericNames
|
|
}
|
|
|
|
void
|
|
TimeZoneFormat::setTimeZoneNames(const TimeZoneNames &tznames) {
|
|
delete fTimeZoneNames;
|
|
fTimeZoneNames = tznames.clone();
|
|
|
|
// TODO - We should also update fTimeZoneGenericNames
|
|
}
|
|
|
|
void
|
|
TimeZoneFormat::setDefaultParseOptions(uint32_t flags) {
|
|
fDefParseOptionFlags = flags;
|
|
}
|
|
|
|
uint32_t
|
|
TimeZoneFormat::getDefaultParseOptions(void) const {
|
|
return fDefParseOptionFlags;
|
|
}
|
|
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::getGMTPattern(UnicodeString& pattern) const {
|
|
return pattern.setTo(fGMTPattern);
|
|
}
|
|
|
|
void
|
|
TimeZoneFormat::setGMTPattern(const UnicodeString& pattern, UErrorCode& status) {
|
|
initGMTPattern(pattern, status);
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::getGMTOffsetPattern(UTimeZoneFormatGMTOffsetPatternType type, UnicodeString& pattern) const {
|
|
return pattern.setTo(fGMTOffsetPatterns[type]);
|
|
}
|
|
|
|
void
|
|
TimeZoneFormat::setGMTOffsetPattern(UTimeZoneFormatGMTOffsetPatternType type, const UnicodeString& pattern, UErrorCode& status) {
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
if (pattern == fGMTOffsetPatterns[type]) {
|
|
// No need to reset
|
|
return;
|
|
}
|
|
|
|
OffsetFields required = FIELDS_HM;
|
|
switch (type) {
|
|
case UTZFMT_PAT_POSITIVE_H:
|
|
case UTZFMT_PAT_NEGATIVE_H:
|
|
required = FIELDS_H;
|
|
break;
|
|
case UTZFMT_PAT_POSITIVE_HM:
|
|
case UTZFMT_PAT_NEGATIVE_HM:
|
|
required = FIELDS_HM;
|
|
break;
|
|
case UTZFMT_PAT_POSITIVE_HMS:
|
|
case UTZFMT_PAT_NEGATIVE_HMS:
|
|
required = FIELDS_HMS;
|
|
break;
|
|
default:
|
|
U_ASSERT(FALSE);
|
|
break;
|
|
}
|
|
|
|
UVector* patternItems = parseOffsetPattern(pattern, required, status);
|
|
if (patternItems == NULL) {
|
|
return;
|
|
}
|
|
|
|
fGMTOffsetPatterns[type].setTo(pattern);
|
|
delete fGMTOffsetPatternItems[type];
|
|
fGMTOffsetPatternItems[type] = patternItems;
|
|
checkAbuttingHoursAndMinutes();
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::getGMTOffsetDigits(UnicodeString& digits) const {
|
|
digits.remove();
|
|
for (int32_t i = 0; i < 10; i++) {
|
|
digits.append(fGMTOffsetDigits[i]);
|
|
}
|
|
return digits;
|
|
}
|
|
|
|
void
|
|
TimeZoneFormat::setGMTOffsetDigits(const UnicodeString& digits, UErrorCode& status) {
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
UChar32 digitArray[10];
|
|
if (!toCodePoints(digits, digitArray, 10)) {
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
uprv_memcpy(fGMTOffsetDigits, digitArray, sizeof(UChar32)*10);
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::getGMTZeroFormat(UnicodeString& gmtZeroFormat) const {
|
|
return gmtZeroFormat.setTo(fGMTZeroFormat);
|
|
}
|
|
|
|
void
|
|
TimeZoneFormat::setGMTZeroFormat(const UnicodeString& gmtZeroFormat, UErrorCode& status) {
|
|
if (U_SUCCESS(status)) {
|
|
if (gmtZeroFormat.isEmpty()) {
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
} else if (gmtZeroFormat != fGMTZeroFormat) {
|
|
fGMTZeroFormat.setTo(gmtZeroFormat);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Format and Parse
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::format(UTimeZoneFormatStyle style, const TimeZone& tz, UDate date,
|
|
UnicodeString& name, UTimeZoneFormatTimeType* timeType /* = NULL */) const {
|
|
if (timeType) {
|
|
*timeType = UTZFMT_TIME_TYPE_UNKNOWN;
|
|
}
|
|
|
|
UBool noOffsetFormatFallback = FALSE;
|
|
|
|
switch (style) {
|
|
case UTZFMT_STYLE_GENERIC_LOCATION:
|
|
formatGeneric(tz, UTZGNM_LOCATION, date, name);
|
|
break;
|
|
case UTZFMT_STYLE_GENERIC_LONG:
|
|
formatGeneric(tz, UTZGNM_LONG, date, name);
|
|
break;
|
|
case UTZFMT_STYLE_GENERIC_SHORT:
|
|
formatGeneric(tz, UTZGNM_SHORT, date, name);
|
|
break;
|
|
case UTZFMT_STYLE_SPECIFIC_LONG:
|
|
formatSpecific(tz, UTZNM_LONG_STANDARD, UTZNM_LONG_DAYLIGHT, date, name, timeType);
|
|
break;
|
|
case UTZFMT_STYLE_SPECIFIC_SHORT:
|
|
formatSpecific(tz, UTZNM_SHORT_STANDARD, UTZNM_SHORT_DAYLIGHT, date, name, timeType);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_ZONE_ID:
|
|
tz.getID(name);
|
|
noOffsetFormatFallback = TRUE;
|
|
break;
|
|
case UTZFMT_STYLE_ZONE_ID_SHORT:
|
|
{
|
|
const UChar* shortID = ZoneMeta::getShortID(tz);
|
|
if (shortID == NULL) {
|
|
shortID = UNKNOWN_SHORT_ZONE_ID;
|
|
}
|
|
name.setTo(shortID, -1);
|
|
}
|
|
noOffsetFormatFallback = TRUE;
|
|
break;
|
|
|
|
case UTZFMT_STYLE_EXEMPLAR_LOCATION:
|
|
formatExemplarLocation(tz, name);
|
|
noOffsetFormatFallback = TRUE;
|
|
break;
|
|
|
|
default:
|
|
// will be handled below
|
|
break;
|
|
}
|
|
|
|
if (name.isEmpty() && !noOffsetFormatFallback) {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
int32_t rawOffset, dstOffset;
|
|
tz.getOffset(date, FALSE, rawOffset, dstOffset, status);
|
|
int32_t offset = rawOffset + dstOffset;
|
|
if (U_SUCCESS(status)) {
|
|
switch (style) {
|
|
case UTZFMT_STYLE_GENERIC_LOCATION:
|
|
case UTZFMT_STYLE_GENERIC_LONG:
|
|
case UTZFMT_STYLE_SPECIFIC_LONG:
|
|
case UTZFMT_STYLE_LOCALIZED_GMT:
|
|
formatOffsetLocalizedGMT(offset, name, status);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_GENERIC_SHORT:
|
|
case UTZFMT_STYLE_SPECIFIC_SHORT:
|
|
case UTZFMT_STYLE_LOCALIZED_GMT_SHORT:
|
|
formatOffsetShortLocalizedGMT(offset, name, status);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_ISO_BASIC_SHORT:
|
|
formatOffsetISO8601Basic(offset, TRUE, TRUE, TRUE, name, status);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_ISO_BASIC_LOCAL_SHORT:
|
|
formatOffsetISO8601Basic(offset, FALSE, TRUE, TRUE, name, status);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_ISO_BASIC_FIXED:
|
|
formatOffsetISO8601Basic(offset, TRUE, FALSE, TRUE, name, status);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_ISO_BASIC_LOCAL_FIXED:
|
|
formatOffsetISO8601Basic(offset, FALSE, FALSE, TRUE, name, status);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_ISO_EXTENDED_FIXED:
|
|
formatOffsetISO8601Extended(offset, TRUE, FALSE, TRUE, name, status);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FIXED:
|
|
formatOffsetISO8601Extended(offset, FALSE, FALSE, TRUE, name, status);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_ISO_BASIC_FULL:
|
|
formatOffsetISO8601Basic(offset, TRUE, FALSE, FALSE, name, status);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL:
|
|
formatOffsetISO8601Basic(offset, FALSE, FALSE, FALSE, name, status);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_ISO_EXTENDED_FULL:
|
|
formatOffsetISO8601Extended(offset, TRUE, FALSE, FALSE, name, status);
|
|
break;
|
|
|
|
case UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL:
|
|
formatOffsetISO8601Extended(offset, FALSE, FALSE, FALSE, name, status);
|
|
break;
|
|
|
|
default:
|
|
// UTZFMT_STYLE_ZONE_ID, UTZFMT_STYLE_ZONE_ID_SHORT, UTZFMT_STYLE_EXEMPLAR_LOCATION
|
|
break;
|
|
}
|
|
|
|
if (timeType) {
|
|
*timeType = (dstOffset != 0) ? UTZFMT_TIME_TYPE_DAYLIGHT : UTZFMT_TIME_TYPE_STANDARD;
|
|
}
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::format(const Formattable& obj, UnicodeString& appendTo,
|
|
FieldPosition& pos, UErrorCode& status) const {
|
|
if (U_FAILURE(status)) {
|
|
return appendTo;
|
|
}
|
|
UDate date = Calendar::getNow();
|
|
if (obj.getType() == Formattable::kObject) {
|
|
const UObject* formatObj = obj.getObject();
|
|
const TimeZone* tz = dynamic_cast<const TimeZone*>(formatObj);
|
|
if (tz == NULL) {
|
|
const Calendar* cal = dynamic_cast<const Calendar*>(formatObj);
|
|
if (cal != NULL) {
|
|
tz = &cal->getTimeZone();
|
|
date = cal->getTime(status);
|
|
}
|
|
}
|
|
if (tz != NULL) {
|
|
int32_t rawOffset, dstOffset;
|
|
tz->getOffset(date, FALSE, rawOffset, dstOffset, status);
|
|
UnicodeString result;
|
|
formatOffsetLocalizedGMT(rawOffset + dstOffset, result, status);
|
|
if (U_SUCCESS(status)) {
|
|
appendTo.append(result);
|
|
if (pos.getField() == UDAT_TIMEZONE_FIELD) {
|
|
pos.setBeginIndex(0);
|
|
pos.setEndIndex(result.length());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return appendTo;
|
|
}
|
|
|
|
TimeZone*
|
|
TimeZoneFormat::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
|
|
UTimeZoneFormatTimeType* timeType /*= NULL*/) const {
|
|
return parse(style, text, pos, getDefaultParseOptions(), timeType);
|
|
}
|
|
|
|
TimeZone*
|
|
TimeZoneFormat::parse(UTimeZoneFormatStyle style, const UnicodeString& text, ParsePosition& pos,
|
|
int32_t parseOptions, UTimeZoneFormatTimeType* timeType /* = NULL */) const {
|
|
if (timeType) {
|
|
*timeType = UTZFMT_TIME_TYPE_UNKNOWN;
|
|
}
|
|
|
|
int32_t startIdx = pos.getIndex();
|
|
int32_t maxPos = text.length();
|
|
int32_t offset;
|
|
|
|
// Styles using localized GMT format as fallback
|
|
UBool fallbackLocalizedGMT =
|
|
(style == UTZFMT_STYLE_SPECIFIC_LONG || style == UTZFMT_STYLE_GENERIC_LONG || style == UTZFMT_STYLE_GENERIC_LOCATION);
|
|
UBool fallbackShortLocalizedGMT =
|
|
(style == UTZFMT_STYLE_SPECIFIC_SHORT || style == UTZFMT_STYLE_GENERIC_SHORT);
|
|
|
|
int32_t evaluated = 0; // bit flags representing already evaluated styles
|
|
ParsePosition tmpPos(startIdx);
|
|
|
|
int32_t parsedOffset = UNKNOWN_OFFSET; // stores successfully parsed offset for later use
|
|
int32_t parsedPos = -1; // stores successfully parsed offset position for later use
|
|
|
|
// Try localized GMT format first if necessary
|
|
if (fallbackLocalizedGMT || fallbackShortLocalizedGMT) {
|
|
UBool hasDigitOffset = FALSE;
|
|
offset = parseOffsetLocalizedGMT(text, tmpPos, fallbackShortLocalizedGMT, &hasDigitOffset);
|
|
if (tmpPos.getErrorIndex() == -1) {
|
|
// Even when the input text was successfully parsed as a localized GMT format text,
|
|
// we may still need to evaluate the specified style if -
|
|
// 1) GMT zero format was used, and
|
|
// 2) The input text was not completely processed
|
|
if (tmpPos.getIndex() == maxPos || hasDigitOffset) {
|
|
pos.setIndex(tmpPos.getIndex());
|
|
return createTimeZoneForOffset(offset);
|
|
}
|
|
parsedOffset = offset;
|
|
parsedPos = tmpPos.getIndex();
|
|
}
|
|
// Note: For now, no distinction between long/short localized GMT format in the parser.
|
|
// This might be changed in future.
|
|
// evaluated |= (fallbackLocalizedGMT ? STYLE_PARSE_FLAGS[UTZFMT_STYLE_LOCALIZED_GMT] : STYLE_PARSE_FLAGS[UTZFMT_STYLE_LOCALIZED_GMT_SHORT]);
|
|
evaluated |= STYLE_PARSE_FLAGS[UTZFMT_STYLE_LOCALIZED_GMT] | STYLE_PARSE_FLAGS[UTZFMT_STYLE_LOCALIZED_GMT_SHORT];
|
|
}
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
UnicodeString tzID;
|
|
|
|
// Try the specified style
|
|
switch (style) {
|
|
case UTZFMT_STYLE_LOCALIZED_GMT:
|
|
{
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
offset = parseOffsetLocalizedGMT(text, tmpPos);
|
|
if (tmpPos.getErrorIndex() == -1) {
|
|
pos.setIndex(tmpPos.getIndex());
|
|
return createTimeZoneForOffset(offset);
|
|
}
|
|
|
|
// Note: For now, no distinction between long/short localized GMT format in the parser.
|
|
// This might be changed in future.
|
|
evaluated |= STYLE_PARSE_FLAGS[UTZFMT_STYLE_LOCALIZED_GMT_SHORT];
|
|
|
|
break;
|
|
}
|
|
case UTZFMT_STYLE_LOCALIZED_GMT_SHORT:
|
|
{
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
offset = parseOffsetShortLocalizedGMT(text, tmpPos);
|
|
if (tmpPos.getErrorIndex() == -1) {
|
|
pos.setIndex(tmpPos.getIndex());
|
|
return createTimeZoneForOffset(offset);
|
|
}
|
|
|
|
// Note: For now, no distinction between long/short localized GMT format in the parser.
|
|
// This might be changed in future.
|
|
evaluated |= STYLE_PARSE_FLAGS[UTZFMT_STYLE_LOCALIZED_GMT];
|
|
|
|
break;
|
|
}
|
|
case UTZFMT_STYLE_ISO_BASIC_SHORT:
|
|
case UTZFMT_STYLE_ISO_BASIC_FIXED:
|
|
case UTZFMT_STYLE_ISO_BASIC_FULL:
|
|
case UTZFMT_STYLE_ISO_EXTENDED_FIXED:
|
|
case UTZFMT_STYLE_ISO_EXTENDED_FULL:
|
|
{
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
offset = parseOffsetISO8601(text, tmpPos);
|
|
if (tmpPos.getErrorIndex() == -1) {
|
|
pos.setIndex(tmpPos.getIndex());
|
|
return createTimeZoneForOffset(offset);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case UTZFMT_STYLE_ISO_BASIC_LOCAL_SHORT:
|
|
case UTZFMT_STYLE_ISO_BASIC_LOCAL_FIXED:
|
|
case UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL:
|
|
case UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FIXED:
|
|
case UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL:
|
|
{
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
// Exclude the case of UTC Indicator "Z" here
|
|
UBool hasDigitOffset = FALSE;
|
|
offset = parseOffsetISO8601(text, tmpPos, FALSE, &hasDigitOffset);
|
|
if (tmpPos.getErrorIndex() == -1 && hasDigitOffset) {
|
|
pos.setIndex(tmpPos.getIndex());
|
|
return createTimeZoneForOffset(offset);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case UTZFMT_STYLE_SPECIFIC_LONG:
|
|
case UTZFMT_STYLE_SPECIFIC_SHORT:
|
|
{
|
|
// Specific styles
|
|
int32_t nameTypes = 0;
|
|
if (style == UTZFMT_STYLE_SPECIFIC_LONG) {
|
|
nameTypes = (UTZNM_LONG_STANDARD | UTZNM_LONG_DAYLIGHT);
|
|
} else {
|
|
U_ASSERT(style == UTZFMT_STYLE_SPECIFIC_SHORT);
|
|
nameTypes = (UTZNM_SHORT_STANDARD | UTZNM_SHORT_DAYLIGHT);
|
|
}
|
|
LocalPointer<TimeZoneNames::MatchInfoCollection> specificMatches(fTimeZoneNames->find(text, startIdx, nameTypes, status));
|
|
if (U_FAILURE(status)) {
|
|
pos.setErrorIndex(startIdx);
|
|
return NULL;
|
|
}
|
|
if (!specificMatches.isNull()) {
|
|
int32_t matchIdx = -1;
|
|
int32_t matchPos = -1;
|
|
for (int32_t i = 0; i < specificMatches->size(); i++) {
|
|
matchPos = startIdx + specificMatches->getMatchLengthAt(i);
|
|
if (matchPos > parsedPos) {
|
|
matchIdx = i;
|
|
parsedPos = matchPos;
|
|
}
|
|
}
|
|
if (matchIdx >= 0) {
|
|
if (timeType) {
|
|
*timeType = getTimeType(specificMatches->getNameTypeAt(matchIdx));
|
|
}
|
|
pos.setIndex(matchPos);
|
|
getTimeZoneID(specificMatches.getAlias(), matchIdx, tzID);
|
|
U_ASSERT(!tzID.isEmpty());
|
|
return TimeZone::createTimeZone(tzID);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case UTZFMT_STYLE_GENERIC_LONG:
|
|
case UTZFMT_STYLE_GENERIC_SHORT:
|
|
case UTZFMT_STYLE_GENERIC_LOCATION:
|
|
{
|
|
int32_t genericNameTypes = 0;
|
|
switch (style) {
|
|
case UTZFMT_STYLE_GENERIC_LOCATION:
|
|
genericNameTypes = UTZGNM_LOCATION;
|
|
break;
|
|
|
|
case UTZFMT_STYLE_GENERIC_LONG:
|
|
genericNameTypes = UTZGNM_LONG | UTZGNM_LOCATION;
|
|
break;
|
|
|
|
case UTZFMT_STYLE_GENERIC_SHORT:
|
|
genericNameTypes = UTZGNM_SHORT | UTZGNM_LOCATION;
|
|
break;
|
|
|
|
default:
|
|
U_ASSERT(FALSE);
|
|
}
|
|
|
|
int32_t len = 0;
|
|
UTimeZoneFormatTimeType tt = UTZFMT_TIME_TYPE_UNKNOWN;
|
|
const TimeZoneGenericNames *gnames = getTimeZoneGenericNames(status);
|
|
if (U_SUCCESS(status)) {
|
|
len = gnames->findBestMatch(text, startIdx, genericNameTypes, tzID, tt, status);
|
|
}
|
|
if (U_FAILURE(status)) {
|
|
pos.setErrorIndex(startIdx);
|
|
return NULL;
|
|
}
|
|
if (len > 0) {
|
|
// Found a match
|
|
if (timeType) {
|
|
*timeType = tt;
|
|
}
|
|
pos.setIndex(startIdx + len);
|
|
U_ASSERT(!tzID.isEmpty());
|
|
return TimeZone::createTimeZone(tzID);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case UTZFMT_STYLE_ZONE_ID:
|
|
{
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
parseZoneID(text, tmpPos, tzID);
|
|
if (tmpPos.getErrorIndex() == -1) {
|
|
pos.setIndex(tmpPos.getIndex());
|
|
return TimeZone::createTimeZone(tzID);
|
|
}
|
|
break;
|
|
}
|
|
case UTZFMT_STYLE_ZONE_ID_SHORT:
|
|
{
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
parseShortZoneID(text, tmpPos, tzID);
|
|
if (tmpPos.getErrorIndex() == -1) {
|
|
pos.setIndex(tmpPos.getIndex());
|
|
return TimeZone::createTimeZone(tzID);
|
|
}
|
|
break;
|
|
}
|
|
case UTZFMT_STYLE_EXEMPLAR_LOCATION:
|
|
{
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
parseExemplarLocation(text, tmpPos, tzID);
|
|
if (tmpPos.getErrorIndex() == -1) {
|
|
pos.setIndex(tmpPos.getIndex());
|
|
return TimeZone::createTimeZone(tzID);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
evaluated |= STYLE_PARSE_FLAGS[style];
|
|
|
|
|
|
if (parsedPos > startIdx) {
|
|
// When the specified style is one of SPECIFIC_XXX or GENERIC_XXX, we tried to parse the input
|
|
// as localized GMT format earlier. If parsedOffset is positive, it means it was successfully
|
|
// parsed as localized GMT format, but offset digits were not detected (more specifically, GMT
|
|
// zero format). Then, it tried to find a match within the set of display names, but could not
|
|
// find a match. At this point, we can safely assume the input text contains the localized
|
|
// GMT format.
|
|
U_ASSERT(parsedOffset != UNKNOWN_OFFSET);
|
|
pos.setIndex(parsedPos);
|
|
return createTimeZoneForOffset(parsedOffset);
|
|
}
|
|
|
|
// Failed to parse the input text as the time zone format in the specified style.
|
|
// Check the longest match among other styles below.
|
|
UnicodeString parsedID;
|
|
UTimeZoneFormatTimeType parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
|
|
|
|
U_ASSERT(parsedPos < 0);
|
|
U_ASSERT(parsedOffset == UNKNOWN_OFFSET);
|
|
|
|
// ISO 8601
|
|
if (parsedPos < maxPos &&
|
|
((evaluated & ISO_Z_STYLE_FLAG) == 0 || (evaluated & ISO_LOCAL_STYLE_FLAG) == 0)) {
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
UBool hasDigitOffset = FALSE;
|
|
offset = parseOffsetISO8601(text, tmpPos, FALSE, &hasDigitOffset);
|
|
if (tmpPos.getErrorIndex() == -1) {
|
|
if (tmpPos.getIndex() == maxPos || hasDigitOffset) {
|
|
pos.setIndex(tmpPos.getIndex());
|
|
return createTimeZoneForOffset(offset);
|
|
}
|
|
// Note: When ISO 8601 format contains offset digits, it should not
|
|
// collide with other formats. However, ISO 8601 UTC format "Z" (single letter)
|
|
// may collide with other names. In this case, we need to evaluate other names.
|
|
if (parsedPos < tmpPos.getIndex()) {
|
|
parsedOffset = offset;
|
|
parsedID.setToBogus();
|
|
parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
|
|
parsedPos = tmpPos.getIndex();
|
|
U_ASSERT(parsedPos == startIdx + 1); // only when "Z" is used
|
|
}
|
|
}
|
|
}
|
|
|
|
// Localized GMT format
|
|
if (parsedPos < maxPos &&
|
|
(evaluated & STYLE_PARSE_FLAGS[UTZFMT_STYLE_LOCALIZED_GMT]) == 0) {
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
UBool hasDigitOffset = FALSE;
|
|
offset = parseOffsetLocalizedGMT(text, tmpPos, FALSE, &hasDigitOffset);
|
|
if (tmpPos.getErrorIndex() == -1) {
|
|
if (tmpPos.getIndex() == maxPos || hasDigitOffset) {
|
|
pos.setIndex(tmpPos.getIndex());
|
|
return createTimeZoneForOffset(offset);
|
|
}
|
|
// Evaluate other names - see the comment earlier in this method.
|
|
if (parsedPos < tmpPos.getIndex()) {
|
|
parsedOffset = offset;
|
|
parsedID.setToBogus();
|
|
parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
|
|
parsedPos = tmpPos.getIndex();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parsedPos < maxPos &&
|
|
(evaluated & STYLE_PARSE_FLAGS[UTZFMT_STYLE_LOCALIZED_GMT_SHORT]) == 0) {
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
UBool hasDigitOffset = FALSE;
|
|
offset = parseOffsetLocalizedGMT(text, tmpPos, TRUE, &hasDigitOffset);
|
|
if (tmpPos.getErrorIndex() == -1) {
|
|
if (tmpPos.getIndex() == maxPos || hasDigitOffset) {
|
|
pos.setIndex(tmpPos.getIndex());
|
|
return createTimeZoneForOffset(offset);
|
|
}
|
|
// Evaluate other names - see the comment earlier in this method.
|
|
if (parsedPos < tmpPos.getIndex()) {
|
|
parsedOffset = offset;
|
|
parsedID.setToBogus();
|
|
parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
|
|
parsedPos = tmpPos.getIndex();
|
|
}
|
|
}
|
|
}
|
|
|
|
// When ParseOption.ALL_STYLES is available, we also try to look all possible display names and IDs.
|
|
// For example, when style is GENERIC_LONG, "EST" (SPECIFIC_SHORT) is never
|
|
// used for America/New_York. With parseAllStyles true, this code parses "EST"
|
|
// as America/New_York.
|
|
|
|
// Note: Adding all possible names into the trie used by the implementation is quite heavy operation,
|
|
// which we want to avoid normally (note that we cache the trie, so this is applicable to the
|
|
// first time only as long as the cache does not expire).
|
|
|
|
if (parseOptions & UTZFMT_PARSE_OPTION_ALL_STYLES) {
|
|
// Try all specific names and exemplar location names
|
|
if (parsedPos < maxPos) {
|
|
LocalPointer<TimeZoneNames::MatchInfoCollection> specificMatches(fTimeZoneNames->find(text, startIdx, ALL_SIMPLE_NAME_TYPES, status));
|
|
if (U_FAILURE(status)) {
|
|
pos.setErrorIndex(startIdx);
|
|
return NULL;
|
|
}
|
|
int32_t specificMatchIdx = -1;
|
|
int32_t matchPos = -1;
|
|
if (!specificMatches.isNull()) {
|
|
for (int32_t i = 0; i < specificMatches->size(); i++) {
|
|
if (startIdx + specificMatches->getMatchLengthAt(i) > matchPos) {
|
|
specificMatchIdx = i;
|
|
matchPos = startIdx + specificMatches->getMatchLengthAt(i);
|
|
}
|
|
}
|
|
}
|
|
if (parsedPos < matchPos) {
|
|
U_ASSERT(specificMatchIdx >= 0);
|
|
parsedPos = matchPos;
|
|
getTimeZoneID(specificMatches.getAlias(), specificMatchIdx, parsedID);
|
|
parsedTimeType = getTimeType(specificMatches->getNameTypeAt(specificMatchIdx));
|
|
parsedOffset = UNKNOWN_OFFSET;
|
|
}
|
|
}
|
|
// Try generic names
|
|
if (parsedPos < maxPos) {
|
|
int32_t genMatchLen = -1;
|
|
UTimeZoneFormatTimeType tt = UTZFMT_TIME_TYPE_UNKNOWN;
|
|
|
|
const TimeZoneGenericNames *gnames = getTimeZoneGenericNames(status);
|
|
if (U_SUCCESS(status)) {
|
|
genMatchLen = gnames->findBestMatch(text, startIdx, ALL_GENERIC_NAME_TYPES, tzID, tt, status);
|
|
}
|
|
if (U_FAILURE(status)) {
|
|
pos.setErrorIndex(startIdx);
|
|
return NULL;
|
|
}
|
|
|
|
if (parsedPos < startIdx + genMatchLen) {
|
|
parsedPos = startIdx + genMatchLen;
|
|
parsedID.setTo(tzID);
|
|
parsedTimeType = tt;
|
|
parsedOffset = UNKNOWN_OFFSET;
|
|
}
|
|
}
|
|
|
|
// Try time zone ID
|
|
if (parsedPos < maxPos && (evaluated & STYLE_PARSE_FLAGS[UTZFMT_STYLE_ZONE_ID]) == 0) {
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
parseZoneID(text, tmpPos, tzID);
|
|
if (tmpPos.getErrorIndex() == -1 && parsedPos < tmpPos.getIndex()) {
|
|
parsedPos = tmpPos.getIndex();
|
|
parsedID.setTo(tzID);
|
|
parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
|
|
parsedOffset = UNKNOWN_OFFSET;
|
|
}
|
|
}
|
|
// Try short time zone ID
|
|
if (parsedPos < maxPos && (evaluated & STYLE_PARSE_FLAGS[UTZFMT_STYLE_ZONE_ID]) == 0) {
|
|
tmpPos.setIndex(startIdx);
|
|
tmpPos.setErrorIndex(-1);
|
|
|
|
parseShortZoneID(text, tmpPos, tzID);
|
|
if (tmpPos.getErrorIndex() == -1 && parsedPos < tmpPos.getIndex()) {
|
|
parsedPos = tmpPos.getIndex();
|
|
parsedID.setTo(tzID);
|
|
parsedTimeType = UTZFMT_TIME_TYPE_UNKNOWN;
|
|
parsedOffset = UNKNOWN_OFFSET;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (parsedPos > startIdx) {
|
|
// Parsed successfully
|
|
TimeZone* parsedTZ;
|
|
if (parsedID.length() > 0) {
|
|
parsedTZ = TimeZone::createTimeZone(parsedID);
|
|
} else {
|
|
U_ASSERT(parsedOffset != UNKNOWN_OFFSET);
|
|
parsedTZ = createTimeZoneForOffset(parsedOffset);
|
|
}
|
|
if (timeType) {
|
|
*timeType = parsedTimeType;
|
|
}
|
|
pos.setIndex(parsedPos);
|
|
return parsedTZ;
|
|
}
|
|
|
|
pos.setErrorIndex(startIdx);
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
TimeZoneFormat::parseObject(const UnicodeString& source, Formattable& result,
|
|
ParsePosition& parse_pos) const {
|
|
result.adoptObject(parse(UTZFMT_STYLE_GENERIC_LOCATION, source, parse_pos, UTZFMT_PARSE_OPTION_ALL_STYLES));
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
// Private zone name format/parse implementation
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::formatGeneric(const TimeZone& tz, int32_t genType, UDate date, UnicodeString& name) const {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
const TimeZoneGenericNames* gnames = getTimeZoneGenericNames(status);
|
|
if (U_FAILURE(status)) {
|
|
name.setToBogus();
|
|
return name;
|
|
}
|
|
|
|
if (genType == UTZGNM_LOCATION) {
|
|
const UChar* canonicalID = ZoneMeta::getCanonicalCLDRID(tz);
|
|
if (canonicalID == NULL) {
|
|
name.setToBogus();
|
|
return name;
|
|
}
|
|
return gnames->getGenericLocationName(UnicodeString(canonicalID), name);
|
|
}
|
|
return gnames->getDisplayName(tz, (UTimeZoneGenericNameType)genType, date, name);
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::formatSpecific(const TimeZone& tz, UTimeZoneNameType stdType, UTimeZoneNameType dstType,
|
|
UDate date, UnicodeString& name, UTimeZoneFormatTimeType *timeType) const {
|
|
if (fTimeZoneNames == NULL) {
|
|
name.setToBogus();
|
|
return name;
|
|
}
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
UBool isDaylight = tz.inDaylightTime(date, status);
|
|
const UChar* canonicalID = ZoneMeta::getCanonicalCLDRID(tz);
|
|
|
|
if (U_FAILURE(status) || canonicalID == NULL) {
|
|
name.setToBogus();
|
|
return name;
|
|
}
|
|
|
|
if (isDaylight) {
|
|
fTimeZoneNames->getDisplayName(UnicodeString(canonicalID), dstType, date, name);
|
|
} else {
|
|
fTimeZoneNames->getDisplayName(UnicodeString(canonicalID), stdType, date, name);
|
|
}
|
|
|
|
if (timeType && !name.isEmpty()) {
|
|
*timeType = isDaylight ? UTZFMT_TIME_TYPE_DAYLIGHT : UTZFMT_TIME_TYPE_STANDARD;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
const TimeZoneGenericNames*
|
|
TimeZoneFormat::getTimeZoneGenericNames(UErrorCode& status) const {
|
|
if (U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
|
|
umtx_lock(&gLock);
|
|
if (fTimeZoneGenericNames == NULL) {
|
|
TimeZoneFormat *nonConstThis = const_cast<TimeZoneFormat *>(this);
|
|
nonConstThis->fTimeZoneGenericNames = TimeZoneGenericNames::createInstance(fLocale, status);
|
|
}
|
|
umtx_unlock(&gLock);
|
|
|
|
return fTimeZoneGenericNames;
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::formatExemplarLocation(const TimeZone& tz, UnicodeString& name) const {
|
|
UnicodeString location;
|
|
const UChar* canonicalID = ZoneMeta::getCanonicalCLDRID(tz);
|
|
|
|
if (canonicalID) {
|
|
fTimeZoneNames->getExemplarLocationName(UnicodeString(canonicalID), location);
|
|
}
|
|
if (location.length() > 0) {
|
|
name.setTo(location);
|
|
} else {
|
|
// Use "unknown" location
|
|
fTimeZoneNames->getExemplarLocationName(UnicodeString(UNKNOWN_ZONE_ID), location);
|
|
if (location.length() > 0) {
|
|
name.setTo(location);
|
|
} else {
|
|
// last resort
|
|
name.setTo(UNKNOWN_LOCATION, -1);
|
|
}
|
|
}
|
|
return name;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
// Zone offset format and parse
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::formatOffsetISO8601Basic(int32_t offset, UBool useUtcIndicator, UBool isShort, UBool ignoreSeconds,
|
|
UnicodeString& result, UErrorCode& status) const {
|
|
return formatOffsetISO8601(offset, TRUE, useUtcIndicator, isShort, ignoreSeconds, result, status);
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::formatOffsetISO8601Extended(int32_t offset, UBool useUtcIndicator, UBool isShort, UBool ignoreSeconds,
|
|
UnicodeString& result, UErrorCode& status) const {
|
|
return formatOffsetISO8601(offset, FALSE, useUtcIndicator, isShort, ignoreSeconds, result, status);
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::formatOffsetLocalizedGMT(int32_t offset, UnicodeString& result, UErrorCode& status) const {
|
|
return formatOffsetLocalizedGMT(offset, FALSE, result, status);
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::formatOffsetShortLocalizedGMT(int32_t offset, UnicodeString& result, UErrorCode& status) const {
|
|
return formatOffsetLocalizedGMT(offset, TRUE, result, status);
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseOffsetISO8601(const UnicodeString& text, ParsePosition& pos) const {
|
|
return parseOffsetISO8601(text, pos, FALSE);
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseOffsetLocalizedGMT(const UnicodeString& text, ParsePosition& pos) const {
|
|
return parseOffsetLocalizedGMT(text, pos, FALSE, NULL);
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseOffsetShortLocalizedGMT(const UnicodeString& text, ParsePosition& pos) const {
|
|
return parseOffsetLocalizedGMT(text, pos, TRUE, NULL);
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Private zone offset format/parse implementation
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::formatOffsetISO8601(int32_t offset, UBool isBasic, UBool useUtcIndicator,
|
|
UBool isShort, UBool ignoreSeconds, UnicodeString& result, UErrorCode& status) const {
|
|
if (U_FAILURE(status)) {
|
|
result.setToBogus();
|
|
return result;
|
|
}
|
|
int32_t absOffset = offset < 0 ? -offset : offset;
|
|
if (useUtcIndicator && (absOffset < MILLIS_PER_SECOND || (ignoreSeconds && absOffset < MILLIS_PER_MINUTE))) {
|
|
result.setTo(ISO8601_UTC);
|
|
return result;
|
|
}
|
|
|
|
OffsetFields minFields = isShort ? FIELDS_H : FIELDS_HM;
|
|
OffsetFields maxFields = ignoreSeconds ? FIELDS_HM : FIELDS_HMS;
|
|
UChar sep = isBasic ? 0 : ISO8601_SEP;
|
|
|
|
// Note: FIELDS_HMS as maxFields is a CLDR/ICU extension. ISO 8601 specification does
|
|
// not support seconds field.
|
|
|
|
if (absOffset >= MAX_OFFSET) {
|
|
result.setToBogus();
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return result;
|
|
}
|
|
|
|
int fields[3];
|
|
fields[0] = absOffset / MILLIS_PER_HOUR;
|
|
absOffset = absOffset % MILLIS_PER_HOUR;
|
|
fields[1] = absOffset / MILLIS_PER_MINUTE;
|
|
absOffset = absOffset % MILLIS_PER_MINUTE;
|
|
fields[2] = absOffset / MILLIS_PER_SECOND;
|
|
|
|
U_ASSERT(fields[0] >= 0 && fields[0] <= MAX_OFFSET_HOUR);
|
|
U_ASSERT(fields[1] >= 0 && fields[1] <= MAX_OFFSET_MINUTE);
|
|
U_ASSERT(fields[2] >= 0 && fields[2] <= MAX_OFFSET_SECOND);
|
|
|
|
int32_t lastIdx = maxFields;
|
|
while (lastIdx > minFields) {
|
|
if (fields[lastIdx] != 0) {
|
|
break;
|
|
}
|
|
lastIdx--;
|
|
}
|
|
|
|
UChar sign = PLUS;
|
|
if (offset < 0) {
|
|
// if all output fields are 0s, do not use negative sign
|
|
for (int32_t idx = 0; idx <= lastIdx; idx++) {
|
|
if (fields[idx] != 0) {
|
|
sign = MINUS;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
result.setTo(sign);
|
|
|
|
for (int32_t idx = 0; idx <= lastIdx; idx++) {
|
|
if (sep && idx != 0) {
|
|
result.append(sep);
|
|
}
|
|
result.append((UChar)(0x0030 + fields[idx]/10));
|
|
result.append((UChar)(0x0030 + fields[idx]%10));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::formatOffsetLocalizedGMT(int32_t offset, UBool isShort, UnicodeString& result, UErrorCode& status) const {
|
|
if (U_FAILURE(status)) {
|
|
result.setToBogus();
|
|
return result;
|
|
}
|
|
if (offset <= -MAX_OFFSET || offset >= MAX_OFFSET) {
|
|
result.setToBogus();
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return result;
|
|
}
|
|
|
|
if (offset == 0) {
|
|
result.setTo(fGMTZeroFormat);
|
|
return result;
|
|
}
|
|
|
|
UBool positive = TRUE;
|
|
if (offset < 0) {
|
|
offset = -offset;
|
|
positive = FALSE;
|
|
}
|
|
|
|
int32_t offsetH = offset / MILLIS_PER_HOUR;
|
|
offset = offset % MILLIS_PER_HOUR;
|
|
int32_t offsetM = offset / MILLIS_PER_MINUTE;
|
|
offset = offset % MILLIS_PER_MINUTE;
|
|
int32_t offsetS = offset / MILLIS_PER_SECOND;
|
|
|
|
U_ASSERT(offsetH <= MAX_OFFSET_HOUR && offsetM <= MAX_OFFSET_MINUTE && offsetS <= MAX_OFFSET_SECOND);
|
|
|
|
const UVector* offsetPatternItems = NULL;
|
|
if (positive) {
|
|
if (offsetS != 0) {
|
|
offsetPatternItems = fGMTOffsetPatternItems[UTZFMT_PAT_POSITIVE_HMS];
|
|
} else if (offsetM != 0 || !isShort) {
|
|
offsetPatternItems = fGMTOffsetPatternItems[UTZFMT_PAT_POSITIVE_HM];
|
|
} else {
|
|
offsetPatternItems = fGMTOffsetPatternItems[UTZFMT_PAT_POSITIVE_H];
|
|
}
|
|
} else {
|
|
if (offsetS != 0) {
|
|
offsetPatternItems = fGMTOffsetPatternItems[UTZFMT_PAT_NEGATIVE_HMS];
|
|
} else if (offsetM != 0 || !isShort) {
|
|
offsetPatternItems = fGMTOffsetPatternItems[UTZFMT_PAT_NEGATIVE_HM];
|
|
} else {
|
|
offsetPatternItems = fGMTOffsetPatternItems[UTZFMT_PAT_NEGATIVE_H];
|
|
}
|
|
}
|
|
|
|
U_ASSERT(offsetPatternItems != NULL);
|
|
|
|
// Building the GMT format string
|
|
result.setTo(fGMTPatternPrefix);
|
|
|
|
for (int32_t i = 0; i < offsetPatternItems->size(); i++) {
|
|
const GMTOffsetField* item = (GMTOffsetField*)offsetPatternItems->elementAt(i);
|
|
GMTOffsetField::FieldType type = item->getType();
|
|
|
|
switch (type) {
|
|
case GMTOffsetField::TEXT:
|
|
result.append(item->getPatternText(), -1);
|
|
break;
|
|
|
|
case GMTOffsetField::HOUR:
|
|
appendOffsetDigits(result, offsetH, (isShort ? 1 : 2));
|
|
break;
|
|
|
|
case GMTOffsetField::MINUTE:
|
|
appendOffsetDigits(result, offsetM, 2);
|
|
break;
|
|
|
|
case GMTOffsetField::SECOND:
|
|
appendOffsetDigits(result, offsetS, 2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
result.append(fGMTPatternSuffix);
|
|
return result;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseOffsetISO8601(const UnicodeString& text, ParsePosition& pos, UBool extendedOnly, UBool* hasDigitOffset /* = NULL */) const {
|
|
if (hasDigitOffset) {
|
|
*hasDigitOffset = FALSE;
|
|
}
|
|
int32_t start = pos.getIndex();
|
|
if (start >= text.length()) {
|
|
pos.setErrorIndex(start);
|
|
return 0;
|
|
}
|
|
|
|
UChar firstChar = text.charAt(start);
|
|
if (firstChar == ISO8601_UTC || firstChar == (UChar)(ISO8601_UTC + 0x20)) {
|
|
// "Z" (or "z") - indicates UTC
|
|
pos.setIndex(start + 1);
|
|
return 0;
|
|
}
|
|
|
|
int32_t sign = 1;
|
|
if (firstChar == PLUS) {
|
|
sign = 1;
|
|
} else if (firstChar == MINUS) {
|
|
sign = -1;
|
|
} else {
|
|
// Not an ISO 8601 offset string
|
|
pos.setErrorIndex(start);
|
|
return 0;
|
|
}
|
|
ParsePosition posOffset(start + 1);
|
|
int32_t offset = parseAsciiOffsetFields(text, posOffset, ISO8601_SEP, FIELDS_H, FIELDS_HMS);
|
|
if (posOffset.getErrorIndex() == -1 && !extendedOnly && (posOffset.getIndex() - start <= 3)) {
|
|
// If the text is successfully parsed as extended format with the options above, it can be also parsed
|
|
// as basic format. For example, "0230" can be parsed as offset 2:00 (only first digits are valid for
|
|
// extended format), but it can be parsed as offset 2:30 with basic format. We use longer result.
|
|
ParsePosition posBasic(start + 1);
|
|
int32_t tmpOffset = parseAbuttingAsciiOffsetFields(text, posBasic, FIELDS_H, FIELDS_HMS, FALSE);
|
|
if (posBasic.getErrorIndex() == -1 && posBasic.getIndex() > posOffset.getIndex()) {
|
|
offset = tmpOffset;
|
|
posOffset.setIndex(posBasic.getIndex());
|
|
}
|
|
}
|
|
|
|
if (posOffset.getErrorIndex() != -1) {
|
|
pos.setErrorIndex(start);
|
|
return 0;
|
|
}
|
|
|
|
pos.setIndex(posOffset.getIndex());
|
|
if (hasDigitOffset) {
|
|
*hasDigitOffset = TRUE;
|
|
}
|
|
return sign * offset;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseOffsetLocalizedGMT(const UnicodeString& text, ParsePosition& pos, UBool isShort, UBool* hasDigitOffset) const {
|
|
int32_t start = pos.getIndex();
|
|
int32_t offset = 0;
|
|
int32_t parsedLength = 0;
|
|
|
|
if (hasDigitOffset) {
|
|
*hasDigitOffset = FALSE;
|
|
}
|
|
|
|
offset = parseOffsetLocalizedGMTPattern(text, start, isShort, parsedLength);
|
|
|
|
// For now, parseOffsetLocalizedGMTPattern handles both long and short
|
|
// formats, no matter isShort is true or false. This might be changed in future
|
|
// when strict parsing is necessary, or different set of patterns are used for
|
|
// short/long formats.
|
|
#if 0
|
|
if (parsedLength == 0) {
|
|
offset = parseOffsetLocalizedGMTPattern(text, start, !isShort, parsedLength);
|
|
}
|
|
#endif
|
|
|
|
if (parsedLength > 0) {
|
|
if (hasDigitOffset) {
|
|
*hasDigitOffset = TRUE;
|
|
}
|
|
pos.setIndex(start + parsedLength);
|
|
return offset;
|
|
}
|
|
|
|
// Try the default patterns
|
|
offset = parseOffsetDefaultLocalizedGMT(text, start, parsedLength);
|
|
if (parsedLength > 0) {
|
|
if (hasDigitOffset) {
|
|
*hasDigitOffset = TRUE;
|
|
}
|
|
pos.setIndex(start + parsedLength);
|
|
return offset;
|
|
}
|
|
|
|
// Check if this is a GMT zero format
|
|
if (text.caseCompare(start, fGMTZeroFormat.length(), fGMTZeroFormat, 0) == 0) {
|
|
pos.setIndex(start + fGMTZeroFormat.length());
|
|
return 0;
|
|
}
|
|
|
|
// Check if this is a default GMT zero format
|
|
for (int32_t i = 0; ALT_GMT_STRINGS[i][0] != 0; i++) {
|
|
const UChar* defGMTZero = ALT_GMT_STRINGS[i];
|
|
int32_t defGMTZeroLen = u_strlen(defGMTZero);
|
|
if (text.caseCompare(start, defGMTZeroLen, defGMTZero, 0) == 0) {
|
|
pos.setIndex(start + defGMTZeroLen);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Nothing matched
|
|
pos.setErrorIndex(start);
|
|
return 0;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseOffsetLocalizedGMTPattern(const UnicodeString& text, int32_t start, UBool /*isShort*/, int32_t& parsedLen) const {
|
|
int32_t idx = start;
|
|
int32_t offset = 0;
|
|
UBool parsed = FALSE;
|
|
|
|
do {
|
|
// Prefix part
|
|
int32_t len = fGMTPatternPrefix.length();
|
|
if (len > 0 && text.caseCompare(idx, len, fGMTPatternPrefix, 0) != 0) {
|
|
// prefix match failed
|
|
break;
|
|
}
|
|
idx += len;
|
|
|
|
// Offset part
|
|
offset = parseOffsetFields(text, idx, FALSE, len);
|
|
if (len == 0) {
|
|
// offset field match failed
|
|
break;
|
|
}
|
|
idx += len;
|
|
|
|
len = fGMTPatternSuffix.length();
|
|
if (len > 0 && text.caseCompare(idx, len, fGMTPatternSuffix, 0) != 0) {
|
|
// no suffix match
|
|
break;
|
|
}
|
|
idx += len;
|
|
parsed = TRUE;
|
|
} while (FALSE);
|
|
|
|
parsedLen = parsed ? idx - start : 0;
|
|
return offset;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseOffsetFields(const UnicodeString& text, int32_t start, UBool /*isShort*/, int32_t& parsedLen) const {
|
|
int32_t outLen = 0;
|
|
int32_t offset = 0;
|
|
int32_t sign = 1;
|
|
|
|
parsedLen = 0;
|
|
|
|
int32_t offsetH, offsetM, offsetS;
|
|
offsetH = offsetM = offsetS = 0;
|
|
|
|
for (int32_t patidx = 0; PARSE_GMT_OFFSET_TYPES[patidx] >= 0; patidx++) {
|
|
int32_t gmtPatType = PARSE_GMT_OFFSET_TYPES[patidx];
|
|
UVector* items = fGMTOffsetPatternItems[gmtPatType];
|
|
U_ASSERT(items != NULL);
|
|
|
|
outLen = parseOffsetFieldsWithPattern(text, start, items, FALSE, offsetH, offsetM, offsetS);
|
|
if (outLen > 0) {
|
|
sign = (gmtPatType == UTZFMT_PAT_POSITIVE_H || gmtPatType == UTZFMT_PAT_POSITIVE_HM || gmtPatType == UTZFMT_PAT_POSITIVE_HMS) ?
|
|
1 : -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (outLen > 0 && fAbuttingOffsetHoursAndMinutes) {
|
|
// When hours field is sabutting minutes field,
|
|
// the parse result above may not be appropriate.
|
|
// For example, "01020" is parsed as 01:02: above,
|
|
// but it should be parsed as 00:10:20.
|
|
int32_t tmpLen = 0;
|
|
int32_t tmpSign = 1;
|
|
int32_t tmpH, tmpM, tmpS;
|
|
|
|
for (int32_t patidx = 0; PARSE_GMT_OFFSET_TYPES[patidx] >= 0; patidx++) {
|
|
int32_t gmtPatType = PARSE_GMT_OFFSET_TYPES[patidx];
|
|
UVector* items = fGMTOffsetPatternItems[gmtPatType];
|
|
U_ASSERT(items != NULL);
|
|
|
|
// forcing parse to use single hour digit
|
|
tmpLen = parseOffsetFieldsWithPattern(text, start, items, TRUE, tmpH, tmpM, tmpS);
|
|
if (tmpLen > 0) {
|
|
tmpSign = (gmtPatType == UTZFMT_PAT_POSITIVE_H || gmtPatType == UTZFMT_PAT_POSITIVE_HM || gmtPatType == UTZFMT_PAT_POSITIVE_HMS) ?
|
|
1 : -1;
|
|
break;
|
|
}
|
|
}
|
|
if (tmpLen > outLen) {
|
|
// Better parse result with single hour digit
|
|
outLen = tmpLen;
|
|
sign = tmpSign;
|
|
offsetH = tmpH;
|
|
offsetM = tmpM;
|
|
offsetS = tmpS;
|
|
}
|
|
}
|
|
|
|
if (outLen > 0) {
|
|
offset = ((((offsetH * 60) + offsetM) * 60) + offsetS) * 1000 * sign;
|
|
parsedLen = outLen;
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseOffsetFieldsWithPattern(const UnicodeString& text, int32_t start,
|
|
UVector* patternItems, UBool forceSingleHourDigit, int32_t& hour, int32_t& min, int32_t& sec) const {
|
|
UBool failed = FALSE;
|
|
int32_t offsetH, offsetM, offsetS;
|
|
offsetH = offsetM = offsetS = 0;
|
|
int32_t idx = start;
|
|
|
|
for (int32_t i = 0; i < patternItems->size(); i++) {
|
|
int32_t len = 0;
|
|
const GMTOffsetField* field = (const GMTOffsetField*)patternItems->elementAt(i);
|
|
GMTOffsetField::FieldType fieldType = field->getType();
|
|
if (fieldType == GMTOffsetField::TEXT) {
|
|
const UChar* patStr = field->getPatternText();
|
|
len = u_strlen(patStr);
|
|
if (text.caseCompare(idx, len, patStr, 0) != 0) {
|
|
failed = TRUE;
|
|
break;
|
|
}
|
|
idx += len;
|
|
} else {
|
|
if (fieldType == GMTOffsetField::HOUR) {
|
|
uint8_t maxDigits = forceSingleHourDigit ? 1 : 2;
|
|
offsetH = parseOffsetFieldWithLocalizedDigits(text, idx, 1, maxDigits, 0, MAX_OFFSET_HOUR, len);
|
|
} else if (fieldType == GMTOffsetField::MINUTE) {
|
|
offsetM = parseOffsetFieldWithLocalizedDigits(text, idx, 2, 2, 0, MAX_OFFSET_MINUTE, len);
|
|
} else if (fieldType == GMTOffsetField::SECOND) {
|
|
offsetS = parseOffsetFieldWithLocalizedDigits(text, idx, 2, 2, 0, MAX_OFFSET_SECOND, len);
|
|
}
|
|
|
|
if (len == 0) {
|
|
failed = TRUE;
|
|
break;
|
|
}
|
|
idx += len;
|
|
}
|
|
}
|
|
|
|
if (failed) {
|
|
hour = min = sec = 0;
|
|
return 0;
|
|
}
|
|
|
|
hour = offsetH;
|
|
min = offsetM;
|
|
sec = offsetS;
|
|
|
|
return idx - start;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseAbuttingOffsetFields(const UnicodeString& text, int32_t start, int32_t& parsedLen) const {
|
|
int32_t digits[MAX_OFFSET_DIGITS];
|
|
int32_t parsed[MAX_OFFSET_DIGITS]; // accumulative offsets
|
|
|
|
// Parse digits into int[]
|
|
int32_t idx = start;
|
|
int32_t len = 0;
|
|
int32_t numDigits = 0;
|
|
for (int32_t i = 0; i < MAX_OFFSET_DIGITS; i++) {
|
|
digits[i] = parseSingleLocalizedDigit(text, idx, len);
|
|
if (digits[i] < 0) {
|
|
break;
|
|
}
|
|
idx += len;
|
|
parsed[i] = idx - start;
|
|
numDigits++;
|
|
}
|
|
|
|
if (numDigits == 0) {
|
|
parsedLen = 0;
|
|
return 0;
|
|
}
|
|
|
|
int32_t offset = 0;
|
|
while (numDigits > 0) {
|
|
int32_t hour = 0;
|
|
int32_t min = 0;
|
|
int32_t sec = 0;
|
|
|
|
U_ASSERT(numDigits > 0 && numDigits <= MAX_OFFSET_DIGITS);
|
|
switch (numDigits) {
|
|
case 1: // H
|
|
hour = digits[0];
|
|
break;
|
|
case 2: // HH
|
|
hour = digits[0] * 10 + digits[1];
|
|
break;
|
|
case 3: // Hmm
|
|
hour = digits[0];
|
|
min = digits[1] * 10 + digits[2];
|
|
break;
|
|
case 4: // HHmm
|
|
hour = digits[0] * 10 + digits[1];
|
|
min = digits[2] * 10 + digits[3];
|
|
break;
|
|
case 5: // Hmmss
|
|
hour = digits[0];
|
|
min = digits[1] * 10 + digits[2];
|
|
sec = digits[3] * 10 + digits[4];
|
|
break;
|
|
case 6: // HHmmss
|
|
hour = digits[0] * 10 + digits[1];
|
|
min = digits[2] * 10 + digits[3];
|
|
sec = digits[4] * 10 + digits[5];
|
|
break;
|
|
}
|
|
if (hour <= MAX_OFFSET_HOUR && min <= MAX_OFFSET_MINUTE && sec <= MAX_OFFSET_SECOND) {
|
|
// found a valid combination
|
|
offset = hour * MILLIS_PER_HOUR + min * MILLIS_PER_MINUTE + sec * MILLIS_PER_SECOND;
|
|
parsedLen = parsed[numDigits - 1];
|
|
break;
|
|
}
|
|
numDigits--;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseOffsetDefaultLocalizedGMT(const UnicodeString& text, int start, int32_t& parsedLen) const {
|
|
int32_t idx = start;
|
|
int32_t offset = 0;
|
|
int32_t parsed = 0;
|
|
|
|
do {
|
|
// check global default GMT alternatives
|
|
int32_t gmtLen = 0;
|
|
|
|
for (int32_t i = 0; ALT_GMT_STRINGS[i][0] != 0; i++) {
|
|
const UChar* gmt = ALT_GMT_STRINGS[i];
|
|
int32_t len = u_strlen(gmt);
|
|
if (text.caseCompare(start, len, gmt, 0) == 0) {
|
|
gmtLen = len;
|
|
break;
|
|
}
|
|
}
|
|
if (gmtLen == 0) {
|
|
break;
|
|
}
|
|
idx += gmtLen;
|
|
|
|
// offset needs a sign char and a digit at minimum
|
|
if (idx + 1 >= text.length()) {
|
|
break;
|
|
}
|
|
|
|
// parse sign
|
|
int32_t sign = 1;
|
|
UChar c = text.charAt(idx);
|
|
if (c == PLUS) {
|
|
sign = 1;
|
|
} else if (c == MINUS) {
|
|
sign = -1;
|
|
} else {
|
|
break;
|
|
}
|
|
idx++;
|
|
|
|
// offset part
|
|
// try the default pattern with the separator first
|
|
int32_t lenWithSep = 0;
|
|
int32_t offsetWithSep = parseDefaultOffsetFields(text, idx, DEFAULT_GMT_OFFSET_SEP, lenWithSep);
|
|
if (lenWithSep == text.length() - idx) {
|
|
// maximum match
|
|
offset = offsetWithSep * sign;
|
|
idx += lenWithSep;
|
|
} else {
|
|
// try abutting field pattern
|
|
int32_t lenAbut = 0;
|
|
int32_t offsetAbut = parseAbuttingOffsetFields(text, idx, lenAbut);
|
|
|
|
if (lenWithSep > lenAbut) {
|
|
offset = offsetWithSep * sign;
|
|
idx += lenWithSep;
|
|
} else {
|
|
offset = offsetAbut * sign;
|
|
idx += lenAbut;
|
|
}
|
|
}
|
|
parsed = idx - start;
|
|
} while (false);
|
|
|
|
parsedLen = parsed;
|
|
return offset;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseDefaultOffsetFields(const UnicodeString& text, int32_t start, UChar separator, int32_t& parsedLen) const {
|
|
int32_t max = text.length();
|
|
int32_t idx = start;
|
|
int32_t len = 0;
|
|
int32_t hour = 0, min = 0, sec = 0;
|
|
|
|
parsedLen = 0;
|
|
|
|
do {
|
|
hour = parseOffsetFieldWithLocalizedDigits(text, idx, 1, 2, 0, MAX_OFFSET_HOUR, len);
|
|
if (len == 0) {
|
|
break;
|
|
}
|
|
idx += len;
|
|
|
|
if (idx + 1 < max && text.charAt(idx) == separator) {
|
|
min = parseOffsetFieldWithLocalizedDigits(text, idx + 1, 2, 2, 0, MAX_OFFSET_MINUTE, len);
|
|
if (len == 0) {
|
|
break;
|
|
}
|
|
idx += (1 + len);
|
|
|
|
if (idx + 1 < max && text.charAt(idx) == separator) {
|
|
sec = parseOffsetFieldWithLocalizedDigits(text, idx + 1, 2, 2, 0, MAX_OFFSET_SECOND, len);
|
|
if (len == 0) {
|
|
break;
|
|
}
|
|
idx += (1 + len);
|
|
}
|
|
}
|
|
} while (FALSE);
|
|
|
|
if (idx == start) {
|
|
return 0;
|
|
}
|
|
|
|
parsedLen = idx - start;
|
|
return hour * MILLIS_PER_HOUR + min * MILLIS_PER_MINUTE + sec * MILLIS_PER_SECOND;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseOffsetFieldWithLocalizedDigits(const UnicodeString& text, int32_t start, uint8_t minDigits, uint8_t maxDigits, uint16_t minVal, uint16_t maxVal, int32_t& parsedLen) const {
|
|
parsedLen = 0;
|
|
|
|
int32_t decVal = 0;
|
|
int32_t numDigits = 0;
|
|
int32_t idx = start;
|
|
int32_t digitLen = 0;
|
|
|
|
while (idx < text.length() && numDigits < maxDigits) {
|
|
int32_t digit = parseSingleLocalizedDigit(text, idx, digitLen);
|
|
if (digit < 0) {
|
|
break;
|
|
}
|
|
int32_t tmpVal = decVal * 10 + digit;
|
|
if (tmpVal > maxVal) {
|
|
break;
|
|
}
|
|
decVal = tmpVal;
|
|
numDigits++;
|
|
idx += digitLen;
|
|
}
|
|
|
|
// Note: maxVal is checked in the while loop
|
|
if (numDigits < minDigits || decVal < minVal) {
|
|
decVal = -1;
|
|
numDigits = 0;
|
|
} else {
|
|
parsedLen = idx - start;
|
|
}
|
|
|
|
return decVal;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseSingleLocalizedDigit(const UnicodeString& text, int32_t start, int32_t& len) const {
|
|
int32_t digit = -1;
|
|
len = 0;
|
|
if (start < text.length()) {
|
|
UChar32 cp = text.char32At(start);
|
|
|
|
// First, try digits configured for this instance
|
|
for (int32_t i = 0; i < 10; i++) {
|
|
if (cp == fGMTOffsetDigits[i]) {
|
|
digit = i;
|
|
break;
|
|
}
|
|
}
|
|
// If failed, check if this is a Unicode digit
|
|
if (digit < 0) {
|
|
int32_t tmp = u_charDigitValue(cp);
|
|
digit = (tmp >= 0 && tmp <= 9) ? tmp : -1;
|
|
}
|
|
|
|
if (digit >= 0) {
|
|
int32_t next = text.moveIndex32(start, 1);
|
|
len = next - start;
|
|
}
|
|
}
|
|
return digit;
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::formatOffsetWithAsciiDigits(int32_t offset, UChar sep, OffsetFields minFields, OffsetFields maxFields, UnicodeString& result) {
|
|
U_ASSERT(maxFields >= minFields);
|
|
U_ASSERT(offset > -MAX_OFFSET && offset < MAX_OFFSET);
|
|
|
|
UChar sign = PLUS;
|
|
if (offset < 0) {
|
|
sign = MINUS;
|
|
offset = -offset;
|
|
}
|
|
result.setTo(sign);
|
|
|
|
int fields[3];
|
|
fields[0] = offset / MILLIS_PER_HOUR;
|
|
offset = offset % MILLIS_PER_HOUR;
|
|
fields[1] = offset / MILLIS_PER_MINUTE;
|
|
offset = offset % MILLIS_PER_MINUTE;
|
|
fields[2] = offset / MILLIS_PER_SECOND;
|
|
|
|
U_ASSERT(fields[0] >= 0 && fields[0] <= MAX_OFFSET_HOUR);
|
|
U_ASSERT(fields[1] >= 0 && fields[1] <= MAX_OFFSET_MINUTE);
|
|
U_ASSERT(fields[2] >= 0 && fields[2] <= MAX_OFFSET_SECOND);
|
|
|
|
int32_t lastIdx = maxFields;
|
|
while (lastIdx > minFields) {
|
|
if (fields[lastIdx] != 0) {
|
|
break;
|
|
}
|
|
lastIdx--;
|
|
}
|
|
|
|
for (int32_t idx = 0; idx <= lastIdx; idx++) {
|
|
if (sep && idx != 0) {
|
|
result.append(sep);
|
|
}
|
|
result.append((UChar)(0x0030 + fields[idx]/10));
|
|
result.append((UChar)(0x0030 + fields[idx]%10));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseAbuttingAsciiOffsetFields(const UnicodeString& text, ParsePosition& pos, OffsetFields minFields, OffsetFields maxFields, UBool fixedHourWidth) {
|
|
int32_t start = pos.getIndex();
|
|
|
|
int32_t minDigits = 2 * (minFields + 1) - (fixedHourWidth ? 0 : 1);
|
|
int32_t maxDigits = 2 * (maxFields + 1);
|
|
|
|
U_ASSERT(maxDigits <= MAX_OFFSET_DIGITS);
|
|
|
|
int32_t digits[MAX_OFFSET_DIGITS] = {};
|
|
int32_t numDigits = 0;
|
|
int32_t idx = start;
|
|
while (numDigits < maxDigits && idx < text.length()) {
|
|
UChar uch = text.charAt(idx);
|
|
int32_t digit = DIGIT_VAL(uch);
|
|
if (digit < 0) {
|
|
break;
|
|
}
|
|
digits[numDigits] = digit;
|
|
numDigits++;
|
|
idx++;
|
|
}
|
|
|
|
if (fixedHourWidth && (numDigits & 1)) {
|
|
// Fixed digits, so the number of digits must be even number. Truncating.
|
|
numDigits--;
|
|
}
|
|
|
|
if (numDigits < minDigits) {
|
|
pos.setErrorIndex(start);
|
|
return 0;
|
|
}
|
|
|
|
int32_t hour = 0, min = 0, sec = 0;
|
|
UBool bParsed = FALSE;
|
|
while (numDigits >= minDigits) {
|
|
switch (numDigits) {
|
|
case 1: //H
|
|
hour = digits[0];
|
|
break;
|
|
case 2: //HH
|
|
hour = digits[0] * 10 + digits[1];
|
|
break;
|
|
case 3: //Hmm
|
|
hour = digits[0];
|
|
min = digits[1] * 10 + digits[2];
|
|
break;
|
|
case 4: //HHmm
|
|
hour = digits[0] * 10 + digits[1];
|
|
min = digits[2] * 10 + digits[3];
|
|
break;
|
|
case 5: //Hmmss
|
|
hour = digits[0];
|
|
min = digits[1] * 10 + digits[2];
|
|
sec = digits[3] * 10 + digits[4];
|
|
break;
|
|
case 6: //HHmmss
|
|
hour = digits[0] * 10 + digits[1];
|
|
min = digits[2] * 10 + digits[3];
|
|
sec = digits[4] * 10 + digits[5];
|
|
break;
|
|
}
|
|
|
|
if (hour <= MAX_OFFSET_HOUR && min <= MAX_OFFSET_MINUTE && sec <= MAX_OFFSET_SECOND) {
|
|
// Successfully parsed
|
|
bParsed = true;
|
|
break;
|
|
}
|
|
|
|
// Truncating
|
|
numDigits -= (fixedHourWidth ? 2 : 1);
|
|
hour = min = sec = 0;
|
|
}
|
|
|
|
if (!bParsed) {
|
|
pos.setErrorIndex(start);
|
|
return 0;
|
|
}
|
|
pos.setIndex(start + numDigits);
|
|
return ((((hour * 60) + min) * 60) + sec) * 1000;
|
|
}
|
|
|
|
int32_t
|
|
TimeZoneFormat::parseAsciiOffsetFields(const UnicodeString& text, ParsePosition& pos, UChar sep, OffsetFields minFields, OffsetFields maxFields) {
|
|
int32_t start = pos.getIndex();
|
|
int32_t fieldVal[] = {0, 0, 0};
|
|
int32_t fieldLen[] = {0, -1, -1};
|
|
for (int32_t idx = start, fieldIdx = 0; idx < text.length() && fieldIdx <= maxFields; idx++) {
|
|
UChar c = text.charAt(idx);
|
|
if (c == sep) {
|
|
if (fieldIdx == 0) {
|
|
if (fieldLen[0] == 0) {
|
|
// no hours field
|
|
break;
|
|
}
|
|
// 1 digit hour, move to next field
|
|
} else {
|
|
if (fieldLen[fieldIdx] != -1) {
|
|
// premature minute or seconds field
|
|
break;
|
|
}
|
|
fieldLen[fieldIdx] = 0;
|
|
}
|
|
continue;
|
|
} else if (fieldLen[fieldIdx] == -1) {
|
|
// no separator after 2 digit field
|
|
break;
|
|
}
|
|
int32_t digit = DIGIT_VAL(c);
|
|
if (digit < 0) {
|
|
// not a digit
|
|
break;
|
|
}
|
|
fieldVal[fieldIdx] = fieldVal[fieldIdx] * 10 + digit;
|
|
fieldLen[fieldIdx]++;
|
|
if (fieldLen[fieldIdx] >= 2) {
|
|
// parsed 2 digits, move to next field
|
|
fieldIdx++;
|
|
}
|
|
}
|
|
|
|
int32_t offset = 0;
|
|
int32_t parsedLen = 0;
|
|
int32_t parsedFields = -1;
|
|
do {
|
|
// hour
|
|
if (fieldLen[0] == 0) {
|
|
break;
|
|
}
|
|
if (fieldVal[0] > MAX_OFFSET_HOUR) {
|
|
offset = (fieldVal[0] / 10) * MILLIS_PER_HOUR;
|
|
parsedFields = FIELDS_H;
|
|
parsedLen = 1;
|
|
break;
|
|
}
|
|
offset = fieldVal[0] * MILLIS_PER_HOUR;
|
|
parsedLen = fieldLen[0];
|
|
parsedFields = FIELDS_H;
|
|
|
|
// minute
|
|
if (fieldLen[1] != 2 || fieldVal[1] > MAX_OFFSET_MINUTE) {
|
|
break;
|
|
}
|
|
offset += fieldVal[1] * MILLIS_PER_MINUTE;
|
|
parsedLen += (1 + fieldLen[1]);
|
|
parsedFields = FIELDS_HM;
|
|
|
|
// second
|
|
if (fieldLen[2] != 2 || fieldVal[2] > MAX_OFFSET_SECOND) {
|
|
break;
|
|
}
|
|
offset += fieldVal[2] * MILLIS_PER_SECOND;
|
|
parsedLen += (1 + fieldLen[2]);
|
|
parsedFields = FIELDS_HMS;
|
|
} while (false);
|
|
|
|
if (parsedFields < minFields) {
|
|
pos.setErrorIndex(start);
|
|
return 0;
|
|
}
|
|
|
|
pos.setIndex(start + parsedLen);
|
|
return offset;
|
|
}
|
|
|
|
void
|
|
TimeZoneFormat::appendOffsetDigits(UnicodeString& buf, int32_t n, uint8_t minDigits) const {
|
|
U_ASSERT(n >= 0 && n < 60);
|
|
int32_t numDigits = n >= 10 ? 2 : 1;
|
|
for (int32_t i = 0; i < minDigits - numDigits; i++) {
|
|
buf.append(fGMTOffsetDigits[0]);
|
|
}
|
|
if (numDigits == 2) {
|
|
buf.append(fGMTOffsetDigits[n / 10]);
|
|
}
|
|
buf.append(fGMTOffsetDigits[n % 10]);
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Private misc
|
|
void
|
|
TimeZoneFormat::initGMTPattern(const UnicodeString& gmtPattern, UErrorCode& status) {
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
// This implementation not perfect, but sufficient practically.
|
|
int32_t idx = gmtPattern.indexOf(ARG0, ARG0_LEN, 0);
|
|
if (idx < 0) {
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
fGMTPattern.setTo(gmtPattern);
|
|
unquote(gmtPattern.tempSubString(0, idx), fGMTPatternPrefix);
|
|
unquote(gmtPattern.tempSubString(idx + ARG0_LEN), fGMTPatternSuffix);
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::unquote(const UnicodeString& pattern, UnicodeString& result) {
|
|
if (pattern.indexOf(SINGLEQUOTE) < 0) {
|
|
result.setTo(pattern);
|
|
return result;
|
|
}
|
|
result.remove();
|
|
UBool isPrevQuote = FALSE;
|
|
UBool inQuote = FALSE;
|
|
for (int32_t i = 0; i < pattern.length(); i++) {
|
|
UChar c = pattern.charAt(i);
|
|
if (c == SINGLEQUOTE) {
|
|
if (isPrevQuote) {
|
|
result.append(c);
|
|
isPrevQuote = FALSE;
|
|
} else {
|
|
isPrevQuote = TRUE;
|
|
}
|
|
inQuote = !inQuote;
|
|
} else {
|
|
isPrevQuote = FALSE;
|
|
result.append(c);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
UVector*
|
|
TimeZoneFormat::parseOffsetPattern(const UnicodeString& pattern, OffsetFields required, UErrorCode& status) {
|
|
if (U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
UVector* result = new UVector(deleteGMTOffsetField, NULL, status);
|
|
if (result == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
|
|
int32_t checkBits = 0;
|
|
UBool isPrevQuote = FALSE;
|
|
UBool inQuote = FALSE;
|
|
UnicodeString text;
|
|
GMTOffsetField::FieldType itemType = GMTOffsetField::TEXT;
|
|
int32_t itemLength = 1;
|
|
|
|
for (int32_t i = 0; i < pattern.length(); i++) {
|
|
UChar ch = pattern.charAt(i);
|
|
if (ch == SINGLEQUOTE) {
|
|
if (isPrevQuote) {
|
|
text.append(SINGLEQUOTE);
|
|
isPrevQuote = FALSE;
|
|
} else {
|
|
isPrevQuote = TRUE;
|
|
if (itemType != GMTOffsetField::TEXT) {
|
|
if (GMTOffsetField::isValid(itemType, itemLength)) {
|
|
GMTOffsetField* fld = GMTOffsetField::createTimeField(itemType, (uint8_t)itemLength, status);
|
|
result->addElement(fld, status);
|
|
if (U_FAILURE(status)) {
|
|
break;
|
|
}
|
|
} else {
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
itemType = GMTOffsetField::TEXT;
|
|
}
|
|
}
|
|
inQuote = !inQuote;
|
|
} else {
|
|
isPrevQuote = FALSE;
|
|
if (inQuote) {
|
|
text.append(ch);
|
|
} else {
|
|
GMTOffsetField::FieldType tmpType = GMTOffsetField::getTypeByLetter(ch);
|
|
if (tmpType != GMTOffsetField::TEXT) {
|
|
// an offset time pattern character
|
|
if (tmpType == itemType) {
|
|
itemLength++;
|
|
} else {
|
|
if (itemType == GMTOffsetField::TEXT) {
|
|
if (text.length() > 0) {
|
|
GMTOffsetField* textfld = GMTOffsetField::createText(text, status);
|
|
result->addElement(textfld, status);
|
|
if (U_FAILURE(status)) {
|
|
break;
|
|
}
|
|
text.remove();
|
|
}
|
|
} else {
|
|
if (GMTOffsetField::isValid(itemType, itemLength)) {
|
|
GMTOffsetField* fld = GMTOffsetField::createTimeField(itemType, itemLength, status);
|
|
result->addElement(fld, status);
|
|
if (U_FAILURE(status)) {
|
|
break;
|
|
}
|
|
} else {
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
itemType = tmpType;
|
|
itemLength = 1;
|
|
checkBits |= tmpType;
|
|
}
|
|
} else {
|
|
// a string literal
|
|
if (itemType != GMTOffsetField::TEXT) {
|
|
if (GMTOffsetField::isValid(itemType, itemLength)) {
|
|
GMTOffsetField* fld = GMTOffsetField::createTimeField(itemType, itemLength, status);
|
|
result->addElement(fld, status);
|
|
if (U_FAILURE(status)) {
|
|
break;
|
|
}
|
|
} else {
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
break;
|
|
}
|
|
itemType = GMTOffsetField::TEXT;
|
|
}
|
|
text.append(ch);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// handle last item
|
|
if (U_SUCCESS(status)) {
|
|
if (itemType == GMTOffsetField::TEXT) {
|
|
if (text.length() > 0) {
|
|
GMTOffsetField* tfld = GMTOffsetField::createText(text, status);
|
|
result->addElement(tfld, status);
|
|
}
|
|
} else {
|
|
if (GMTOffsetField::isValid(itemType, itemLength)) {
|
|
GMTOffsetField* fld = GMTOffsetField::createTimeField(itemType, itemLength, status);
|
|
result->addElement(fld, status);
|
|
} else {
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
}
|
|
}
|
|
|
|
// Check all required fields are set
|
|
if (U_SUCCESS(status)) {
|
|
int32_t reqBits = 0;
|
|
switch (required) {
|
|
case FIELDS_H:
|
|
reqBits = GMTOffsetField::HOUR;
|
|
break;
|
|
case FIELDS_HM:
|
|
reqBits = GMTOffsetField::HOUR | GMTOffsetField::MINUTE;
|
|
break;
|
|
case FIELDS_HMS:
|
|
reqBits = GMTOffsetField::HOUR | GMTOffsetField::MINUTE | GMTOffsetField::SECOND;
|
|
break;
|
|
}
|
|
if (checkBits == reqBits) {
|
|
// all required fields are set, no extra fields
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
// error
|
|
delete result;
|
|
return NULL;
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::expandOffsetPattern(const UnicodeString& offsetHM, UnicodeString& result, UErrorCode& status) {
|
|
result.setToBogus();
|
|
if (U_FAILURE(status)) {
|
|
return result;
|
|
}
|
|
U_ASSERT(u_strlen(DEFAULT_GMT_OFFSET_MINUTE_PATTERN) == 2);
|
|
|
|
int32_t idx_mm = offsetHM.indexOf(DEFAULT_GMT_OFFSET_MINUTE_PATTERN, 2, 0);
|
|
if (idx_mm < 0) {
|
|
// Bad time zone hour pattern data
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return result;
|
|
}
|
|
|
|
UnicodeString sep;
|
|
int32_t idx_H = offsetHM.tempSubString(0, idx_mm).lastIndexOf((UChar)0x0048 /* H */);
|
|
if (idx_H >= 0) {
|
|
sep = offsetHM.tempSubString(idx_H + 1, idx_mm - (idx_H + 1));
|
|
}
|
|
result.setTo(offsetHM.tempSubString(0, idx_mm + 2));
|
|
result.append(sep);
|
|
result.append(DEFAULT_GMT_OFFSET_SECOND_PATTERN, -1);
|
|
result.append(offsetHM.tempSubString(idx_mm + 2));
|
|
return result;
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::truncateOffsetPattern(const UnicodeString& offsetHM, UnicodeString& result, UErrorCode& status) {
|
|
result.setToBogus();
|
|
if (U_FAILURE(status)) {
|
|
return result;
|
|
}
|
|
U_ASSERT(u_strlen(DEFAULT_GMT_OFFSET_MINUTE_PATTERN) == 2);
|
|
|
|
int32_t idx_mm = offsetHM.indexOf(DEFAULT_GMT_OFFSET_MINUTE_PATTERN, 2, 0);
|
|
if (idx_mm < 0) {
|
|
// Bad time zone hour pattern data
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return result;
|
|
}
|
|
UChar HH[] = {0x0048, 0x0048};
|
|
int32_t idx_HH = offsetHM.tempSubString(0, idx_mm).lastIndexOf(HH, 2, 0);
|
|
if (idx_HH >= 0) {
|
|
return result.setTo(offsetHM.tempSubString(0, idx_HH + 2));
|
|
}
|
|
int32_t idx_H = offsetHM.tempSubString(0, idx_mm).lastIndexOf((UChar)0x0048, 0);
|
|
if (idx_H >= 0) {
|
|
return result.setTo(offsetHM.tempSubString(0, idx_H + 1));
|
|
}
|
|
// Bad time zone hour pattern data
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return result;
|
|
}
|
|
|
|
void
|
|
TimeZoneFormat::initGMTOffsetPatterns(UErrorCode& status) {
|
|
for (int32_t type = 0; type < UTZFMT_PAT_COUNT; type++) {
|
|
switch (type) {
|
|
case UTZFMT_PAT_POSITIVE_H:
|
|
case UTZFMT_PAT_NEGATIVE_H:
|
|
fGMTOffsetPatternItems[type] = parseOffsetPattern(fGMTOffsetPatterns[type], FIELDS_H, status);
|
|
break;
|
|
case UTZFMT_PAT_POSITIVE_HM:
|
|
case UTZFMT_PAT_NEGATIVE_HM:
|
|
fGMTOffsetPatternItems[type] = parseOffsetPattern(fGMTOffsetPatterns[type], FIELDS_HM, status);
|
|
break;
|
|
case UTZFMT_PAT_POSITIVE_HMS:
|
|
case UTZFMT_PAT_NEGATIVE_HMS:
|
|
fGMTOffsetPatternItems[type] = parseOffsetPattern(fGMTOffsetPatterns[type], FIELDS_HMS, status);
|
|
break;
|
|
}
|
|
}
|
|
checkAbuttingHoursAndMinutes();
|
|
}
|
|
|
|
void
|
|
TimeZoneFormat::checkAbuttingHoursAndMinutes() {
|
|
fAbuttingOffsetHoursAndMinutes= FALSE;
|
|
for (int32_t type = 0; type < UTZFMT_PAT_COUNT; type++) {
|
|
UBool afterH = FALSE;
|
|
UVector *items = fGMTOffsetPatternItems[type];
|
|
for (int32_t i = 0; i < items->size(); i++) {
|
|
const GMTOffsetField* item = (GMTOffsetField*)items->elementAt(i);
|
|
GMTOffsetField::FieldType type = item->getType();
|
|
if (type != GMTOffsetField::TEXT) {
|
|
if (afterH) {
|
|
fAbuttingOffsetHoursAndMinutes = TRUE;
|
|
break;
|
|
} else if (type == GMTOffsetField::HOUR) {
|
|
afterH = TRUE;
|
|
}
|
|
} else if (afterH) {
|
|
break;
|
|
}
|
|
}
|
|
if (fAbuttingOffsetHoursAndMinutes) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
UBool
|
|
TimeZoneFormat::toCodePoints(const UnicodeString& str, UChar32* codeArray, int32_t size) {
|
|
int32_t count = str.countChar32();
|
|
if (count != size) {
|
|
return FALSE;
|
|
}
|
|
|
|
for (int32_t idx = 0, start = 0; idx < size; idx++) {
|
|
codeArray[idx] = str.char32At(start);
|
|
start = str.moveIndex32(start, 1);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
TimeZone*
|
|
TimeZoneFormat::createTimeZoneForOffset(int32_t offset) const {
|
|
if (offset == 0) {
|
|
// when offset is 0, we should use "Etc/GMT"
|
|
return TimeZone::createTimeZone(UnicodeString(TZID_GMT));
|
|
}
|
|
return ZoneMeta::createCustomTimeZone(offset);
|
|
}
|
|
|
|
UTimeZoneFormatTimeType
|
|
TimeZoneFormat::getTimeType(UTimeZoneNameType nameType) {
|
|
switch (nameType) {
|
|
case UTZNM_LONG_STANDARD:
|
|
case UTZNM_SHORT_STANDARD:
|
|
return UTZFMT_TIME_TYPE_STANDARD;
|
|
|
|
case UTZNM_LONG_DAYLIGHT:
|
|
case UTZNM_SHORT_DAYLIGHT:
|
|
return UTZFMT_TIME_TYPE_DAYLIGHT;
|
|
|
|
default:
|
|
U_ASSERT(FALSE);
|
|
}
|
|
return UTZFMT_TIME_TYPE_UNKNOWN;
|
|
}
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::getTimeZoneID(const TimeZoneNames::MatchInfoCollection* matches, int32_t idx, UnicodeString& tzID) const {
|
|
if (!matches->getTimeZoneIDAt(idx, tzID)) {
|
|
UnicodeString mzID;
|
|
if (matches->getMetaZoneIDAt(idx, mzID)) {
|
|
fTimeZoneNames->getReferenceZoneID(mzID, fTargetRegion, tzID);
|
|
}
|
|
}
|
|
return tzID;
|
|
}
|
|
|
|
|
|
class ZoneIdMatchHandler : public TextTrieMapSearchResultHandler {
|
|
public:
|
|
ZoneIdMatchHandler();
|
|
virtual ~ZoneIdMatchHandler();
|
|
|
|
UBool handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status);
|
|
const UChar* getID();
|
|
int32_t getMatchLen();
|
|
private:
|
|
int32_t fLen;
|
|
const UChar* fID;
|
|
};
|
|
|
|
ZoneIdMatchHandler::ZoneIdMatchHandler()
|
|
: fLen(0), fID(NULL) {
|
|
}
|
|
|
|
ZoneIdMatchHandler::~ZoneIdMatchHandler() {
|
|
}
|
|
|
|
UBool
|
|
ZoneIdMatchHandler::handleMatch(int32_t matchLength, const CharacterNode *node, UErrorCode &status) {
|
|
if (U_FAILURE(status)) {
|
|
return FALSE;
|
|
}
|
|
if (node->hasValues()) {
|
|
const UChar* id = (const UChar*)node->getValue(0);
|
|
if (id != NULL) {
|
|
if (fLen < matchLength) {
|
|
fID = id;
|
|
fLen = matchLength;
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
const UChar*
|
|
ZoneIdMatchHandler::getID() {
|
|
return fID;
|
|
}
|
|
|
|
int32_t
|
|
ZoneIdMatchHandler::getMatchLen() {
|
|
return fLen;
|
|
}
|
|
|
|
|
|
static void U_CALLCONV initZoneIdTrie(UErrorCode &status) {
|
|
U_ASSERT(gZoneIdTrie == NULL);
|
|
ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEFORMAT, tzfmt_cleanup);
|
|
gZoneIdTrie = new TextTrieMap(TRUE, NULL); // No deleter, because values are pooled by ZoneMeta
|
|
if (gZoneIdTrie == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
StringEnumeration *tzenum = TimeZone::createEnumeration();
|
|
const UnicodeString *id;
|
|
while ((id = tzenum->snext(status))) {
|
|
const UChar* uid = ZoneMeta::findTimeZoneID(*id);
|
|
if (uid) {
|
|
gZoneIdTrie->put(uid, const_cast<UChar *>(uid), status);
|
|
}
|
|
}
|
|
delete tzenum;
|
|
}
|
|
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::parseZoneID(const UnicodeString& text, ParsePosition& pos, UnicodeString& tzID) const {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
umtx_initOnce(gZoneIdTrieInitOnce, &initZoneIdTrie, status);
|
|
|
|
int32_t start = pos.getIndex();
|
|
int32_t len = 0;
|
|
tzID.setToBogus();
|
|
|
|
if (U_SUCCESS(status)) {
|
|
LocalPointer<ZoneIdMatchHandler> handler(new ZoneIdMatchHandler());
|
|
gZoneIdTrie->search(text, start, handler.getAlias(), status);
|
|
len = handler->getMatchLen();
|
|
if (len > 0) {
|
|
tzID.setTo(handler->getID(), -1);
|
|
}
|
|
}
|
|
|
|
if (len > 0) {
|
|
pos.setIndex(start + len);
|
|
} else {
|
|
pos.setErrorIndex(start);
|
|
}
|
|
|
|
return tzID;
|
|
}
|
|
|
|
static void U_CALLCONV initShortZoneIdTrie(UErrorCode &status) {
|
|
U_ASSERT(gShortZoneIdTrie == NULL);
|
|
ucln_i18n_registerCleanup(UCLN_I18N_TIMEZONEFORMAT, tzfmt_cleanup);
|
|
StringEnumeration *tzenum = TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL, NULL, NULL, status);
|
|
if (U_SUCCESS(status)) {
|
|
gShortZoneIdTrie = new TextTrieMap(TRUE, NULL); // No deleter, because values are pooled by ZoneMeta
|
|
if (gShortZoneIdTrie == NULL) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
} else {
|
|
const UnicodeString *id;
|
|
while ((id = tzenum->snext(status))) {
|
|
const UChar* uID = ZoneMeta::findTimeZoneID(*id);
|
|
const UChar* shortID = ZoneMeta::getShortID(*id);
|
|
if (shortID && uID) {
|
|
gShortZoneIdTrie->put(shortID, const_cast<UChar *>(uID), status);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
delete tzenum;
|
|
}
|
|
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::parseShortZoneID(const UnicodeString& text, ParsePosition& pos, UnicodeString& tzID) const {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
umtx_initOnce(gShortZoneIdTrieInitOnce, &initShortZoneIdTrie, status);
|
|
|
|
int32_t start = pos.getIndex();
|
|
int32_t len = 0;
|
|
tzID.setToBogus();
|
|
|
|
if (U_SUCCESS(status)) {
|
|
LocalPointer<ZoneIdMatchHandler> handler(new ZoneIdMatchHandler());
|
|
gShortZoneIdTrie->search(text, start, handler.getAlias(), status);
|
|
len = handler->getMatchLen();
|
|
if (len > 0) {
|
|
tzID.setTo(handler->getID(), -1);
|
|
}
|
|
}
|
|
|
|
if (len > 0) {
|
|
pos.setIndex(start + len);
|
|
} else {
|
|
pos.setErrorIndex(start);
|
|
}
|
|
|
|
return tzID;
|
|
}
|
|
|
|
|
|
UnicodeString&
|
|
TimeZoneFormat::parseExemplarLocation(const UnicodeString& text, ParsePosition& pos, UnicodeString& tzID) const {
|
|
int32_t startIdx = pos.getIndex();
|
|
int32_t parsedPos = -1;
|
|
tzID.setToBogus();
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
LocalPointer<TimeZoneNames::MatchInfoCollection> exemplarMatches(fTimeZoneNames->find(text, startIdx, UTZNM_EXEMPLAR_LOCATION, status));
|
|
if (U_FAILURE(status)) {
|
|
pos.setErrorIndex(startIdx);
|
|
return tzID;
|
|
}
|
|
int32_t matchIdx = -1;
|
|
if (!exemplarMatches.isNull()) {
|
|
for (int32_t i = 0; i < exemplarMatches->size(); i++) {
|
|
if (startIdx + exemplarMatches->getMatchLengthAt(i) > parsedPos) {
|
|
matchIdx = i;
|
|
parsedPos = startIdx + exemplarMatches->getMatchLengthAt(i);
|
|
}
|
|
}
|
|
if (parsedPos > 0) {
|
|
pos.setIndex(parsedPos);
|
|
getTimeZoneID(exemplarMatches.getAlias(), matchIdx, tzID);
|
|
}
|
|
}
|
|
|
|
if (tzID.length() == 0) {
|
|
pos.setErrorIndex(startIdx);
|
|
}
|
|
|
|
return tzID;
|
|
}
|
|
|
|
U_NAMESPACE_END
|
|
|
|
#endif
|