6 * Timezones conversion from/to linear time scale.
8 * Copyright (c) 2004-2009 by Synthesis AG (www.synthesis.ch)
10 * 2004-04-18 : bfo : initial version
17 * 1) done 04/04/18 <aISOstr> does not contain TZ info, how to bring it in ? -> new param
18 * 2) 04/04/18 <aOffsetSecs> not yet limited to one day
19 * 3) done 04/04/18 Is 20040101Z a valid 8601 ? (yes it is)
20 * 4) done 04/04/20 Fill in all valid time zones
21 * 5) done 04/04/20 Adapt straight forward conversions Enum <=> Name
22 * 6) done 04/06/14 System time zone calculation for Linux
23 * 7) done 04/06/14 InsertTZ / RemoveTZ implementation
24 * 8) 06/04/12 Offset calculation for more complicated vTZ ("$") records
25 * 9) done 07/02/26 Multi year list support
28 // must be first in file, everything above is ignored by MVC compilers
29 #include "prefix_file.h"
31 #define TIMEZONES_INTERNAL 1
33 // do not import the whole thing to make life easier for standalone apps
34 #ifdef FULLY_STANDALONE
35 #include "sysync_globs.h"
36 #include "sysync_debug.h"
41 #include "lineartime.h"
42 #include "timezones.h"
44 #include "stringutils.h"
45 #include "vtimezone.h"
49 static bool tzcmp( const tz_entry &t, const tz_entry &tzi );
50 static bool YearFit( const tz_entry &t, const tz_entry &tzi, GZones* g );
52 // ---- global structure -----------------------------------------------------------
54 // %%% this is now a global variable, which is BAD. Unlike a const struct array, which can be put into the
55 // code section, this "const" is a run-time generated structure (won't work e.g. for Symbian)
56 // We should avoid this and create that object in the app level GZones, and let further GZone objects
57 // allow using it (without copying!) - similar mechanism as the old CopyCustomTZFrom() had
58 // (see 8742d15b65fecf02db54ce9b7447cee09f913ad0 commit which removed it)
60 const class tzdata : public std::vector<tz_entry>
65 // add the global entries
66 reserve(tctx_numtimezones);
67 for (int i=0; i<tctx_numtimezones; i++) {
68 const tbl_tz_entry &t = tbl_tz[i];
69 if (i > 0 && back().name == t.name) {
70 // the previous entry wasn't really the last of its group,
72 back().groupEnd = false;
74 // add new entry, assuming that it terminates its group
76 t.name, t.bias, t.biasDST, t.ident, t.dynYear,
77 tChange(t.dst.wMonth, t.dst.wDayOfWeek, t.dst.wNth, t.dst.wHour, t.dst.wMinute),
78 tChange(t.std.wMonth, t.std.wDayOfWeek, t.std.wNth, t.std.wHour, t.std.wMinute),
82 //push_back(tz_entry("unknown", 0, 0, "x", "", tChange( 0, 0,0, 0,0), tChange( 0, 0,0, 0,0))); // 0
88 // ---------------------------------------------------------------------------------
91 bool GZones::initialize()
94 // load system wide definitions.
95 bool nobuiltin = loadSystemZoneDefinitions(this);
96 // %%%% later, we'll load system zones not into each GZones, but only once into
97 // a global list. Then, the return value of loadSystemZoneDefinitions() will
98 // determine if zones from the built-in list should be added or not
99 // %%%% for now, we can't do that yet, built-in zones are always active
101 //%%% add entries from tz_table
107 bool GZones::matchTZ(const tz_entry &aTZ, timecontext_t &aContext)
109 // keeps track of best match while iterating
110 class comparison : public visitor {
111 /** best solution so far has matching rules */
113 /** best solution has matching location */
115 /** best solution so far */
116 timecontext_t fContext;
118 /** the time zone we try to match */
120 /** the TZID we try to match */
123 /** the last entry without dynYear, i.e., the main entry of a group */
124 timecontext_t fLeadContext;
130 comparison(const tz_entry &aTZ, GZones *g) :
132 fLocationMatch(false),
133 fContext(TCTX_UNKNOWN),
135 fLeadContext(TCTX_UNKNOWN),
138 // prepare information for tzcmp() and YearFit()
140 fTZ.biasDST = aTZ.biasDST;
143 // setting the year here instead of 'CUR' avoids repeated calls
144 // to MyYear() inside YearFit()
145 int year = MyYear(g);
146 StringObjPrintf(fTZ.dynYear, "%d", year);
149 bool visit(const tz_entry &aTZ, timecontext_t aContext)
151 if (aTZ.ident=="x") return false; // comparison with this type is not possible
153 bool rule_match = tzcmp(fTZ, aTZ) && YearFit(fTZ, aTZ, fG);
155 if (aTZ.dynYear.empty())
156 fLeadContext = aContext;
159 !fTZ.name.empty() && // empty match is NOT better !!
160 fTZ.name == aTZ.name) {
161 // name AND rule match => best possible match, return early
162 fContext = fLeadContext;
166 if (!aTZ.location.empty() &&
167 fTZID.find(aTZ.location) != fTZID.npos) {
168 // location name is part of the TZID we try to match
169 if (!fLocationMatch ||
170 (!fRuleMatch && rule_match)) {
171 // previous match did not match location or
172 // not the rules and we do, so this match is better
173 fLocationMatch = true;
174 fRuleMatch = rule_match;
175 fContext = fLeadContext;
180 // a rule match with no better match yet?
181 if (!fLocationMatch &&
184 fContext = fLeadContext;
191 bool result(timecontext_t &aContext)
193 if (fContext != TCTX_UNKNOWN) {
206 return c.result(aContext);
209 bool GZones::foreachTZ(visitor &v)
211 int i; // visit hard coded elements first
212 for (i= 1; i<(int)tctx_numtimezones; i++) {
213 if (v.visit(tz[i], TCTX_ENUMCONTEXT(i))) {
223 for (TZList::iterator pos= tzP.begin();
226 if (v.visit(*pos, TCTX_ENUMCONTEXT(i)))
237 // ---------------------------------------------------------------------------------
239 // Get signed minute offset
240 sInt16 TCTX_MINOFFSET( timecontext_t tctx ) {
241 return (sInt16)(tctx & TCTX_OFFSETMASK );
245 // Get time zone enum
246 TTimeZones TCTX_TZENUM( timecontext_t tctx ) {
247 return (TTimeZones)(tctx & TCTX_OFFSETMASK );
251 // Check if <tctx> is a symbolic TZ info
252 bool TCTX_IS_TZ( timecontext_t tctx ) {
253 return (tctx & TCTX_SYMBOLIC_TZ)!=0;
257 // Check if <tctx> is a built-in symbolic TZ info
258 bool TCTX_IS_BUILTIN( timecontext_t tctx ) {
260 (tctx & TCTX_SYMBOLIC_TZ) && // is a symbolic time zone
261 (TCTX_TZENUM(tctx)<tctx_numtimezones); // and is in the internal list
265 // Check if <tctx> has TCTX_DATEONLY mode set (but not TIMEONLY - both set means same as both not set)
266 bool TCTX_IS_DATEONLY( timecontext_t tctx ) {
267 return (tctx & TCTX_DATEONLY) && !(tctx & TCTX_TIMEONLY);
268 } // TCTX_IS_DATEONLY
271 // Check if <tctx> has TCTX_TIMEONLY mode set (but not DATEONLY - both set means same as both not set)
272 bool TCTX_IS_TIMEONLY( timecontext_t tctx ) {
273 return (tctx & TCTX_TIMEONLY) && !(tctx & TCTX_DATEONLY);
274 } // TCTX_IS_TIMEONLY
277 // Check if <tctx> has TCTX_DURATION mode set
278 bool TCTX_IS_DURATION( timecontext_t tctx ) {
279 return ( tctx & TCTX_DURATION )!=0;
280 } // TCTX_IS_DURATION
283 // Check if <tctx> is a unknown time zone
284 bool TCTX_IS_UNKNOWN( timecontext_t tctx ) {
285 return TCTX_IS_TZ( tctx ) && ( TCTX_TZENUM( tctx )==tctx_tz_unknown );
289 // Check if <tctx> is the system time zone
290 bool TCTX_IS_SYSTEM ( timecontext_t tctx ) {
291 return TCTX_IS_TZ( tctx ) && ( TCTX_TZENUM( tctx )==tctx_tz_system );
295 // Check if <tctx> is the UTC time zone
296 bool TCTX_IS_UTC ( timecontext_t tctx ) {
297 return TCTX_IS_TZ( tctx ) && ( TCTX_TZENUM( tctx )==tctx_tz_UTC );
301 //! result is render flags from <aRFlagContext> with zone info from <aZoneContext>
302 timecontext_t TCTX_JOIN_RFLAGS_TZ ( timecontext_t aRFlagContext, timecontext_t aZoneContext )
305 (aRFlagContext & TCTX_RFLAGMASK) |
306 (aZoneContext & ~TCTX_RFLAGMASK);
307 } // TCTX_JOIN_RFLAGS_TZ
311 // ---- utility functions --------------------------------------------------------------
312 // Specific bias string. Unit: hours
313 string BiasStr( int bias )
316 float f= (float)bias/MinsPerHour;
318 if (bias % MinsPerHour == 0) sprintf( hrs,"%3.0f ", f ); // not with .0
319 else if (abs(bias % MinsPerHour)==30) sprintf( hrs,"%5.1f ", f );
320 else sprintf( hrs,"%6.2f ", f );
326 // Clear the DST info of <t>
327 void ClrDST( tz_entry &t )
329 memset( &t.dst, 0, sizeof(t.dst) );
330 memset( &t.std, 0, sizeof(t.std) );
334 // -------------------------------------------------------------------------------------
335 // Special cases: <year> = 0, the one w/o dynYear
336 // = -1, direct index
337 bool GetTZ( timecontext_t aContext, tz_entry &t, GZones* g, int year )
339 if (!TCTX_IS_TZ( aContext )) return false;
341 int aTZ= aContext & TCTX_OFFSETMASK;
342 if (aTZ>=0 && aTZ<tctx_numtimezones) {
344 t= tz[ aTZ ]; if (t.dynYear.empty() || year==-1) break; // replace it by a non-dynYear
349 if (year<=0) return true;
352 while (nx<tctx_numtimezones) { // search for the dynamic year
353 if (t.name != tz[ nx ].name) break; // no or no more
355 if (year<=atoi( t.dynYear.c_str() )) break; // this is the year line we are looking for
359 return true; // this is it !!
362 t= tz[ tctx_tz_unknown ]; // default, if not yet ok
363 if (g==NULL) return false; // If there is no <g>, it is definitely false
365 // -----------------------
367 //if (g==NULL) g= gz(); // either <g> or global list
373 int i = tctx_numtimezones;
374 TZList::iterator pos;
375 for (pos= g->tzP.begin(); // go thru the additional list
376 pos!=g->tzP.end(); pos++) {
377 if (aTZ==i && // no removed elements !!
378 !(pos->ident=="-")) {
381 if (year<=0) break; // pass unlock now
384 while (pos!=g->tzP.end()) { // search for the dynamic year
385 if (t.name != pos->name) break; // no or no more
387 if (year<=atoi( t.dynYear.c_str() )) break; // this is the year line we are looking for
391 break; // pass unlock now
398 unlockMutex( g->muP );
400 // -----------------------
407 void Get_tChange( lineartime_t tim, tChange &v, bool asDate )
409 sInt16 y, day, d, sec, ms;
411 lineartime2date( tim, &y, &v.wMonth, &day );
412 lineartime2time( tim, &v.wHour, &v.wMinute, &sec, &ms );
415 v.wDayOfWeek= -1; // use it as date directly
419 v.wDayOfWeek= lineartime2weekday( tim );
420 v.wNth = ( day-1 ) / DaysPerWk + 1;
422 if (v.wNth==4) { // the last one within month ?
424 AdjustDay( d, v.wMonth, y );
425 if ( d==day ) v.wNth= 5;
432 static bool Fill_tChange( string iso8601, int bias, int biasDST, tChange &tc, bool isDST )
436 //sInt16 y, day, d, sec, ms;
438 string::size_type rslt= ISO8601StrToTimestamp( iso8601.c_str(), l, c );
439 if (rslt!=iso8601.length()) return false;
442 if (!isDST) bMins+= biasDST;
443 l+= seconds2lineartime( bMins*SecsPerMin );
445 Get_tChange( l, tc );
448 lineartime2date( l, &y, &tc.wMonth, &day );
449 lineartime2time( l, &tc.wHour, &tc.wMinute, &sec, &ms );
450 tc.wDayOfWeek= lineartime2weekday( l );
451 tc.wNth = ( day-1 ) / DaysPerWk + 1;
453 if (tc.wNth==4) { // the last one within month ?
455 AdjustDay( d, tc.wMonth, y );
456 if ( d==day ) tc.wNth= 5;
464 bool GetTZ( string std, string dst, int bias, int biasDST, tz_entry &t, GZones* g )
472 return Fill_tChange( std, bias,biasDST, t.std, false ) &&
473 Fill_tChange( dst, bias,biasDST, t.dst, true );
477 static bool Same_tChange( const tChange &tCh1, const tChange &tCh2 )
479 return tCh1.wMonth ==tCh2.wMonth &&
480 tCh1.wDayOfWeek==tCh2.wDayOfWeek &&
481 tCh1.wNth ==tCh2.wNth &&
482 tCh1.wHour ==tCh2.wHour &&
483 tCh1.wMinute ==tCh2.wMinute;
487 /*! Compare time zone information */
488 static bool tzcmp( const tz_entry &t, const tz_entry &tzi )
490 if (!t.name.empty() &&
491 strucmp( t.name.c_str(), tzi.name.c_str() )!=0) return false;
493 bool idN= t.ident.empty();
494 if (!idN && t.ident == "?" && // search for the name only
495 strucmp( t.name.c_str(), tzi.name.c_str() )==0) return true;
497 if (!idN && t.ident == "$" &&
498 t.ident == tzi.ident &&
499 strucmp( t.name.c_str(), tzi.name.c_str() )==0) return true;
502 PNCDEBUGPRINTFX( DBG_SESSION,( "tS m=%d dw=%d n=%d h=%d M=%d\n", t.std.wMonth, t.std.wDayOfWeek, t.std.wNth,
503 t.std.wHour, t.std.wMinute ) );
504 PNCDEBUGPRINTFX( DBG_SESSION,( "tD m=%d dw=%d n=%d h=%d M=%d\n", t.dst.wMonth, t.dst.wDayOfWeek, t.dst.wNth,
505 t.dst.wHour, t.dst.wMinute ) );
506 PNCDEBUGPRINTFX( DBG_SESSION,( "tziS m=%d dw=%d n=%d h=%d M=%d\n", tzi.std.wMonth, tzi.std.wDayOfWeek, tzi.std.wNth,
507 tzi.std.wHour, tzi.std.wMinute ) );
508 PNCDEBUGPRINTFX( DBG_SESSION,( "tziD m=%d dw=%d n=%d h=%d M=%d\n", tzi.dst.wMonth, tzi.dst.wDayOfWeek, tzi.dst.wNth,
509 tzi.dst.wHour, tzi.dst.wMinute ) );
511 PNCDEBUGPRINTFX( DBG_SESSION,( "t bs=%d bd=%d\n", t.bias, t.biasDST ) );
512 PNCDEBUGPRINTFX( DBG_SESSION,( "tzi bs=%d bd=%d\n", tzi.bias, tzi.biasDST ) );
515 if (t.bias!=tzi.bias) return false; // bias must be identical
517 if (t.ident == "o") {
518 // stop comparing, return result of last check
519 return t.biasDST == tzi.biasDST;
522 bool tIsDst= DSTCond( t );
523 bool tziIsDst= DSTCond( tzi );
524 if (tIsDst!=tziIsDst) return false; // DST cond must be on or off for both
527 //if (memcmp(&t.dst, &tzi.dst, sizeof(t.dst))!=0 ||
528 // memcmp(&t.std, &tzi.std, sizeof(t.std))!=0 ||
529 if (!Same_tChange( t.dst, tzi.dst ) ||
530 !Same_tChange( t.std, tzi.std ) ||
531 t.biasDST!=tzi.biasDST) return false;
534 return !(tzi.ident=="-") && // not removed
537 t.ident == tzi.ident );
540 return memcmp(&t.dst, &tzi.dst, sizeof(t.dst))==0 &&
541 memcmp(&t.std, &tzi.std, sizeof(t.std))==0 &&
543 t.biasDST==tzi.biasDST && //%%% luz: this must be compared as well
544 strcmp( "-", tzi.ident )!=0 && // removed
546 strcmp( t.ident, "" )==0 ||
547 strcmp( t.ident, tzi.ident )==0 );
553 sInt16 MyYear( GZones* g )
557 lineartime_t t= getSystemNowAs( TCTX_UTC, g, true );
558 lineartime2date( t, &y,&m,&d );
563 static bool YearFit( const tz_entry &t, const tz_entry &tzi, GZones* g )
566 if (t.dynYear == "CUR") yearS= MyYear( g );
567 else yearS= atoi( t.dynYear.c_str() );
568 if (yearS==0) return true;
570 int yearI= atoi( tzi.dynYear.c_str() );
571 if (yearI==0) return true;
573 if (tzi.groupEnd) return yearS>=yearI;
574 else return yearS<=yearI;
579 /* Returns true, if the given TZ is existing already
580 * <t> tz_entry to search for:
581 * If <t.name> == "" search for any entry with these values.
582 * <t.name> != "" name must fit
583 * If <t.ident>== "" search for any entry with these values
584 * <t.ident>!= "" name must fit
585 * <aName> is <t.name> of the found record
586 * <aContext> is the assigned context
587 * <createIt> create an entry, if not yet existing / default: false
588 * <searchOffset> says, where to start searching / default: at the beginning
590 * supported <t.ident> values:
592 * "?" name only (to search)
593 * "o" compare offsets, but not changes (to search); name is compared if set
600 * "$" not converted zones (pure text)
603 bool FoundTZ( const tz_entry &tc,
605 timecontext_t &aContext, GZones* g, bool createIt,
606 timecontext_t searchOffset )
609 aContext = TCTX_UNKNOWN;
610 int offs = TCTX_OFFSCONTEXT( searchOffset );
614 if (!t.ident.empty() && // specific items will not contain more info
619 int i; // search hard coded elements first
620 for (i= offs+1; i<(int)tctx_numtimezones; i++) {
621 const tz_entry &tzi = tz[ i ];
622 if (tzcmp ( t, tzi ) &&
623 YearFit( t, tzi, g )) {
629 // don't go thru the mutex, if not really needed
630 if (!ok && g!=NULL) {
631 // -------------------------------------------
632 //if (g==NULL) g= gz();
638 TZList::iterator pos;
639 int j= offs-(int)tctx_numtimezones; // remaining gap to be skipped
641 // Search for all not removed elements first
642 for (pos= g->tzP.begin();
643 pos!=g->tzP.end(); pos++) {
645 !(pos->ident=="-") && // element must not be removed
655 // now check, if an already removed element can be reactivated
656 if (createIt && !ok) {
657 i= (int)tctx_numtimezones;
658 j= offs-(int)tctx_numtimezones; // remaining gap to be skipped
660 for (pos= g->tzP.begin();
661 pos!=g->tzP.end(); pos++) {
663 pos->ident == "-" && // removed element ?
665 pos->ident= t.ident; // reactivate the identifier
666 aName = pos->name; // should be the same
675 // no such element => must be created
676 if (createIt && !ok) { // create it, if not yet ok
677 g->tzP.push_back( t );
682 unlockMutex( g->muP );
684 // -------------------------------------------
687 if (ok) aContext= TCTX_ENUMCONTEXT( i );
688 return aContext!=TCTX_UNKNOWN;
693 /* Remove a time zone definition, if already existing
694 * NOTE: Currently only dynamic entries can be removed
696 bool RemoveTZ( const tz_entry &t, GZones* g )
698 //printf( "RemoveTZ '%s' %d\n", t.name, t.bias );
701 // ---------------------------
702 if (g==NULL) return ok;
703 //if (g==NULL) g= gz();
709 TZList::iterator pos;
710 for (pos= g->tzP.begin();
711 pos!=g->tzP.end(); pos++) {
712 if (!(pos->ident=="-") && // element must not be removed
715 //gz()->tzP.erase( pos ); // do not remove it, keep it persistent
721 unlockMutex( g->muP );
723 // ---------------------------
730 bool TimeZoneNameToContext( cAppCharP aName, timecontext_t &aContext, GZones* g )
732 // check some special cases
733 if (strucmp(aName,"DATE")==0) {
734 aContext = TCTX_UNKNOWN|TCTX_DATEONLY;
737 else if (strucmp(aName,"DURATION")==0) {
738 aContext = TCTX_UNKNOWN|TCTX_DURATION;
741 else if (*aName==0 || strucmp(aName,"FLOATING")==0) {
742 aContext = TCTX_UNKNOWN;
746 const char* GMT= "GMT";
747 const char* UTC= "UTC";
753 t.name = aName; // prepare searching
755 t.dynYear= ""; // luz: must be initialized!
758 if (FoundTZ( t, tName, aContext, g )) return true;
761 int i; aContext= TCTX_UNKNOWN;
762 for (i=0; i<(int)tctx_numtimezones; i++) {
763 if (strucmp( tz[i].name,aName )==0) { aContext= TCTX_ENUMCONTEXT(i); break; }
766 if (aContext!=TCTX_UNKNOWN) return true;
769 // calculate the UTC offset
770 n= 1; q= (char *)strstr( aName,UTC );
771 if (q==NULL) q= (char *)strstr( aName,GMT );
772 if (q!=NULL && strlen( q )>strlen( UTC )) {
781 if (v!=0 || strcmp( q, "0" )==0
782 || strcmp( q,"+0" )==0
783 || strcmp( q,"-0" )==0) {
784 aContext= TCTX_OFFSCONTEXT(v*n); return true;
788 } /* TimeZoneNameToContext */
792 bool TimeZoneContextToName( timecontext_t aContext, string &aName, GZones* g,
793 cAppCharP aPrefIdent )
795 // check some special cases
796 if (!TCTX_IS_TZ( aContext )) {
797 short lBias= TCTX_MINOFFSET( aContext );
798 aName= "OFFS" + HourMinStr( lBias );
802 if (TCTX_IS_UNKNOWN(aContext)) {
803 aName = TCTX_IS_DURATION(aContext) ? "DURATION" : (TCTX_IS_DATEONLY(aContext) ? "DATE" : "FLOATING");
810 // if aPrefIndent contains "o", this means we'd like to see olson name, if possible
811 // %%% for now, we can return olson for the built-ins only
812 if (TCTX_IS_BUILTIN(aContext) && aPrefIdent && strchr(aPrefIdent, 'o')!=NULL) {
813 #ifndef NO_BUILTIN_TZ
814 // look up in hardcoded table
815 cAppCharP oname = tbl_tz[TCTX_TZENUM(aContext)].olsonName;
817 // we have an olson name for this entry, return it
824 // %%% <aPrefIdent> has not yet any influence
825 if (GetTZ( aContext, t, g )) {
826 if (t.ident == "$") return false; // unchanged elements are not yet supported
831 } /* TimeZoneContextToName */
835 /*! Is it a DST time zone ? (both months must be defined for DST mode) */
836 bool DSTCond( const tz_entry &t ) {
837 return (t.dst.wMonth!=0 &&
843 /* adjust to day number within month <m>, %% valid till year <y> 2099 */
844 void AdjustDay( sInt16 &d, sInt16 m, sInt16 y )
846 while (d>31) d-= DaysPerWk;
847 if (d>30 && (m==4 || m==6 || m==9 || m==11)) d-= DaysPerWk;
848 if (d>29 && m==2) d-= DaysPerWk;
849 if (d>28 && m==2 && (y % 4)!=0) d-= DaysPerWk;
853 /* Get the day where STD <=> DST switch will be done */
854 static sInt16 DaySwitch( lineartime_t aTime, const tChange* c )
857 lineartime2date( aTime, &y, &m, &d );
859 if (c->wDayOfWeek==-1) {
863 sInt16 wkDay= lineartime2weekday( aTime );
864 ds = ( wkDay + 5*DaysPerWk - ( d-1 ) ) % DaysPerWk; /* wkday of 1st */
865 ds = ( DaysPerWk - ds + c->wDayOfWeek ) % DaysPerWk + 1; /* 1st occurance */
866 ds+= ( c->wNth-1 )*DaysPerWk;
868 AdjustDay( ds, c->wMonth, y );
875 /*! Get lineartime_t of <t> for a given <year>, either from std <toDST> or vice versa */
876 lineartime_t DST_Switch( const tz_entry &t, int bias, sInt16 aYear, bool toDST )
881 if (toDST) c= &t.dst;
884 lineartime_t tim= date2lineartime( aYear, c->wMonth, 1 );
886 ds= DaySwitch( tim, c );
888 sInt32 bMins = t.bias;
889 if (!toDST) bMins+= t.biasDST;
891 return date2lineartime( aYear, c->wMonth, ds )
892 + time2lineartime( c->wHour, c->wMinute, 0,0 )
893 - seconds2lineartime( bMins*SecsPerMin );
898 /* Check whether <aValue> of time zone <t> is DST based */
899 static bool IsDST( lineartime_t aTime, const tz_entry &t )
902 sInt16 y, m, d, h, min, ds;
904 if (!DSTCond( t )) return false;
906 lineartime2date( aTime, &y, &m, &d );
907 lineartime2time( aTime, &h, &min, NULL, NULL );
909 const tChange* c= NULL;
910 if (m==t.std.wMonth) c= &t.std;
911 if (m==t.dst.wMonth) c= &t.dst;
913 /* calculation is a little bit more tricky within the two switching months */
916 if (c->wDayOfWeek==-1) {
920 sInt16 wkDay= lineartime2weekday( aTime );
921 ds = ( wkDay + 5*DaysPerWk - (d-1) ) % DaysPerWk; // wkday of 1st
922 ds = ( DaysPerWk - ds + c->wDayOfWeek ) % DaysPerWk + 1; // 1st occurance
923 ds+= ( c->wNth-1 )*DaysPerWk;
925 AdjustDay( ds, m, y );
929 /* <ds> is the day when dst<=>std takes place */
930 ds= DaySwitch( aTime, c );
931 if (ds < d ) return c==&t.dst;
932 if (ds > d ) return c==&t.std;
934 /* day correct => compare hours */
935 if (c->wHour < h ) return c==&t.dst;
936 if (c->wHour > h ) return c==&t.std;
938 /* hour correct => compare minutes */
939 if (c->wMinute< min ) return c==&t.dst;
940 if (c->wMinute> min ) return c==&t.std;
942 /* decide for the margin, if identical */
946 /* northern and southern hemnisphere supported */
947 if (t.dst.wMonth<t.std.wMonth)
948 ok= m>t.dst.wMonth && m<t.std.wMonth;
949 else ok= m<t.std.wMonth || m>t.dst.wMonth;
955 static sInt32 DST_Offs( lineartime_t aValue, const tz_entry &t, bool backwards )
957 if (backwards) aValue+= (lineartime_t)(t.bias*SecsPerMin)*secondToLinearTimeFactor;
958 if (IsDST( aValue,t )) return t.bias + t.biasDST;
964 /* get offset in minutes */
965 static bool TimeZoneToOffs( lineartime_t aValue, timecontext_t aContext,
966 bool backwards, sInt32 &offs, GZones* g )
970 if (TCTX_IS_TZ( aContext )) {
974 lineartime2date( aValue, &year, NULL, NULL );
975 if (GetTZ( aContext, t, g, year )) {
976 if (t.ident == "$") return false; // unchanged elements are not yet supported
977 offs= DST_Offs( aValue, t, backwards );
981 offs= TCTX_MINOFFSET(aContext);
985 } /* TimeZoneToOffs */
990 timecontext_t SelectTZ( TDaylightSavingZone zone, int bias, int biasDST, lineartime_t tNow, bool isDbg )
993 bool withDST= zone!=EDstNone;
994 timecontext_t t= tctx_tz_unknown;
995 bool special= false; // possibly needed true for NGage
997 int i; // go thru the whole list of time zones
998 for (i=(int)tctx_tz_system+1; i<(int)tctx_numtimezones; i++) {
999 bool tCond= DSTCond( tz[ i ] ); // are there any DST rules ?
1000 if (tCond==withDST) {
1001 if (withDST) dst= IsDST( tNow, tz[ i ] ); // check, if now in DST
1005 if (dst && special) b= bias - biasDST;
1006 if (tz[ i ].bias==b) { // the bias must fit exactly
1009 case EDstNorthern : ok= tz[ i ].dst.wMonth<tz[ i ].std.wMonth; break;
1010 case EDstSouthern : ok= tz[ i ].dst.wMonth>tz[ i ].std.wMonth; break;
1014 if (isDbg) PNCDEBUGPRINTFX( DBG_SESSION,( " %d %d dst=%d cond=%d '%s'",
1015 i, bias, dst, tCond, tz[ i ].name.c_str() ));
1016 if (ok) { // check, if european time zone
1017 ok= (zone==EDstEuropean) ==
1018 (tz[ i ].name.find("Europe") != string::npos ||
1019 tz[ i ].name == "CET/CEST" ||
1020 tz[ i ].name == "Romance" ||
1021 tz[ i ].name == "GMT");
1022 if (ok) { t= i; break; }
1028 if (isDbg) PNCDEBUGPRINTFX( DBG_SESSION,( "SelectTZ: zone=%s bias=%d",
1029 tz[ t ].name.c_str(), bias ) );
1030 return TCTX_SYMBOLIC_TZ+t;
1035 /* %%% luz 2009-04-02 seems of no relevance except for Symbian/Epoc -> Epoc code moved to Symbian/platform_timezones.cpp.
1037 Note: added a replacement using MyContext which should return the same result (but not
1038 the debug output, as SystemTZ is still being called from sysytest.
1040 timecontext_t SystemTZ( GZones *g, bool isDbg )
1042 TDaylightSavingZone zone= EDstNone;
1044 lineartime_t tNow = 0;
1048 zone= tl.HomeDaylightSavingZone();
1049 TTimeIntervalSeconds o= tl.UniversalTimeOffset();
1050 bias= o.Int() / SecsPerMin; // bias is based on minutes
1051 bool isDst= tl.QueryHomeHasDaylightSavingOn();
1056 case EDstHome : zs= "EDstHome"; break;
1057 case EDstNone : zs= "EDstNone"; break;
1058 case EDstEuropean : zs= "EDstEuropean"; break;
1059 case EDstNorthern : zs= "EDstNorthern"; break;
1060 case EDstSouthern : zs= "EDstSouthern"; break;
1061 default : zs= "???"; break;
1064 PNCDEBUGPRINTFX( DBG_SESSION,( "SystemTZ (EPOC): %s %d dst=%s", zs,bias, isDst ? "on":"off" ));
1067 PNCDEBUGPRINTFX( DBG_SESSION,( "SystemTZ (EPOC): on emulator" ));
1071 #if defined _WIN32 && !defined __EPOC_OS__
1072 TIME_ZONE_INFORMATION tzi;
1073 DWORD rslt= GetTimeZoneInformation( &tzi );
1075 bias= -tzi.Bias; // as negative value
1076 PNCDEBUGPRINTFX( DBG_SESSION,( "SystemTZ (WIN): %s %d", "", bias ) );
1079 // is only dependendent on current time/date, if any DST zone
1080 if (zone!=EDstNone) tNow= getSystemNowAs(TCTX_SYSTEM, g);
1081 return SelectTZ( zone,bias,tNow, isDbg );
1086 // Get int value <i> as string
1087 static string IntStr( sInt32 i )
1089 const int FLen= 15; /* max length of (internal) item name */
1091 // cheating: this printf format assumes that sInt32 == int
1092 sprintf ( f, "%d", int(i) );
1098 /* get system's time zone */
1099 static bool MyContext( timecontext_t &aContext, GZones* g )
1102 // check for cached system time zone, return it if available
1105 // luz: added safety here to avoid that incorrectly initialized GZone
1106 // (with tctx_tz_unknown instead of TCTX_UNKNOWN, as it was in timezones.h)
1107 // does not cause that system timezone is assumed known (as UTC) and returned WRONG!)
1108 // Note: rearranged to make non-locked writing to g->sysTZ is safe (for hacks needed pre-3.1.2.x!)
1109 timecontext_t curSysTZ= g->sysTZ;
1110 if (!(TCTX_IS_UNKNOWN( curSysTZ ) ||
1111 TCTX_IS_SYSTEM ( curSysTZ )
1112 )) { aContext= curSysTZ; return true; }
1115 // there is no system time zone cached, we need to determine it from the operating system
1116 // - call platform specific routine
1117 bool ok = getSystemTimeZoneContext( aContext, g );
1119 if (isDbg) PNCDEBUGPRINTFX( DBG_SESSION, ( "MyContext: %08X ok=%d", aContext, ok ));
1121 // update cached system context
1122 if (g && ok) g->sysTZ= aContext; // assign the system context
1127 /* %%% luz 2009-04-02 seems of no relevance except for Symbian/Epoc -> Epoc code moved to Symbian/platform_timezones.cpp
1128 added a replacement here as it is still used from sysytest, which should return the same result (but not
1130 timecontext_t SystemTZ( GZones *g, bool isDbg )
1133 if (MyContext(tctx, g))
1136 return TCTX_UNKNOWN;
1141 bool ContextForEntry( timecontext_t &aContext, tz_entry &t, bool chkNameFirst, GZones* g )
1144 string sName = t.name;
1147 if (chkNameFirst && !sName.empty() &&
1148 TimeZoneNameToContext( sName.c_str(),aContext, g )) break;
1150 // if there are no rules defined => switch it off
1151 if (t.dst.wMonth==0 ||
1153 Same_tChange( t.dst, t.std )) {
1157 t.name = (char*)sName.c_str();
1159 t.dynYear= ""; // MUST be set as this is assigned unchecked to a string
1160 // (which crashes when assigned NULL or low number)
1161 if (FoundTZ( t, s, aContext, g )) break; // with this name
1165 if (FoundTZ( t, s, aContext, g )) break; // with different name
1168 if (sName.empty()) sName= "unassigned";
1173 if (i>0) v+= "_" + IntStr( i );
1174 if (!TimeZoneNameToContext( v.c_str(),aContext, g )) break; // A timezone with this name must not exist
1178 t.name= (char*)v.c_str();
1181 FoundTZ( t, tz_Name, aContext, g, true ); // create it
1182 if (TimeZoneNameToContext( v.c_str(),aContext, g )) break;
1186 t.name= (char*)sName.c_str();
1189 FoundTZ( t, tzName, aContext, g, true ); // create it
1190 if (TimeZoneNameToContext( sName.c_str(),aContext, g )) break;
1195 } /* ContextFromNameAndEntry */
1199 /* returns true, if the context rules are identical */
1200 static bool IdenticalRules( timecontext_t aSourceContext,
1201 timecontext_t aTargetContext, GZones* g )
1205 /* only performed in TZ mode */
1206 if (!GetTZ( aSourceContext, ts, g, 0 ) ||
1207 !GetTZ( aTargetContext, tt, g, 0 )) return false;
1209 TTimeZones asTZ= TCTX_TZENUM( aSourceContext );
1210 TTimeZones atTZ= TCTX_TZENUM( aTargetContext );
1211 if (asTZ==atTZ) return true; // identical
1213 if (ts.ident == "$" ||
1214 tt.ident == "$") return false;
1216 if (ts.bias==tt.bias &&
1217 // memcmp( &ts.dst, &tt.dst, sizeof(tChange))==0 &&
1218 // memcmp( &ts.std, &tt.std, sizeof(tChange))==0) return true;
1219 Same_tChange( ts.dst, tt.dst ) &&
1220 Same_tChange( ts.std, tt.std )) return true;
1223 if (!TCTX_IS_TZ( aSourceContext ) ||
1224 !TCTX_IS_TZ( aTargetContext )) return false;
1226 // get the time zones for calculation
1227 TTimeZones asTZ= TCTX_TZENUM(aSourceContext);
1228 TTimeZones atTZ= TCTX_TZENUM(aTargetContext);
1230 if (asTZ==atTZ) return true;
1232 if ( tz[ asTZ ].bias==tz[ atTZ ].bias &&
1233 memcmp( &tz[ asTZ ].dst, &tz[ atTZ ].dst, sizeof(tChange))==0 &&
1234 memcmp( &tz[ asTZ ].std, &tz[ atTZ ].std, sizeof(tChange))==0) {
1240 } /* IdenticalRules */
1244 /*! get system's time zone context (i.e. resolve the TCTX_SYSTEM meta-context)
1245 * @param[in,out] aContext : context will be made non-meta, that is, if input is TCTX_SYSTEM,
1246 * actual time zone will be determined.
1248 bool TzResolveMetaContext( timecontext_t &aContext, GZones* g )
1250 if (!TCTX_IS_SYSTEM(aContext))
1251 return true; // no meta zone, just return unmodified
1252 // is meta-context TCTX_SYSTEM, determine actual symbolic context
1253 return MyContext(aContext,g);
1254 } // TzResolveMetaContext
1257 /* make time context non-symbolic (= calculate minute offset east of UTC for aRefTime) */
1258 bool TzResolveContext( timecontext_t &aContext, lineartime_t aRefTime, bool aRefTimeUTC, GZones* g )
1261 // resolve possible meta context (TCTX_SYSTEM at this time)
1262 if (!TzResolveMetaContext(aContext,g)) return false;
1263 // check if already an offset (non-symbolic)
1264 if (!TCTX_IS_TZ(aContext))
1265 return true; // yes, no conversion needed
1266 // is symbolic, needs conversion to offset
1267 bool ok = TimeZoneToOffs(
1268 aRefTime, // reference time for which we want to know the offset
1269 aContext, // context
1270 aRefTimeUTC, // "backwards" means refTime is UTC and we want know offset to go back to local
1271 offs, // here we get the offset
1275 aContext = TCTX_JOIN_RFLAGS_TZ(
1276 aContext, // join original rendering flags...
1277 TCTX_OFFSCONTEXT(offs) // ...with new offset based context
1281 } // TzResolveContext
1284 /*! calculate minute offset east of UTC for aRefTime
1285 * @param[in] aContext : time context to resolve
1286 * @param[out] aMinuteOffset : receives minute offset east of UTC
1287 * @param[in] aRefTime : reference time point for resolving the offset
1288 * @param[in] aRefTimeUTC : if set, reference time must be UTC,
1289 * otherwise, reference time must be in context of aContext
1291 bool TzResolveToOffset( timecontext_t aContext, sInt16 &aMinuteOffset, lineartime_t aRefTime, bool aRefTimeUTC, GZones* g )
1293 bool ok = TzResolveContext(aContext, aRefTime, aRefTimeUTC, g);
1295 aMinuteOffset = TCTX_MINOFFSET(aContext);
1298 } // TzResolveToOffset
1302 /*! Offset between two contexts (in seconds)
1303 * Complex time zones (type "$") can't be currently resolved, they return false
1304 * @param[in] aSourceValue : reference time for which the offset should be calculated
1305 * @param[in] aSourceContext : source time zone context
1306 * @param[in] aTargetContext : source time zone context
1307 * @param[out] sDiff : receives offset east of UTC in seconds
1309 bool TzOffsetSeconds( lineartime_t aSourceValue, timecontext_t aSourceContext,
1310 timecontext_t aTargetContext,
1311 sInt32 &sDiff, GZones* g,
1312 timecontext_t aDefaultContext )
1316 sInt32 sOffs= 0, tOffs= 0; // initialize them for sure
1317 lineartime_t aTargetValue;
1321 if (g) isDbg= g->isDbg;
1323 // set default context for unknown zones
1324 if (TCTX_IS_UNKNOWN(aSourceContext)) aSourceContext=aDefaultContext;
1325 if (TCTX_IS_UNKNOWN(aTargetContext)) aTargetContext=aDefaultContext;
1329 if (aSourceContext==aTargetContext) break; /* no conversion, if identical context */
1331 /* both unknown or system is still ok */
1332 if (TCTX_IS_UNKNOWN( aSourceContext ) &&
1333 TCTX_IS_UNKNOWN( aTargetContext )) break;
1334 sSys= TCTX_IS_SYSTEM ( aSourceContext );
1335 tSys= TCTX_IS_SYSTEM ( aTargetContext ); if (sSys && tSys) break;
1337 /* calculate specifically for the system's time zone */
1338 if (sSys) MyContext( aSourceContext, g );
1339 if (tSys) MyContext( aTargetContext, g );
1341 /* if both are unknown now, then it's good as well */
1342 sUnk= TCTX_IS_UNKNOWN( aSourceContext );
1343 tUnk= TCTX_IS_UNKNOWN( aTargetContext ); if (sUnk && tUnk) break;
1344 /* this case can't be resolved: */ if (sUnk || tUnk) { ok= false; break; }
1346 if (IdenticalRules( aSourceContext,aTargetContext, g )) break;
1348 /* now do the "hard" things */
1349 if (!TimeZoneToOffs( aSourceValue, aSourceContext, false, sOffs, g )) return false;
1350 aTargetValue= aSourceValue
1351 - (lineartime_t)(sOffs*SecsPerMin)*secondToLinearTimeFactor;
1352 if (!TimeZoneToOffs( aTargetValue, aTargetContext, true, tOffs, g )) return false;
1354 sDiff= ( tOffs - sOffs )*SecsPerMin;
1358 PNCDEBUGPRINTFX( DBG_SESSION,( "sSys=%d tSys=%d / sUnk=%d tUnk=%d / sOffs=%d tOffs=%d",
1359 sSys,tSys, sUnk,tUnk, sOffs,tOffs ));
1360 PNCDEBUGPRINTFX( DBG_SESSION,( "TzOffsetSeconds=%d", sDiff ));
1364 } /* TzOffsetSeconds */
1367 /*! Converts timestamp value from one zone to another
1368 * Complex time zones (type "$") can't be currently resolved, they return false
1369 * @param[in/out] aValue : will be converted from source context to target context. If==noLinearTime==0, no conversion is done
1370 * @param[in] aSourceContext : source time zone context
1371 * @param[in] aTargetContext : source time zone context
1372 * @param[in] aDefaultContext: default context to use if source or target is TCTX_UNKNOWN
1374 bool TzConvertTimestamp( lineartime_t &aValue, timecontext_t aSourceContext,
1375 timecontext_t aTargetContext,
1377 timecontext_t aDefaultContext )
1380 if (aValue==noLinearTime) return true; // no time, don't convert
1381 bool ok = TzOffsetSeconds( aValue, aSourceContext, aTargetContext, sdiff, g, aDefaultContext );
1383 aValue += (lineartime_t)(sdiff)*secondToLinearTimeFactor;
1385 } /* TzConvertTimestamp */
1388 } // namespace sysync