]>
git.saurik.com Git - apple/icu.git/blob - icuSources/i18n/olsontz.cpp
2 **********************************************************************
3 * Copyright (c) 2003-2004, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 **********************************************************************
7 * Created: July 21 2003
9 **********************************************************************
14 #if !UCONFIG_NO_FORMATTING
16 #include "unicode/ures.h"
17 #include "unicode/simpletz.h"
18 #include "unicode/gregocal.h"
22 #include <float.h> // DBL_MAX
26 # include "uresimp.h" // for debugging
28 static void debug_tz_loc(const char *f
, int32_t l
)
30 fprintf(stderr
, "%s:%d: ", f
, l
);
33 static void debug_tz_msg(const char *pat
, ...)
37 vfprintf(stderr
, pat
, ap
);
40 // must use double parens, i.e.: U_DEBUG_TZ_MSG(("four is: %d",4));
41 #define U_DEBUG_TZ_MSG(x) {debug_tz_loc(__FILE__,__LINE__);debug_tz_msg x;}
43 #define U_DEBUG_TZ_MSG(x)
48 #define SECONDS_PER_DAY (24*60*60)
50 static const int32_t ZEROS
[] = {0,0};
52 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(OlsonTimeZone
)
55 * Default constructor. Creates a time zone with an empty ID and
56 * a fixed GMT offset of zero.
58 OlsonTimeZone::OlsonTimeZone() : finalYear(INT32_MAX
), finalMillis(DBL_MAX
), finalZone(0) {
63 * Construct a GMT+0 zone with no transitions. This is done when a
64 * constructor fails so the resultant object is well-behaved.
66 void OlsonTimeZone::constructEmpty() {
69 transitionTimes
= typeOffsets
= ZEROS
;
70 typeData
= (const uint8_t*) ZEROS
;
74 * Construct from a resource bundle
75 * @param top the top-level zoneinfo resource bundle. This is used
76 * to lookup the rule that `res' may refer to, if there is one.
77 * @param res the resource bundle of the zone to be constructed
78 * @param ec input-output error code
80 OlsonTimeZone::OlsonTimeZone(const UResourceBundle
* top
,
81 const UResourceBundle
* res
,
83 finalYear(INT32_MAX
), finalMillis(DBL_MAX
), finalZone(0)
85 U_DEBUG_TZ_MSG(("OlsonTimeZone(%s)\n", ures_getKey((UResourceBundle
*)res
)));
86 if ((top
== NULL
|| res
== NULL
) && U_SUCCESS(ec
)) {
87 ec
= U_ILLEGAL_ARGUMENT_ERROR
;
90 // TODO -- clean up -- Doesn't work if res points to an alias
91 // // TODO remove nonconst casts below when ures_* API is fixed
92 // setID(ures_getKey((UResourceBundle*) res)); // cast away const
94 // Size 1 is an alias TO another zone (int)
95 // HOWEVER, the caller should dereference this and never pass it in to us
96 // Size 3 is a purely historical zone (no final rules)
97 // Size 4 is like size 3, but with an alias list at the end
98 // Size 5 is a hybrid zone, with historical and final elements
99 // Size 6 is like size 5, but with an alias list at the end
100 int32_t size
= ures_getSize((UResourceBundle
*) res
); // cast away const
101 if (size
< 3 || size
> 6) {
102 ec
= U_INVALID_FORMAT_ERROR
;
105 // Transitions list may be empty
107 UResourceBundle
* r
= ures_getByIndex(res
, 0, NULL
, &ec
);
108 transitionTimes
= ures_getIntVector(r
, &i
, &ec
);
110 if ((i
<0 || i
>0x7FFF) && U_SUCCESS(ec
)) {
111 ec
= U_INVALID_FORMAT_ERROR
;
113 transitionCount
= (int16_t) i
;
115 // Type offsets list must be of even size, with size >= 2
116 r
= ures_getByIndex(res
, 1, NULL
, &ec
);
117 typeOffsets
= ures_getIntVector(r
, &i
, &ec
);
119 if ((i
<2 || i
>0x7FFE || ((i
&1)!=0)) && U_SUCCESS(ec
)) {
120 ec
= U_INVALID_FORMAT_ERROR
;
122 typeCount
= (int16_t) i
>> 1;
124 // Type data must be of the same size as the transitions list
125 r
= ures_getByIndex(res
, 2, NULL
, &ec
);
127 typeData
= ures_getBinary(r
, &len
, &ec
);
129 if (len
!= transitionCount
&& U_SUCCESS(ec
)) {
130 ec
= U_INVALID_FORMAT_ERROR
;
133 #if defined (U_DEBUG_TZ)
134 U_DEBUG_TZ_MSG(("OlsonTimeZone(%s) - size = %d, typecount %d transitioncount %d - err %s\n", ures_getKey((UResourceBundle
*)res
), size
, typeCount
, transitionCount
, u_errorName(ec
)));
137 for(jj
=0;jj
<transitionCount
;jj
++) {
138 U_DEBUG_TZ_MSG((" Transition %d: time %d, typedata%d\n", jj
, transitionTimes
[jj
], typeData
[jj
]));
140 for(jj
=0;jj
<transitionCount
;jj
++) {
141 U_DEBUG_TZ_MSG((" Type %d: offset%d\n", jj
, typeOffsets
[jj
]));
146 // Process final rule and data, if any
148 int32_t ruleidLen
= 0;
149 const UChar
* idUStr
= ures_getStringByIndex(res
, 3, &ruleidLen
, &ec
);
150 UnicodeString
ruleid(TRUE
, idUStr
, ruleidLen
);
151 r
= ures_getByIndex(res
, 4, NULL
, &ec
);
152 const int32_t* data
= ures_getIntVector(r
, &len
, &ec
);
153 #if defined U_DEBUG_TZ
154 const char *rKey
= ures_getKey(r
);
155 const char *zKey
= ures_getKey((UResourceBundle
*)res
);
159 if (data
!= 0 && len
== 2) {
160 int32_t rawOffset
= data
[0] * U_MILLIS_PER_SECOND
;
161 // Subtract one from the actual final year; we
162 // actually store final year - 1, and compare
163 // using > rather than >=. This allows us to use
164 // INT32_MAX as an exclusive upper limit for all
165 // years, including INT32_MAX.
166 U_ASSERT(data
[1] > INT32_MIN
);
167 finalYear
= data
[1] - 1;
168 // Also compute the millis for Jan 1, 0:00 GMT of the
169 // finalYear. This reduces runtime computations.
170 finalMillis
= Grego::fieldsToDay(data
[1], 0, 1) * U_MILLIS_PER_DAY
;
171 U_DEBUG_TZ_MSG(("zone%s|%s: {%d,%d}, finalYear%d, finalMillis%.1lf\n",
172 zKey
,rKey
, data
[0], data
[1], finalYear
, finalMillis
));
173 r
= TimeZone::loadRule(top
, ruleid
, NULL
, ec
);
175 // 3, 1, -1, 7200, 0, 9, -31, -1, 7200, 0, 3600
176 data
= ures_getIntVector(r
, &len
, &ec
);
177 if (U_SUCCESS(ec
) && len
== 11) {
178 UnicodeString emptyStr
;
179 U_DEBUG_TZ_MSG(("zone%s, rule%s: {%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d}", zKey
, ures_getKey(r
),
180 data
[0], data
[1], data
[2], data
[3], data
[4], data
[5], data
[6], data
[7], data
[8], data
[9], data
[10]));
181 finalZone
= new SimpleTimeZone(rawOffset
, emptyStr
,
182 (int8_t)data
[0], (int8_t)data
[1], (int8_t)data
[2],
183 data
[3] * U_MILLIS_PER_SECOND
,
184 (SimpleTimeZone::TimeMode
) data
[4],
185 (int8_t)data
[5], (int8_t)data
[6], (int8_t)data
[7],
186 data
[8] * U_MILLIS_PER_SECOND
,
187 (SimpleTimeZone::TimeMode
) data
[9],
188 data
[10] * U_MILLIS_PER_SECOND
, ec
);
190 ec
= U_INVALID_FORMAT_ERROR
;
195 ec
= U_INVALID_FORMAT_ERROR
;
209 OlsonTimeZone::OlsonTimeZone(const OlsonTimeZone
& other
) :
210 TimeZone(other
), finalZone(0) {
215 * Assignment operator
217 OlsonTimeZone
& OlsonTimeZone::operator=(const OlsonTimeZone
& other
) {
218 transitionCount
= other
.transitionCount
;
219 typeCount
= other
.typeCount
;
220 transitionTimes
= other
.transitionTimes
;
221 typeOffsets
= other
.typeOffsets
;
222 typeData
= other
.typeData
;
223 finalYear
= other
.finalYear
;
224 finalMillis
= other
.finalMillis
;
226 finalZone
= (other
.finalZone
!= 0) ?
227 (SimpleTimeZone
*) other
.finalZone
->clone() : 0;
234 OlsonTimeZone::~OlsonTimeZone() {
239 * Returns true if the two TimeZone objects are equal.
241 UBool
OlsonTimeZone::operator==(const TimeZone
& other
) const {
242 const OlsonTimeZone
* z
= (const OlsonTimeZone
*) &other
;
244 return TimeZone::operator==(other
) &&
245 // [sic] pointer comparison: typeData points into
246 // memory-mapped or DLL space, so if two zones have the same
247 // pointer, they are equal.
248 (typeData
== z
->typeData
||
249 // If the pointers are not equal, the zones may still
250 // be equal if their rules and transitions are equal
251 (finalYear
== z
->finalYear
&&
252 // Don't compare finalMillis; if finalYear is ==, so is finalMillis
253 ((finalZone
== 0 && z
->finalZone
== 0) ||
254 (finalZone
!= 0 && z
->finalZone
!= 0 &&
255 *finalZone
== *z
->finalZone
)) &&
256 transitionCount
== z
->transitionCount
&&
257 typeCount
== z
->typeCount
&&
258 uprv_memcmp(transitionTimes
, z
->transitionTimes
,
259 sizeof(transitionTimes
[0]) * transitionCount
) == 0 &&
260 uprv_memcmp(typeOffsets
, z
->typeOffsets
,
261 (sizeof(typeOffsets
[0]) * typeCount
) << 1) == 0 &&
262 uprv_memcmp(typeData
, z
->typeData
,
263 (sizeof(typeData
[0]) * typeCount
)) == 0
270 TimeZone
* OlsonTimeZone::clone() const {
271 return new OlsonTimeZone(*this);
277 int32_t OlsonTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
,
278 int32_t dom
, uint8_t dow
,
279 int32_t millis
, UErrorCode
& ec
) const {
280 if (month
< UCAL_JANUARY
|| month
> UCAL_DECEMBER
) {
282 ec
= U_ILLEGAL_ARGUMENT_ERROR
;
286 return getOffset(era
, year
, month
, dom
, dow
, millis
,
287 Grego::monthLength(year
, month
),
295 int32_t OlsonTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
,
296 int32_t dom
, uint8_t dow
,
297 int32_t millis
, int32_t monthLength
,
298 UErrorCode
& ec
) const {
303 if ((era
!= GregorianCalendar::AD
&& era
!= GregorianCalendar::BC
)
304 || month
< UCAL_JANUARY
305 || month
> UCAL_DECEMBER
309 || dow
> UCAL_SATURDAY
311 || millis
>= U_MILLIS_PER_DAY
313 || monthLength
> 31) {
314 ec
= U_ILLEGAL_ARGUMENT_ERROR
;
318 if (era
== GregorianCalendar::BC
) {
322 if (year
> finalYear
) { // [sic] >, not >=; see above
323 U_ASSERT(finalZone
!= 0);
324 return finalZone
->getOffset(era
, year
, month
, dom
, dow
,
325 millis
, monthLength
, ec
);
328 // Compute local epoch seconds from input fields
329 double time
= Grego::fieldsToDay(year
, month
, dom
) * SECONDS_PER_DAY
+
330 uprv_floor(millis
/ (double) U_MILLIS_PER_SECOND
);
332 return zoneOffset(findTransition(time
, TRUE
)) * U_MILLIS_PER_SECOND
;
338 void OlsonTimeZone::getOffset(UDate date
, UBool local
, int32_t& rawoff
,
339 int32_t& dstoff
, UErrorCode
& ec
) const {
344 // The check against finalMillis will suffice most of the time, except
345 // for the case in which finalMillis == DBL_MAX, date == DBL_MAX,
346 // and finalZone == 0. For this case we add "&& finalZone != 0".
347 if (date
>= finalMillis
&& finalZone
!= 0) {
348 int32_t year
, month
, dom
, dow
;
350 double days
= Math::floorDivide(date
, (double)U_MILLIS_PER_DAY
, millis
);
352 Grego::dayToFields(days
, year
, month
, dom
, dow
);
354 rawoff
= finalZone
->getRawOffset();
357 // Adjust from GMT to local
359 double days2
= Math::floorDivide(date
, (double)U_MILLIS_PER_DAY
, millis
);
361 Grego::dayToFields(days2
, year
, month
, dom
, dow
);
365 dstoff
= finalZone
->getOffset(
366 GregorianCalendar::AD
, year
, month
,
367 dom
, (uint8_t) dow
, (int32_t) millis
, ec
) - rawoff
;
371 double secs
= uprv_floor(date
/ U_MILLIS_PER_SECOND
);
372 int16_t i
= findTransition(secs
, local
);
373 rawoff
= rawOffset(i
) * U_MILLIS_PER_SECOND
;
374 dstoff
= dstOffset(i
) * U_MILLIS_PER_SECOND
;
380 void OlsonTimeZone::setRawOffset(int32_t /*offsetMillis*/) {
381 // We don't support this operation, since OlsonTimeZones are
382 // immutable (except for the ID, which is in the base class).
390 int32_t OlsonTimeZone::getRawOffset() const {
391 UErrorCode ec
= U_ZERO_ERROR
;
393 getOffset((double) uprv_getUTCtime() * U_MILLIS_PER_SECOND
,
394 FALSE
, raw
, dst
, ec
);
399 * Find the smallest i (in 0..transitionCount-1) such that time >=
400 * transition(i), where transition(i) is either the GMT or the local
401 * transition time, as specified by `local'.
402 * @param time epoch seconds, either GMT or local wall
403 * @param local if TRUE, `time' is in local wall units, otherwise it
405 * @return an index i, where 0 <= i < transitionCount, and
406 * transition(i) <= time < transition(i+1), or i == 0 if
407 * transitionCount == 0 or time < transition(0).
409 int16_t OlsonTimeZone::findTransition(double time
, UBool local
) const {
412 if (transitionCount
!= 0) {
413 // Linear search from the end is the fastest approach, since
414 // most lookups will happen at/near the end.
415 for (i
= transitionCount
- 1; i
> 0; --i
) {
416 int32_t transition
= transitionTimes
[i
];
418 transition
+= zoneOffset(typeData
[i
]);
420 if (time
>= transition
) {
425 U_ASSERT(i
>=0 && i
<transitionCount
);
427 // Check invariants for GMT times; if these pass for GMT times
428 // the local logic should be working too.
429 U_ASSERT(local
|| time
< transitionTimes
[0] || time
>= transitionTimes
[i
]);
430 U_ASSERT(local
|| i
== transitionCount
-1 || time
< transitionTimes
[i
+1]);
435 U_ASSERT(i
>=0 && i
<typeCount
);
443 UBool
OlsonTimeZone::useDaylightTime() const {
444 // If DST was observed in 1942 (for example) but has never been
445 // observed from 1943 to the present, most clients will expect
446 // this method to return FALSE. This method determines whether
447 // DST is in use in the current year (at any point in the year)
448 // and returns TRUE if so.
450 int32_t days
= (int32_t)Math::floorDivide(uprv_getUTCtime(), (double)U_MILLIS_PER_DAY
); // epoch days
452 int32_t year
, month
, dom
, dow
;
454 Grego::dayToFields(days
, year
, month
, dom
, dow
);
456 if (year
> finalYear
) { // [sic] >, not >=; see above
457 U_ASSERT(finalZone
!= 0 && finalZone
->useDaylightTime());
461 // Find start of this year, and start of next year
462 int32_t start
= (int32_t) Grego::fieldsToDay(year
, 0, 1) * SECONDS_PER_DAY
;
463 int32_t limit
= (int32_t) Grego::fieldsToDay(year
+1, 0, 1) * SECONDS_PER_DAY
;
465 // Return TRUE if DST is observed at any time during the current
467 for (int16_t i
=0; i
<transitionCount
; ++i
) {
468 if (transitionTimes
[i
] >= limit
) {
471 if (transitionTimes
[i
] >= start
&&
472 dstOffset(typeData
[i
]) != 0) {
482 UBool
OlsonTimeZone::inDaylightTime(UDate date
, UErrorCode
& ec
) const {
484 getOffset(date
, FALSE
, raw
, dst
, ec
);
490 #endif // !UCONFIG_NO_FORMATTING