File indexing completed on 2024-04-14 14:11:32
0001 /* 0002 SPDX-FileCopyrightText: 2002 Jason Harris <kstars@30doradus.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "timezonerule.h" 0008 #include "kstars_debug.h" 0009 0010 #include <KLocalizedString> 0011 0012 #include <QString> 0013 0014 TimeZoneRule::TimeZoneRule() 0015 { 0016 setEmpty(); 0017 } 0018 0019 TimeZoneRule::TimeZoneRule(const QString &smonth, const QString &sday, const QTime &stime, const QString &rmonth, 0020 const QString &rday, const QTime &rtime, const double &dh) 0021 { 0022 dTZ = 0.0; 0023 if (smonth != "0") 0024 { 0025 StartMonth = initMonth(smonth); 0026 RevertMonth = initMonth(rmonth); 0027 0028 if (StartMonth && RevertMonth && initDay(sday, StartDay, StartWeek) && initDay(rday, RevertDay, RevertWeek) && 0029 stime.isValid() && rtime.isValid()) 0030 { 0031 StartTime = stime; 0032 RevertTime = rtime; 0033 HourOffset = dh; 0034 } 0035 else 0036 { 0037 qCWarning(KSTARS) << i18n("Error parsing TimeZoneRule, setting to empty rule."); 0038 setEmpty(); 0039 } 0040 } 0041 else //Empty rule 0042 { 0043 setEmpty(); 0044 } 0045 } 0046 0047 void TimeZoneRule::setEmpty() 0048 { 0049 StartMonth = 0; 0050 RevertMonth = 0; 0051 StartDay = 0; 0052 RevertDay = 0; 0053 StartWeek = -1; 0054 RevertWeek = -1; 0055 StartTime = QTime(); 0056 RevertTime = QTime(); 0057 HourOffset = 0.0; 0058 dTZ = 0.0; 0059 } 0060 0061 void TimeZoneRule::setDST(bool activate) 0062 { 0063 if (activate) 0064 { 0065 qCDebug(KSTARS) << "Daylight Saving Time active"; 0066 dTZ = HourOffset; 0067 } 0068 else 0069 { 0070 qCDebug(KSTARS) << "Daylight Saving Time inactive"; 0071 dTZ = 0.0; 0072 } 0073 } 0074 0075 int TimeZoneRule::initMonth(const QString &mn) 0076 { 0077 //Check whether the argument is a three-letter English month code. 0078 QString ml = mn.toLower(); 0079 if (ml == "jan") 0080 return 1; 0081 else if (ml == "feb") 0082 return 2; 0083 else if (ml == "mar") 0084 return 3; 0085 else if (ml == "apr") 0086 return 4; 0087 else if (ml == "may") 0088 return 5; 0089 else if (ml == "jun") 0090 return 6; 0091 else if (ml == "jul") 0092 return 7; 0093 else if (ml == "aug") 0094 return 8; 0095 else if (ml == "sep") 0096 return 9; 0097 else if (ml == "oct") 0098 return 10; 0099 else if (ml == "nov") 0100 return 11; 0101 else if (ml == "dec") 0102 return 12; 0103 0104 qCWarning(KSTARS) << i18n("Could not parse %1 as a valid month code.", mn); 0105 return 0; 0106 } 0107 0108 bool TimeZoneRule::initDay(const QString &dy, int &Day, int &Week) 0109 { 0110 //Three possible ways to express a day. 0111 //1. simple integer; the calendar date...set Week=0 to indicate that Date is not the day of the week 0112 bool ok; 0113 int day = dy.toInt(&ok); 0114 if (ok) 0115 { 0116 Day = day; 0117 Week = 0; 0118 return true; 0119 } 0120 0121 QString dl = dy.toLower(); 0122 //2. 3-letter day of week string, indicating the last of that day of the month 0123 // ...set Week to 5 to indicate the last weekday of the month 0124 if (dl == "mon") 0125 { 0126 Day = 1; 0127 Week = 5; 0128 return true; 0129 } 0130 else if (dl == "tue") 0131 { 0132 Day = 2; 0133 Week = 5; 0134 return true; 0135 } 0136 else if (dl == "wed") 0137 { 0138 Day = 3; 0139 Week = 5; 0140 return true; 0141 } 0142 else if (dl == "thu") 0143 { 0144 Day = 4; 0145 Week = 5; 0146 return true; 0147 } 0148 else if (dl == "fri") 0149 { 0150 Day = 5; 0151 Week = 5; 0152 return true; 0153 } 0154 else if (dl == "sat") 0155 { 0156 Day = 6; 0157 Week = 5; 0158 return true; 0159 } 0160 else if (dl == "sun") 0161 { 0162 Day = 7; 0163 Week = 5; 0164 return true; 0165 } 0166 0167 //3. 1,2 or 3 followed by 3-letter day of week string; this indicates 0168 // the (1st/2nd/3rd) weekday of the month. 0169 int wn = dl.leftRef(1).toInt(); 0170 if (wn > 0 && wn < 4) 0171 { 0172 QString dm = dl.mid(1, dl.length()).toLower(); 0173 if (dm == "mon") 0174 { 0175 Day = 1; 0176 Week = wn; 0177 return true; 0178 } 0179 else if (dm == "tue") 0180 { 0181 Day = 2; 0182 Week = wn; 0183 return true; 0184 } 0185 else if (dm == "wed") 0186 { 0187 Day = 3; 0188 Week = wn; 0189 return true; 0190 } 0191 else if (dm == "thu") 0192 { 0193 Day = 4; 0194 Week = wn; 0195 return true; 0196 } 0197 else if (dm == "fri") 0198 { 0199 Day = 5; 0200 Week = wn; 0201 return true; 0202 } 0203 else if (dm == "sat") 0204 { 0205 Day = 6; 0206 Week = wn; 0207 return true; 0208 } 0209 else if (dm == "sun") 0210 { 0211 Day = 7; 0212 Week = wn; 0213 return true; 0214 } 0215 } 0216 0217 qCWarning(KSTARS) << i18n("Could not parse %1 as a valid day code.", dy); 0218 return false; 0219 } 0220 0221 int TimeZoneRule::findStartDay(const KStarsDateTime &d) 0222 { 0223 // Determine the calendar date of StartDay for the month and year of the given date. 0224 QDate test; 0225 0226 // TimeZoneRule is empty, return -1 0227 if (isEmptyRule()) 0228 return -1; 0229 0230 // If StartWeek=0, just return the integer. 0231 if (StartWeek == 0) 0232 return StartDay; 0233 0234 // Since StartWeek was not zero, StartDay is the day of the week, not the calendar date 0235 else if (StartWeek == 5) // count back from end of month until StartDay is found. 0236 { 0237 for (test = QDate(d.date().year(), d.date().month(), d.date().daysInMonth()); test.day() > 21; 0238 test = test.addDays(-1)) 0239 if (test.dayOfWeek() == StartDay) 0240 break; 0241 } 0242 else // Count forward from day 1, 8 or 15 (depending on StartWeek) until correct day of week is found 0243 { 0244 for (test = QDate(d.date().year(), d.date().month(), (StartWeek - 1) * 7 + 1); test.day() < 7 * StartWeek; 0245 test = test.addDays(1)) 0246 if (test.dayOfWeek() == StartDay) 0247 break; 0248 } 0249 return test.day(); 0250 } 0251 0252 int TimeZoneRule::findRevertDay(const KStarsDateTime &d) 0253 { 0254 // Determine the calendar date of RevertDay for the month and year of the given date. 0255 QDate test; 0256 0257 // TimeZoneRule is empty, return -1 0258 if (isEmptyRule()) 0259 return -1; 0260 0261 // If RevertWeek=0, just return the integer. 0262 if (RevertWeek == 0) 0263 return RevertDay; 0264 0265 // Since RevertWeek was not zero, RevertDay is the day of the week, not the calendar date 0266 else if (RevertWeek == 5) //count back from end of month until RevertDay is found. 0267 { 0268 for (test = QDate(d.date().year(), d.date().month(), d.date().daysInMonth()); test.day() > 21; 0269 test = test.addDays(-1)) 0270 if (test.dayOfWeek() == RevertDay) 0271 break; 0272 } 0273 else //Count forward from day 1, 8 or 15 (depending on RevertWeek) until correct day of week is found 0274 { 0275 for (test = QDate(d.date().year(), d.date().month(), (RevertWeek - 1) * 7 + 1); test.day() < 7 * RevertWeek; 0276 test = test.addDays(1)) 0277 if (test.dayOfWeek() == StartDay) 0278 break; 0279 } 0280 return test.day(); 0281 } 0282 0283 bool TimeZoneRule::isDSTActive(const KStarsDateTime &date) 0284 { 0285 // The empty rule always returns false 0286 if (isEmptyRule()) 0287 return false; 0288 0289 // First, check whether the month is outside the DST interval. Note that 0290 // the interval check is different if StartMonth > RevertMonth (indicating that 0291 // the DST interval includes the end of the year). 0292 int month = date.date().month(); 0293 0294 if (StartMonth < RevertMonth) 0295 { 0296 if (month < StartMonth || month > RevertMonth) 0297 return false; 0298 } 0299 else 0300 { 0301 if (month < StartMonth && month > RevertMonth) 0302 return false; 0303 } 0304 0305 // OK, if the month is equal to StartMonth or Revert Month, we have more 0306 // detailed checking to do... 0307 int day = date.date().day(); 0308 0309 if (month == StartMonth) 0310 { 0311 int sday = findStartDay(date); 0312 if (day < sday) 0313 return false; 0314 if (day == sday && date.time() < StartTime) 0315 return false; 0316 } 0317 else if (month == RevertMonth) 0318 { 0319 int rday = findRevertDay(date); 0320 if (day > rday) 0321 return false; 0322 if (day == rday && date.time() > RevertTime) 0323 return false; 0324 } 0325 0326 // passed all tests, so we must be in DST. 0327 return true; 0328 } 0329 0330 void TimeZoneRule::nextDSTChange_LTime(const KStarsDateTime &date) 0331 { 0332 KStarsDateTime result; 0333 0334 // return an invalid date if the rule is the empty rule. 0335 if (isEmptyRule()) 0336 result = KStarsDateTime(QDateTime()); 0337 0338 else if (deltaTZ()) 0339 { 0340 // Next change is reverting back to standard time. 0341 0342 //y is the year for the next DST Revert date. It's either the current year, or 0343 //the next year if the current month is already past RevertMonth 0344 int y = date.date().year(); 0345 if (RevertMonth < date.date().month()) 0346 ++y; 0347 0348 result = KStarsDateTime(QDate(y, RevertMonth, 1), RevertTime); 0349 result = KStarsDateTime(QDate(y, RevertMonth, findRevertDay(result)), RevertTime); 0350 } 0351 else 0352 { 0353 // Next change is starting DST. 0354 0355 //y is the year for the next DST Start date. It's either the current year, or 0356 //the next year if the current month is already past StartMonth 0357 int y = date.date().year(); 0358 if (StartMonth < date.date().month()) 0359 ++y; 0360 0361 result = KStarsDateTime(QDate(y, StartMonth, 1), StartTime); 0362 result = KStarsDateTime(QDate(y, StartMonth, findStartDay(result)), StartTime); 0363 } 0364 0365 qCDebug(KSTARS) << "Next Daylight Savings Time change (Local Time): " << result.toString(); 0366 next_change_ltime = result; 0367 } 0368 0369 void TimeZoneRule::previousDSTChange_LTime(const KStarsDateTime &date) 0370 { 0371 KStarsDateTime result; 0372 0373 // return an invalid date if the rule is the empty rule 0374 if (isEmptyRule()) 0375 next_change_ltime = KStarsDateTime(QDateTime()); 0376 0377 if (deltaTZ()) 0378 { 0379 // Last change was starting DST. 0380 0381 //y is the year for the previous DST Start date. It's either the current year, or 0382 //the previous year if the current month is earlier than StartMonth 0383 int y = date.date().year(); 0384 if (StartMonth > date.date().month()) 0385 --y; 0386 0387 result = KStarsDateTime(QDate(y, StartMonth, 1), StartTime); 0388 result = KStarsDateTime(QDate(y, StartMonth, findStartDay(result)), StartTime); 0389 } 0390 else if (StartMonth) 0391 { 0392 //Last change was reverting to standard time. 0393 0394 //y is the year for the previous DST Start date. It's either the current year, or 0395 //the previous year if the current month is earlier than StartMonth 0396 int y = date.date().year(); 0397 if (RevertMonth > date.date().month()) 0398 --y; 0399 0400 result = KStarsDateTime(QDate(y, RevertMonth, 1), RevertTime); 0401 result = KStarsDateTime(QDate(y, RevertMonth, findRevertDay(result)), RevertTime); 0402 } 0403 0404 qCDebug(KSTARS) << "Previous Daylight Savings Time change (Local Time): " << result.toString(); 0405 next_change_ltime = result; 0406 } 0407 0408 /**Convert current local DST change time in universal time */ 0409 void TimeZoneRule::nextDSTChange(const KStarsDateTime &local_date, const double TZoffset) 0410 { 0411 // just decrement timezone offset and hour offset 0412 KStarsDateTime result = local_date.addSecs(int((TZoffset + deltaTZ()) * -3600)); 0413 0414 qCDebug(KSTARS) << "Next Daylight Savings Time change (UTC): " << result.toString(); 0415 next_change_utc = result; 0416 } 0417 0418 /**Convert current local DST change time in universal time */ 0419 void TimeZoneRule::previousDSTChange(const KStarsDateTime &local_date, const double TZoffset) 0420 { 0421 // just decrement timezone offset 0422 KStarsDateTime result = local_date.addSecs(int(TZoffset * -3600)); 0423 0424 // if prev DST change is a revert change, so the revert time is in daylight saving time 0425 if (result.date().month() == RevertMonth) 0426 result = result.addSecs(int(HourOffset * -3600)); 0427 0428 qCDebug(KSTARS) << "Previous Daylight Savings Time change (UTC): " << result.toString(); 0429 next_change_utc = result; 0430 } 0431 0432 void TimeZoneRule::reset_with_ltime(KStarsDateTime <ime, const double TZoffset, const bool time_runs_forward, 0433 const bool automaticDSTchange) 0434 { 0435 /**There are some problems using local time for getting next daylight saving change time. 0436 1. The local time is the start time of DST change. So the local time doesn't exists and must 0437 corrected. 0438 2. The local time is the revert time. So the local time exists twice. 0439 3. Neither start time nor revert time. There is no problem. 0440 0441 Problem #1 is more complicated and we have to change the local time by reference. 0442 Problem #2 we just have to reset status of DST. 0443 0444 automaticDSTchange should only set to true if DST status changed due to running automatically over 0445 a DST change time. If local time will changed manually the automaticDSTchange should always set to 0446 false, to hold current DST status if possible (just on start and revert time possible). 0447 */ 0448 0449 //don't need to do anything for empty rule 0450 if (isEmptyRule()) 0451 return; 0452 0453 // check if DST is active before resetting with new time 0454 bool wasDSTactive(false); 0455 0456 if (deltaTZ() != 0.0) 0457 { 0458 wasDSTactive = true; 0459 } 0460 0461 // check if current time is start time, this means if a DST change happened in last hour(s) 0462 bool active_with_houroffset = isDSTActive(ltime.addSecs(int(HourOffset * -3600))); 0463 bool active_normal = isDSTActive(ltime); 0464 0465 // store a valid local time 0466 KStarsDateTime ValidLTime = ltime; 0467 0468 if (active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth) 0469 { 0470 // current time is the start time 0471 qCDebug(KSTARS) << "Current time = Starttime: invalid local time due to daylight saving time"; 0472 0473 // set a correct local time because the current time doesn't exists 0474 // if automatic DST change happened, new DST setting is the opposite of current setting 0475 if (automaticDSTchange) 0476 { 0477 // revert DST status 0478 setDST(!wasDSTactive); 0479 // new setting DST is inactive, so subtract hour offset to new time 0480 if (wasDSTactive) 0481 { 0482 // DST inactive 0483 ValidLTime = ltime.addSecs(int(HourOffset * -3600)); 0484 } 0485 else 0486 { 0487 // DST active 0488 // add hour offset to new time 0489 ValidLTime = ltime.addSecs(int(HourOffset * 3600)); 0490 } 0491 } 0492 else // if ( automaticDSTchange ) 0493 { 0494 // no automatic DST change happened, so stay in current DST mode 0495 setDST(wasDSTactive); 0496 if (wasDSTactive) 0497 { 0498 // DST active 0499 // add hour offset to current time, because time doesn't exists 0500 ValidLTime = ltime.addSecs(int(HourOffset * 3600)); 0501 } 0502 else 0503 { 0504 // DST inactive 0505 // subtrace hour offset to current time, because time doesn't exists 0506 ValidLTime = ltime.addSecs(int(HourOffset * -3600)); 0507 } 0508 } // else { // if ( automaticDSTchange ) 0509 } 0510 else // if ( active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth ) 0511 { 0512 // If current time was not start time, so check if current time is revert time 0513 // this means if a DST change happened in next hour(s) 0514 active_with_houroffset = isDSTActive(ltime.addSecs(int(HourOffset * 3600))); 0515 if (active_with_houroffset != active_normal && RevertMonth == ValidLTime.date().month()) 0516 { 0517 // current time is the revert time 0518 qCDebug(KSTARS) << "Current time = Reverttime"; 0519 0520 // we don't kneed to change the local time, because local time always exists, but 0521 // some times exists twice, so we have to reset DST status 0522 if (automaticDSTchange) 0523 { 0524 // revert DST status 0525 setDST(!wasDSTactive); 0526 } 0527 else 0528 { 0529 // no automatic DST change so stay in current DST mode 0530 setDST(wasDSTactive); 0531 } 0532 } 0533 else 0534 { 0535 //Current time was neither starttime nor reverttime, so use normal calculated DST status 0536 setDST(active_normal); 0537 } 0538 } // if ( active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth ) 0539 0540 // qDebug() << Q_FUNC_INFO << "Using Valid Local Time = " << ValidLTime.toString(); 0541 0542 if (time_runs_forward) 0543 { 0544 // get next DST change time in local time 0545 nextDSTChange_LTime(ValidLTime); 0546 nextDSTChange(next_change_ltime, TZoffset); 0547 } 0548 else 0549 { 0550 // get previous DST change time in local time 0551 previousDSTChange_LTime(ValidLTime); 0552 previousDSTChange(next_change_ltime, TZoffset); 0553 } 0554 ltime = ValidLTime; 0555 } 0556 0557 bool TimeZoneRule::equals(TimeZoneRule *r) 0558 { 0559 if (StartDay == r->StartDay && RevertDay == r->RevertDay && StartWeek == r->StartWeek && 0560 RevertWeek == r->RevertWeek && StartMonth == r->StartMonth && RevertMonth == r->RevertMonth && 0561 StartTime == r->StartTime && RevertTime == r->RevertTime && isEmptyRule() == r->isEmptyRule()) 0562 return true; 0563 else 0564 return false; 0565 }