]> git.saurik.com Git - apple/icu.git/blame - icuSources/i18n/olsontz.cpp
ICU-6.2.4.tar.gz
[apple/icu.git] / icuSources / i18n / olsontz.cpp
CommitLineData
374ca955
A
1/*
2**********************************************************************
3* Copyright (c) 2003-2004, International Business Machines
4* Corporation and others. All Rights Reserved.
5**********************************************************************
6* Author: Alan Liu
7* Created: July 21 2003
8* Since: ICU 2.8
9**********************************************************************
10*/
11
12#include "olsontz.h"
13
14#if !UCONFIG_NO_FORMATTING
15
16#include "unicode/ures.h"
17#include "unicode/simpletz.h"
18#include "unicode/gregocal.h"
19#include "gregoimp.h"
20#include "cmemory.h"
21#include "uassert.h"
22#include <float.h> // DBL_MAX
23
24#ifdef U_DEBUG_TZ
25# include <stdio.h>
26# include "uresimp.h" // for debugging
27
28static void debug_tz_loc(const char *f, int32_t l)
29{
30 fprintf(stderr, "%s:%d: ", f, l);
31}
32
33static void debug_tz_msg(const char *pat, ...)
34{
35 va_list ap;
36 va_start(ap, pat);
37 vfprintf(stderr, pat, ap);
38 fflush(stderr);
39}
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;}
42#else
43#define U_DEBUG_TZ_MSG(x)
44#endif
45
46U_NAMESPACE_BEGIN
47
48#define SECONDS_PER_DAY (24*60*60)
49
50static const int32_t ZEROS[] = {0,0};
51
52UOBJECT_DEFINE_RTTI_IMPLEMENTATION(OlsonTimeZone)
53
54/**
55 * Default constructor. Creates a time zone with an empty ID and
56 * a fixed GMT offset of zero.
57 */
58OlsonTimeZone::OlsonTimeZone() : finalYear(INT32_MAX), finalMillis(DBL_MAX), finalZone(0) {
59 constructEmpty();
60}
61
62/**
63 * Construct a GMT+0 zone with no transitions. This is done when a
64 * constructor fails so the resultant object is well-behaved.
65 */
66void OlsonTimeZone::constructEmpty() {
67 transitionCount = 0;
68 typeCount = 1;
69 transitionTimes = typeOffsets = ZEROS;
70 typeData = (const uint8_t*) ZEROS;
71}
72
73/**
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
79 */
80OlsonTimeZone::OlsonTimeZone(const UResourceBundle* top,
81 const UResourceBundle* res,
82 UErrorCode& ec) :
83 finalYear(INT32_MAX), finalMillis(DBL_MAX), finalZone(0)
84{
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;
88 }
89 if (U_SUCCESS(ec)) {
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
93
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;
103 }
104
105 // Transitions list may be empty
106 int32_t i;
107 UResourceBundle* r = ures_getByIndex(res, 0, NULL, &ec);
108 transitionTimes = ures_getIntVector(r, &i, &ec);
109 ures_close(r);
110 if ((i<0 || i>0x7FFF) && U_SUCCESS(ec)) {
111 ec = U_INVALID_FORMAT_ERROR;
112 }
113 transitionCount = (int16_t) i;
114
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);
118 ures_close(r);
119 if ((i<2 || i>0x7FFE || ((i&1)!=0)) && U_SUCCESS(ec)) {
120 ec = U_INVALID_FORMAT_ERROR;
121 }
122 typeCount = (int16_t) i >> 1;
123
124 // Type data must be of the same size as the transitions list
125 r = ures_getByIndex(res, 2, NULL, &ec);
126 int32_t len;
127 typeData = ures_getBinary(r, &len, &ec);
128 ures_close(r);
129 if (len != transitionCount && U_SUCCESS(ec)) {
130 ec = U_INVALID_FORMAT_ERROR;
131 }
132
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)));
135 if(U_SUCCESS(ec)) {
136 int32_t jj;
137 for(jj=0;jj<transitionCount;jj++) {
138 U_DEBUG_TZ_MSG((" Transition %d: time %d, typedata%d\n", jj, transitionTimes[jj], typeData[jj]));
139 }
140 for(jj=0;jj<transitionCount;jj++) {
141 U_DEBUG_TZ_MSG((" Type %d: offset%d\n", jj, typeOffsets[jj]));
142 }
143 }
144#endif
145
146 // Process final rule and data, if any
147 if (size >= 5) {
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);
156#endif
157 ures_close(r);
158 if (U_SUCCESS(ec)) {
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);
174 if (U_SUCCESS(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);
189 } else {
190 ec = U_INVALID_FORMAT_ERROR;
191 }
192 }
193 ures_close(r);
194 } else {
195 ec = U_INVALID_FORMAT_ERROR;
196 }
197 }
198 }
199 }
200
201 if (U_FAILURE(ec)) {
202 constructEmpty();
203 }
204}
205
206/**
207 * Copy constructor
208 */
209OlsonTimeZone::OlsonTimeZone(const OlsonTimeZone& other) :
210 TimeZone(other), finalZone(0) {
211 *this = other;
212}
213
214/**
215 * Assignment operator
216 */
217OlsonTimeZone& 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;
225 delete finalZone;
226 finalZone = (other.finalZone != 0) ?
227 (SimpleTimeZone*) other.finalZone->clone() : 0;
228 return *this;
229}
230
231/**
232 * Destructor
233 */
234OlsonTimeZone::~OlsonTimeZone() {
235 delete finalZone;
236}
237
238/**
239 * Returns true if the two TimeZone objects are equal.
240 */
241UBool OlsonTimeZone::operator==(const TimeZone& other) const {
242 const OlsonTimeZone* z = (const OlsonTimeZone*) &other;
243
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
264 ));
265}
266
267/**
268 * TimeZone API.
269 */
270TimeZone* OlsonTimeZone::clone() const {
271 return new OlsonTimeZone(*this);
272}
273
274/**
275 * TimeZone API.
276 */
277int32_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) {
281 if (U_SUCCESS(ec)) {
282 ec = U_ILLEGAL_ARGUMENT_ERROR;
283 }
284 return 0;
285 } else {
286 return getOffset(era, year, month, dom, dow, millis,
287 Grego::monthLength(year, month),
288 ec);
289 }
290}
291
292/**
293 * TimeZone API.
294 */
295int32_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 {
299 if (U_FAILURE(ec)) {
300 return 0;
301 }
302
303 if ((era != GregorianCalendar::AD && era != GregorianCalendar::BC)
304 || month < UCAL_JANUARY
305 || month > UCAL_DECEMBER
306 || dom < 1
307 || dom > monthLength
308 || dow < UCAL_SUNDAY
309 || dow > UCAL_SATURDAY
310 || millis < 0
311 || millis >= U_MILLIS_PER_DAY
312 || monthLength < 28
313 || monthLength > 31) {
314 ec = U_ILLEGAL_ARGUMENT_ERROR;
315 return 0;
316 }
317
318 if (era == GregorianCalendar::BC) {
319 year = -year;
320 }
321
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);
326 }
327
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);
331
332 return zoneOffset(findTransition(time, TRUE)) * U_MILLIS_PER_SECOND;
333}
334
335/**
336 * TimeZone API.
337 */
338void OlsonTimeZone::getOffset(UDate date, UBool local, int32_t& rawoff,
339 int32_t& dstoff, UErrorCode& ec) const {
340 if (U_FAILURE(ec)) {
341 return;
342 }
343
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;
349 double millis;
350 double days = Math::floorDivide(date, (double)U_MILLIS_PER_DAY, millis);
351
352 Grego::dayToFields(days, year, month, dom, dow);
353
354 rawoff = finalZone->getRawOffset();
355
356 if (!local) {
357 // Adjust from GMT to local
358 date += rawoff;
359 double days2 = Math::floorDivide(date, (double)U_MILLIS_PER_DAY, millis);
360 if (days2 != days) {
361 Grego::dayToFields(days2, year, month, dom, dow);
362 }
363 }
364
365 dstoff = finalZone->getOffset(
366 GregorianCalendar::AD, year, month,
367 dom, (uint8_t) dow, (int32_t) millis, ec) - rawoff;
368 return;
369 }
370
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;
375}
376
377/**
378 * TimeZone API.
379 */
380void 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).
383
384 // Nothing to do!
385}
386
387/**
388 * TimeZone API.
389 */
390int32_t OlsonTimeZone::getRawOffset() const {
391 UErrorCode ec = U_ZERO_ERROR;
392 int32_t raw, dst;
393 getOffset((double) uprv_getUTCtime() * U_MILLIS_PER_SECOND,
394 FALSE, raw, dst, ec);
395 return raw;
396}
397
398/**
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
404 * is GMT
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).
408 */
409int16_t OlsonTimeZone::findTransition(double time, UBool local) const {
410 int16_t i = 0;
411
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];
417 if (local) {
418 transition += zoneOffset(typeData[i]);
419 }
420 if (time >= transition) {
421 break;
422 }
423 }
424
425 U_ASSERT(i>=0 && i<transitionCount);
426
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]);
431
432 i = typeData[i];
433 }
434
435 U_ASSERT(i>=0 && i<typeCount);
436
437 return i;
438}
439
440/**
441 * TimeZone API.
442 */
443UBool 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.
449
450 int32_t days = (int32_t)Math::floorDivide(uprv_getUTCtime(), (double)U_MILLIS_PER_DAY); // epoch days
451
452 int32_t year, month, dom, dow;
453
454 Grego::dayToFields(days, year, month, dom, dow);
455
456 if (year > finalYear) { // [sic] >, not >=; see above
457 U_ASSERT(finalZone != 0 && finalZone->useDaylightTime());
458 return TRUE;
459 }
460
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;
464
465 // Return TRUE if DST is observed at any time during the current
466 // year.
467 for (int16_t i=0; i<transitionCount; ++i) {
468 if (transitionTimes[i] >= limit) {
469 break;
470 }
471 if (transitionTimes[i] >= start &&
472 dstOffset(typeData[i]) != 0) {
473 return TRUE;
474 }
475 }
476 return FALSE;
477}
478
479/**
480 * TimeZone API.
481 */
482UBool OlsonTimeZone::inDaylightTime(UDate date, UErrorCode& ec) const {
483 int32_t raw, dst;
484 getOffset(date, FALSE, raw, dst, ec);
485 return dst != 0;
486}
487
488U_NAMESPACE_END
489
490#endif // !UCONFIG_NO_FORMATTING
491
492//eof