You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdeedu/kstars/kstars/timezonerule.cpp

431 lines
16 KiB

/***************************************************************************
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 <kdebug.h>
#include <tdelocale.h>
#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 &ltime, 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;
}