1 // © 2018 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
6 #include "unicode/utypes.h"
8 #if !UCONFIG_NO_FORMATTING
11 #include "unicode/ucal.h"
12 #include "unicode/ures.h"
13 #include "unicode/ustring.h"
14 #include "unicode/timezone.h"
23 static const int32_t MAX_ENCODED_START_YEAR
= 32767;
24 static const int32_t MIN_ENCODED_START_YEAR
= -32768;
25 static const int32_t MIN_ENCODED_START
= -2147483391; // encodeDate(MIN_ENCODED_START_YEAR, 1, 1, ...);
27 static const int32_t YEAR_MASK
= 0xFFFF0000;
28 static const int32_t MONTH_MASK
= 0x0000FF00;
29 static const int32_t DAY_MASK
= 0x000000FF;
31 static const int32_t MAX_INT32
= 0x7FFFFFFF;
32 static const int32_t MIN_INT32
= 0xFFFFFFFF;
34 static const UChar VAL_FALSE
[] = {0x66, 0x61, 0x6c, 0x73, 0x65}; // "false"
35 static const UChar VAL_FALSE_LEN
= 5;
37 static UBool
isSet(int startDate
) {
38 return startDate
!= 0;
41 static UBool
isValidRuleStartDate(int32_t year
, int32_t month
, int32_t day
) {
42 return year
>= MIN_ENCODED_START_YEAR
&& year
<= MAX_ENCODED_START_YEAR
43 && month
>= 1 && month
<= 12 && day
>=1 && day
<= 31;
47 * Encode year/month/date to a single integer.
48 * year is high 16 bits (-32768 to 32767), month is
49 * next 8 bits and day of month is last 8 bits.
52 * @param month month (1-base)
53 * @param day day of month
54 * @return an encoded date.
56 static int32_t encodeDate(int32_t year
, int32_t month
, int32_t day
) {
57 return year
<< 16 | month
<< 8 | day
;
60 static void decodeDate(int32_t encodedDate
, int32_t (&fields
)[3]) {
61 if (encodedDate
== MIN_ENCODED_START
) {
62 fields
[0] = MIN_INT32
;
66 fields
[0] = (encodedDate
& YEAR_MASK
) >> 16;
67 fields
[1] = (encodedDate
& MONTH_MASK
) >> 8;
68 fields
[2] = encodedDate
& DAY_MASK
;
73 * Compare an encoded date with another date specified by year/month/day.
74 * @param encoded An encoded date
75 * @param year Year of another date
76 * @param month Month of another date
77 * @param day Day of another date
78 * @return -1 when encoded date is earlier, 0 when two dates are same,
79 * and 1 when encoded date is later.
81 static int32_t compareEncodedDateWithYMD(int encoded
, int year
, int month
, int day
) {
82 if (year
< MIN_ENCODED_START_YEAR
) {
83 if (encoded
== MIN_ENCODED_START
) {
84 if (year
> MIN_INT32
|| month
> 1 || day
> 1) {
91 } else if (year
> MAX_ENCODED_START_YEAR
) {
94 int tmp
= encodeDate(year
, month
, day
);
97 } else if (encoded
== tmp
) {
105 EraRules::EraRules(LocalMemory
<int32_t>& eraStartDates
, int32_t numEras
)
107 startDates
= std::move(eraStartDates
);
111 EraRules::~EraRules() {
114 EraRules
* EraRules::createInstance(const char *calType
, UBool includeTentativeEra
, UErrorCode
& status
) {
115 if(U_FAILURE(status
)) {
118 LocalUResourceBundlePointer
rb(ures_openDirect(nullptr, "supplementalData", &status
));
119 ures_getByKey(rb
.getAlias(), "calendarData", rb
.getAlias(), &status
);
120 ures_getByKey(rb
.getAlias(), calType
, rb
.getAlias(), &status
);
121 ures_getByKey(rb
.getAlias(), "eras", rb
.getAlias(), &status
);
123 if (U_FAILURE(status
)) {
127 int32_t numEras
= ures_getSize(rb
.getAlias());
128 int32_t firstTentativeIdx
= MAX_INT32
;
130 LocalMemory
<int32_t> startDates(static_cast<int32_t *>(uprv_malloc(numEras
* sizeof(int32_t))));
131 if (startDates
.isNull()) {
132 status
= U_MEMORY_ALLOCATION_ERROR
;
135 uprv_memset(startDates
.getAlias(), 0 , numEras
* sizeof(int32_t));
137 while (ures_hasNext(rb
.getAlias())) {
138 LocalUResourceBundlePointer
eraRuleRes(ures_getNextResource(rb
.getAlias(), nullptr, &status
));
139 if (U_FAILURE(status
)) {
142 const char *eraIdxStr
= ures_getKey(eraRuleRes
.getAlias());
144 int32_t eraIdx
= (int32_t)strtol(eraIdxStr
, &endp
, 10);
145 if ((size_t)(endp
- eraIdxStr
) != uprv_strlen(eraIdxStr
)) {
146 status
= U_INVALID_FORMAT_ERROR
;
149 if (eraIdx
< 0 || eraIdx
>= numEras
) {
150 status
= U_INVALID_FORMAT_ERROR
;
153 if (isSet(startDates
[eraIdx
])) {
154 // start date of the index was already set
155 status
= U_INVALID_FORMAT_ERROR
;
159 UBool hasName
= TRUE
;
162 while (ures_hasNext(eraRuleRes
.getAlias())) {
163 LocalUResourceBundlePointer
res(ures_getNextResource(eraRuleRes
.getAlias(), nullptr, &status
));
164 if (U_FAILURE(status
)) {
167 const char *key
= ures_getKey(res
.getAlias());
168 if (uprv_strcmp(key
, "start") == 0) {
169 const int32_t *fields
= ures_getIntVector(res
.getAlias(), &len
, &status
);
170 if (U_FAILURE(status
)) {
173 if (len
!= 3 || !isValidRuleStartDate(fields
[0], fields
[1], fields
[2])) {
174 status
= U_INVALID_FORMAT_ERROR
;
177 startDates
[eraIdx
] = encodeDate(fields
[0], fields
[1], fields
[2]);
178 } else if (uprv_strcmp(key
, "named") == 0) {
179 const UChar
*val
= ures_getString(res
.getAlias(), &len
, &status
);
180 if (u_strncmp(val
, VAL_FALSE
, VAL_FALSE_LEN
) == 0) {
183 } else if (uprv_strcmp(key
, "end") == 0) {
188 if (isSet(startDates
[eraIdx
])) {
190 // This implementation assumes either start or end is available, not both.
191 // For now, just ignore the end rule.
196 // This implementation does not support end only rule for eras other than
198 status
= U_INVALID_FORMAT_ERROR
;
201 U_ASSERT(eraIdx
== 0);
202 startDates
[eraIdx
] = MIN_ENCODED_START
;
204 status
= U_INVALID_FORMAT_ERROR
;
210 if (eraIdx
>= firstTentativeIdx
) {
211 status
= U_INVALID_FORMAT_ERROR
;
215 if (eraIdx
< firstTentativeIdx
) {
216 firstTentativeIdx
= eraIdx
;
222 if (firstTentativeIdx
< MAX_INT32
&& !includeTentativeEra
) {
223 result
= new EraRules(startDates
, firstTentativeIdx
);
225 result
= new EraRules(startDates
, numEras
);
228 if (result
== nullptr) {
229 status
= U_MEMORY_ALLOCATION_ERROR
;
234 void EraRules::getStartDate(int32_t eraIdx
, int32_t (&fields
)[3], UErrorCode
& status
) const {
235 if(U_FAILURE(status
)) {
238 if (eraIdx
< 0 || eraIdx
>= numEras
) {
239 status
= U_ILLEGAL_ARGUMENT_ERROR
;
242 decodeDate(startDates
[eraIdx
], fields
);
245 int32_t EraRules::getStartYear(int32_t eraIdx
, UErrorCode
& status
) const {
246 int year
= MAX_INT32
/2; // bogus value (/2 so don't overflow elsewhere <rdar://problem/49714633>)
247 if(U_FAILURE(status
)) {
250 if (eraIdx
< 0 || eraIdx
>= numEras
) {
251 status
= U_ILLEGAL_ARGUMENT_ERROR
;
255 decodeDate(startDates
[eraIdx
], fields
);
261 int32_t EraRules::getEraIndex(int32_t year
, int32_t month
, int32_t day
, UErrorCode
& status
) const {
262 if(U_FAILURE(status
)) {
266 if (month
< 1 || month
> 12 || day
< 1 || day
> 31) {
267 status
= U_ILLEGAL_ARGUMENT_ERROR
;
270 int32_t high
= numEras
; // last index + 1
273 // Short circuit for recent years. Most modern computations will
274 // occur in the last few eras.
275 if (compareEncodedDateWithYMD(startDates
[getCurrentEraIndex()], year
, month
, day
) <= 0) {
276 low
= getCurrentEraIndex();
282 while (low
< high
- 1) {
283 int i
= (low
+ high
) / 2;
284 if (compareEncodedDateWithYMD(startDates
[i
], year
, month
, day
) <= 0) {
293 void EraRules::initCurrentEra() {
294 // Compute local wall time in millis using ICU's default time zone.
295 UErrorCode ec
= U_ZERO_ERROR
;
296 UDate localMillis
= ucal_getNow();
298 int32_t rawOffset
, dstOffset
;
299 TimeZone
* zone
= TimeZone::createDefault();
300 // If we failed to create the default time zone, we are in a bad state and don't
301 // really have many options. Carry on using UTC millis as a fallback.
302 if (zone
!= nullptr) {
303 zone
->getOffset(localMillis
, FALSE
, rawOffset
, dstOffset
, ec
);
305 localMillis
+= (rawOffset
+ dstOffset
);
308 int year
, month0
, dom
, dow
, doy
, mid
;
309 Grego::timeToFields(localMillis
, year
, month0
, dom
, dow
, doy
, mid
);
310 int currentEncodedDate
= encodeDate(year
, month0
+ 1 /* changes to 1-base */, dom
);
311 int eraIdx
= numEras
- 1;
313 if (currentEncodedDate
>= startDates
[eraIdx
]) {
318 // Note: current era could be before the first era.
319 // In this case, this implementation returns the first era index (0).
324 #endif /* #if !UCONFIG_NO_FORMATTING */