File indexing completed on 2024-04-21 03:54:16

0001 /*
0002     Original version from plan:
0003         SPDX-FileCopyrightText: Thomas Driemeyer <thomas@bitrot.de>
0004 
0005     Adapted for use in KOrganizer:
0006         SPDX-FileCopyrightText: Preston Brown <pbrown@kde.org>
0007         SPDX-FileCopyrightText: Reinhold Kainhofer <reinhold@kainhofer.com>
0008 
0009     Portions contributed:
0010         SPDX-FileCopyrightText: Peter Littlefield <plittle@sofkin.ca>
0011         SPDX-FileCopyrightText: Armin Liebl <liebla@informatik.tu-muenchen.de>
0012         SPDX-FileCopyrightText: Efthimios Mavrogeorgiadis <emav@enl.auth.gr>
0013         SPDX-FileCopyrightText: Erwin Hugo Achermann <acherman@inf.ethz.ch>
0014 
0015     Major rewrite using Bison C++ skeleton:
0016         SPDX-FileCopyrightText: 2010 John Layt <john@layt.net>
0017 
0018     SPDX-License-Identifier: LGPL-2.0-or-later
0019 */
0020 
0021 #include "holiday_p.h"
0022 #include "holidayparserdriverplan_p.h"
0023 #include "holidayparserplan.hpp"
0024 #include "holidayscannerplan_p.h"
0025 #include <kholidays_debug.h>
0026 
0027 #include <QFileInfo>
0028 
0029 #include <sstream>
0030 
0031 #define LAST 99999
0032 #define ANY (-99999)
0033 #define BEFORE (-1)
0034 #define AFTER 1
0035 
0036 using namespace KHolidays;
0037 
0038 HolidayParserDriverPlan::HolidayParserDriverPlan(const QString &planFilePath)
0039     : HolidayParserDriver(planFilePath)
0040     , m_traceParsing(false)
0041     , m_traceScanning(false)
0042     , m_parseMetadataOnly(false)
0043     , m_eventYear(0)
0044     , m_eventMonth(0)
0045     , m_eventDay(0)
0046 {
0047     QFile holidayFile(filePath());
0048     if (holidayFile.open(QIODevice::ReadOnly)) {
0049         m_scanData = holidayFile.readAll();
0050         holidayFile.close();
0051     }
0052     m_scanner = new HolidayScannerPlan();
0053     m_scanner->set_debug(m_traceScanning);
0054     m_parser = new HolidayParserPlan(*this);
0055     m_parser->set_debug_level(m_traceParsing);
0056     m_fileToParse = new std::string(filePath().toLocal8Bit().data());
0057     parseMetadata();
0058 }
0059 
0060 HolidayParserDriverPlan::~HolidayParserDriverPlan()
0061 {
0062     delete m_parser;
0063     delete m_scanner;
0064     delete m_fileToParse;
0065 }
0066 
0067 // TODO Figure why it doesn't compile
0068 void HolidayParserDriverPlan::error(const KHolidays::location &errorLocation, const QString &errorMessage)
0069 {
0070     Q_UNUSED(errorLocation);
0071     // std::cerr << errorLocation << " : " << errorMessage;  //Doesn't work???
0072     // qDebug() << errorLocation << " : " << errorMessage;  //Doesn't work???
0073     qCDebug(KHOLIDAYS_LOG) << errorMessage;
0074 }
0075 
0076 void HolidayParserDriverPlan::error(const QString &errorMessage)
0077 {
0078     qCDebug(KHOLIDAYS_LOG) << errorMessage;
0079 }
0080 
0081 void HolidayParserDriverPlan::parse()
0082 {
0083     // Parse the file using every calendar system in the file
0084     for (const QString &calendarType : std::as_const(m_fileCalendarTypes)) {
0085         // Cater for events defined in other Calendar Systems where request year could cover 2 or 3 event years
0086         // Perhaps also parse year before and year after to allow events to span years or shift to other year?
0087         setParseCalendar(calendarType);
0088         setParseStartEnd();
0089 
0090         // Generate all events for this calendar in the required year(s)
0091         for (m_parseYear = m_parseStartYear; m_parseYear <= m_parseEndYear; ++m_parseYear) {
0092             m_parseYearStart = m_parseCalendar.firstDayOfYear(m_parseYear);
0093             m_parseYearEaster = easter(m_parseYear);
0094             m_parseYearPascha = pascha(m_parseYear);
0095 
0096             std::istringstream iss2(std::string(m_scanData.data()));
0097             m_scanner->yyrestart(&iss2);
0098 
0099             m_parser->parse();
0100         }
0101     }
0102 }
0103 
0104 void HolidayParserDriverPlan::parseMetadata()
0105 {
0106     m_parseMetadataOnly = true;
0107     m_fileCountryCode.clear();
0108     m_fileLanguageCode.clear();
0109     m_fileName.clear();
0110     m_fileDescription.clear();
0111     m_fileCalendarTypes.clear();
0112     m_fileCalendarTypes.append(QStringLiteral("gregorian"));
0113 
0114     // Default to files internal metadata
0115     setParseCalendar(QStringLiteral("gregorian"));
0116     m_parseYear = QDate::currentDate().year();
0117     std::istringstream iss2(std::string(m_scanData.data()));
0118     m_scanner->yyrestart(&iss2);
0119     m_parser->parse();
0120     m_resultList.clear();
0121 
0122     // If not populated, then use filename metadata, this may change later
0123     // metadata is encoded in filename in form holiday_<region>_<type>_<language>_<name>
0124     // with region, type and language sub groups separated by -, and with name optional
0125     QFileInfo file(m_filePath);
0126     if (file.exists()) {
0127         QStringList metadata = file.fileName().split(QLatin1Char('_'));
0128         if (metadata[0] == QLatin1String("holiday") && metadata.count() > 2) {
0129             if (m_fileCountryCode.isEmpty()) {
0130                 setFileCountryCode(metadata[1].toUpper());
0131             }
0132             if (m_fileLanguageCode.isEmpty()) {
0133                 QStringList language = metadata[2].split(QLatin1Char('-'));
0134                 m_fileLanguageCode = language[0];
0135                 if (language.count() > 1) {
0136                     setFileLanguageCode(language[0].append(QLatin1Char('_')).append(language[1].toUpper()));
0137                 } else {
0138                     setFileLanguageCode(language[0]);
0139                 }
0140             }
0141             if (m_fileLanguageCode.isEmpty() && metadata.count() > 3) {
0142                 m_fileName = metadata[3];
0143             }
0144         }
0145     }
0146 
0147     m_parseMetadataOnly = false;
0148 }
0149 
0150 void HolidayParserDriverPlan::setParseCalendar(QCalendarSystem::CalendarSystem calendar)
0151 {
0152     m_parseCalendarType = systemToType(calendar);
0153     HolidayParserDriver::setParseCalendar(calendar);
0154 }
0155 
0156 QString HolidayParserDriverPlan::filePath()
0157 {
0158     return m_filePath;
0159 }
0160 
0161 std::string *HolidayParserDriverPlan::fileToParse() const
0162 {
0163     return m_fileToParse;
0164 }
0165 
0166 /*****************************************
0167   Calendar and Date convenience routines
0168 ******************************************/
0169 
0170 // Adjust month numbering for Hebrew civil calendar leap month
0171 int HolidayParserDriverPlan::adjustedMonthNumber(int month)
0172 {
0173     if (m_eventCalendarType != QLatin1String("hebrew") || // Only adjust Hebrew months
0174         m_parseCalendarType != QLatin1String("hebrew") || !m_parseCalendar.isLeapYear(m_parseYear) || // Only adjust in leap year
0175         month < 6) { // Only adjust from Adar onwards
0176         return month;
0177     }
0178 
0179     if (month == 13) { // Adar I
0180         return 6;
0181     }
0182 
0183     if (month == 14) { // Adar II
0184         return 7;
0185     }
0186 
0187     return month + 1; // Inserting Adar I moves other months up by 1
0188 }
0189 
0190 bool HolidayParserDriverPlan::isLeapYear(int year)
0191 {
0192     return m_parseCalendar.isLeapYear(year);
0193 }
0194 
0195 int HolidayParserDriverPlan::parseYear()
0196 {
0197     return m_parseYear;
0198 }
0199 
0200 int HolidayParserDriverPlan::julianDay(int year, int month, int day)
0201 {
0202     return m_parseCalendar.date(year, month, day).toJulianDay();
0203 }
0204 
0205 void HolidayParserDriverPlan::julianDayToDate(int jd, int *year, int *month, int *day)
0206 {
0207     QDate tempDate = QDate::fromJulianDay(jd);
0208 
0209     if (year) {
0210         *year = m_parseCalendar.year(tempDate);
0211     }
0212     if (month) {
0213         *month = m_parseCalendar.month(tempDate);
0214     }
0215     if (day) {
0216         *day = m_parseCalendar.day(tempDate);
0217     }
0218 }
0219 
0220 QDate HolidayParserDriverPlan::easter(int year)
0221 {
0222     if (m_parseCalendar.calendarSystem() != QCalendarSystem::GregorianCalendar) {
0223         return QDate();
0224     }
0225     if (year < 0) {
0226         return QDate();
0227     }
0228 
0229     // Algorithm taken from Tondering
0230     // https://www.tondering.dk/claus/cal/easter.php
0231     int g = year % 19;
0232     int c = year / 100;
0233     int h = (c - (c / 4) - (((8 * c) + 13) / 25) + (19 * g) + 15) % 30;
0234     int i = h - ((h / 28) * (1 - ((29 / (h + 1)) * ((21 - g) / 11))));
0235     int j = (year + (year / 4) + i + 2 - c + (c / 4)) % 7;
0236     int l = i - j;
0237     int month = 3 + ((l + 40) / 44);
0238     int day = l + 28 - (31 * (month / 4));
0239 
0240     return QDate::fromJulianDay(julianDay(year, month, day));
0241 }
0242 
0243 QDate HolidayParserDriverPlan::pascha(int year)
0244 {
0245     if (year < 0) {
0246         return QDate();
0247     }
0248 
0249     if (m_parseCalendar.calendarSystem() == QCalendarSystem::GregorianCalendar || m_parseCalendar.calendarSystem() == QCalendarSystem::JulianCalendar) {
0250         // Algorithm taken from Tondering
0251         // https://www.tondering.dk/claus/cal/easter.php#orthodoxeast
0252         // Gives Orthodox Easter in the Julian Calendar, need to convert afterwards to Gregorian if needed
0253         int g = year % 19;
0254         int i = ((19 * g) + 15) % 30;
0255         int j = (year + (year / 4) + i) % 7;
0256         int l = i - j;
0257         int month = 3 + ((l + 40) / 44);
0258         int day = l + 28 - (31 * (month / 4));
0259 
0260         if (m_parseCalendar.calendarSystem() == QCalendarSystem::JulianCalendar) {
0261             return QDate::fromJulianDay(julianDay(year, month, day));
0262         }
0263 
0264         if (m_parseCalendar.calendarSystem() == QCalendarSystem::GregorianCalendar) {
0265             setParseCalendar(QStringLiteral("julian"));
0266             int paschaJd = julianDay(year, month, day);
0267             setParseCalendar(QStringLiteral("gregorian"));
0268             return QDate::fromJulianDay(paschaJd);
0269         }
0270     }
0271 
0272     return QDate();
0273 }
0274 
0275 QCalendarSystem::CalendarSystem HolidayParserDriverPlan::typeToSystem(const QString &calendarType)
0276 {
0277     if (calendarType == QStringLiteral("gregorian")) {
0278         return QCalendarSystem::GregorianCalendar;
0279     } else if (calendarType == QStringLiteral("hebrew")) {
0280         return QCalendarSystem::HebrewCalendar;
0281     } else if (calendarType == QStringLiteral("hijri")) {
0282         return QCalendarSystem::IslamicCivilCalendar;
0283     } else if (calendarType == QStringLiteral("jalali")) {
0284         return QCalendarSystem::PersianCalendar;
0285     } else if (calendarType == QStringLiteral("julian")) {
0286         return QCalendarSystem::JulianCalendar;
0287     } else if (calendarType == QStringLiteral("coptic")) {
0288         return QCalendarSystem::CopticCalendar;
0289     } else if (calendarType == QStringLiteral("ethiopian")) {
0290         return QCalendarSystem::EthiopicCalendar;
0291     } else if (calendarType == QStringLiteral("indiannational")) {
0292         return QCalendarSystem::IndianNationalCalendar;
0293     }
0294     return QCalendarSystem::GregorianCalendar;
0295 }
0296 
0297 QString HolidayParserDriverPlan::systemToType(QCalendarSystem::CalendarSystem calendar)
0298 {
0299     switch (calendar) {
0300     case QCalendarSystem::GregorianCalendar:
0301         return QStringLiteral("gregorian");
0302     case QCalendarSystem::HebrewCalendar:
0303         return QStringLiteral("hebrew");
0304     case QCalendarSystem::IslamicCivilCalendar:
0305         return QStringLiteral("hijri");
0306     case QCalendarSystem::PersianCalendar:
0307         return QStringLiteral("jalali");
0308     case QCalendarSystem::JulianCalendar:
0309         return QStringLiteral("julian");
0310     case QCalendarSystem::CopticCalendar:
0311         return QStringLiteral("coptic");
0312     case QCalendarSystem::EthiopicCalendar:
0313         return QStringLiteral("ethiopian");
0314     case QCalendarSystem::IndianNationalCalendar:
0315         return QStringLiteral("indiannational");
0316     default:
0317         return QStringLiteral("gregorian");
0318     }
0319 }
0320 
0321 /*************************
0322  * Calculate jd routines *
0323  *************************/
0324 
0325 // Return the jd of an existing event, assumes unique names and correct order in file
0326 int HolidayParserDriverPlan::julianDayFromEventName(const QString &eventName)
0327 {
0328     for (const KHolidays::Holiday &thisHoliday : std::as_const(m_resultList)) {
0329         if (thisHoliday.name() == eventName) {
0330             return thisHoliday.observedStartDate().toJulianDay();
0331         }
0332     }
0333     return -1;
0334 }
0335 
0336 // Return jd of Easter if Gregorian
0337 int HolidayParserDriverPlan::julianDayFromEaster()
0338 {
0339     if (m_eventCalendarType == QLatin1String("gregorian")) {
0340         return m_parseYearEaster.toJulianDay();
0341     } else {
0342         error(QStringLiteral("Can only use Easter in Gregorian event rule"));
0343         return -1;
0344     }
0345 }
0346 
0347 // Return jd of Orthodox Easter if Gregorian or Julian
0348 int HolidayParserDriverPlan::julianDayFromPascha()
0349 {
0350     if (m_eventCalendarType == QLatin1String("gregorian") || m_eventCalendarType == QLatin1String("julian")) {
0351         return m_parseYearPascha.toJulianDay();
0352     } else {
0353         error(QStringLiteral("Can only use Easter in Gregorian or Julian event rule"));
0354         return -1;
0355     }
0356 }
0357 
0358 // Return jd of weekday from a month/day in parse year
0359 int HolidayParserDriverPlan::julianDayFromMonthDay(int month, int day)
0360 {
0361     return julianDay(m_parseYear, month, day);
0362 }
0363 
0364 // Return jd of weekday relative to a Julian Day number
0365 int HolidayParserDriverPlan::julianDayFromRelativeWeekday(int occurrence, int weekday, int jd)
0366 {
0367     if (occurrence == ANY) { /* Should never get this, convert to AFTER instead */
0368         occurrence = AFTER;
0369     }
0370 
0371     int thisWeekday = m_parseCalendar.dayOfWeek(QDate::fromJulianDay(jd));
0372 
0373     /* AFTER actually means on or after */
0374     /* BEFORE actually means on or before */
0375     if (occurrence > 0) {
0376         occurrence = occurrence - 1;
0377     } else if (occurrence < 0 && weekday == thisWeekday) {
0378         occurrence = occurrence + 1;
0379     }
0380 
0381     if (weekday < thisWeekday) {
0382         occurrence = occurrence + 1;
0383     }
0384 
0385     return jd + weekday - thisWeekday + (occurrence * 7);
0386 }
0387 
0388 // Return jd of weekday occurrence in a given month and day in the parse year
0389 int HolidayParserDriverPlan::julianDayFromWeekdayInMonth(int occurrence, int weekday, int month)
0390 {
0391     if (occurrence == LAST) { // Is weekday on or before last day of month
0392         return julianDayFromRelativeWeekday(BEFORE, weekday, julianDay(m_parseYear, month, m_parseCalendar.daysInMonth(m_parseYear, month)));
0393     } else { // Is nth weekday on or after first day of month
0394         return julianDayFromRelativeWeekday(occurrence, weekday, julianDay(m_parseYear, month, 1));
0395     }
0396 }
0397 
0398 /****************************************************
0399  * Set parsed event variables convenience functions *
0400  ****************************************************/
0401 
0402 void HolidayParserDriverPlan::setParseCalendar(const QString &calendarType)
0403 {
0404     m_parseCalendarType = calendarType;
0405     setParseCalendar(typeToSystem(calendarType));
0406 }
0407 
0408 void HolidayParserDriverPlan::setFileCountryCode(const QString &countryCode)
0409 {
0410     m_fileCountryCode = countryCode;
0411 }
0412 
0413 void HolidayParserDriverPlan::setFileLanguageCode(const QString &languageCode)
0414 {
0415     m_fileLanguageCode = languageCode;
0416 }
0417 
0418 void HolidayParserDriverPlan::setFileName(const QString &name)
0419 {
0420     m_fileName = name;
0421 }
0422 
0423 void HolidayParserDriverPlan::setFileDescription(const QString &description)
0424 {
0425     m_fileDescription = description;
0426 }
0427 
0428 void HolidayParserDriverPlan::setEventName(const QString &eventName)
0429 {
0430     // Assume if setting an event name then is start of new event line, so clear categories
0431     m_eventCategories.clear();
0432     m_eventName = eventName;
0433 }
0434 
0435 void HolidayParserDriverPlan::setEventCategory(const QString &category)
0436 {
0437     m_eventCategories.append(category);
0438 }
0439 
0440 void HolidayParserDriverPlan::setEventCalendarType(const QString &calendarType)
0441 {
0442     m_eventCalendarType = calendarType;
0443     if (m_parseMetadataOnly && !m_fileCalendarTypes.contains(calendarType)) {
0444         m_fileCalendarTypes.append(calendarType);
0445     }
0446 }
0447 
0448 void HolidayParserDriverPlan::setEventDate(int eventYear, int eventMonth, int eventDay)
0449 {
0450     m_eventYear = eventYear;
0451     m_eventMonth = eventMonth;
0452     m_eventDay = eventDay;
0453 }
0454 
0455 void HolidayParserDriverPlan::setEventDate(int jd)
0456 {
0457     julianDayToDate(jd, &m_eventYear, &m_eventMonth, &m_eventDay);
0458 }
0459 
0460 /********************************************
0461  * Set event date from event rules routines *
0462  ********************************************/
0463 
0464 /*
0465  * Set event by weekday (Monday..Sunday). The rule expression is
0466  * "every <occurrence> <weekday> of <month> plus <offset> days length <duration> days".
0467  * Occurrence and month can be ANY or LAST, offset and duration are optional.
0468  */
0469 
0470 void HolidayParserDriverPlan::setFromWeekdayInMonth(int occurrence, int weekday, int month, int offset, int duration)
0471 {
0472     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
0473     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
0474         return;
0475     }
0476 
0477     int startMonth, endMonth;
0478     if (month == LAST) {
0479         startMonth = m_parseCalendar.monthsInYear(m_parseYear);
0480         endMonth = startMonth;
0481     } else if (month == ANY) {
0482         startMonth = 1;
0483         endMonth = m_parseCalendar.monthsInYear(m_parseYear);
0484     } else {
0485         startMonth = month;
0486         endMonth = month;
0487     }
0488 
0489     // Generate all events in the required event month(s)
0490     for (int thisMonth = startMonth; thisMonth <= endMonth; ++thisMonth) {
0491         if (m_parseCalendar.isValid(m_parseYear, thisMonth, 1)) {
0492             int startOccurrence, endOccurrence;
0493             if (occurrence == ANY) { // Generate 1st through 5th weekdays, assumes no month with > 35 days
0494                 startOccurrence = 1;
0495                 endOccurrence = 5;
0496             } else { // Generate just nth or LAST weekday
0497                 startOccurrence = occurrence;
0498                 endOccurrence = occurrence;
0499             }
0500 
0501             int jdMonthStart = julianDay(m_parseYear, thisMonth, 1);
0502             int jdMonthEnd = julianDay(m_parseYear, thisMonth, m_parseCalendar.daysInMonth(m_parseYear, thisMonth));
0503 
0504             // Generate each required occurrence of weekday in month, check occurrence actually falls in month
0505             for (int thisOccurrence = startOccurrence; thisOccurrence <= endOccurrence; ++thisOccurrence) {
0506                 int thisJd = julianDayFromWeekdayInMonth(thisOccurrence, weekday, thisMonth);
0507                 if (thisJd >= jdMonthStart && thisJd <= jdMonthEnd) {
0508                     setEvent(thisJd + offset, 0, duration);
0509                 }
0510             }
0511         }
0512     }
0513 }
0514 
0515 /*
0516  * Set event by weekday (Monday..Sunday) relative to a date. The expression is
0517  * "<weekday> <occurrence> <date> plus <offset> days length <duration> days".
0518  * Occurrence, month and day can be ANY or LAST, year can be ANY, offset and duration are optional.
0519  */
0520 
0521 void HolidayParserDriverPlan::setFromRelativeWeekday(int occurrence, int weekday, int offset, int duration)
0522 {
0523     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
0524     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
0525         return;
0526     }
0527 
0528     int thisYear;
0529     if (m_eventYear == ANY) { // Generate the parse year
0530         thisYear = m_parseYear;
0531     } else { // Generate a specific event year
0532         thisYear = m_eventYear;
0533     }
0534 
0535     int startMonth, endMonth;
0536     if (m_eventMonth == LAST) { // Generate just the last month
0537         startMonth = m_parseCalendar.monthsInYear(thisYear);
0538         endMonth = startMonth;
0539     } else if (m_eventMonth == ANY) { // Generate every month
0540         startMonth = 1;
0541         endMonth = m_parseCalendar.monthsInYear(thisYear);
0542     } else { // Generate just the specific event month
0543         startMonth = m_eventMonth;
0544         endMonth = m_eventMonth;
0545     }
0546 
0547     // Generate all events in the required month(s)
0548     int thisMonth;
0549     for (thisMonth = startMonth; thisMonth <= endMonth; ++thisMonth) {
0550         int startDay, endDay;
0551         if (m_eventDay == LAST) { // Generate just the last day in the month
0552             startDay = m_parseCalendar.daysInMonth(thisYear, thisMonth);
0553             endDay = startDay;
0554         } else if (m_eventDay == ANY) { // Generate every day in the month
0555             startDay = 1;
0556             endDay = m_parseCalendar.daysInMonth(thisYear, thisMonth);
0557         } else { // Generate just the specific event day
0558             startDay = m_eventDay;
0559             endDay = m_eventDay;
0560         }
0561 
0562         // Generate all events on the required day(s)
0563         for (int thisDay = startDay; thisDay <= endDay; ++thisDay) {
0564             if (m_parseCalendar.isValid(thisYear, thisMonth, thisDay)) {
0565                 int relativeJd = julianDayFromRelativeWeekday(occurrence, weekday, julianDay(thisYear, thisMonth, thisDay));
0566                 setEvent(relativeJd + offset, 0, duration);
0567             }
0568         }
0569     }
0570 }
0571 
0572 // TODO Figure out how this works :-)
0573 int HolidayParserDriverPlan::conditionalOffset(int year, int month, int day, int condition)
0574 {
0575     /** The encoding of the condition is:
0576           8 lower bits: conditions to shift (bit-register, bit 1=mon, ..., bit 7=sun)
0577           8 higher bits: weekday to shift to (bit-register, bit 1=mon, ..., bit 7=sun)
0578     */
0579 
0580     int offset = 0;
0581 
0582     int weekday = m_parseCalendar.dayOfWeek(year, month, day);
0583 
0584     if (condition & (1 << weekday)) {
0585         /* condition matches -> higher 8 bits contain the possible days to shift to */
0586         int to = (condition >> 8);
0587         while (!(to & (1 << ((weekday + offset) % 7))) && (offset < 8)) {
0588             ++offset;
0589         }
0590     }
0591 
0592     if (offset >= 8) {
0593         offset = 0;
0594     }
0595 
0596     return offset;
0597 }
0598 
0599 /*
0600  * Set event by date. The expression is
0601  * "<date> plus <offset> days shift <condition> length <duration> days".
0602  * Occurrence, month and day can be ANY or LAST, year can be ANY, offset and duration are optional.
0603  */
0604 
0605 void HolidayParserDriverPlan::setFromDate(int offset, int condition, int duration)
0606 {
0607     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
0608     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
0609         return;
0610     }
0611 
0612     int thisYear;
0613     if (m_eventYear == ANY) { // Generate the parse year
0614         thisYear = m_parseYear;
0615     } else { // Generate a specific event year
0616         thisYear = m_eventYear;
0617     }
0618 
0619     int startMonth, endMonth;
0620     if (m_eventMonth == LAST) { // Generate just the last month
0621         startMonth = m_parseCalendar.monthsInYear(thisYear);
0622         endMonth = startMonth;
0623     } else if (m_eventMonth == ANY) { // Generate every month
0624         startMonth = 1;
0625         endMonth = m_parseCalendar.monthsInYear(thisYear);
0626     } else { // Generate just the specific event month
0627         startMonth = m_eventMonth;
0628         endMonth = m_eventMonth;
0629     }
0630 
0631     // Generate all events in the required month(s)
0632     for (int thisMonth = startMonth; thisMonth <= endMonth; ++thisMonth) {
0633         int startDay, endDay;
0634         if (m_eventDay == LAST) { // Generate just the last day in the month
0635             startDay = m_parseCalendar.daysInMonth(thisYear, thisMonth);
0636             endDay = startDay;
0637         } else if (m_eventDay == ANY) { // Generate every day in the month
0638             startDay = 1;
0639             endDay = m_parseCalendar.daysInMonth(thisYear, thisMonth);
0640         } else { // Generate just the specific event day
0641             startDay = m_eventDay;
0642             endDay = m_eventDay;
0643         }
0644 
0645         // Generate all events on the required day(s)
0646         for (int thisDay = startDay; thisDay <= endDay; ++thisDay) {
0647             if (m_parseCalendar.isValid(thisYear, thisMonth, thisDay)) {
0648                 setEvent(julianDay(thisYear, thisMonth, thisDay) + offset, //
0649                          conditionalOffset(thisYear, thisMonth, thisDay, condition), //
0650                          duration);
0651             }
0652         }
0653     }
0654 }
0655 
0656 /*
0657  * Set event relative to Easter. The expression is
0658  * "EASTER plus <offset> days length <duration> days".
0659  * Offset and duration are optional.
0660  */
0661 
0662 void HolidayParserDriverPlan::setFromEaster(int offset, int duration)
0663 {
0664     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
0665     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
0666         return;
0667     }
0668 
0669     if (m_eventCalendarType == QLatin1String("gregorian")) {
0670         setEvent(m_parseYearEaster.toJulianDay() + offset, 0, duration);
0671     } else {
0672         error(QStringLiteral("Can only use Easter in Gregorian event rule"));
0673     }
0674 }
0675 
0676 /*
0677  * Set event relative to Pascha. The expression is
0678  * "PASCHA plus <offset> days length <duration> days".
0679  * Offset and duration are optional.
0680  */
0681 
0682 void HolidayParserDriverPlan::setFromPascha(int offset, int duration)
0683 {
0684     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
0685     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
0686         return;
0687     }
0688 
0689     if (m_eventCalendarType == QLatin1String("gregorian") || m_eventCalendarType == QLatin1String("julian")) {
0690         setEvent(m_parseYearPascha.toJulianDay(), offset, duration);
0691     } else {
0692         error(QStringLiteral("Can only use Pascha in Julian and Gregorian event rule"));
0693     }
0694 }
0695 
0696 // Set the event if it falls inside the requested date range
0697 void HolidayParserDriverPlan::setEvent(int jd, int observeOffset, int duration)
0698 {
0699     // Don't set if only parsing metadata or calendar for event rule is not the current parse calendar
0700     if (m_parseMetadataOnly || m_eventCalendarType != m_parseCalendarType) {
0701         return;
0702     }
0703 
0704     // Date the holiday will be observed on
0705     int observeJd = jd + observeOffset;
0706 
0707     addHoliday(QDate::fromJulianDay(observeJd), duration);
0708 }
0709 
0710 void HolidayParserDriverPlan::addHoliday(const QDate &observedDate, int duration)
0711 {
0712     // Only set if event falls in requested date range, i.e. either starts or ends during range
0713     if (m_parseCalendar.isValid(observedDate) //
0714         && observedDate <= m_requestEnd //
0715         && observedDate.addDays(duration - 1) >= m_requestStart) {
0716         KHolidays::Holiday holiday;
0717         holiday.d->mObservedDate = observedDate;
0718         holiday.d->mDuration = duration;
0719         holiday.d->mName = m_eventName;
0720         holiday.d->mDescription = m_eventName;
0721         holiday.d->mCategoryList = m_eventCategories;
0722         if (m_eventCategories.contains(QStringLiteral("public"))) {
0723             holiday.d->mDayType = KHolidays::Holiday::NonWorkday;
0724         } else {
0725             holiday.d->mDayType = KHolidays::Holiday::Workday;
0726         }
0727         m_resultList.append(holiday);
0728     }
0729 }