/*************************************************************************** timezonerule.cpp - description ------------------- begin : Tue Apr 2 2002 copyright : (C) 2002 by Jason Harris email : kstars@30doradus.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include #include "timezonerule.h" #include "kstarsdatetime.h" TimeZoneRule::TimeZoneRule() { //Build the empty TimeZoneRule. StartMonth = 0; RevertMonth = 0; StartDay = 0; RevertDay = 0; StartWeek = -1; RevertWeek = -1; StartTime = TQTime(); RevertTime = TQTime(); HourOffset = 0.0; dTZ = 0.0; } TimeZoneRule::TimeZoneRule( const TQString &smonth, const TQString &sday, const TQTime &stime, const TQString &rmonth, const TQString &rday, const TQTime &rtime, const double &dh ) { dTZ = 0.0; if ( smonth != "0" ) { StartMonth = initMonth( smonth ); RevertMonth = initMonth( rmonth ); if ( StartMonth && RevertMonth && initDay( sday, StartDay, StartWeek ) && initDay( rday, RevertDay, RevertWeek ) && stime.isValid() && rtime.isValid() ) { StartTime = stime; RevertTime = rtime; HourOffset = dh; } else { kdWarning() << i18n( "Error parsing TimeZoneRule, setting to empty rule." ) << endl; StartMonth = 0; RevertMonth = 0; StartDay = 0; RevertDay = 0; StartWeek = -1; RevertWeek = -1; StartTime = TQTime(); RevertTime = TQTime(); HourOffset = 0.0; } } else { //Empty rule StartMonth = 0; RevertMonth = 0; StartDay = 0; RevertDay = 0; StartWeek = -1; RevertWeek = -1; StartTime = TQTime(); RevertTime = TQTime(); HourOffset = 0.0; } } TimeZoneRule::~TimeZoneRule() { } void TimeZoneRule::setDST( bool activate ) { if ( activate ) { kdDebug() << i18n( "Daylight Saving Time active" ) << endl; dTZ = HourOffset; } else { kdDebug() << i18n( "Daylight Saving Time inactive" ) << endl; dTZ = 0.0; } } int TimeZoneRule::initMonth( const TQString &mn ) { //Check whether the argument is a three-letter English month code. TQString ml = mn.lower(); if ( ml == "jan" ) return 1; else if ( ml == "feb" ) return 2; else if ( ml == "mar" ) return 3; else if ( ml == "apr" ) return 4; else if ( ml == "may" ) return 5; else if ( ml == "jun" ) return 6; else if ( ml == "jul" ) return 7; else if ( ml == "aug" ) return 8; else if ( ml == "sep" ) return 9; else if ( ml == "oct" ) return 10; else if ( ml == "nov" ) return 11; else if ( ml == "dec" ) return 12; kdWarning() << i18n( "Could not parse " ) << mn << i18n( " as a valid month code." ) << endl; return false; } bool TimeZoneRule::initDay( const TQString &dy, int &Day, int &Week ) { //Three possible ways to express a day. //1. simple integer; the calendar date...set Week=0 to indicate that Date is not the day of the week bool ok; int day = dy.toInt( &ok ); if ( ok ) { Day = day; Week = 0; return true; } TQString dl = dy.lower(); //2. 3-letter day of week string, indicating the last of that day of the month // ...set Week to 5 to indicate the last weekday of the month if ( dl == "mon" ) { Day = 1; Week = 5; return true; } else if ( dl == "tue" ) { Day = 2; Week = 5; return true; } else if ( dl == "wed" ) { Day = 3; Week = 5; return true; } else if ( dl == "thu" ) { Day = 4; Week = 5; return true; } else if ( dl == "fri" ) { Day = 5; Week = 5; return true; } else if ( dl == "sat" ) { Day = 6; Week = 5; return true; } else if ( dl == "sun" ) { Day = 7; Week = 5; return true; } //3. 1,2 or 3 followed by 3-letter day of week string; this indicates // the (1st/2nd/3rd) weekday of the month. int wn = dl.left(1).toInt(); if ( wn >0 && wn <4 ) { TQString dm = dl.mid( 1, dl.length() ).lower(); if ( dm == "mon" ) { Day = 1; Week = wn; return true; } else if ( dm == "tue" ) { Day = 2; Week = wn; return true; } else if ( dm == "wed" ) { Day = 3; Week = wn; return true; } else if ( dm == "thu" ) { Day = 4; Week = wn; return true; } else if ( dm == "fri" ) { Day = 5; Week = wn; return true; } else if ( dm == "sat" ) { Day = 6; Week = wn; return true; } else if ( dm == "sun" ) { Day = 7; Week = wn; return true; } } kdWarning() << i18n( "Could not parse " ) << dy << i18n( " as a valid day code." ) << endl; return false; } int TimeZoneRule::findStartDay( const KStarsDateTime &d ) { // Determine the calendar date of StartDay for the month and year of the given date. ExtDate test; // TimeZoneRule is empty, return -1 if ( isEmptyRule() ) return -1; // If StartWeek=0, just return the integer. if ( StartWeek==0 ) return StartDay; // Since StartWeek was not zero, StartDay is the day of the week, not the calendar date else if ( StartWeek==5 ) { // count back from end of month until StartDay is found. for ( test = ExtDate( d.date().year(), d.date().month(), d.date().daysInMonth() ); test.day() > 21; test = test.addDays( -1 ) ) if ( test.dayOfWeek() == StartDay ) break; } else { // Count forward from day 1, 8 or 15 (depending on StartWeek) until correct day of week is found for ( test = ExtDate( d.date().year(), d.date().month(), (StartWeek-1)*7 + 1 ); test.day() < 7*StartWeek; test = test.addDays( 1 ) ) if ( test.dayOfWeek() == StartDay ) break; } return test.day(); } int TimeZoneRule::findRevertDay( const KStarsDateTime &d ) { // Determine the calendar date of RevertDay for the month and year of the given date. ExtDate test; // TimeZoneRule is empty, return -1 if ( isEmptyRule() ) return -1; // If RevertWeek=0, just return the integer. if ( RevertWeek==0 ) return RevertDay; // Since RevertWeek was not zero, RevertDay is the day of the week, not the calendar date else if ( RevertWeek==5 ) { //count back from end of month until RevertDay is found. for ( test = ExtDate( d.date().year(), d.date().month(), d.date().daysInMonth() ); test.day() > 21; test = test.addDays( -1 ) ) if ( test.dayOfWeek() == RevertDay ) break; } else { //Count forward from day 1, 8 or 15 (depending on RevertWeek) until correct day of week is found for ( test = ExtDate( d.date().year(), d.date().month(), (RevertWeek-1)*7 + 1 ); test.day() < 7*RevertWeek; test = test.addDays( 1 ) ) if ( test.dayOfWeek() == StartDay ) break; } return test.day(); } bool TimeZoneRule::isDSTActive( const KStarsDateTime &date ) { // The empty rule always returns false if ( isEmptyRule() ) return false; // First, check whether the month is outside the DST interval. Note that // the interval check is different if StartMonth > RevertMonth (indicating that // the DST interval includes the end of the year). int month = date.date().month(); if ( StartMonth < RevertMonth ) { if ( month < StartMonth || month > RevertMonth ) return false; } else { if ( month < StartMonth && month > RevertMonth ) return false; } // OK, if the month is equal to StartMonth or Revert Month, we have more // detailed checking to do... int day = date.date().day(); if ( month == StartMonth ) { int sday = findStartDay( date ); if ( day < sday ) return false; if ( day==sday && date.time() < StartTime ) return false; } else if ( month == RevertMonth ) { int rday = findRevertDay( date ); if ( day > rday ) return false; if ( day==rday && date.time() > RevertTime ) return false; } // passed all tests, so we must be in DST. return true; } void TimeZoneRule::nextDSTChange_LTime( const KStarsDateTime &date ) { KStarsDateTime result; // return a very remote date if the rule is the empty rule. if ( isEmptyRule() ) result = KStarsDateTime( INVALID_DAY ); else if ( deltaTZ() ) { // Next change is reverting back to standard time. //y is the year for the next DST Revert date. It's either the current year, or //the next year if the current month is already past RevertMonth int y = date.date().year(); if ( RevertMonth < date.date().month() ) ++y; result = KStarsDateTime( ExtDate( y, RevertMonth, 1 ), RevertTime ); result = KStarsDateTime( ExtDate( y, RevertMonth, findRevertDay( result ) ), RevertTime ); } else { // Next change is starting DST. //y is the year for the next DST Start date. It's either the current year, or //the next year if the current month is already past StartMonth int y = date.date().year(); if ( StartMonth < date.date().month() ) ++y; result = KStarsDateTime( ExtDate( y, StartMonth, 1 ), StartTime ); result = KStarsDateTime( ExtDate( y, StartMonth, findStartDay( result ) ), StartTime ); } kdDebug() << i18n( "Next Daylight Savings Time change (Local Time): " ) << result.toString() << endl; next_change_ltime = result; } void TimeZoneRule::previousDSTChange_LTime( const KStarsDateTime &date ) { KStarsDateTime result; // return a very remote date if the rule is the empty rule if ( isEmptyRule() ) next_change_ltime = KStarsDateTime( INVALID_DAY ); if ( deltaTZ() ) { // Last change was starting DST. //y is the year for the previous DST Start date. It's either the current year, or //the previous year if the current month is earlier than StartMonth int y = date.date().year(); if ( StartMonth > date.date().month() ) --y; result = KStarsDateTime( ExtDate( y, StartMonth, 1 ), StartTime ); result = KStarsDateTime( ExtDate( y, StartMonth, findStartDay( result ) ), StartTime ); } else if ( StartMonth ) { //Last change was reverting to standard time. //y is the year for the previous DST Start date. It's either the current year, or //the previous year if the current month is earlier than StartMonth int y = date.date().year(); if ( RevertMonth > date.date().month() ) --y; result = KStarsDateTime( ExtDate( y, RevertMonth, 1 ), RevertTime ); result = KStarsDateTime( ExtDate( y, RevertMonth, findRevertDay( result ) ), RevertTime ); } kdDebug() << i18n( "Previous Daylight Savings Time change (Local Time): " ) << result.toString() << endl; next_change_ltime = result; } /**Convert current local DST change time in universal time */ void TimeZoneRule::nextDSTChange( const KStarsDateTime &local_date, const double TZoffset ) { // just decrement timezone offset and hour offset KStarsDateTime result = local_date.addSecs( int( (TZoffset + deltaTZ()) * -3600) ); kdDebug() << i18n( "Next Daylight Savings Time change (UTC): " ) << result.toString() << endl; next_change_utc = result; } /**Convert current local DST change time in universal time */ void TimeZoneRule::previousDSTChange( const KStarsDateTime &local_date, const double TZoffset ) { // just decrement timezone offset KStarsDateTime result = local_date.addSecs( int( TZoffset * -3600) ); // if prev DST change is a revert change, so the revert time is in daylight saving time if ( result.date().month() == RevertMonth ) result = result.addSecs( int(HourOffset * -3600) ); kdDebug() << i18n( "Previous Daylight Savings Time change (UTC): " ) << result.toString() << endl; next_change_utc = result; } void TimeZoneRule::reset_with_ltime( KStarsDateTime <ime, const double TZoffset, const bool time_runs_forward, const bool automaticDSTchange ) { /**There are some problems using local time for getting next daylight saving change time. 1. The local time is the start time of DST change. So the local time doesn't exists and must corrected. 2. The local time is the revert time. So the local time exists twice. 3. Neither start time nor revert time. There is no problem. Problem #1 is more complicated and we have to change the local time by reference. Problem #2 we just have to reset status of DST. automaticDSTchange should only set to true if DST status changed due to running automatically over a DST change time. If local time will changed manually the automaticDSTchange should always set to false, to hold current DST status if possible (just on start and revert time possible). */ //don't need to do anything for empty rule if ( isEmptyRule() ) return; // check if DST is active before resetting with new time bool wasDSTactive(false); if ( deltaTZ() != 0.0 ) { wasDSTactive = true; } // check if current time is start time, this means if a DST change happend in last hour(s) bool active_with_houroffset = isDSTActive(ltime.addSecs( int(HourOffset * -3600) ) ); bool active_normal = isDSTActive( ltime ); // store a valid local time KStarsDateTime ValidLTime = ltime; if ( active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth ) { // current time is the start time kdDebug() << "Current time = Starttime: invalid local time due to daylight saving time" << endl; // set a correct local time because the current time doesn't exists // if automatic DST change happend, new DST setting is the opposite of current setting if ( automaticDSTchange ) { // revert DST status setDST( !wasDSTactive ); // new setting DST is inactive, so subtract hour offset to new time if ( wasDSTactive ) { // DST inactive ValidLTime = ltime.addSecs( int( HourOffset * - 3600) ); } else { // DST active // add hour offset to new time ValidLTime = ltime.addSecs( int( HourOffset * 3600) ); } } else { // if ( automaticDSTchange ) // no automatic DST change happend, so stay in current DST mode setDST( wasDSTactive ); if ( wasDSTactive ) { // DST active // add hour offset to current time, because time doesn't exists ValidLTime = ltime.addSecs( int( HourOffset * 3600) ); } else { // DST inactive // subtrace hour offset to current time, because time doesn't exists ValidLTime = ltime.addSecs( int( HourOffset * -3600) ); } } // else { // if ( automaticDSTchange ) } else { // if ( active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth ) // If current time was not start time, so check if current time is revert time // this means if a DST change happend in next hour(s) active_with_houroffset = isDSTActive(ltime.addSecs( int(HourOffset * 3600) ) ); if ( active_with_houroffset != active_normal && RevertMonth == ValidLTime.date().month() ) { // current time is the revert time kdDebug() << "Current time = Reverttime" << endl; // we don't kneed to change the local time, because local time always exists, but // some times exists twice, so we have to reset DST status if ( automaticDSTchange ) { // revert DST status setDST( !wasDSTactive ); } else { // no automatic DST change so stay in current DST mode setDST( wasDSTactive ); } } else { //Current time was neither starttime nor reverttime, so use normal calculated DST status setDST( active_normal ); } } // if ( active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth ) // kdDebug() << "Using Valid Local Time = " << ValidLTime.toString() << endl; if (time_runs_forward) { // get next DST change time in local time nextDSTChange_LTime( ValidLTime ); nextDSTChange( next_change_ltime, TZoffset ); } else { // get previous DST change time in local time previousDSTChange_LTime( ValidLTime ); previousDSTChange( next_change_ltime, TZoffset ); } ltime = ValidLTime; } bool TimeZoneRule::equals( TimeZoneRule *r ) { if ( StartDay == r->StartDay && RevertDay == r->RevertDay && StartWeek == r->StartWeek && RevertWeek == r->RevertWeek && StartMonth == r->StartMonth && RevertMonth == r->RevertMonth && StartTime == r->StartTime && RevertTime == r->RevertTime && isEmptyRule() == r->isEmptyRule() ) return true; else return false; }