1 /*
2     Original version from plan:
3         SPDX-FileCopyrightText: Thomas Driemeyer <thomas@bitrot.de>
4 
5     Adapted for use in KOrganizer:
6         SPDX-FileCopyrightText: Preston Brown <pbrown@kde.org>
7         SPDX-FileCopyrightText: Reinhold Kainhofer <reinhold@kainhofer.com>
8 
9     Portions contributed:
10         SPDX-FileCopyrightText: Peter Littlefield <plittle@sofkin.ca>
11         SPDX-FileCopyrightText: Armin Liebl <liebla@informatik.tu-muenchen.de>
12         SPDX-FileCopyrightText: Efthimios Mavrogeorgiadis <emav@enl.auth.gr>
13         SPDX-FileCopyrightText: Erwin Hugo Achermann <acherman@inf.ethz.ch>
14 
15     Major rewrite using Bison C++ skeleton:
16         SPDX-FileCopyrightText: 2010 John Layt <john@layt.net>
17 
18     SPDX-License-Identifier: LGPL-2.0-or-later
19 */
20 
21 #include "holiday_p.h"
22 #include "holidayparserdriverplan_p.h"
23 #include "holidayparserplan.hpp"
24 #include "holidayscannerplan_p.h"
25 #include <kholidays_debug.h>
26 
27 #include <QFileInfo>
28 
29 #include <sstream>
30 
31 #define LAST 99999
32 #define ANY (-99999)
33 #define BEFORE (-1)
34 #define AFTER 1
35 
36 using namespace KHolidays;
37 
HolidayParserDriverPlan(const QString & planFilePath)38 HolidayParserDriverPlan::HolidayParserDriverPlan(const QString &planFilePath)
39     : HolidayParserDriver(planFilePath)
40     , m_traceParsing(false)
41     , m_traceScanning(false)
42     , m_parseMetadataOnly(false)
43     , m_eventYear(0)
44     , m_eventMonth(0)
45     , m_eventDay(0)
46 {
47     QFile holidayFile(filePath());
48     if (holidayFile.open(QIODevice::ReadOnly)) {
49         m_scanData = holidayFile.readAll();
50         holidayFile.close();
51     }
52     m_scanner = new HolidayScannerPlan();
53     m_scanner->set_debug(m_traceScanning);
54     m_parser = new HolidayParserPlan(*this);
55     m_parser->set_debug_level(m_traceParsing);
56     m_fileToParse = new std::string(filePath().toLocal8Bit().data());
57     parseMetadata();
58 }
59 
~HolidayParserDriverPlan()60 HolidayParserDriverPlan::~HolidayParserDriverPlan()
61 {
62     delete m_parser;
63     delete m_scanner;
64     delete m_fileToParse;
65 }
66 
67 // TODO Figure why it doesn't compile
error(const KHolidays::location & errorLocation,const QString & errorMessage)68 void HolidayParserDriverPlan::error(const KHolidays::location &errorLocation, const QString &errorMessage)
69 {
70     Q_UNUSED(errorLocation);
71     // std::cerr << errorLocation << " : " << errorMessage;  //Doesn't work???
72     // qDebug() << errorLocation << " : " << errorMessage;  //Doesn't work???
73     qCDebug(KHOLIDAYS_LOG) << errorMessage;
74 }
75 
error(const QString & errorMessage)76 void HolidayParserDriverPlan::error(const QString &errorMessage)
77 {
78     qCDebug(KHOLIDAYS_LOG) << errorMessage;
79 }
80 
parse()81 void HolidayParserDriverPlan::parse()
82 {
83     // Parse the file using every calendar system in the file
84     for (const QString &calendarType : std::as_const(m_fileCalendarTypes)) {
85         // Cater for events defined in other Calendar Systems where request year could cover 2 or 3 event years
86         // Perhaps also parse year before and year after to allow events to span years or shift to other year?
87         setParseCalendar(calendarType);
88         setParseStartEnd();
89 
90         // Generate all events for this calendar in the required year(s)
91         for (m_parseYear = m_parseStartYear; m_parseYear <= m_parseEndYear; ++m_parseYear) {
92             m_parseYearStart = m_parseCalendar.firstDayOfYear(m_parseYear);
93             m_parseYearEaster = easter(m_parseYear);
94             m_parseYearPascha = pascha(m_parseYear);
95 
96             std::istringstream iss2(std::string(m_scanData.data()));
97             m_scanner->yyrestart(&iss2);
98 
99             m_parser->parse();
100         }
101     }
102 }
103 
parseMetadata()104 void HolidayParserDriverPlan::parseMetadata()
105 {
106     m_parseMetadataOnly = true;
107     m_fileCountryCode.clear();
108     m_fileLanguageCode.clear();
109     m_fileName.clear();
110     m_fileDescription.clear();
111     m_fileCalendarTypes.clear();
112     m_fileCalendarTypes.append(QStringLiteral("gregorian"));
113 
114     // Default to files internal metadata
115     setParseCalendar(QStringLiteral("gregorian"));
116     m_parseYear = QDate::currentDate().year();
117     std::istringstream iss2(std::string(m_scanData.data()));
118     m_scanner->yyrestart(&iss2);
119     m_parser->parse();
120     m_resultList.clear();
121 
122     // If not populated, then use filename metadata, this may change later
123     // metadata is encoded in filename in form holiday_<region>_<type>_<language>_<name>
124     // with region, type and language sub groups separated by -, and with name optional
125     QFileInfo file(m_filePath);
126     if (file.exists()) {
127         QStringList metadata = file.fileName().split(QLatin1Char('_'));
128         if (metadata[0] == QLatin1String("holiday") && metadata.count() > 2) {
129             if (m_fileCountryCode.isEmpty()) {
130                 setFileCountryCode(metadata[1].toUpper());
131             }
132             if (m_fileLanguageCode.isEmpty()) {
133                 QStringList language = metadata[2].split(QLatin1Char('-'));
134                 m_fileLanguageCode = language[0];
135                 if (language.count() > 1) {
136                     setFileLanguageCode(language[0].append(QLatin1Char('_')).append(language[1].toUpper()));
137                 } else {
138                     setFileLanguageCode(language[0]);
139                 }
140             }
141             if (m_fileLanguageCode.isEmpty() && metadata.count() > 3) {
142                 m_fileName = metadata[3];
143             }
144         }
145     }
146 
147     m_parseMetadataOnly = false;
148 }
149 
setParseCalendar(QCalendarSystem::CalendarSystem calendar)150 void HolidayParserDriverPlan::setParseCalendar(QCalendarSystem::CalendarSystem calendar)
151 {
152     m_parseCalendarType = systemToType(calendar);
153     HolidayParserDriver::setParseCalendar(calendar);
154 }
155 
filePath()156 QString HolidayParserDriverPlan::filePath()
157 {
158     return m_filePath;
159 }
160 
fileToParse() const161 std::string *HolidayParserDriverPlan::fileToParse() const
162 {
163     return m_fileToParse;
164 }
165 
166 /*****************************************
167   Calendar and Date convenience routines
168 ******************************************/
169 
170 // Adjust month numbering for Hebrew civil calendar leap month
adjustedMonthNumber(int month)171 int HolidayParserDriverPlan::adjustedMonthNumber(int month)
172 {
173     if (m_eventCalendarType != QLatin1String("hebrew") || // Only adjust Hebrew months
174         m_parseCalendarType != QLatin1String("hebrew") || !m_parseCalendar.isLeapYear(m_parseYear) || // Only adjust in leap year
175         month < 6) { // Only adjust from Adar onwards
176         return month;
177     }
178 
179     if (month == 13) { // Adar I
180         return 6;
181     }
182 
183     if (month == 14) { // Adar II
184         return 7;
185     }
186 
187     return month + 1; // Inserting Adar I moves other months up by 1
188 }
189 
isLeapYear(int year)190 bool HolidayParserDriverPlan::isLeapYear(int year)
191 {
192     return m_parseCalendar.isLeapYear(year);
193 }
194 
parseYear()195 int HolidayParserDriverPlan::parseYear()
196 {
197     return m_parseYear;
198 }
199 
julianDay(int year,int month,int day)200 int HolidayParserDriverPlan::julianDay(int year, int month, int day)
201 {
202     return m_parseCalendar.date(year, month, day).toJulianDay();
203 }
204 
julianDayToDate(int jd,int * year,int * month,int * day)205 void HolidayParserDriverPlan::julianDayToDate(int jd, int *year, int *month, int *day)
206 {
207     QDate tempDate = QDate::fromJulianDay(jd);
208 
209     if (year) {
210         *year = m_parseCalendar.year(tempDate);
211     }
212     if (month) {
213         *month = m_parseCalendar.month(tempDate);
214     }
215     if (day) {
216         *day = m_parseCalendar.day(tempDate);
217     }
218 }
219 
easter(int year)220 QDate HolidayParserDriverPlan::easter(int year)
221 {
222     if (m_parseCalendar.calendarSystem() != QCalendarSystem::GregorianCalendar) {
223         return QDate();
224     }
225     if (year < 0) {
226         return QDate();
227     }
228 
229     // Algorithm taken from Tondering
230     // https://www.tondering.dk/claus/cal/easter.php
231     int g = year % 19;
232     int c = year / 100;
233     int h = (c - (c / 4) - (((8 * c) + 13) / 25) + (19 * g) + 15) % 30;
234     int i = h - ((h / 28) * (1 - ((29 / (h + 1)) * ((21 - g) / 11))));
235     int j = (year + (year / 4) + i + 2 - c + (c / 4)) % 7;
236     int l = i - j;
237     int month = 3 + ((l + 40) / 44);
238     int day = l + 28 - (31 * (month / 4));
239 
240     return QDate::fromJulianDay(julianDay(year, month, day));
241 }
242 
pascha(int year)243 QDate HolidayParserDriverPlan::pascha(int year)
244 {
245     if (year < 0) {
246         return QDate();
247     }
248 
249     if (m_parseCalendar.calendarSystem() == QCalendarSystem::GregorianCalendar || m_parseCalendar.calendarSystem() == QCalendarSystem::JulianCalendar) {
250         // Algorithm taken from Tondering
251         // https://www.tondering.dk/claus/cal/easter.php#orthodoxeast
252         // Gives Orthodox Easter in the Julian Calendar, need to convert afterwards to Gregorian if needed
253         int g = year % 19;
254         int i = ((19 * g) + 15) % 30;
255         int j = (year + (year / 4) + i) % 7;
256         int l = i - j;
257         int month = 3 + ((l + 40) / 44);
258         int day = l + 28 - (31 * (month / 4));
259 
260         if (m_parseCalendar.calendarSystem() == QCalendarSystem::JulianCalendar) {
261             return QDate::fromJulianDay(julianDay(year, month, day));
262         }
263 
264         if (m_parseCalendar.calendarSystem() == QCalendarSystem::GregorianCalendar) {
265             setParseCalendar(QStringLiteral("julian"));
266             int paschaJd = julianDay(year, month, day);
267             setParseCalendar(QStringLiteral("gregorian"));
268             return QDate::fromJulianDay(paschaJd);
269         }
270     }
271 
272     return QDate();
273 }
274 
typeToSystem(const QString & calendarType)275 QCalendarSystem::CalendarSystem HolidayParserDriverPlan::typeToSystem(const QString &calendarType)
276 {
277     if (calendarType == QStringLiteral("gregorian")) {
278         return QCalendarSystem::GregorianCalendar;
279     } else if (calendarType == QStringLiteral("hebrew")) {
280         return QCalendarSystem::HebrewCalendar;
281     } else if (calendarType == QStringLiteral("hijri")) {
282         return QCalendarSystem::IslamicCivilCalendar;
283     } else if (calendarType == QStringLiteral("jalali")) {
284         return QCalendarSystem::PersianCalendar;
285     } else if (calendarType == QStringLiteral("julian")) {
286         return QCalendarSystem::JulianCalendar;
287     } else if (calendarType == QStringLiteral("coptic")) {
288         return QCalendarSystem::CopticCalendar;
289     } else if (calendarType == QStringLiteral("ethiopian")) {
290         return QCalendarSystem::EthiopicCalendar;
291     } else if (calendarType == QStringLiteral("indiannational")) {
292         return QCalendarSystem::IndianNationalCalendar;
293     }
294     return QCalendarSystem::GregorianCalendar;
295 }
296 
systemToType(QCalendarSystem::CalendarSystem calendar)297 QString HolidayParserDriverPlan::systemToType(QCalendarSystem::CalendarSystem calendar)
298 {
299     switch (calendar) {
300     case QCalendarSystem::GregorianCalendar:
301         return QStringLiteral("gregorian");
302     case QCalendarSystem::HebrewCalendar:
303         return QStringLiteral("hebrew");
304     case QCalendarSystem::IslamicCivilCalendar:
305         return QStringLiteral("hijri");
306     case QCalendarSystem::PersianCalendar:
307         return QStringLiteral("jalali");
308     case QCalendarSystem::JulianCalendar:
309         return QStringLiteral("julian");
310     case QCalendarSystem::CopticCalendar:
311         return QStringLiteral("coptic");
312     case QCalendarSystem::EthiopicCalendar:
313         return QStringLiteral("ethiopian");
314     case QCalendarSystem::IndianNationalCalendar:
315         return QStringLiteral("indiannational");
316     default:
317         return QStringLiteral("gregorian");
318     }
319 }
320 
321 /*************************
322  * Calculate jd routines *
323  *************************/
324 
325 // Return the jd of an existing event, assumes unique names and correct order in file
julianDayFromEventName(const QString & eventName)326 int HolidayParserDriverPlan::julianDayFromEventName(const QString &eventName)
327 {
328     for (const KHolidays::Holiday &thisHoliday : std::as_const(m_resultList)) {
329         if (thisHoliday.name() == eventName) {
330             return thisHoliday.observedStartDate().toJulianDay();
331         }
332     }
333     return -1;
334 }
335 
336 // Return jd of Easter if Gregorian
julianDayFromEaster()337 int HolidayParserDriverPlan::julianDayFromEaster()
338 {
339     if (m_eventCalendarType == QLatin1String("gregorian")) {
340         return m_parseYearEaster.toJulianDay();
341     } else {
342         error(QStringLiteral("Can only use Easter in Gregorian event rule"));
343         return -1;
344     }
345 }
346 
347 // Return jd of Orthodox Easter if Gregorian or Julian
julianDayFromPascha()348 int HolidayParserDriverPlan::julianDayFromPascha()
349 {
350     if (m_eventCalendarType == QLatin1String("gregorian") || m_eventCalendarType == QLatin1String("julian")) {
351         return m_parseYearPascha.toJulianDay();
352     } else {
353         error(QStringLiteral("Can only use Easter in Gregorian or Julian event rule"));
354         return -1;
355     }
356 }
357 
358 // Return jd of weekday from a month/day in parse year
julianDayFromMonthDay(int month,int day)359 int HolidayParserDriverPlan::julianDayFromMonthDay(int month, int day)
360 {
361     return julianDay(m_parseYear, month, day);
362 }
363 
364 // Return jd of weekday relative to a Julian Day number
julianDayFromRelativeWeekday(int occurrence,int weekday,int jd)365 int HolidayParserDriverPlan::julianDayFromRelativeWeekday(int occurrence, int weekday, int jd)
366 {
367     if (occurrence == ANY) { /* Should never get this, convert to AFTER instead */
368         occurrence = AFTER;
369     }
370 
371     int thisWeekday = m_parseCalendar.dayOfWeek(QDate::fromJulianDay(jd));
372 
373     /* AFTER actually means on or after */
374     /* BEFORE actually means on or before */
375     if (occurrence > 0) {
376         occurrence = occurrence - 1;
377     } else if (occurrence < 0 && weekday == thisWeekday) {
378         occurrence = occurrence + 1;
379     }
380 
381     if (weekday < thisWeekday) {
382         occurrence = occurrence + 1;
383     }
384 
385     return jd + weekday - thisWeekday + (occurrence * 7);
386 }
387 
388 // Return jd of weekday occurrence in a given month and day in the parse year
julianDayFromWeekdayInMonth(int occurrence,int weekday,int month)389 int HolidayParserDriverPlan::julianDayFromWeekdayInMonth(int occurrence, int weekday, int month)
390 {
391     if (occurrence == LAST) { // Is weekday on or before last day of month
392         return julianDayFromRelativeWeekday(BEFORE, weekday, julianDay(m_parseYear, month, m_parseCalendar.daysInMonth(m_parseYear, month)));
393     } else { // Is nth weekday on or after first day of month
394         return julianDayFromRelativeWeekday(occurrence, weekday, julianDay(m_parseYear, month, 1));
395     }
396 }
397 
398 /****************************************************
399  * Set parsed event variables convenience functions *
400  ****************************************************/
401 
setParseCalendar(const QString & calendarType)402 void HolidayParserDriverPlan::setParseCalendar(const QString &calendarType)
403 {
404     m_parseCalendarType = calendarType;
405     setParseCalendar(typeToSystem(calendarType));
406 }
407 
setFileCountryCode(const QString & countryCode)408 void HolidayParserDriverPlan::setFileCountryCode(const QString &countryCode)
409 {
410     m_fileCountryCode = countryCode;
411 }
412 
setFileLanguageCode(const QString & languageCode)413 void HolidayParserDriverPlan::setFileLanguageCode(const QString &languageCode)
414 {
415     m_fileLanguageCode = languageCode;
416 }
417 
setFileName(const QString & name)418 void HolidayParserDriverPlan::setFileName(const QString &name)
419 {
420     m_fileName = name;
421 }
422 
setFileDescription(const QString & description)423 void HolidayParserDriverPlan::setFileDescription(const QString &description)
424 {
425     m_fileDescription = description;
426 }
427 
setEventName(const QString & eventName)428 void HolidayParserDriverPlan::setEventName(const QString &eventName)
429 {
430     // Assume if setting an event name then is start of new event line, so clear categories
431     m_eventCategories.clear();
432     m_eventName = eventName;
433 }
434 
setEventCategory(const QString & category)435 void HolidayParserDriverPlan::setEventCategory(const QString &category)
436 {
437     m_eventCategories.append(category);
438 }
439 
setEventCalendarType(const QString & calendarType)440 void HolidayParserDriverPlan::setEventCalendarType(const QString &calendarType)
441 {
442     m_eventCalendarType = calendarType;
443     if (m_parseMetadataOnly && !m_fileCalendarTypes.contains(calendarType)) {
444         m_fileCalendarTypes.append(calendarType);
445     }
446 }
447 
setEventDate(int eventYear,int eventMonth,int eventDay)448 void HolidayParserDriverPlan::setEventDate(int eventYear, int eventMonth, int eventDay)
449 {
450     m_eventYear = eventYear;
451     m_eventMonth = eventMonth;
452     m_eventDay = eventDay;
453 }
454 
setEventDate(int jd)455 void HolidayParserDriverPlan::setEventDate(int jd)
456 {
457     julianDayToDate(jd, &m_eventYear, &m_eventMonth, &m_eventDay);
458 }
459 
460 /********************************************
461  * Set event date from event rules routines *
462  ********************************************/
463 
464 /*
465  * Set event by weekday (Monday..Sunday). The rule expression is
466  * "every <occurrence> <weekday> of <month> plus <offset> days length <duration> days".
467  * Occurrence and month can be ANY or LAST, offset and duration are optional.
468  */
469 
setFromWeekdayInMonth(int occurrence,int weekday,int month,int offset,int duration)470 void HolidayParserDriverPlan::setFromWeekdayInMonth(int occurrence, int weekday, int month, int offset, int duration)
471 {
472     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
473     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
474         return;
475     }
476 
477     int startMonth, endMonth;
478     if (month == LAST) {
479         startMonth = m_parseCalendar.monthsInYear(m_parseYear);
480         endMonth = startMonth;
481     } else if (month == ANY) {
482         startMonth = 1;
483         endMonth = m_parseCalendar.monthsInYear(m_parseYear);
484     } else {
485         startMonth = month;
486         endMonth = month;
487     }
488 
489     // Generate all events in the required event month(s)
490     for (int thisMonth = startMonth; thisMonth <= endMonth; ++thisMonth) {
491         if (m_parseCalendar.isValid(m_parseYear, thisMonth, 1)) {
492             int startOccurrence, endOccurrence;
493             if (occurrence == ANY) { // Generate 1st through 5th weekdays, assumes no month with > 35 days
494                 startOccurrence = 1;
495                 endOccurrence = 5;
496             } else { // Generate just nth or LAST weekday
497                 startOccurrence = occurrence;
498                 endOccurrence = occurrence;
499             }
500 
501             int jdMonthStart = julianDay(m_parseYear, thisMonth, 1);
502             int jdMonthEnd = julianDay(m_parseYear, thisMonth, m_parseCalendar.daysInMonth(m_parseYear, thisMonth));
503 
504             // Generate each required occurrence of weekday in month, check occurrence actually falls in month
505             for (int thisOccurrence = startOccurrence; thisOccurrence <= endOccurrence; ++thisOccurrence) {
506                 int thisJd = julianDayFromWeekdayInMonth(thisOccurrence, weekday, thisMonth);
507                 if (thisJd >= jdMonthStart && thisJd <= jdMonthEnd) {
508                     setEvent(thisJd + offset, 0, duration);
509                 }
510             }
511         }
512     }
513 }
514 
515 /*
516  * Set event by weekday (Monday..Sunday) relative to a date. The expression is
517  * "<weekday> <occurrence> <date> plus <offset> days length <duration> days".
518  * Occurrence, month and day can be ANY or LAST, year can be ANY, offset and duration are optional.
519  */
520 
setFromRelativeWeekday(int occurrence,int weekday,int offset,int duration)521 void HolidayParserDriverPlan::setFromRelativeWeekday(int occurrence, int weekday, int offset, int duration)
522 {
523     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
524     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
525         return;
526     }
527 
528     int thisYear;
529     if (m_eventYear == ANY) { // Generate the parse year
530         thisYear = m_parseYear;
531     } else { // Generate a specific event year
532         thisYear = m_eventYear;
533     }
534 
535     int startMonth, endMonth;
536     if (m_eventMonth == LAST) { // Generate just the last month
537         startMonth = m_parseCalendar.monthsInYear(thisYear);
538         endMonth = startMonth;
539     } else if (m_eventMonth == ANY) { // Generate every month
540         startMonth = 1;
541         endMonth = m_parseCalendar.monthsInYear(thisYear);
542     } else { // Generate just the specific event month
543         startMonth = m_eventMonth;
544         endMonth = m_eventMonth;
545     }
546 
547     // Generate all events in the required month(s)
548     int thisMonth;
549     for (thisMonth = startMonth; thisMonth <= endMonth; ++thisMonth) {
550         int startDay, endDay;
551         if (m_eventDay == LAST) { // Generate just the last day in the month
552             startDay = m_parseCalendar.daysInMonth(thisYear, thisMonth);
553             endDay = startDay;
554         } else if (m_eventDay == ANY) { // Generate every day in the month
555             startDay = 1;
556             endDay = m_parseCalendar.daysInMonth(thisYear, thisMonth);
557         } else { // Generate just the specific event day
558             startDay = m_eventDay;
559             endDay = m_eventDay;
560         }
561 
562         // Generate all events on the required day(s)
563         for (int thisDay = startDay; thisDay <= endDay; ++thisDay) {
564             if (m_parseCalendar.isValid(thisYear, thisMonth, thisDay)) {
565                 int relativeJd = julianDayFromRelativeWeekday(occurrence, weekday, julianDay(thisYear, thisMonth, thisDay));
566                 setEvent(relativeJd + offset, 0, duration);
567             }
568         }
569     }
570 }
571 
572 // TODO Figure out how this works :-)
conditionalOffset(int year,int month,int day,int condition)573 int HolidayParserDriverPlan::conditionalOffset(int year, int month, int day, int condition)
574 {
575     /** The encoding of the condition is:
576           8 lower bits: conditions to shift (bit-register, bit 1=mon, ..., bit 7=sun)
577           8 higher bits: weekday to shift to (bit-register, bit 1=mon, ..., bit 7=sun)
578     */
579 
580     int offset = 0;
581 
582     int weekday = m_parseCalendar.dayOfWeek(year, month, day);
583 
584     if (condition & (1 << weekday)) {
585         /* condition matches -> higher 8 bits contain the possible days to shift to */
586         int to = (condition >> 8);
587         while (!(to & (1 << ((weekday + offset) % 7))) && (offset < 8)) {
588             ++offset;
589         }
590     }
591 
592     if (offset >= 8) {
593         offset = 0;
594     }
595 
596     return offset;
597 }
598 
599 /*
600  * Set event by date. The expression is
601  * "<date> plus <offset> days shift <condition> length <duration> days".
602  * Occurrence, month and day can be ANY or LAST, year can be ANY, offset and duration are optional.
603  */
604 
setFromDate(int offset,int condition,int duration)605 void HolidayParserDriverPlan::setFromDate(int offset, int condition, int duration)
606 {
607     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
608     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
609         return;
610     }
611 
612     int thisYear;
613     if (m_eventYear == ANY) { // Generate the parse year
614         thisYear = m_parseYear;
615     } else { // Generate a specific event year
616         thisYear = m_eventYear;
617     }
618 
619     int startMonth, endMonth;
620     if (m_eventMonth == LAST) { // Generate just the last month
621         startMonth = m_parseCalendar.monthsInYear(thisYear);
622         endMonth = startMonth;
623     } else if (m_eventMonth == ANY) { // Generate every month
624         startMonth = 1;
625         endMonth = m_parseCalendar.monthsInYear(thisYear);
626     } else { // Generate just the specific event month
627         startMonth = m_eventMonth;
628         endMonth = m_eventMonth;
629     }
630 
631     // Generate all events in the required month(s)
632     for (int thisMonth = startMonth; thisMonth <= endMonth; ++thisMonth) {
633         int startDay, endDay;
634         if (m_eventDay == LAST) { // Generate just the last day in the month
635             startDay = m_parseCalendar.daysInMonth(thisYear, thisMonth);
636             endDay = startDay;
637         } else if (m_eventDay == ANY) { // Generate every day in the month
638             startDay = 1;
639             endDay = m_parseCalendar.daysInMonth(thisYear, thisMonth);
640         } else { // Generate just the specific event day
641             startDay = m_eventDay;
642             endDay = m_eventDay;
643         }
644 
645         // Generate all events on the required day(s)
646         for (int thisDay = startDay; thisDay <= endDay; ++thisDay) {
647             if (m_parseCalendar.isValid(thisYear, thisMonth, thisDay)) {
648                 setEvent(julianDay(thisYear, thisMonth, thisDay) + offset, //
649                          conditionalOffset(thisYear, thisMonth, thisDay, condition), //
650                          duration);
651             }
652         }
653     }
654 }
655 
656 /*
657  * Set event relative to Easter. The expression is
658  * "EASTER plus <offset> days length <duration> days".
659  * Offset and duration are optional.
660  */
661 
setFromEaster(int offset,int duration)662 void HolidayParserDriverPlan::setFromEaster(int offset, int duration)
663 {
664     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
665     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
666         return;
667     }
668 
669     if (m_eventCalendarType == QLatin1String("gregorian")) {
670         setEvent(m_parseYearEaster.toJulianDay() + offset, 0, duration);
671     } else {
672         error(QStringLiteral("Can only use Easter in Gregorian event rule"));
673     }
674 }
675 
676 /*
677  * Set event relative to Pascha. The expression is
678  * "PASCHA plus <offset> days length <duration> days".
679  * Offset and duration are optional.
680  */
681 
setFromPascha(int offset,int duration)682 void HolidayParserDriverPlan::setFromPascha(int offset, int duration)
683 {
684     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
685     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
686         return;
687     }
688 
689     if (m_eventCalendarType == QLatin1String("gregorian") || m_eventCalendarType == QLatin1String("julian")) {
690         setEvent(m_parseYearPascha.toJulianDay(), offset, duration);
691     } else {
692         error(QStringLiteral("Can only use Pascha in Julian and Gregorian event rule"));
693     }
694 }
695 
696 // Set the event if it falls inside the requested date range
setEvent(int jd,int observeOffset,int duration)697 void HolidayParserDriverPlan::setEvent(int jd, int observeOffset, int duration)
698 {
699     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
700     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
701         return;
702     }
703 
704     // Date the holiday will be observed on
705     int observeJd = jd + observeOffset;
706 
707     addHoliday(QDate::fromJulianDay(observeJd), duration);
708 }
709 
addHoliday(const QDate & observedDate,int duration)710 void HolidayParserDriverPlan::addHoliday(const QDate &observedDate, int duration)
711 {
712     // Only set if event falls in requested date range, i.e. either starts or ends during range
713     if (m_parseCalendar.isValid(observedDate) //
714         && observedDate <= m_requestEnd //
715         && observedDate.addDays(duration - 1) >= m_requestStart) {
716         KHolidays::Holiday holiday;
717         holiday.d->mObservedDate = observedDate;
718         holiday.d->mDuration = duration;
719         holiday.d->mName = m_eventName;
720         holiday.d->mDescription = m_eventName;
721         holiday.d->mCategoryList = m_eventCategories;
722         if (m_eventCategories.contains(QStringLiteral("public"))) {
723             holiday.d->mDayType = KHolidays::Holiday::NonWorkday;
724         } else {
725             holiday.d->mDayType = KHolidays::Holiday::Workday;
726         }
727         m_resultList.append(holiday);
728     }
729 }
730