]>
git.saurik.com Git - apple/icu.git/blob - icuSources/i18n/olsontz.cpp
2 **********************************************************************
3 * Copyright (c) 2003-2006, 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(res
);
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
);
109 if ((i
<0 || i
>0x7FFF) && U_SUCCESS(ec
)) {
110 ec
= U_INVALID_FORMAT_ERROR
;
112 transitionCount
= (int16_t) i
;
114 // Type offsets list must be of even size, with size >= 2
115 r
= ures_getByIndex(res
, 1, r
, &ec
);
116 typeOffsets
= ures_getIntVector(r
, &i
, &ec
);
117 if ((i
<2 || i
>0x7FFE || ((i
&1)!=0)) && U_SUCCESS(ec
)) {
118 ec
= U_INVALID_FORMAT_ERROR
;
120 typeCount
= (int16_t) i
>> 1;
122 // Type data must be of the same size as the transitions list
123 r
= ures_getByIndex(res
, 2, r
, &ec
);
125 typeData
= ures_getBinary(r
, &len
, &ec
);
127 if (len
!= transitionCount
&& U_SUCCESS(ec
)) {
128 ec
= U_INVALID_FORMAT_ERROR
;
131 #if defined (U_DEBUG_TZ)
132 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 for(jj
=0;jj
<transitionCount
;jj
++) {
136 int32_t year
, month
, dom
, dow
;
138 double days
= Math::floorDivide(((double)transitionTimes
[jj
])*1000.0, (double)U_MILLIS_PER_DAY
, millis
);
140 Grego::dayToFields(days
, year
, month
, dom
, dow
);
141 U_DEBUG_TZ_MSG((" Transition %d: time %d (%04d.%02d.%02d+%.1fh), typedata%d\n", jj
, transitionTimes
[jj
],
142 year
, month
+1, dom
, (millis
/kOneHour
), typeData
[jj
]));
143 // U_DEBUG_TZ_MSG((" offset%d\n", typeOffsets[jj]));
146 U_DEBUG_TZ_MSG((" offsets[%d+%d]=(%d+%d)=(%d==%d)\n", (int)f
,(int)f
+1,(int)typeOffsets
[f
],(int)typeOffsets
[f
+1],(int)zoneOffset(jj
),
147 (int)typeOffsets
[f
]+(int)typeOffsets
[f
+1]));
152 // Process final rule and data, if any
154 int32_t ruleidLen
= 0;
155 const UChar
* idUStr
= ures_getStringByIndex(res
, 3, &ruleidLen
, &ec
);
156 UnicodeString
ruleid(TRUE
, idUStr
, ruleidLen
);
157 r
= ures_getByIndex(res
, 4, NULL
, &ec
);
158 const int32_t* data
= ures_getIntVector(r
, &len
, &ec
);
159 #if defined U_DEBUG_TZ
160 const char *rKey
= ures_getKey(r
);
161 const char *zKey
= ures_getKey((UResourceBundle
*)res
);
165 if (data
!= 0 && len
== 2) {
166 int32_t rawOffset
= data
[0] * U_MILLIS_PER_SECOND
;
167 // Subtract one from the actual final year; we
168 // actually store final year - 1, and compare
169 // using > rather than >=. This allows us to use
170 // INT32_MAX as an exclusive upper limit for all
171 // years, including INT32_MAX.
172 U_ASSERT(data
[1] > INT32_MIN
);
173 finalYear
= data
[1] - 1;
174 // Also compute the millis for Jan 1, 0:00 GMT of the
175 // finalYear. This reduces runtime computations.
176 finalMillis
= Grego::fieldsToDay(data
[1], 0, 1) * U_MILLIS_PER_DAY
;
177 U_DEBUG_TZ_MSG(("zone%s|%s: {%d,%d}, finalYear%d, finalMillis%.1lf\n",
178 zKey
,rKey
, data
[0], data
[1], finalYear
, finalMillis
));
179 r
= TimeZone::loadRule(top
, ruleid
, NULL
, ec
);
181 // 3, 1, -1, 7200, 0, 9, -31, -1, 7200, 0, 3600
182 data
= ures_getIntVector(r
, &len
, &ec
);
183 if (U_SUCCESS(ec
) && len
== 11) {
184 UnicodeString emptyStr
;
185 U_DEBUG_TZ_MSG(("zone%s, rule%s: {%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d}\n", zKey
, ures_getKey(r
),
186 data
[0], data
[1], data
[2], data
[3], data
[4], data
[5], data
[6], data
[7], data
[8], data
[9], data
[10]));
187 finalZone
= new SimpleTimeZone(rawOffset
, emptyStr
,
188 (int8_t)data
[0], (int8_t)data
[1], (int8_t)data
[2],
189 data
[3] * U_MILLIS_PER_SECOND
,
190 (SimpleTimeZone::TimeMode
) data
[4],
191 (int8_t)data
[5], (int8_t)data
[6], (int8_t)data
[7],
192 data
[8] * U_MILLIS_PER_SECOND
,
193 (SimpleTimeZone::TimeMode
) data
[9],
194 data
[10] * U_MILLIS_PER_SECOND
, ec
);
196 ec
= U_INVALID_FORMAT_ERROR
;
201 ec
= U_INVALID_FORMAT_ERROR
;
215 OlsonTimeZone::OlsonTimeZone(const OlsonTimeZone
& other
) :
216 TimeZone(other
), finalZone(0) {
221 * Assignment operator
223 OlsonTimeZone
& OlsonTimeZone::operator=(const OlsonTimeZone
& other
) {
224 transitionCount
= other
.transitionCount
;
225 typeCount
= other
.typeCount
;
226 transitionTimes
= other
.transitionTimes
;
227 typeOffsets
= other
.typeOffsets
;
228 typeData
= other
.typeData
;
229 finalYear
= other
.finalYear
;
230 finalMillis
= other
.finalMillis
;
232 finalZone
= (other
.finalZone
!= 0) ?
233 (SimpleTimeZone
*) other
.finalZone
->clone() : 0;
240 OlsonTimeZone::~OlsonTimeZone() {
245 * Returns true if the two TimeZone objects are equal.
247 UBool
OlsonTimeZone::operator==(const TimeZone
& other
) const {
248 const OlsonTimeZone
* z
= (const OlsonTimeZone
*) &other
;
250 return TimeZone::operator==(other
) &&
251 // [sic] pointer comparison: typeData points into
252 // memory-mapped or DLL space, so if two zones have the same
253 // pointer, they are equal.
254 (typeData
== z
->typeData
||
255 // If the pointers are not equal, the zones may still
256 // be equal if their rules and transitions are equal
257 (finalYear
== z
->finalYear
&&
258 // Don't compare finalMillis; if finalYear is ==, so is finalMillis
259 ((finalZone
== 0 && z
->finalZone
== 0) ||
260 (finalZone
!= 0 && z
->finalZone
!= 0 &&
261 *finalZone
== *z
->finalZone
)) &&
262 transitionCount
== z
->transitionCount
&&
263 typeCount
== z
->typeCount
&&
264 uprv_memcmp(transitionTimes
, z
->transitionTimes
,
265 sizeof(transitionTimes
[0]) * transitionCount
) == 0 &&
266 uprv_memcmp(typeOffsets
, z
->typeOffsets
,
267 (sizeof(typeOffsets
[0]) * typeCount
) << 1) == 0 &&
268 uprv_memcmp(typeData
, z
->typeData
,
269 (sizeof(typeData
[0]) * typeCount
)) == 0
276 TimeZone
* OlsonTimeZone::clone() const {
277 return new OlsonTimeZone(*this);
283 int32_t OlsonTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
,
284 int32_t dom
, uint8_t dow
,
285 int32_t millis
, UErrorCode
& ec
) const {
286 if (month
< UCAL_JANUARY
|| month
> UCAL_DECEMBER
) {
288 ec
= U_ILLEGAL_ARGUMENT_ERROR
;
292 return getOffset(era
, year
, month
, dom
, dow
, millis
,
293 Grego::monthLength(year
, month
),
301 int32_t OlsonTimeZone::getOffset(uint8_t era
, int32_t year
, int32_t month
,
302 int32_t dom
, uint8_t dow
,
303 int32_t millis
, int32_t monthLength
,
304 UErrorCode
& ec
) const {
309 if ((era
!= GregorianCalendar::AD
&& era
!= GregorianCalendar::BC
)
310 || month
< UCAL_JANUARY
311 || month
> UCAL_DECEMBER
315 || dow
> UCAL_SATURDAY
317 || millis
>= U_MILLIS_PER_DAY
319 || monthLength
> 31) {
320 ec
= U_ILLEGAL_ARGUMENT_ERROR
;
324 if (era
== GregorianCalendar::BC
) {
328 if (year
> finalYear
) { // [sic] >, not >=; see above
329 U_ASSERT(finalZone
!= 0);
330 return finalZone
->getOffset(era
, year
, month
, dom
, dow
,
331 millis
, monthLength
, ec
);
334 // Compute local epoch seconds from input fields
335 double time
= Grego::fieldsToDay(year
, month
, dom
) * SECONDS_PER_DAY
+
336 uprv_floor(millis
/ (double) U_MILLIS_PER_SECOND
);
338 return zoneOffset(findTransition(time
, TRUE
)) * U_MILLIS_PER_SECOND
;
344 void OlsonTimeZone::getOffset(UDate date
, UBool local
, int32_t& rawoff
,
345 int32_t& dstoff
, UErrorCode
& ec
) const {
350 // The check against finalMillis will suffice most of the time, except
351 // for the case in which finalMillis == DBL_MAX, date == DBL_MAX,
352 // and finalZone == 0. For this case we add "&& finalZone != 0".
353 if (date
>= finalMillis
&& finalZone
!= 0) {
354 int32_t year
, month
, dom
, dow
;
356 double days
= Math::floorDivide(date
, (double)U_MILLIS_PER_DAY
, millis
);
358 Grego::dayToFields(days
, year
, month
, dom
, dow
);
360 rawoff
= finalZone
->getRawOffset();
363 // Adjust from GMT to local
365 double days2
= Math::floorDivide(date
, (double)U_MILLIS_PER_DAY
, millis
);
367 Grego::dayToFields(days2
, year
, month
, dom
, dow
);
371 dstoff
= finalZone
->getOffset(
372 GregorianCalendar::AD
, year
, month
,
373 dom
, (uint8_t) dow
, (int32_t) millis
, ec
) - rawoff
;
377 double secs
= uprv_floor(date
/ U_MILLIS_PER_SECOND
);
378 int16_t i
= findTransition(secs
, local
);
379 rawoff
= rawOffset(i
) * U_MILLIS_PER_SECOND
;
380 dstoff
= dstOffset(i
) * U_MILLIS_PER_SECOND
;
386 void OlsonTimeZone::setRawOffset(int32_t /*offsetMillis*/) {
387 // We don't support this operation, since OlsonTimeZones are
388 // immutable (except for the ID, which is in the base class).
396 int32_t OlsonTimeZone::getRawOffset() const {
397 UErrorCode ec
= U_ZERO_ERROR
;
399 getOffset((double) uprv_getUTCtime() * U_MILLIS_PER_SECOND
,
400 FALSE
, raw
, dst
, ec
);
404 #if defined U_DEBUG_TZ
405 void printTime(double ms
) {
406 int32_t year
, month
, dom
, dow
;
408 double days
= Math::floorDivide(((double)ms
), (double)U_MILLIS_PER_DAY
, millis
);
410 Grego::dayToFields(days
, year
, month
, dom
, dow
);
411 U_DEBUG_TZ_MSG((" findTransition: time %.1f (%04d.%02d.%02d+%.1fh)\n", ms
,
412 year
, month
+1, dom
, (millis
/kOneHour
)));
417 * Find the smallest i (in 0..transitionCount-1) such that time >=
418 * transition(i), where transition(i) is either the GMT or the local
419 * transition time, as specified by `local'.
420 * @param time epoch seconds, either GMT or local wall
421 * @param local if TRUE, `time' is in local wall units, otherwise it
423 * @return an index i, where 0 <= i < transitionCount, and
424 * transition(i) <= time < transition(i+1), or i == 0 if
425 * transitionCount == 0 or time < transition(0).
427 int16_t OlsonTimeZone::findTransition(double time
, UBool local
) const {
429 U_DEBUG_TZ_MSG(("findTransition(%.1f, %s)\n", time
, local
?"T":"F"));
430 #if defined U_DEBUG_TZ
431 printTime(time
*1000.0);
434 if (transitionCount
!= 0) {
435 // Linear search from the end is the fastest approach, since
436 // most lookups will happen at/near the end.
437 for (i
= transitionCount
- 1; i
> 0; --i
) {
438 int32_t transition
= transitionTimes
[i
];
440 int32_t zoneOffsetPrev
= zoneOffset(typeData
[i
-1]);
441 int32_t zoneOffsetCurr
= zoneOffset(typeData
[i
]);
443 // use the lowest offset ( == standard time ). as per tzregts.cpp which says:
447 * The expected behavior of TimeZone around the boundaries is:
448 * (Assume transition time of 2:00 AM)
449 * day of onset 1:59 AM STD = display name 1:59 AM ST
450 * 2:00 AM STD = display name 3:00 AM DT
451 * day of end 0:59 AM STD = display name 1:59 AM DT
452 * 1:00 AM STD = display name 1:00 AM ST
454 if(zoneOffsetPrev
<zoneOffsetCurr
) {
455 transition
+= zoneOffsetPrev
;
457 transition
+= zoneOffsetCurr
;
460 if (time
>= transition
) {
461 U_DEBUG_TZ_MSG(("Found@%d: time=%.1f, localtransition=%d (orig %d) dz %d\n", i
, time
, transition
, transitionTimes
[i
],
462 zoneOffset(typeData
[i
-1])));
463 #if defined U_DEBUG_TZ
464 printTime(transition
*1000.0);
465 printTime(transitionTimes
[i
]*1000.0);
469 U_DEBUG_TZ_MSG(("miss@%d: time=%.1f, localtransition=%d (orig %d) dz %d\n", i
, time
, transition
, transitionTimes
[i
],
470 zoneOffset(typeData
[i
-1])));
471 #if defined U_DEBUG_TZ
472 printTime(transition
*1000.0);
473 printTime(transitionTimes
[i
]*1000.0);
478 U_ASSERT(i
>=0 && i
<transitionCount
);
480 // Check invariants for GMT times; if these pass for GMT times
481 // the local logic should be working too.
482 U_ASSERT(local
|| time
< transitionTimes
[0] || time
>= transitionTimes
[i
]);
483 U_ASSERT(local
|| i
== transitionCount
-1 || time
< transitionTimes
[i
+1]);
485 U_DEBUG_TZ_MSG(("findTransition(%.1f, %s)= trans %d\n", time
, local
?"T":"F", i
));
489 U_ASSERT(i
>=0 && i
<typeCount
);
491 U_DEBUG_TZ_MSG(("findTransition(%.1f, %s)=%d, offset %d\n", time
, local
?"T":"F", i
, zoneOffset(i
)));
498 UBool
OlsonTimeZone::useDaylightTime() const {
499 // If DST was observed in 1942 (for example) but has never been
500 // observed from 1943 to the present, most clients will expect
501 // this method to return FALSE. This method determines whether
502 // DST is in use in the current year (at any point in the year)
503 // and returns TRUE if so.
505 int32_t days
= (int32_t)Math::floorDivide(uprv_getUTCtime(), (double)U_MILLIS_PER_DAY
); // epoch days
507 int32_t year
, month
, dom
, dow
;
509 Grego::dayToFields(days
, year
, month
, dom
, dow
);
511 if (year
> finalYear
) { // [sic] >, not >=; see above
512 U_ASSERT(finalZone
!= 0 && finalZone
->useDaylightTime());
516 // Find start of this year, and start of next year
517 int32_t start
= (int32_t) Grego::fieldsToDay(year
, 0, 1) * SECONDS_PER_DAY
;
518 int32_t limit
= (int32_t) Grego::fieldsToDay(year
+1, 0, 1) * SECONDS_PER_DAY
;
520 // Return TRUE if DST is observed at any time during the current
522 for (int16_t i
=0; i
<transitionCount
; ++i
) {
523 if (transitionTimes
[i
] >= limit
) {
526 if (transitionTimes
[i
] >= start
&&
527 dstOffset(typeData
[i
]) != 0) {
534 OlsonTimeZone::getDSTSavings() const{
536 return finalZone
->getDSTSavings();
538 return TimeZone::getDSTSavings();
543 UBool
OlsonTimeZone::inDaylightTime(UDate date
, UErrorCode
& ec
) const {
545 getOffset(date
, FALSE
, raw
, dst
, ec
);
551 #endif // !UCONFIG_NO_FORMATTING