This topic is a detailed technical discussion of the rules governing the generation of variable holiday names.
➢The less technical holiday name variations topic should be your first source of information, unless you have a very specific question or issue.
A variable holiday name consists of a plain name followed by variations such as:
Holiday
>>>2027=Special Holiday
>>>DUP=<hol> Holiday
Each variation has a condition to the left of the equal sign and replacement text to its right. This appendix explains how conditions are matched, which combinations are recognized, and what happens when several variations match.
There is no single universal rule that "the most specific variation wins". Ordinary variations can accumulate in their listed order. Year-qualified variations have a separate priority system. Exact DAY conditions have another special rule, and DEFAULT and ALWAYS are handled separately.
The recurrence rule first produces the holiday's occurrences. Name variations then operate on each occurrence separately.
Calendar conditions test the calculated date passed to the name evaluator: its weekday, day of month, month and Gregorian year. Thus a moved occurrence is tested using its resulting date, including the resulting year if it crosses New Year.
•MOV, DUP and BASE test the occurrence's recorded status. A date does not become a DUP merely because its name says "day in lieu".
•DAY and DUR use the duration information recorded for that occurrence. They describe its position within the generated multi-day holiday, not its day of the month. Use these conditions on occurrences with duration information.
•Weekdays are numbered Monday=1 through Sunday=7. Month abbreviations are JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV and DEC.
Use uppercase keywords and month abbreviations, and write combined conditions without spaces. An unrecognized condition does not provide a useful fallback; do not assume that a plausible-looking combination is understood.
Conditions that can be used without a year
Condition |
Meaning |
1 through 7 |
Weekday: 1 means Monday and 7 means Sunday. |
01 through 31 |
Day of the month: 01 means the first. |
JAN through DEC |
Month. |
MMM_w |
Month and weekday: JAN_5 means a Friday in January. |
MMM_dd |
Month and day: JAN_05 means January 5. |
The number of digits matters. JAN_5 and JAN_05 are different conditions.
Condition |
Meaning |
MOV |
A moved occurrence. |
DUP |
An additional, duplicated occurrence. |
BASE |
The base occurrence marked as such by the recurrence rule. |
MOV_w / DUP_w |
Moved/duplicated occurrence on weekday w. |
MOV_dd / DUP_dd |
Moved/duplicated occurrence on day dd of the month. |
MOV_MMM / DUP_MMM |
Moved/duplicated occurrence in month MMM. |
Examples: DUP_1, DUP_01, DUP_DEC, MOV_5, MOV_25 and MOV_MAY.
Condition |
Meaning |
DAYn |
Day n of the holiday, such as DAY2. |
DAY~n |
Any duration position other than n. |
DAY<n |
Position before n: DAY<3 means days 1 and 2. |
DAY>n |
Position after n: DAY>3 means day 4 onward. |
DURn/t |
Day n of a holiday whose recorded total duration is t. |
For example, DUR2/3 tests both "second day" and "three days in total". It is more restrictive than DAY2, but it does not automatically outrank DAY2. The special treatment of exact DAYn conditions is described in section 7.
The following year conditions may stand alone:
Condition |
Meaning |
YYYY |
That Gregorian year. |
YYYY..ZZZZ |
Inclusive range YYYY through ZZZZ. |
YYYY+ |
YYYY and subsequent years. |
YYYY- |
YYYY and preceding years. |
YYYY+L |
Leap years from YYYY onward. |
YYYY+C |
Common years from YYYY onward. |
YYYY+Lw |
Leap years from YYYY onward whose January 1 is weekday w. |
YYYY+Cw |
Common years from YYYY onward whose January 1 is weekday w. |
For example:
Example |
Meaning |
2027 |
All occurrences dated in 2027. |
2027..2030 |
All occurrences dated from 2027 through 2030. |
2024+L |
Leap years from 2024 onward. |
2023+C6 |
Common years from 2023 onward beginning on Saturday. |
In the last example, 6 tests the weekday of January 1, not the weekday of the holiday itself.
Use the documented four-digit year range 2000–9999. This is a year test, not an alternative calendar date: a holiday calculated from another calendar still uses the Gregorian year of the occurrence here.
Which combinations are recognized?
Any of the year conditions in section 3 can prefix any ordinary condition in section 2, separated by one underscore. The ordinary condition can itself be one of the recognized compound forms.
Recognized examples:
Recognized combination |
Meaning |
2027_5 |
Friday occurrence in 2027. |
2027_05 |
Occurrence on the fifth of a month in 2027. |
2027_JAN |
Occurrence in January 2027. |
2027_JAN_5 |
Friday occurrence in January 2027. |
2027_JAN_05 |
Occurrence on January 5, 2027. |
2027_DUP |
Duplicated occurrence in 2027. |
2027_DUP_1 |
Duplicated Monday occurrence in 2027. |
2027_DUP_DEC |
Duplicated occurrence in December 2027. |
2027_BASE |
Base occurrence in 2027. |
2027_DAY2 |
Second duration day in 2027. |
2027..2030_DUR2/3 |
Second of three duration days in 2027–2030. |
2024+L_MOV_MAR |
Moved March occurrence in leap years from 2024 onward. |
2023+C6_5 |
Friday occurrence in a qualifying common year whose January 1 is Saturday. |
The underscore is not a general-purpose AND operator. Conditions cannot be combined arbitrarily.
These combinations are NOT recognized by this implementation:
Unsupported combination |
Reason |
BASE_1 / BASE_25 / BASE_DEC |
BASE does not accept a weekday, day-of-month or month suffix. |
DUP_DEC_1 |
DUP accepts one qualifier, not a month plus weekday. |
MOV_JAN_05 |
MOV does not accept a combined month-and-day qualifier. |
JAN_DUP |
The supported order is DUP_JAN. |
DAY2_DUP / DAY2_JAN |
Duration cannot be joined to status or month in this way. |
2027_DEFAULT / 2027_ALWAYS |
DEFAULT and ALWAYS are standalone control keywords. |
2027-L / 2027-C |
Leap/common-year qualifiers exist for +, not -. |
Adding a year prefix does not make an unsupported suffix valid. For example, 2027_BASE_1 and 2027_DUP_DEC_1 are also unsupported.
There is no general Boolean expression syntax: no parentheses, AND, OR, or arbitrary negation. DAY~n is a specifically implemented duration condition.
Ordinary variations: listed order and <hol>
Evaluation begins with the plain name. Matching ordinary variations are processed in their listed order.
A matching variation without <hol> replaces the current name. A matching variation containing <hol> incorporates the current name.
Example:
Holiday
>>>JAN=<hol> in January
>>>5=<hol> on Friday
For a Friday occurrence in January, the result is:
Holiday in January on Friday
Changing the last line to:
>>>5=Friday Holiday
makes the result:
Friday Holiday
There is no automatic specificity ordering between JAN, JAN_5, 5, 01, MOV, DUP, BASE or DUR. For example, a later JAN variation can replace the result of an earlier JAN_5 variation. Put broader variations first and intended refinements later, or use self-contained replacement text where appropriate.
Important: the year-priority system does not prevent a later ordinary variation from changing a year-specific name.
Example:
Holiday
>>>2027=Special Holiday
>>>5=Friday Holiday
On a Friday in 2027, the result is Friday Holiday. Reversing those two lines makes the result Special Holiday.
Use <hol> only once in a replacement. In the current implementation only the first occurrence is substituted; further occurrences remain literal at this stage.
Year-qualified variations: priority order
A year-qualified condition must match completely, including its ordinary suffix if present, before it can claim a priority.
Lower numbers below mean higher priority:
Priority |
Condition category |
Example |
1 |
Exact year with suffix |
2027_DUP |
2 |
Exact year alone |
2027 |
3 |
Inclusive year range with suffix |
2027..2030_DUP |
4 |
Inclusive year range alone |
2027..2030 |
5 |
Leap year + January 1 weekday + suffix |
2024+L4_DUP |
6 |
Leap year + January 1 weekday alone |
2024+L4 |
7 |
Common year + January 1 weekday + suffix |
2023+C6_DUP |
8 |
Common year + January 1 weekday alone |
2023+C6 |
9 |
Leap year from minimum, with suffix |
2024+L_DUP |
10 |
Leap year from minimum, alone |
2024+L |
11 |
Common year from minimum, with suffix |
2023+C_DUP |
12 |
Common year from minimum, alone |
2023+C |
13 |
Minimum year with suffix |
2027+_DUP |
14 |
Minimum year alone |
2027+ |
15 |
Maximum year with suffix |
2030-_DUP |
16 |
Maximum year alone |
2030- |
Leap-year and common-year rows cannot compete for the same occurrence, but the list gives the ranks assigned by the implementation.
Within each category, "with suffix" has priority over "alone". The complexity of the suffix does not create further priority levels. For example, 2027_JAN and 2027_JAN_5 both have rank 1 when they match.
The priority does not depend on how recent a minimum year is, how narrow a year range is, or how close an upper limit is.
Examples:
•2027 beats 2020+.
•2027..2030 beats 2020+.
•A matching 2027_DUP beats 2027.
•A non-matching 2027_DUP does not prevent 2027 from applying.
•A matching 2020+ beats a matching 2030-, regardless of line order.
•2020+ and 2025+ have equal rank.
•2020..2030 and 2027..2028 have equal rank.
In particular, matching minimum-year conditions (YYYY+) rank above matching maximum-year conditions (YYYY-), both with and without a suffix. This is a category priority, not a comparison of the numerical year limits.
Do not use overlapping equal-rank conditions as if the newest or narrowest one automatically wins.
The first pass processes the variations in line order and records the first matching year variation at the best rank reached so far.
• A later lower-priority year variation is skipped.
• A later higher-priority year variation is applied and becomes the new recorded best match.
• A later equal-priority year variation is also applied, but does not replace the recorded best match.
If a higher-priority match was discovered after an earlier year match, the evaluator performs a second pass. It starts again with the plain name and processes the ordinary variations, but admits only the recorded best year-qualified line.
This removes the text contributed by earlier, lower-priority year matches. The chosen year-qualified line still runs at its original position relative to ordinary variations.
Equal-priority conditions therefore have a significant implementation detail: if no second pass is required, multiple equal-priority year matches can accumulate. If a second pass is required, only the first matching line at the best rank is retained.
For an occurrence in 2027:
Holiday
>>>2020..2030=<hol> A
>>>2027..2028=<hol> B
produces Holiday A B.
But:
Holiday
>>>2000+=General
>>>2020..2030=<hol> A
>>>2027..2028=<hol> B
produces Holiday A in the current implementation. The improvement from a minimum-year condition to a range triggers the second pass, which retains the first matching range.
For predictable names, avoid competing equal-rank year conditions or make their date conditions mutually exclusive.
Exact DAY conditions and broader DAY conditions
An exact DAYn match has special treatment relative to DAY~n, DAY<n and DAY>n.
Before evaluating names, Q++Studio looks for a matching unqualified exact DAYn line or a current-exact-year DAYn line.
For the second day of a holiday, either of these can trigger that recognition:
DAY2
2027_DAY2 (when the occurrence is in 2027)
If found, the broader DAY~n, DAY<n and DAY>n branches are suppressed, even when one of those broader variations appears earlier in the list.
Example, on day 2:
Festival
>>>DAY>1=Later Day
>>>DAY2=Second Day
The result is Second Day. Reversing these two lines still gives Second Day.
This is not a universal priority for all duration conditions:
• DUR2/3 is not suppressed by DAY2.
• A weekday, month or other ordinary condition can still change the name.
• A DAY2 line is still subject to year priority if it has a year prefix.
The preliminary scan recognizes an exact current-year prefix, but does not strip year ranges or + / - prefixes. Thus 2020+_DAY2 is first recognized during normal evaluation, when reached, rather than in the preliminary scan.
When an exact DAYn match is encountered during evaluation, the flag suppressing broader DAY conditions is also set. That flag is not reset between the first and second passes.
Consequently, combinations of year-qualified DAY conditions, broad DAY conditions and competing year priorities can have effects that are not described by a simple "most specific line wins" rule. Prefer a straightforward exact DAYn arrangement and avoid relying on this interaction.
DEFAULT is a fallback, not an initial replacement for the plain name.
If no ordinary or year-qualified variation is applied in the relevant pass, the DEFAULT replacement is used. Its <hol> refers to the plain name.
Holiday
>>>DEFAULT=Ordinary Holiday
>>>5=Friday Holiday
A Friday occurrence uses Friday Holiday. An occurrence on another weekday uses Ordinary Holiday.
Moving DEFAULT above or below the other lines does not change this behavior. It is remembered during the scan and used only afterward, if no condition matched. If several DEFAULT lines exist, the last one supplies the fallback; use only one.
DEFAULT is not a supported suffix and cannot be restricted using 2027_DEFAULT.
ALWAYS is a final replacement, rather than another ordinary matching line. Its position in the list does not give it ordinary line-order behavior.
Unless deletion has been detected at the end of the relevant variation pass, the evaluator eventually replaces the name with the right-hand side of ALWAYS.
Use a complete, fixed name:
>>>ALWAYS=Easter Holidays
The current implementation assigns this text literally. It does not expand <hol> inside ALWAYS, and the assignment occurs after the local token-processing stage in this function. Do not use ALWAYS as a way to accumulate text.
If several ALWAYS lines exist, the last one supplies the final replacement; use only one. Forms such as 2027_ALWAYS are not recognized.
<DELETE>: check the name that remains
<DELETE> is a right-hand-side marker, not a left-side condition.
A matching variation such as >>>JAN=<DELETE> replaces the current holiday name with the literal marker <DELETE>. Consequently, a later <hol> expands to that marker, or to the accumulated text containing it. A later matching variation that bypasses <hol> supplies an independent replacement and is not affected by that earlier deletion marker.
This follows the same replacement model as ordinary text: the name produced so far is retained only when the next replacement incorporates <hol>. Deletion is not an irreversible action at the moment the marker is assigned.
At the end of each variation pass, the evaluator checks whether the current name contains <DELETE>. If it does, the name is reduced to <DELETE> and ALWAYS is prevented from overriding that deletion in that pass.
However, encountering <DELETE> does not immediately stop the loop and is not remembered in a separate permanent "deleted" flag. A later replacement that does not preserve the current name can remove it before the end-of-pass test.
Example, on a Friday in January:
Holiday
>>>JAN=<DELETE>
>>>5=Friday Holiday
The resulting name is Friday Holiday at the variation stage: the second line replaced the deletion marker.
If the last line instead reads:
>>>5=<hol> on Friday
the deletion marker survives within the current name, and the final end-of-pass test reduces that name to <DELETE>.
The statement "<DELETE> takes precedence over ALWAYS" therefore applies when <DELETE> is still present when the deletion check runs. It does not mean that every matching deletion line is immune to subsequent replacements.
To make a deletion reliable, ensure that subsequent applicable variations cannot replace it with ordinary text. For year-qualified deletions, also check whether the line survives the year-priority selection.
Automatic annotations and abbreviations
Some callers request automatic additions such as "(day in lieu)", "(celebration)" or "(day n of t)". These options are processed after the main variation passes; they are not additional left-side keywords or extra priority levels.
Explicit status/duration branches can suppress the corresponding automatic annotations. In the current implementation, some branches disable an annotation even when their full condition does not match, and changes to these options can survive into a second pass. For diagnosing the exact final text, the caller's options therefore matter too.
ALWAYS is assigned after these automatic additions and the local token-processing stage, so it replaces their result as well.
Holiday abbreviations use the same variation evaluator with <abr> instead of <hol>. The abbreviation wrapper passes no automatic-annotation options. The same condition syntax, priorities and line-order considerations apply.
If a name is unexpected, check:
1. The occurrence's final date and its recorded MOV/DUP/BASE/duration status.
2. Uppercase spelling and the number of digits: 5 differs from 05.
3. Whether the complete left-side combination is supported.
4. Which ordinary lines match, and the order in which they appear.
5. Whether a replacement keeps <hol> or discards the current name.
6. The rank of every completely matching year-qualified condition.
7. Equal-rank overlaps and whether a second pass is triggered.
8. An exact DAYn condition suppressing broader duration conditions.
9. DEFAULT being only a fallback, and ALWAYS being a final replacement.
10. Whether a later replacement removes an earlier <DELETE>.
11. Automatic annotations requested by the caller.
For routine rules, the shorter Variable Holiday Names topic remains the recommended starting point.
See also: variable holiday names, variable holiday abbreviations, holiday duration, weekday recurrence settings.
Topic 190105, last updated on 24-Oct-2026