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