moved kdeaccessibility kdeaddons kdeadmin kdeartwork kdebindings kdeedu kdegames...
[kdeedu.git] / kstars / kstars / timezonerule.cpp
blob20ac8c45bc0f72d3a752f3f094bbf4a2565fccff
1 /***************************************************************************
2 timezonerule.cpp - description
3 -------------------
4 begin : Tue Apr 2 2002
5 copyright : (C) 2002 by Jason Harris
6 email : kstars@30doradus.org
7 ***************************************************************************/
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
18 #include <kdebug.h>
19 #include <klocale.h>
21 #include "timezonerule.h"
22 #include "kstarsdatetime.h"
24 TimeZoneRule::TimeZoneRule() {
25 //Build the empty TimeZoneRule.
26 StartMonth = 0;
27 RevertMonth = 0;
28 StartDay = 0;
29 RevertDay = 0;
30 StartWeek = -1;
31 RevertWeek = -1;
32 StartTime = QTime();
33 RevertTime = QTime();
34 HourOffset = 0.0;
35 dTZ = 0.0;
38 TimeZoneRule::TimeZoneRule( const QString &smonth, const QString &sday, const QTime &stime,
39 const QString &rmonth, const QString &rday, const QTime &rtime, const double &dh ) {
40 dTZ = 0.0;
41 if ( smonth != "0" ) {
42 StartMonth = initMonth( smonth );
43 RevertMonth = initMonth( rmonth );
45 if ( StartMonth && RevertMonth && initDay( sday, StartDay, StartWeek ) &&
46 initDay( rday, RevertDay, RevertWeek ) && stime.isValid() && rtime.isValid() ) {
47 StartTime = stime;
48 RevertTime = rtime;
49 HourOffset = dh;
50 } else {
51 kdWarning() << i18n( "Error parsing TimeZoneRule, setting to empty rule." ) << endl;
52 StartMonth = 0;
53 RevertMonth = 0;
54 StartDay = 0;
55 RevertDay = 0;
56 StartWeek = -1;
57 RevertWeek = -1;
58 StartTime = QTime();
59 RevertTime = QTime();
60 HourOffset = 0.0;
62 } else { //Empty rule
63 StartMonth = 0;
64 RevertMonth = 0;
65 StartDay = 0;
66 RevertDay = 0;
67 StartWeek = -1;
68 RevertWeek = -1;
69 StartTime = QTime();
70 RevertTime = QTime();
71 HourOffset = 0.0;
75 TimeZoneRule::~TimeZoneRule() {
78 void TimeZoneRule::setDST( bool activate ) {
79 if ( activate ) {
80 kdDebug() << i18n( "Daylight Saving Time active" ) << endl;
81 dTZ = HourOffset;
82 } else {
83 kdDebug() << i18n( "Daylight Saving Time inactive" ) << endl;
84 dTZ = 0.0;
88 int TimeZoneRule::initMonth( const QString &mn ) {
89 //Check whether the argument is a three-letter English month code.
90 QString ml = mn.lower();
91 if ( ml == "jan" ) return 1;
92 else if ( ml == "feb" ) return 2;
93 else if ( ml == "mar" ) return 3;
94 else if ( ml == "apr" ) return 4;
95 else if ( ml == "may" ) return 5;
96 else if ( ml == "jun" ) return 6;
97 else if ( ml == "jul" ) return 7;
98 else if ( ml == "aug" ) return 8;
99 else if ( ml == "sep" ) return 9;
100 else if ( ml == "oct" ) return 10;
101 else if ( ml == "nov" ) return 11;
102 else if ( ml == "dec" ) return 12;
104 kdWarning() << i18n( "Could not parse " ) << mn << i18n( " as a valid month code." ) << endl;
105 return false;
108 bool TimeZoneRule::initDay( const QString &dy, int &Day, int &Week ) {
109 //Three possible ways to express a day.
110 //1. simple integer; the calendar date...set Week=0 to indicate that Date is not the day of the week
111 bool ok;
112 int day = dy.toInt( &ok );
113 if ( ok ) {
114 Day = day;
115 Week = 0;
116 return true;
119 QString dl = dy.lower();
120 //2. 3-letter day of week string, indicating the last of that day of the month
121 // ...set Week to 5 to indicate the last weekday of the month
122 if ( dl == "mon" ) { Day = 1; Week = 5; return true; }
123 else if ( dl == "tue" ) { Day = 2; Week = 5; return true; }
124 else if ( dl == "wed" ) { Day = 3; Week = 5; return true; }
125 else if ( dl == "thu" ) { Day = 4; Week = 5; return true; }
126 else if ( dl == "fri" ) { Day = 5; Week = 5; return true; }
127 else if ( dl == "sat" ) { Day = 6; Week = 5; return true; }
128 else if ( dl == "sun" ) { Day = 7; Week = 5; return true; }
130 //3. 1,2 or 3 followed by 3-letter day of week string; this indicates
131 // the (1st/2nd/3rd) weekday of the month.
132 int wn = dl.left(1).toInt();
133 if ( wn >0 && wn <4 ) {
134 QString dm = dl.mid( 1, dl.length() ).lower();
135 if ( dm == "mon" ) { Day = 1; Week = wn; return true; }
136 else if ( dm == "tue" ) { Day = 2; Week = wn; return true; }
137 else if ( dm == "wed" ) { Day = 3; Week = wn; return true; }
138 else if ( dm == "thu" ) { Day = 4; Week = wn; return true; }
139 else if ( dm == "fri" ) { Day = 5; Week = wn; return true; }
140 else if ( dm == "sat" ) { Day = 6; Week = wn; return true; }
141 else if ( dm == "sun" ) { Day = 7; Week = wn; return true; }
144 kdWarning() << i18n( "Could not parse " ) << dy << i18n( " as a valid day code." ) << endl;
145 return false;
148 int TimeZoneRule::findStartDay( const KStarsDateTime &d ) {
149 // Determine the calendar date of StartDay for the month and year of the given date.
150 ExtDate test;
152 // TimeZoneRule is empty, return -1
153 if ( isEmptyRule() ) return -1;
155 // If StartWeek=0, just return the integer.
156 if ( StartWeek==0 ) return StartDay;
158 // Since StartWeek was not zero, StartDay is the day of the week, not the calendar date
159 else if ( StartWeek==5 ) { // count back from end of month until StartDay is found.
160 for ( test = ExtDate( d.date().year(), d.date().month(), d.date().daysInMonth() );
161 test.day() > 21; test = test.addDays( -1 ) )
162 if ( test.dayOfWeek() == StartDay ) break;
163 } else { // Count forward from day 1, 8 or 15 (depending on StartWeek) until correct day of week is found
164 for ( test = ExtDate( d.date().year(), d.date().month(), (StartWeek-1)*7 + 1 );
165 test.day() < 7*StartWeek; test = test.addDays( 1 ) )
166 if ( test.dayOfWeek() == StartDay ) break;
168 return test.day();
171 int TimeZoneRule::findRevertDay( const KStarsDateTime &d ) {
172 // Determine the calendar date of RevertDay for the month and year of the given date.
173 ExtDate test;
175 // TimeZoneRule is empty, return -1
176 if ( isEmptyRule() ) return -1;
178 // If RevertWeek=0, just return the integer.
179 if ( RevertWeek==0 ) return RevertDay;
181 // Since RevertWeek was not zero, RevertDay is the day of the week, not the calendar date
182 else if ( RevertWeek==5 ) { //count back from end of month until RevertDay is found.
183 for ( test = ExtDate( d.date().year(), d.date().month(), d.date().daysInMonth() );
184 test.day() > 21; test = test.addDays( -1 ) )
185 if ( test.dayOfWeek() == RevertDay ) break;
186 } else { //Count forward from day 1, 8 or 15 (depending on RevertWeek) until correct day of week is found
187 for ( test = ExtDate( d.date().year(), d.date().month(), (RevertWeek-1)*7 + 1 );
188 test.day() < 7*RevertWeek; test = test.addDays( 1 ) )
189 if ( test.dayOfWeek() == StartDay ) break;
191 return test.day();
194 bool TimeZoneRule::isDSTActive( const KStarsDateTime &date ) {
195 // The empty rule always returns false
196 if ( isEmptyRule() ) return false;
198 // First, check whether the month is outside the DST interval. Note that
199 // the interval check is different if StartMonth > RevertMonth (indicating that
200 // the DST interval includes the end of the year).
201 int month = date.date().month();
203 if ( StartMonth < RevertMonth ) {
204 if ( month < StartMonth || month > RevertMonth ) return false;
205 } else {
206 if ( month < StartMonth && month > RevertMonth ) return false;
209 // OK, if the month is equal to StartMonth or Revert Month, we have more
210 // detailed checking to do...
211 int day = date.date().day();
213 if ( month == StartMonth ) {
214 int sday = findStartDay( date );
215 if ( day < sday ) return false;
216 if ( day==sday && date.time() < StartTime ) return false;
217 } else if ( month == RevertMonth ) {
218 int rday = findRevertDay( date );
219 if ( day > rday ) return false;
220 if ( day==rday && date.time() > RevertTime ) return false;
223 // passed all tests, so we must be in DST.
224 return true;
227 void TimeZoneRule::nextDSTChange_LTime( const KStarsDateTime &date ) {
228 KStarsDateTime result;
230 // return a very remote date if the rule is the empty rule.
231 if ( isEmptyRule() ) result = KStarsDateTime( INVALID_DAY );
233 else if ( deltaTZ() ) {
234 // Next change is reverting back to standard time.
236 //y is the year for the next DST Revert date. It's either the current year, or
237 //the next year if the current month is already past RevertMonth
238 int y = date.date().year();
239 if ( RevertMonth < date.date().month() ) ++y;
241 result = KStarsDateTime( ExtDate( y, RevertMonth, 1 ), RevertTime );
242 result = KStarsDateTime( ExtDate( y, RevertMonth, findRevertDay( result ) ), RevertTime );
244 } else {
245 // Next change is starting DST.
247 //y is the year for the next DST Start date. It's either the current year, or
248 //the next year if the current month is already past StartMonth
249 int y = date.date().year();
250 if ( StartMonth < date.date().month() ) ++y;
252 result = KStarsDateTime( ExtDate( y, StartMonth, 1 ), StartTime );
253 result = KStarsDateTime( ExtDate( y, StartMonth, findStartDay( result ) ), StartTime );
256 kdDebug() << i18n( "Next Daylight Savings Time change (Local Time): " ) << result.toString() << endl;
257 next_change_ltime = result;
261 void TimeZoneRule::previousDSTChange_LTime( const KStarsDateTime &date ) {
262 KStarsDateTime result;
264 // return a very remote date if the rule is the empty rule
265 if ( isEmptyRule() ) next_change_ltime = KStarsDateTime( INVALID_DAY );
267 if ( deltaTZ() ) {
268 // Last change was starting DST.
270 //y is the year for the previous DST Start date. It's either the current year, or
271 //the previous year if the current month is earlier than StartMonth
272 int y = date.date().year();
273 if ( StartMonth > date.date().month() ) --y;
275 result = KStarsDateTime( ExtDate( y, StartMonth, 1 ), StartTime );
276 result = KStarsDateTime( ExtDate( y, StartMonth, findStartDay( result ) ), StartTime );
278 } else if ( StartMonth ) {
279 //Last change was reverting to standard time.
281 //y is the year for the previous DST Start date. It's either the current year, or
282 //the previous year if the current month is earlier than StartMonth
283 int y = date.date().year();
284 if ( RevertMonth > date.date().month() ) --y;
286 result = KStarsDateTime( ExtDate( y, RevertMonth, 1 ), RevertTime );
287 result = KStarsDateTime( ExtDate( y, RevertMonth, findRevertDay( result ) ), RevertTime );
290 kdDebug() << i18n( "Previous Daylight Savings Time change (Local Time): " ) << result.toString() << endl;
291 next_change_ltime = result;
294 /**Convert current local DST change time in universal time */
295 void TimeZoneRule::nextDSTChange( const KStarsDateTime &local_date, const double TZoffset ) {
296 // just decrement timezone offset and hour offset
297 KStarsDateTime result = local_date.addSecs( int( (TZoffset + deltaTZ()) * -3600) );
299 kdDebug() << i18n( "Next Daylight Savings Time change (UTC): " ) << result.toString() << endl;
300 next_change_utc = result;
303 /**Convert current local DST change time in universal time */
304 void TimeZoneRule::previousDSTChange( const KStarsDateTime &local_date, const double TZoffset ) {
305 // just decrement timezone offset
306 KStarsDateTime result = local_date.addSecs( int( TZoffset * -3600) );
308 // if prev DST change is a revert change, so the revert time is in daylight saving time
309 if ( result.date().month() == RevertMonth )
310 result = result.addSecs( int(HourOffset * -3600) );
312 kdDebug() << i18n( "Previous Daylight Savings Time change (UTC): " ) << result.toString() << endl;
313 next_change_utc = result;
316 void TimeZoneRule::reset_with_ltime( KStarsDateTime &ltime, const double TZoffset, const bool time_runs_forward,
317 const bool automaticDSTchange ) {
319 /**There are some problems using local time for getting next daylight saving change time.
320 1. The local time is the start time of DST change. So the local time doesn't exists and must
321 corrected.
322 2. The local time is the revert time. So the local time exists twice.
323 3. Neither start time nor revert time. There is no problem.
325 Problem #1 is more complicated and we have to change the local time by reference.
326 Problem #2 we just have to reset status of DST.
328 automaticDSTchange should only set to true if DST status changed due to running automatically over
329 a DST change time. If local time will changed manually the automaticDSTchange should always set to
330 false, to hold current DST status if possible (just on start and revert time possible).
333 //don't need to do anything for empty rule
334 if ( isEmptyRule() ) return;
336 // check if DST is active before resetting with new time
337 bool wasDSTactive(false);
339 if ( deltaTZ() != 0.0 ) {
340 wasDSTactive = true;
343 // check if current time is start time, this means if a DST change happend in last hour(s)
344 bool active_with_houroffset = isDSTActive(ltime.addSecs( int(HourOffset * -3600) ) );
345 bool active_normal = isDSTActive( ltime );
347 // store a valid local time
348 KStarsDateTime ValidLTime = ltime;
350 if ( active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth ) {
351 // current time is the start time
352 kdDebug() << "Current time = Starttime: invalid local time due to daylight saving time" << endl;
354 // set a correct local time because the current time doesn't exists
355 // if automatic DST change happend, new DST setting is the opposite of current setting
356 if ( automaticDSTchange ) {
357 // revert DST status
358 setDST( !wasDSTactive );
359 // new setting DST is inactive, so subtract hour offset to new time
360 if ( wasDSTactive ) {
361 // DST inactive
362 ValidLTime = ltime.addSecs( int( HourOffset * - 3600) );
363 } else {
364 // DST active
365 // add hour offset to new time
366 ValidLTime = ltime.addSecs( int( HourOffset * 3600) );
368 } else { // if ( automaticDSTchange )
369 // no automatic DST change happend, so stay in current DST mode
370 setDST( wasDSTactive );
371 if ( wasDSTactive ) {
372 // DST active
373 // add hour offset to current time, because time doesn't exists
374 ValidLTime = ltime.addSecs( int( HourOffset * 3600) );
375 } else {
376 // DST inactive
377 // subtrace hour offset to current time, because time doesn't exists
378 ValidLTime = ltime.addSecs( int( HourOffset * -3600) );
380 } // else { // if ( automaticDSTchange )
382 } else { // if ( active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth )
383 // If current time was not start time, so check if current time is revert time
384 // this means if a DST change happend in next hour(s)
385 active_with_houroffset = isDSTActive(ltime.addSecs( int(HourOffset * 3600) ) );
386 if ( active_with_houroffset != active_normal && RevertMonth == ValidLTime.date().month() ) {
387 // current time is the revert time
388 kdDebug() << "Current time = Reverttime" << endl;
390 // we don't kneed to change the local time, because local time always exists, but
391 // some times exists twice, so we have to reset DST status
392 if ( automaticDSTchange ) {
393 // revert DST status
394 setDST( !wasDSTactive );
395 } else {
396 // no automatic DST change so stay in current DST mode
397 setDST( wasDSTactive );
400 } else {
401 //Current time was neither starttime nor reverttime, so use normal calculated DST status
402 setDST( active_normal );
404 } // if ( active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth )
406 // kdDebug() << "Using Valid Local Time = " << ValidLTime.toString() << endl;
408 if (time_runs_forward) {
409 // get next DST change time in local time
410 nextDSTChange_LTime( ValidLTime );
411 nextDSTChange( next_change_ltime, TZoffset );
412 } else {
413 // get previous DST change time in local time
414 previousDSTChange_LTime( ValidLTime );
415 previousDSTChange( next_change_ltime, TZoffset );
417 ltime = ValidLTime;
421 bool TimeZoneRule::equals( TimeZoneRule *r ) {
422 if ( StartDay == r->StartDay && RevertDay == r->RevertDay &&
423 StartWeek == r->StartWeek && RevertWeek == r->RevertWeek &&
424 StartMonth == r->StartMonth && RevertMonth == r->RevertMonth &&
425 StartTime == r->StartTime && RevertTime == r->RevertTime &&
426 isEmptyRule() == r->isEmptyRule() )
427 return true;
428 else
429 return false;