data/RecurrenceRule.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QDateTime> | ||
| 4 | #include <QList> | ||
| 5 | #include <QString> | ||
| 6 | #include <QStringList> | ||
| 7 | |||
| 8 | // RFC 5545 §3.3.10 recurrence rules (Sprint 93 S93.1.4, CAL-RRULE). | ||
| 9 | // | ||
| 10 | // parse()/toString() round-trip every rule part; unknown parts are kept in | ||
| 11 | // `unknownParts` so a rule MailJD cannot fully interpret is never rewritten | ||
| 12 | // (the editor keeps it, S93.1.5). expandNaive() iterates occurrences on | ||
| 13 | // wall-clock times: the caller converts its DTSTART into the event's zone, | ||
| 14 | // expands, and converts every result back (RecurrenceExpansion.cpp). That | ||
| 15 | // keeps a 10:00 series at 10:00 across DST changes (S93.1.3). | ||
| 16 | struct RecurrenceRule { | ||
| 17 | enum class Frequency { None, Secondly, Minutely, Hourly, Daily, Weekly, Monthly, Yearly }; | ||
| 18 | struct WeekdayNum { | ||
| 19 | 813 | int ordinal = 0; // 0 = every such weekday; +n/-n = nth/nth-last in month or year | |
| 20 | 810 | int weekday = 1; // ISO: 1 = Monday … 7 = Sunday | |
| 21 |
3/4✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 4 taken 810 times.
✓ Branch 4 → 5 taken 810 times.
✗ Branch 4 → 6 not taken.
|
813 | bool operator==(const WeekdayNum &) const = default; |
| 22 | }; | ||
| 23 | |||
| 24 | Frequency frequency = Frequency::None; | ||
| 25 | int interval = 1; | ||
| 26 | int count = 0; // 0 = unbounded | ||
| 27 | QString until; // wire form (yyyyMMdd or yyyyMMddTHHmmss[Z]); empty = none | ||
| 28 | QList<int> bySecond, byMinute, byHour; | ||
| 29 | QList<WeekdayNum> byDay; | ||
| 30 | QList<int> byMonthDay, byYearDay, byWeekNo, byMonth, bySetPos; | ||
| 31 | int weekStart = 1; // WKST, ISO weekday | ||
| 32 | bool weekStartGiven = false; | ||
| 33 | QStringList unknownParts; // "KEY=VALUE" parts MailJD does not interpret | ||
| 34 | bool valid = false; | ||
| 35 | QString error; // parse error when !valid | ||
| 36 | |||
| 37 | static RecurrenceRule parse(const QString &text); | ||
| 38 | // Canonical serialization (FREQ first). Unknown parts are appended as-is. | ||
| 39 | QString toString() const; | ||
| 40 | // True when every part is interpreted by expandNaive(). | ||
| 41 | bool fullySupported() const; | ||
| 42 | 2831 | bool hasUntil() const { return !until.isEmpty(); } | |
| 43 | 358 | bool untilIsDate() const { return until.size() == 8; } | |
| 44 |
1/1✓ Branch 4 → 5 taken 82 times.
|
82 | bool untilIsUtc() const { return until.endsWith(QLatin1Char('Z')); } |
| 45 | // UNTIL as a naive wall-clock stamp (Qt::UTC used as "no zone"); date-only | ||
| 46 | // values end at the given time of day. | ||
| 47 | QDateTime untilNaive(const QTime &dateOnlyTime) const; | ||
| 48 | |||
| 49 | static QString frequencyName(Frequency frequency); | ||
| 50 | static Frequency frequencyFromName(const QString &name); | ||
| 51 | static QString weekdayToken(int isoWeekday); | ||
| 52 | static int weekdayFromToken(const QString &token); | ||
| 53 | }; | ||
| 54 | |||
| 55 | // Result of a naive expansion. All QDateTime values are wall-clock stamps | ||
| 56 | // carried in Qt::UTC (no zone arithmetic happens inside the engine). | ||
| 57 | struct NaiveExpansion { | ||
| 58 | QList<QDateTime> starts; // ascending, DTSTART first when inside the window | ||
| 59 | bool partial = false; // the rule could not be expanded completely | ||
| 60 | bool truncated = false; // the iteration cap stopped the expansion | ||
| 61 | bool limited = false; // `maxStarts` was reached and more starts exist (Sprint 102) | ||
| 62 | }; | ||
| 63 | |||
| 64 | // Expands `rule` from `start` (always the first occurrence, RFC 5545 | ||
| 65 | // §3.3.10: "DTSTART ... counts as the first occurrence"). Only occurrences | ||
| 66 | // in [windowStart, windowEnd] are returned, but COUNT counts every one. | ||
| 67 | // `until` (naive) bounds the series; invalid = no UNTIL. Unsupported rules | ||
| 68 | // return only `start` with partial = true. `maxStarts` > 0 stops after that | ||
| 69 | // many starts at or after `countFrom` (starts before it, a display margin, | ||
| 70 | // do not count) and sets `limited` when another one exists. | ||
| 71 | NaiveExpansion expandNaive(const RecurrenceRule &rule, const QDateTime &start, | ||
| 72 | const QDateTime &windowStart, const QDateTime &windowEnd, | ||
| 73 | const QDateTime &until = {}, qsizetype maxStarts = 0, | ||
| 74 | const QDateTime &countFrom = {}); | ||
| 75 | |||
| 76 | // Human description in the current UI language, e.g. "Every 2 weeks on | ||
| 77 | // Monday and Wednesday, until 31 Dec 2026". `start` supplies the implicit | ||
| 78 | // day/weekday/month of rules without BY parts. Empty for an invalid rule. | ||
| 79 | QString describeRecurrence(const RecurrenceRule &rule, const QDateTime &start); | ||
| 80 |