Kstars

timezonerule.cpp
1 /*
2  SPDX-FileCopyrightText: 2002 Jason Harris <[email protected]>
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 
19 TimeZoneRule::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 
47 void 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 
61 void 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 
75 int 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 
108 bool 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.leftRef(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 
221 int 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 
252 int 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 
369 void 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 */
409 void 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 */
419 void 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 
432 void 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,...
int month() const const
int toInt(bool *ok, int base) const const
QTime time() const const
int year() const const
bool isDSTActive(const KStarsDateTime &date)
Determine whether DST is in effect for the given DateTime, according to this rule.
bool isValid() const const
KStarsDateTime addSecs(double s) const
void setDST(bool activate=true)
Toggle DST on/off.
KGuiItem test()
QString i18n(const char *text, const TYPE &arg...)
int length() const const
int toInt(bool *ok, int base) const const
QString toLower() const const
bool isEmptyRule() const
Definition: timezonerule.h:71
bool equals(TimeZoneRule *r)
QDate date() const const
TimeZoneRule()
Default Constructor.
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 toString(Qt::DateFormat format) const const
QString mid(int position, int n) const const
double deltaTZ() const
Definition: timezonerule.h:80
KStarsDateTime nextDSTChange_LTime() const
Definition: timezonerule.h:98
QStringRef leftRef(int n) const const
KStarsDateTime nextDSTChange() const
Definition: timezonerule.h:95
int day() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Tue Nov 28 2023 03:58:24 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.