Kstars

timezonerule.cpp
1/*
2 SPDX-FileCopyrightText: 2002 Jason Harris <kstars@30doradus.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "timezonerule.h"
8#include "kstars_debug.h"
9
10#include <KLocalizedString>
11
12#include <QString>
13
15{
16 setEmpty();
17}
18
19TimeZoneRule::TimeZoneRule(const QString &smonth, const QString &sday, const QTime &stime, const QString &rmonth,
20 const QString &rday, const QTime &rtime, const double &dh)
21{
22 dTZ = 0.0;
23 if (smonth != "0")
24 {
25 StartMonth = initMonth(smonth);
26 RevertMonth = initMonth(rmonth);
27
28 if (StartMonth && RevertMonth && initDay(sday, StartDay, StartWeek) && initDay(rday, RevertDay, RevertWeek) &&
29 stime.isValid() && rtime.isValid())
30 {
31 StartTime = stime;
32 RevertTime = rtime;
33 HourOffset = dh;
34 }
35 else
36 {
37 qCWarning(KSTARS) << i18n("Error parsing TimeZoneRule, setting to empty rule.");
38 setEmpty();
39 }
40 }
41 else //Empty rule
42 {
43 setEmpty();
44 }
45}
46
47void TimeZoneRule::setEmpty()
48{
49 StartMonth = 0;
50 RevertMonth = 0;
51 StartDay = 0;
52 RevertDay = 0;
53 StartWeek = -1;
54 RevertWeek = -1;
55 StartTime = QTime();
56 RevertTime = QTime();
57 HourOffset = 0.0;
58 dTZ = 0.0;
59}
60
61void TimeZoneRule::setDST(bool activate)
62{
63 if (activate)
64 {
65 qCDebug(KSTARS) << "Daylight Saving Time active";
66 dTZ = HourOffset;
67 }
68 else
69 {
70 qCDebug(KSTARS) << "Daylight Saving Time inactive";
71 dTZ = 0.0;
72 }
73}
74
75int TimeZoneRule::initMonth(const QString &mn)
76{
77 //Check whether the argument is a three-letter English month code.
78 QString ml = mn.toLower();
79 if (ml == "jan")
80 return 1;
81 else if (ml == "feb")
82 return 2;
83 else if (ml == "mar")
84 return 3;
85 else if (ml == "apr")
86 return 4;
87 else if (ml == "may")
88 return 5;
89 else if (ml == "jun")
90 return 6;
91 else if (ml == "jul")
92 return 7;
93 else if (ml == "aug")
94 return 8;
95 else if (ml == "sep")
96 return 9;
97 else if (ml == "oct")
98 return 10;
99 else if (ml == "nov")
100 return 11;
101 else if (ml == "dec")
102 return 12;
103
104 qCWarning(KSTARS) << i18n("Could not parse %1 as a valid month code.", mn);
105 return 0;
106}
107
108bool TimeZoneRule::initDay(const QString &dy, int &Day, int &Week)
109{
110 //Three possible ways to express a day.
111 //1. simple integer; the calendar date...set Week=0 to indicate that Date is not the day of the week
112 bool ok;
113 int day = dy.toInt(&ok);
114 if (ok)
115 {
116 Day = day;
117 Week = 0;
118 return true;
119 }
120
121 QString dl = dy.toLower();
122 //2. 3-letter day of week string, indicating the last of that day of the month
123 // ...set Week to 5 to indicate the last weekday of the month
124 if (dl == "mon")
125 {
126 Day = 1;
127 Week = 5;
128 return true;
129 }
130 else if (dl == "tue")
131 {
132 Day = 2;
133 Week = 5;
134 return true;
135 }
136 else if (dl == "wed")
137 {
138 Day = 3;
139 Week = 5;
140 return true;
141 }
142 else if (dl == "thu")
143 {
144 Day = 4;
145 Week = 5;
146 return true;
147 }
148 else if (dl == "fri")
149 {
150 Day = 5;
151 Week = 5;
152 return true;
153 }
154 else if (dl == "sat")
155 {
156 Day = 6;
157 Week = 5;
158 return true;
159 }
160 else if (dl == "sun")
161 {
162 Day = 7;
163 Week = 5;
164 return true;
165 }
166
167 //3. 1,2 or 3 followed by 3-letter day of week string; this indicates
168 // the (1st/2nd/3rd) weekday of the month.
169 int wn = dl.left(1).toInt();
170 if (wn > 0 && wn < 4)
171 {
172 QString dm = dl.mid(1, dl.length()).toLower();
173 if (dm == "mon")
174 {
175 Day = 1;
176 Week = wn;
177 return true;
178 }
179 else if (dm == "tue")
180 {
181 Day = 2;
182 Week = wn;
183 return true;
184 }
185 else if (dm == "wed")
186 {
187 Day = 3;
188 Week = wn;
189 return true;
190 }
191 else if (dm == "thu")
192 {
193 Day = 4;
194 Week = wn;
195 return true;
196 }
197 else if (dm == "fri")
198 {
199 Day = 5;
200 Week = wn;
201 return true;
202 }
203 else if (dm == "sat")
204 {
205 Day = 6;
206 Week = wn;
207 return true;
208 }
209 else if (dm == "sun")
210 {
211 Day = 7;
212 Week = wn;
213 return true;
214 }
215 }
216
217 qCWarning(KSTARS) << i18n("Could not parse %1 as a valid day code.", dy);
218 return false;
219}
220
221int TimeZoneRule::findStartDay(const KStarsDateTime &d)
222{
223 // Determine the calendar date of StartDay for the month and year of the given date.
224 QDate test;
225
226 // TimeZoneRule is empty, return -1
227 if (isEmptyRule())
228 return -1;
229
230 // If StartWeek=0, just return the integer.
231 if (StartWeek == 0)
232 return StartDay;
233
234 // Since StartWeek was not zero, StartDay is the day of the week, not the calendar date
235 else if (StartWeek == 5) // count back from end of month until StartDay is found.
236 {
237 for (test = QDate(d.date().year(), d.date().month(), d.date().daysInMonth()); test.day() > 21;
238 test = test.addDays(-1))
239 if (test.dayOfWeek() == StartDay)
240 break;
241 }
242 else // Count forward from day 1, 8 or 15 (depending on StartWeek) until correct day of week is found
243 {
244 for (test = QDate(d.date().year(), d.date().month(), (StartWeek - 1) * 7 + 1); test.day() < 7 * StartWeek;
245 test = test.addDays(1))
246 if (test.dayOfWeek() == StartDay)
247 break;
248 }
249 return test.day();
250}
251
252int TimeZoneRule::findRevertDay(const KStarsDateTime &d)
253{
254 // Determine the calendar date of RevertDay for the month and year of the given date.
255 QDate test;
256
257 // TimeZoneRule is empty, return -1
258 if (isEmptyRule())
259 return -1;
260
261 // If RevertWeek=0, just return the integer.
262 if (RevertWeek == 0)
263 return RevertDay;
264
265 // Since RevertWeek was not zero, RevertDay is the day of the week, not the calendar date
266 else if (RevertWeek == 5) //count back from end of month until RevertDay is found.
267 {
268 for (test = QDate(d.date().year(), d.date().month(), d.date().daysInMonth()); test.day() > 21;
269 test = test.addDays(-1))
270 if (test.dayOfWeek() == RevertDay)
271 break;
272 }
273 else //Count forward from day 1, 8 or 15 (depending on RevertWeek) until correct day of week is found
274 {
275 for (test = QDate(d.date().year(), d.date().month(), (RevertWeek - 1) * 7 + 1); test.day() < 7 * RevertWeek;
276 test = test.addDays(1))
277 if (test.dayOfWeek() == StartDay)
278 break;
279 }
280 return test.day();
281}
282
284{
285 // The empty rule always returns false
286 if (isEmptyRule())
287 return false;
288
289 // First, check whether the month is outside the DST interval. Note that
290 // the interval check is different if StartMonth > RevertMonth (indicating that
291 // the DST interval includes the end of the year).
292 int month = date.date().month();
293
294 if (StartMonth < RevertMonth)
295 {
296 if (month < StartMonth || month > RevertMonth)
297 return false;
298 }
299 else
300 {
301 if (month < StartMonth && month > RevertMonth)
302 return false;
303 }
304
305 // OK, if the month is equal to StartMonth or Revert Month, we have more
306 // detailed checking to do...
307 int day = date.date().day();
308
309 if (month == StartMonth)
310 {
311 int sday = findStartDay(date);
312 if (day < sday)
313 return false;
314 if (day == sday && date.time() < StartTime)
315 return false;
316 }
317 else if (month == RevertMonth)
318 {
319 int rday = findRevertDay(date);
320 if (day > rday)
321 return false;
322 if (day == rday && date.time() > RevertTime)
323 return false;
324 }
325
326 // passed all tests, so we must be in DST.
327 return true;
328}
329
331{
332 KStarsDateTime result;
333
334 // return an invalid date if the rule is the empty rule.
335 if (isEmptyRule())
336 result = KStarsDateTime(QDateTime());
337
338 else if (deltaTZ())
339 {
340 // Next change is reverting back to standard time.
341
342 //y is the year for the next DST Revert date. It's either the current year, or
343 //the next year if the current month is already past RevertMonth
344 int y = date.date().year();
345 if (RevertMonth < date.date().month())
346 ++y;
347
348 result = KStarsDateTime(QDate(y, RevertMonth, 1), RevertTime);
349 result = KStarsDateTime(QDate(y, RevertMonth, findRevertDay(result)), RevertTime);
350 }
351 else
352 {
353 // Next change is starting DST.
354
355 //y is the year for the next DST Start date. It's either the current year, or
356 //the next year if the current month is already past StartMonth
357 int y = date.date().year();
358 if (StartMonth < date.date().month())
359 ++y;
360
361 result = KStarsDateTime(QDate(y, StartMonth, 1), StartTime);
362 result = KStarsDateTime(QDate(y, StartMonth, findStartDay(result)), StartTime);
363 }
364
365 qCDebug(KSTARS) << "Next Daylight Savings Time change (Local Time): " << result.toString();
366 next_change_ltime = result;
367}
368
369void TimeZoneRule::previousDSTChange_LTime(const KStarsDateTime &date)
370{
371 KStarsDateTime result;
372
373 // return an invalid date if the rule is the empty rule
374 if (isEmptyRule())
375 next_change_ltime = KStarsDateTime(QDateTime());
376
377 if (deltaTZ())
378 {
379 // Last change was starting DST.
380
381 //y is the year for the previous DST Start date. It's either the current year, or
382 //the previous year if the current month is earlier than StartMonth
383 int y = date.date().year();
384 if (StartMonth > date.date().month())
385 --y;
386
387 result = KStarsDateTime(QDate(y, StartMonth, 1), StartTime);
388 result = KStarsDateTime(QDate(y, StartMonth, findStartDay(result)), StartTime);
389 }
390 else if (StartMonth)
391 {
392 //Last change was reverting to standard time.
393
394 //y is the year for the previous DST Start date. It's either the current year, or
395 //the previous year if the current month is earlier than StartMonth
396 int y = date.date().year();
397 if (RevertMonth > date.date().month())
398 --y;
399
400 result = KStarsDateTime(QDate(y, RevertMonth, 1), RevertTime);
401 result = KStarsDateTime(QDate(y, RevertMonth, findRevertDay(result)), RevertTime);
402 }
403
404 qCDebug(KSTARS) << "Previous Daylight Savings Time change (Local Time): " << result.toString();
405 next_change_ltime = result;
406}
407
408/**Convert current local DST change time in universal time */
409void TimeZoneRule::nextDSTChange(const KStarsDateTime &local_date, const double TZoffset)
410{
411 // just decrement timezone offset and hour offset
412 KStarsDateTime result = local_date.addSecs(int((TZoffset + deltaTZ()) * -3600));
413
414 qCDebug(KSTARS) << "Next Daylight Savings Time change (UTC): " << result.toString();
415 next_change_utc = result;
416}
417
418/**Convert current local DST change time in universal time */
419void TimeZoneRule::previousDSTChange(const KStarsDateTime &local_date, const double TZoffset)
420{
421 // just decrement timezone offset
422 KStarsDateTime result = local_date.addSecs(int(TZoffset * -3600));
423
424 // if prev DST change is a revert change, so the revert time is in daylight saving time
425 if (result.date().month() == RevertMonth)
426 result = result.addSecs(int(HourOffset * -3600));
427
428 qCDebug(KSTARS) << "Previous Daylight Savings Time change (UTC): " << result.toString();
429 next_change_utc = result;
430}
431
432void TimeZoneRule::reset_with_ltime(KStarsDateTime &ltime, const double TZoffset, const bool time_runs_forward,
433 const bool automaticDSTchange)
434{
435 /**There are some problems using local time for getting next daylight saving change time.
436 1. The local time is the start time of DST change. So the local time doesn't exists and must
437 corrected.
438 2. The local time is the revert time. So the local time exists twice.
439 3. Neither start time nor revert time. There is no problem.
440
441 Problem #1 is more complicated and we have to change the local time by reference.
442 Problem #2 we just have to reset status of DST.
443
444 automaticDSTchange should only set to true if DST status changed due to running automatically over
445 a DST change time. If local time will changed manually the automaticDSTchange should always set to
446 false, to hold current DST status if possible (just on start and revert time possible).
447 */
448
449 //don't need to do anything for empty rule
450 if (isEmptyRule())
451 return;
452
453 // check if DST is active before resetting with new time
454 bool wasDSTactive(false);
455
456 if (deltaTZ() != 0.0)
457 {
458 wasDSTactive = true;
459 }
460
461 // check if current time is start time, this means if a DST change happened in last hour(s)
462 bool active_with_houroffset = isDSTActive(ltime.addSecs(int(HourOffset * -3600)));
463 bool active_normal = isDSTActive(ltime);
464
465 // store a valid local time
466 KStarsDateTime ValidLTime = ltime;
467
468 if (active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth)
469 {
470 // current time is the start time
471 qCDebug(KSTARS) << "Current time = Starttime: invalid local time due to daylight saving time";
472
473 // set a correct local time because the current time doesn't exists
474 // if automatic DST change happened, new DST setting is the opposite of current setting
475 if (automaticDSTchange)
476 {
477 // revert DST status
478 setDST(!wasDSTactive);
479 // new setting DST is inactive, so subtract hour offset to new time
480 if (wasDSTactive)
481 {
482 // DST inactive
483 ValidLTime = ltime.addSecs(int(HourOffset * -3600));
484 }
485 else
486 {
487 // DST active
488 // add hour offset to new time
489 ValidLTime = ltime.addSecs(int(HourOffset * 3600));
490 }
491 }
492 else // if ( automaticDSTchange )
493 {
494 // no automatic DST change happened, so stay in current DST mode
495 setDST(wasDSTactive);
496 if (wasDSTactive)
497 {
498 // DST active
499 // add hour offset to current time, because time doesn't exists
500 ValidLTime = ltime.addSecs(int(HourOffset * 3600));
501 }
502 else
503 {
504 // DST inactive
505 // subtrace hour offset to current time, because time doesn't exists
506 ValidLTime = ltime.addSecs(int(HourOffset * -3600));
507 }
508 } // else { // if ( automaticDSTchange )
509 }
510 else // if ( active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth )
511 {
512 // If current time was not start time, so check if current time is revert time
513 // this means if a DST change happened in next hour(s)
514 active_with_houroffset = isDSTActive(ltime.addSecs(int(HourOffset * 3600)));
515 if (active_with_houroffset != active_normal && RevertMonth == ValidLTime.date().month())
516 {
517 // current time is the revert time
518 qCDebug(KSTARS) << "Current time = Reverttime";
519
520 // we don't kneed to change the local time, because local time always exists, but
521 // some times exists twice, so we have to reset DST status
522 if (automaticDSTchange)
523 {
524 // revert DST status
525 setDST(!wasDSTactive);
526 }
527 else
528 {
529 // no automatic DST change so stay in current DST mode
530 setDST(wasDSTactive);
531 }
532 }
533 else
534 {
535 //Current time was neither starttime nor reverttime, so use normal calculated DST status
536 setDST(active_normal);
537 }
538 } // if ( active_with_houroffset != active_normal && ValidLTime.date().month() == StartMonth )
539
540 // qDebug() << Q_FUNC_INFO << "Using Valid Local Time = " << ValidLTime.toString();
541
542 if (time_runs_forward)
543 {
544 // get next DST change time in local time
545 nextDSTChange_LTime(ValidLTime);
546 nextDSTChange(next_change_ltime, TZoffset);
547 }
548 else
549 {
550 // get previous DST change time in local time
551 previousDSTChange_LTime(ValidLTime);
552 previousDSTChange(next_change_ltime, TZoffset);
553 }
554 ltime = ValidLTime;
555}
556
558{
559 if (StartDay == r->StartDay && RevertDay == r->RevertDay && StartWeek == r->StartWeek &&
560 RevertWeek == r->RevertWeek && StartMonth == r->StartMonth && RevertMonth == r->RevertMonth &&
561 StartTime == r->StartTime && RevertTime == r->RevertTime && isEmptyRule() == r->isEmptyRule())
562 return true;
563 else
564 return false;
565}
Extension of QDateTime for KStars KStarsDateTime can represent the date/time as a Julian Day,...
KStarsDateTime addSecs(double s) const
This class provides the information needed to determine whether Daylight Savings Time (DST; a....
KStarsDateTime nextDSTChange() const
double deltaTZ() const
KStarsDateTime nextDSTChange_LTime() const
bool isEmptyRule() const
bool isDSTActive(const KStarsDateTime &date)
Determine whether DST is in effect for the given DateTime, according to this rule.
TimeZoneRule()
Default Constructor.
bool equals(TimeZoneRule *r)
void setDST(bool activate=true)
Toggle DST on/off.
void reset_with_ltime(KStarsDateTime &ltime, const double TZoffset, const bool time_runs_forward, const bool automaticDSTchange=false)
Recalculate next dst change and if DST is active by a given local time with timezone offset and time ...
QString i18n(const char *text, const TYPE &arg...)
KGuiItem test()
int day() const const
int month() const const
int year() const const
QDate date() const const
QTime time() const const
QString toString(QStringView format, QCalendar cal) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
int toInt(bool *ok, int base) const const
QString toLower() const const
bool isValid(int h, int m, int s, int ms)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:38:44 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.