Please enable JavaScript to view this site.

 

Navigation: Appendices > Q++Studio > XML

Holiday XML — Field-by-Field Mapping (In Progress)

Scroll Prev Up Next More

Status and agreed scope

 

Implementation progress — 3 November 2026. cHolidayXML.pas now supports all 22 active rule types exposed by the supplied holiday editor. Olivier has compiled and exercised the codec locally, and reports successful round trips for every family. Database integration and the full XML-loaded generation comparison remain pending. This topic remains In Progress; serialization coverage is not a claim that production persistence has been switched to XML.

 

Scope: a complete recurring holiday, including its name, abbreviations, presentation, recurrence settings, observances, date lists and exceptions. THolidaysSetRec and external calendar/location records remain separately stored resources; a holiday contains references to them, not embedded copies of their full definitions. One-time dates in OneTimeDates are outside this THoliday format.

 

Required production integration (not yet connected): XV4 reads the legacy fields/BLOBs when the XML memo is SQL NULL, empty, or contains only spaces, tabs and line breaks; otherwise it reads XML. Nonblank malformed XML is an error and never falls back to legacy fields. This selection does not use TScript.LoadVersion. The original nonblank text is passed to the reader without trimming its payload. Every normal save writes XML and legacy data from the same validated holiday. XE3 only reads the worldwide database and continues using the legacy representation. Existing calculation semantics are retained.

 

Diary generation already visits every rule before discovering date-less years. The optional conversion/synchronization operation can therefore run at the per-rule load stage in that existing traversal, including rules with no dates. It must also refresh legacy fields from already-present XML, so SQL/regex XML edits reach the XE3 updater. There is no need to invent a second traversal solely for coverage.

 

The tables retain the complete source-field inventory and describe the current XML mapping, with remaining compatibility questions explicitly marked. The XML format separates settings from the binary layout of THolidayRule. The existing variant record is still used internally; replacing it later must retain an explicit legacy conversion for XE3.

 

Completed tests and next acceptance check

 

The harness loads a complete temporary THoliday from the database (including ProvinceID), updates its sorting keys and serializes it to CurrentHoliday. It creates a different THoliday, supplies ID, CountryID, DateModified, RN1 and RN2 as destination context, reads the XML and serializes that object to Restored. XMLText and TStringList.Text are reassigned before the second write. These are two object serializations, not two writes of the same saved string.

 

Direct LocalName and Abreviation comparisons test the actual in-memory text. Binary comparison of paired files checks canonical save → read → save stability. The five context fields are copied, not tested as XML-restored data. Matching XML cannot detect a setting omitted by both writer and reader, and does not independently prove identical calculated occurrences.

 

Reported local results: the earlier full main-database batch of 8,907 pairs matched binary-wise; the French client database, including school-vacation date ranges, also matched. Subsequent family tests completed through Vietnamese, Hindu, Tibetan/Bhutanese/Mongolian, Burmese, Egyptian, Zoroastrian, Persian, Sikh, SouthEastAsian, Sun and Lunisolar. All 22 active families have passed individual tests. A final combined run using the latest code remains a separate regression step.

 

For acceptance, retain a legacy-loaded baseline and regenerate the worldwide holidays for 2023–2055 from stored XML, comparing approximately 130,000 dates and names. Use the same input database and generation settings, with no intervening holiday edits. Confirm every tested rule loads nonblank XML so fallback cannot conceal missing conversion. Use empty comparison folders to exclude stale files. Verify regenerated legacy storage for XE3 separately.

 

Approximate sorting keys: UpdateRn1Rn2Values

 

THoliday.UpdateRn1Rn2Values extracts the RN1/RN2 derivation from the form. Call it before complete persistence; SaveToXML itself must remain a serialization operation. These keys approximate chronological display order, so base calendar dates are sufficient: weekend moves, conflict handling and exceptions need not be applied.

 

The revised hrEgypt and hrZoroastrian branches use the direct calendar conversion routines from FCalculateFor, with the current year, instead of calling CalculateFor. This avoids populating DatesArray through full generation. Initialize gDateOfEgyptian and gDateOfZoroastrian to zero before their subtype cases. Use zoMonth/zoDay in the Parsi guard; the former egMonth/egDay names only worked because of shared variant-record positions. The final Rule.AddDaysToRule adjustment remains once at the end of the method. Olivier will apply the initialization corrections; they are not recorded as verified here.

 

Clearing inactive date collections remains in the UI save preparation where appropriate, not in the sorting helper. XML reads initialize inactive storage explicitly. This keeps sorting estimation separate from editing rule data and calculating occurrence arrays.

 

Document conventions and root layout

 

Root: holiday, with schemaVersion="1". This documentation revision does not change the schema version. Child order is deterministic on writing; readers locate singleton elements by exact name. Repeated text blocks retain document order. Dots are literal XML-name characters: use camelCase within concepts and dots for ownership/hierarchy. Common defaults are explicit; only the active rule family, subtype and reference branch are serialized.

 

Booleans are true/false. Enum values use fixed, case-sensitive English tokens maintained in explicit conversion tables, never UI captions or Ord-to-text shortcuts. Dates use YYYY-MM-DD in the Gregorian calendar for actual dates; calendar-specific month/day numbers remain in their named calendar branch. Integers use decimal without grouping. Longitude values use invariant decimal degrees with exactly three fractional digits, converted exactly to/from the existing integer thousandths. Calendar/location IDs are references, with 0 meaning the set default where that is the existing convention.

 

Structural sections under holiday: holiday.identity, holiday.name, holiday.abbreviation, holiday.presentation, holiday.synchronization, observance, rule, and info. Common rule groups include validity, frequency, occurrences, offset, duration, calendarSources, weekdays, conflict and exceptions where applicable, followed by the active family. A table entry listing several dotted leaves means separate elements, not a combined string.

 

Unknown authoritative elements, duplicate singleton elements, missing required fields, unrecognized enum tokens and unsupported schema versions are errors. Only whitelisted informational elements are ignored; an arbitrary info="true" attribute cannot make an authoritative setting optional. Empty lists are explicit. Two documented schema-1 compatibility defaults apply to older XML: missing observance.noneSpecified is derived from the real flags, and missing observance.legacyPreviousEve defaults to false.

 

Names, abbreviations and multiline text

 

Correct boundary rule: a line begins a variation only when its first three characters are >>>. The reference name substituted for <hol> can contain several #7-separated lines. A #7 alone does not mark a variation. Split the stored text into lines while preserving empty lines, including leading/trailing empty lines; gather the initial lines as the base name, then gather each >>>-initiated block until the next such line. Do not trim, reorder, deduplicate or interpret the variation language during serialization.

 

Implemented lossless structure: base contains one base.line node per #7-delimited line. Each variation is a separate nameVariation (or abbreviationVariation) node containing one or more line nodes. Its first line retains the original >>>condition=value text. Subsequent lines and their order are retained verbatim. The same structure is applied separately to names and abbreviations.

 

<holiday.name>
 <holiday.name.base>
 <holiday.name.base.line><![CDATA[First line of reference name]]></holiday.name.base.line>
 <holiday.name.base.line><![CDATA[Second line of reference name]]></holiday.name.base.line>
 </holiday.name.base>
 <holiday.name.variations>
 <holiday.nameVariation>
 <holiday.nameVariation.line><![CDATA[>>>2023..2025=Earlier name]]></holiday.nameVariation.line>
 </holiday.nameVariation>
 <holiday.nameVariation>
 <holiday.nameVariation.line><![CDATA[>>>DUP=<hol> Holiday]]></holiday.nameVariation.line>
 </holiday.nameVariation>
 </holiday.name.variations>
</holiday.name>

 

Reconstruction joins all base and variation lines with #7 in their original order. An empty name is one empty base.line, and no variations is an empty variations container. A literal >>> inside a line is ordinary text. A nonempty legacy value starting with >>> before any base separator is an edge case for the existing evaluator: identify it during migration rather than silently inserting a leading #7 or changing its interpretation.

 

CDATA is preferred for free-text line payloads. Tabs and XML-legal whitespace inside a line remain text; #7 is represented by node boundaries. Other forbidden control characters must be reported with their code point and field, not emitted as invalid XML or silently removed. A later explicit character-node convention can be added if real data requires it. XML newline normalization means literal CR/CRLF within a line needs an explicit preservation policy; detect it in the migration audit instead of assuming that it is #7.

 

Xml.VerySimple.pas: verified support and integration requirements

 

Verified in the supplied unit: ntCData exists; ParseCData creates a child node containing the payload; Walk writes that node as a CDATA section. TXmlNode.Text is a stored property, not a computed concatenation of descendant text. A holiday text helper must therefore read/write the CDATA children explicitly. Find/FindNodes default to ntElement and do not select CDATA unless the requested types include it.

 

Walk concatenates CDATA delimiters around Node.Text without splitting an embedded ]]> terminator. A dedicated writer must split such payloads into adjacent CDATA sections, and the reader must concatenate those sections in order. For example, the text a]]>b can be written as <![CDATA[a]]]]><![CDATA[>b]]>. Do not read only the first CDATA child.

 

With indented output, Walk may insert a formatting newline before the end tag of an element containing CDATA. The line helper must distinguish structural indentation from payload. Canonical free-text line elements use CDATA-only payload children; ignore whitespace-only formatting outside those CDATA children and reject mixed substantive text/child elements. The implementation writes empty payloads as self-closing line elements. This avoids depending on a parser retaining an empty CDATA node while still preserving empty logical lines. Ordinary escaped text is also accepted; substantive ordinary text mixed with CDATA is rejected. Payload is not blanket-trimmed.

 

The supplied Escape/Unescape code has a custom control-character convention. Escape converts #7 to &#007; and subsequently escapes the ampersand, so the wire form is &amp;#007;. Unescape reverses this convention. That is not a standards-based representation of #7 and can collide with literal strings such as &#007;. The new holiday text helpers should use the structural line representation and CDATA rather than depend on this convention. Do not change the shared library globally as part of the holiday feature without regression testing scripts.

 

The supplied ParseTag closing-tag branch moves to Parent.Parent without comparing the closing tag name. Successful parsing by this unit is therefore not sufficient proof of well-formed XML. Before using the tree, add strict well-formedness validation, or explicitly harden the parser with regression coverage. Also validate the holiday schema and values. This is a requirement of the agreed error handling, not a claim that the entire existing parser has been audited.

 

THoliday: identity, text and presentation

 

Source member / storage

Proposed XML element(s)

Value and conversion

ID

info.holiday.id [info=true]

Database ID_Holiday remains authoritative for identity. The reader treats this as explanatory only and retains the destination identity. Identity/remapping checks belong to the calling edit/copy/import operation. Do not treat an edit to explanatory identity as a key update.

CountryID

info.holiday.setId [info=true]

Owning holidays-set ID (despite the member name). Destination row context supplies it; copying to another set is an explicit operation.

ProvinceID

holiday.identity.provinceId

Preserve the stored numeric association. Load it explicitly: the supplied GetHolidayFromTable does not populate it. Resolve the SmallInt member versus unsigned table-field width before broad import.

AncestorID

holiday.identity.ancestorId

Integer, 0 for no ancestor if that is the current table convention. Validate/remap when synchronizing or copying; preserve normal edit restrictions.

DateModified

info.holiday.modifiedDate [info=true]

Derived from the legacy Observance database field: loader uses Max(35000, 30000 + Observance). This field is a date encoding, not the observance flags. Preserve existing unknown/old-date handling. Only semantic edits update modification bookkeeping.

LocalName

holiday.name.base.line; holiday.nameVariation.line

Ordered structures described above. Complete reference name including multiline #7 content and all variation blocks.

Abreviation

holiday.abbreviation.base.line; holiday.abbreviationVariation.line

Keep the source spelling Abreviation only in this mapping; use abbreviation in XML. Same lossless block rules as names.

Formatting

holiday.presentation.formatting.line

Opaque existing formatting text, with explicit #7-separated line nodes if needed. Do not discard because the main form currently assigns an empty string; its format is outside this source bundle.

ShowFerie

holiday.presentation.watermark.enabled

Boolean; the form treats this as the holiday watermark setting.

AutoBytes

holiday.presentation.symbols.line

Existing symbol/autosticker text, preserved including order. No binary decoding invented; use text helpers and explicit #7 line boundaries if present.

GraphicsPath

holiday.presentation.image.path

Text path, preserved, not dereferenced or embedded. Validate for representability in the legacy memo; no automatic path rewriting.

ImageAlign

holiday.presentation.image.alignment

Confirmed holiday domain: 0 CenterNoStretch, 1 KeepAspectRatio, 2 Stretch, 3 TopLeft. The user confirms that shared enum values 4..6 are not available for holidays. Do not accept SpecificValues, ScaleToFill or None as holiday XML alignment values; report unsupported stored values instead of coercing them.

ImageTransparent

holiday.presentation.image.transparent

Boolean. Legacy field ImageAlign = alignment ordinal + 100 × transparency flag; XML separates these settings.

Priority

holiday.presentation.priority

Zero-based index 0..19, confirmed from cbPriority.Items and Hol.Priority := cbPriority.ItemIndex. The captions display 1..20; XML preserves the stored index.

Ignored

holiday.synchronization.ignored

Boolean. This is a synchronization preference, not a decision to exclude all generated dates. Copy/synchronization policy may override it explicitly.

Kept

holiday.synchronization.kept

Boolean. Preserve the existing synchronization preference; do not reinterpret it as calendar validity.

RuleType

rule.type

Stable family token; numeric mapping appears in the rule-type table. Subtypes remain explicit inside the active family.

Rule

rule.*

Composed fields are mapped individually below; never serialize the raw variant-record buffer.

RuleOptions

rule.* and observance.*

Composed fields are mapped individually below; one authoritative XML location per logical setting.

ArbitraryDates

rule.arbitraryDates.dates / rule.exceptions

Use a date-list structure for hrArbitrary and year-based exception structure for other supported recurring types. Never expose mixed marker/date integers.

DateRanges

rule.ranges.items

List of independent ranges, each with actual start date, duration and seven weekday flags.

RN1; RN2

info.sort.month; info.sort.day [info=true, optional]

Derived approximate sorting values. Recompute in the shared persistence service; do not import as editable recurrence settings.

DatesArray

not serialized

Calculated occurrences, including internal signs/markers, are runtime results. Optional diagnostic previews must be info-only and identify their year/context.

 

Image alignment values — confirmed holiday subset

 

Ordinal

Pascal enum

Proposed XML token

Current holiday editor

0

pbaCenterNoStretch

CenterNoStretch

Center (no stretch)

1

pbaHomeoStretch

KeepAspectRatio

Stretch (keep ratio)

2

pbaFullStretch

Stretch

Full stretch

3

pbaTopLeft

TopLeft

Top-Left (no stretch)

4

pbaSpecificValues

Not permitted

Not supported for holidays

5

pbaScaleToFill

Not permitted

Not supported for holidays

6

pbaNone

Not permitted

Not supported for holidays

 

The stale three-member comment in cHoliday is not a definition of the enum. The actual shared enum contains seven values, but the user confirms that holidays support only ordinals 0..3. This matches the four holiday-editor choices. The holiday XML domain therefore contains only CenterNoStretch, KeepAspectRatio, Stretch and TopLeft. Reject shared enum values 4..6 and all other out-of-domain values in holiday data with an error identifying the holiday and setting. This compatibility question is closed. For detailed image positioning, the user recommends slogans; the linked PictureBox Alignment documentation describes the fuller controls used by slogans, macros and SARAs.

 

Table columns outside THoliday

 

Column(s)

Treatment

NoSite

Database row scope; retain the composite identity checks (NoSite, ID_Holiday). Not an editable setting inside XML.

ReadOnly, Needed

Operational columns not represented by THoliday in the supplied declaration. Keep in database/service context; do not overwrite from a freshly initialized THoliday.

SzRule, SzRuleOptions, SzArbitraryDates, SzDateRanges

Computed by legacy writers. No editable XML equivalents.

BlobRule, BlobRuleOptions, BlobArbitraryDates, BlobDateRanges

Compatibility output only after authoritative XML load; imported through old readers when XML is NULL or blank.

AutoBytesVersion and any schema-specific columns

The supplied table component also declares AutoBytesVersion. Audit the actual data dictionary and other consumers before deciding removal or XML inclusion. Preserve untouched operational columns.

New XML memo

Name and NexusDB field type remain to be selected consistently with script storage. NULL or blank selects legacy storage; a successfully migrated row contains nonblank XML. Add to SELECT, INSERT, UPDATE, cached dataset fields, direct table fields and migration/update schemas.

 

Common rule settings

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.Validity

rule.validity.mode

Always, Beginning, Ending, FromTo, Except ↔ 0, 1, 2, 3, 4. Common node always written; non-applicable families use their existing neutral validity.

Rule.Year1

rule.validity.fromYear / rule.validity.toYear

Beginning: from=Year1. Ending: to=Year1. FromTo/Except: from=Year1. Always: neither bound is active. Use None for inactive XML bounds; never expose Year1 with two undocumented meanings.

Rule.Year2

rule.validity.toYear

FromTo/Except: to=Year2. Other modes: None. Each active bound is 1..9999. Reversed bounds are preserved, not reordered or rejected: FromTo then yields no eligible years; Except excludes no years. Inactive legacy slots are initialized deterministically.

Rule.AddDaysToRule

rule.offset.amount

Signed integer, existing comment specifies -300..300. XML is the amount alone, not the table combined value.

Rule.TypeOfDaysToAddToRule

rule.offset.units

Days, Weekdays, WeekdaysIncludingSaturday ↔ 0,1,2. Legacy table value = 1000 × ordinal + amount. Reader uses the existing +500 unpacking convention.

Rule.HolDuration

rule.duration.amount; rule.duration.units

amount = HolDuration mod 100; units = HolDuration div 100. Tokens Days, Weekdays, WeekdaysIncludingSaturday ↔ 0,1,2. The implementation accepts amount 1..99 and units 0..2. The DFM spin editor allows 300 but the stored value is decoded by mod/div 100; wider intended amounts require a separate compatibility decision. Preserve calculation semantics, including first-day handling. Do not infer that this is an end date.

RuleOptions.OnlyShowFirstOccurrence

rule.occurrences.firstOnly

Boolean; false default.

RuleOptions.HolFreqNumber

rule.frequency.intervalYears

Positive integer; 1 default. Reject zero or negative.

RuleOptions.HolFreqReference

rule.frequency.referenceYear

Integer reference year; 2000 default. Preserve even when interval=1.

RuleOptions.Activated

not editable; legacy normalization

Legacy options representation flag, normally true since 2004. Old readers convert the previous options when necessary. XML reader constructs current options and sets Activated=true; do not expose an off switch that makes settings disappear.

RuleOptions.MuslimID

rule.calendarSources.muslimId

Signed Integer reference preserved exactly, including -1 (undefined) and 0 (set default). Do not resolve or rewrite it during serialization, even when unused by the active family.

RuleOptions.MoonID

rule.calendarSources.moonId

Signed Integer reference preserved exactly, including -1 (undefined) and 0 (set default). Do not resolve or rewrite it during serialization, even when unused by the active family.

RuleOptions.SunID

rule.calendarSources.sunId

Signed Integer reference preserved exactly, including -1 (undefined) and 0 (set default). Do not resolve or rewrite it during serialization, even when unused by the active family.

 

Validity: explicit boundary translation

 

Mode

fromYear

toYear

Legacy fields

Always

None

None

Validity=hvAlways; year slots inactive

Beginning

2023

None

Validity=hvBeginning; Year1=2023

Ending

None

2025

Validity=hvEnding; Year1=2025

FromTo

2023

2025

Validity=hvFromTo; Year1=2023; Year2=2025

Except

2023

2025

Validity=hvExcept; Year1=2023; Year2=2025

 

Weekday actions and conflicts

 

Source member / storage

Proposed XML element(s)

Value and conversion

RuleOptions.DaysAfterIfWeekday

rule.weekdays.<weekday>.offsetDays

Seven separate weekday groups: monday through sunday. Signed day offset; 0 with action=None; legacy 99 for deletion is represented by action=Delete and XML offset=0.

RuleOptions.ActionTypeIfWeekday

rule.weekdays.<weekday>.action

None, Move, Duplicate, Delete. Legacy: offset=0 → None regardless of stale action byte; active 0=Move,1=Duplicate,2=Delete. Delete writes offset=99/action=2. Move/Duplicate must not use zero or the reserved +99 deletion encoding. Emit all seven groups.

RuleOptions.DaysAfterIfConflict

rule.conflict.action; rule.conflict.offset.amount

0 → None; 99 → Delete; otherwise Move with the original signed offset. For None/Delete XML amount=0; reconstruct sentinel only in the legacy adapter.

RuleOptions.TypeDaysIfConflict

rule.conflict.offset.units

0 Days; 1 DaysExcludingHolidays; 2 Weekdays; 3 WeekdaysIncludingSaturday; 4 WeekdaysIncludingSaturdayExcludingHolidays. Legacy -1 maps to LegacyUnspecified only for None/Delete; Move requires specified units.

RuleOptions.ConflictHolId

rule.conflict.target.kind; rule.conflict.target.holidayId; rule.conflict.target.setId

0 → SameSet; positive → Holiday; negative values below -10000 (excluding Low(Integer)) → Set with positive setId. -1 → Unresolved with holidayId=0 and setId=0. Preserve the sentinel, including when action=None; other unsupported negatives are errors.

 

Observance flags

 

Every supported observance flag is explicitly written, including false. The legacy NoneSpecified flag is preserved independently as observance.noneSpecified: real database records can set it together with other flags, and that combination is not rejected or normalized. For older schema-1 XML lacking this element only, the reader derives it from the absence of real observance flags.

 

Source member / storage

Proposed XML element(s)

Value and conversion

RuleOptions.IgnoreObservance

observance.ignore

Boolean. Keep separate from the individual observance settings; preserve the flags even when ignored.

RuleOptions.ObservanceSettings

observance.<distinct-name>

Per-index mapping below. No bitmask, comma-separated code list, or duplicated packed arrays in XML.

 

Array index

XML leaf / disposition

0

observance.noneSpecified (legacy flag preserved independently)

1

[heading] Geographical

2

observance.municipal

3

observance.regional

4

[heading] Categories

5

observance.banks

6

observance.other

7

observance.government

8

observance.linguistic

9

[heading] Times of Day

10

observance.legacyPreviousEve

11

observance.morning

12

observance.afternoon

13

observance.evening

14

[heading] Religious

15

observance.bahai

16

observance.catholic

17

observance.christian

18

observance.coptic

19

observance.buddhist

20

observance.hindu

21

observance.jewish

22

observance.muslim

23

observance.orthodox

24

observance.protestant

25

observance.sikh

26

observance.schools

27

observance.armenian

28

observance.otherReligion

29

observance.webOnly

30

observance.tentative

31

observance.mayDisappear

32

observance.privateSector

33

observance.pontoFacultativo

 

Indices 1, 4, 9 and 14 are reserved headings and must be false. Index 10 is preserved as observance.legacyPreviousEve; older XML without this node defaults to false. Indices 32 and 33 remain distinct flags regardless of their split between legacy packed arrays. NoneSpecified is authoritative compatibility data, not an info-only value.

 

Rule families and subtype tokens

 

Legacy RuleType

rule.type

Subtype / active data

0 hrFixed

Fixed

Gregorian or Julian fixed month/day

1 hrVariable

NthWeekday

Nth weekday of month plus relative day offset

2 hrEaster

Easter

Western/Orthodox and Easter-relative offset

3 hrArbitrary

ArbitraryDates

Explicit dates; occurs-every-year expectation

4 hrJewish

Jewish

Jewish calendar month/day and Adar policy

5 hrRange

DateRanges

Independent ranges with weekdays

6 hrMuslim

Muslim

Muslim month/day and calendar reference

7 hrChinese

Chinese

Lunar=71, SolarTerms=72

8 hrEgypt

Egyptian

Coptic=80, Ethiopic=81, Egyptian=82, Armenian=83

11 hrHindu

Hindu

Lunar=110, Solar=111

13 hrSikh

Sikh

Sikh calendar month/day

14 hrSun

Sun

Solar longitude and day boundary

16 hrLunisolar

Lunisolar

Moon phase relative to solar longitude/month/date

17 hrPersian

Persian

Persian month/day

18 hrKorean

Korean

Lunar=181, SolarTerms=182

19 hrSouthAsian

SouthEastAsian

FullMoon=191, SpecialRule=192, Calendrical=193

20 hrVietnamese

Vietnamese

Lunar=201, SolarTerms=202

21 hrBurmese

Burmese

Lunar=211, NewYearDay=212

22 hrTibetan

Tibetan

Lunar=221, Solar=222

23 hrBhutanese

Bhutanese

Lunar=231, Solar=232

24 hrMongolian

Mongolian

Lunar=241, Solar=242

25 hrZoroastrian

Zoroastrian

ParsiShenshai=250 supported; Kadmi/Fasli unsupported and rejected.

 

The codec covers the 22 active UI rule types. hrOrthodox, hrBahai and hrMoon are not additional active families in the supplied UI; hrConflict=20 aliases hrVietnamese and must not create a second dispatch branch. Zoroastrian Kadmi/Fasli are not implemented calculation choices and are explicitly rejected.

 

Shared weekday-selection component — resolved from PAS and DFM

 

The pasted frmWeekdaysBeforeAfter.pas and DFM resolve the composite encoding, calculation semantics, combo-box index meanings and occurrence bounds. The DFM sets spinNumWkDays.Min=1, Max=99 and Value=1. The supplied DataHolidays DFM does not override those limits in its inherited frame controls, and no spinNumWkDays limit assignment was found in the supplied DataHolidays Pascal source.

 

For each applicable family, write three separate leaves under its uniquely prefixed weekdaySelection group: relation (None, Before, After, OnOrBefore, OnOrAfter), weekday (Monday through Sunday; None when disabled), and occurrence (1 for first, 2 for second, etc.). Do not add a separate inclusivity flag, which would duplicate the meaning of OnOrBefore/OnOrAfter. None uses weekday=None and occurrence=1 as explicit neutral values.

 

Meaning

Legacy value

None

0

Before

-B

After

B

OnOrBefore

-(1000+B)

OnOrAfter

1000+B

 

DFM control / index

Meaning / XML value

cbWeekdayAfter 0: day of

No weekday selection: relation=None, weekday=None, occurrence=1.

cbWeekdayAfter 1..7

Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday.

spinNumWkDays

Integer occurrence 1..99; initial value 1.

rgWeekdayBeforeAfter 0

before → Before

rgWeekdayBeforeAfter 1

after → After

rgWeekdayBeforeAfter 2

on or before → OnOrBefore

rgWeekdayBeforeAfter 3

on or after → OnOrAfter

 

The DFM initially selects relation index 0 (before), but the active relation must always come from WeekdayValue. In particular, SetWeekdayValue(0) selects index 1 (after) while hiding the relation controls. When cbWeekdayAfter is at index 0, the relation and spin controls are irrelevant and GetWeekdayValue returns 0; XML uses the explicit None values above rather than retaining hidden UI state.

 

B = weekdayNumber + 10 × (occurrence - 1), with weekdayNumber Monday=1 through Sunday=7. Decode a nonzero value using A=Abs(value), B=A mod 1000, weekdayNumber=B mod 10, and occurrence=1+(B div 10). Sign selects Before/After; A>=1000 adds OnOr. The XML validation follows the confirmed editor range: weekdayNumber in 1..7 and occurrence in 1..99, giving B<=987 and A<=1987. The arithmetic encoding can also represent occurrence 100, but this lies outside the current editor range. If migration encounters such a stored value, report the holiday and setting for review; do not clamp it to 99 or silently change its calculated date. Reject nonzero encodings with weekday zero, invalid weekday digits, or out-of-domain magnitudes.

 

WeekdayAfterToDate makes Before/After strict: a matching weekday advances or retreats by a full week before adding further occurrences. For OnOrAfter it first subtracts one day from the anchor; for OnOrBefore it first adds one day, then performs the same strict search. This makes the anchor count as the first occurrence when it matches. An inclusive second occurrence is still one week later/earlier when the anchor already matches.

 

Example

Legacy value

Interpretation

Monday after

1

First Monday strictly after the anchor

Monday on or after

1001

First Monday including a matching anchor

Second Monday on or after

1011

Second qualifying Monday; one week after a matching Monday anchor

Fifth Sunday after

47

7 + 10 × (5-1)

Fifth Sunday on or before

-1047

-(1000 + 47)

 

<rule.fixed.weekdaySelection>
 <rule.fixed.weekdaySelection.relation>OnOrAfter</rule.fixed.weekdaySelection.relation>
 <rule.fixed.weekdaySelection.weekday>Monday</rule.fixed.weekdaySelection.weekday>
 <rule.fixed.weekdaySelection.occurrence>2</rule.fixed.weekdaySelection.occurrence>
</rule.fixed.weekdaySelection>

 

The same decoder/encoder is shared by fixed, Jewish, Muslim, Chinese, Korean, Vietnamese, solar and lunisolar family fields. Their XML leaves remain family-specific to support unambiguous SQL/regex changes. This is distinct from the common rule.weekdays actions that move/duplicate/delete an already calculated occurrence.

 

Fixed-date rule

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.Day

rule.fixed.day

Day number in the selected fixed calendar.

Rule.fMonth

rule.fixed.month

Month 1..12.

Rule.fOrthodox

rule.fixed.calendar

false → Gregorian; true → Julian. Preserve the existing conversion, not a fixed 13-day offset.

Rule.WeekdayAfter

rule.fixed.weekdaySelection.relation; rule.fixed.weekdaySelection.weekday; rule.fixed.weekdaySelection.occurrence

Use the resolved shared weekday codec. Legacy 0 maps to relation=None, weekday=None, occurrence=1.

Rule.ImportationYear

rule.fixed.importYear

Preserve the integer provenance value used by the form; 0 remains 0. Meaning of nonzero values should be documented from the import path.

Rule.fDaysAfterIfConflict

no independent XML leaf; common weekday/conflict options

Historical branch-local setting consumed by ConvertToNewRecurrenceOptions. Load through the legacy conversion before export. Current RuleOptions is the single XML authority; keep compatibility output semantics and regression-test old records.

Rule.fDaysAfterIfSaturday

no independent XML leaf; common weekday/conflict options

Historical branch-local setting consumed by ConvertToNewRecurrenceOptions. Load through the legacy conversion before export. Current RuleOptions is the single XML authority; keep compatibility output semantics and regression-test old records.

Rule.fDaysAfterIfSunday

no independent XML leaf; common weekday/conflict options

Historical branch-local setting consumed by ConvertToNewRecurrenceOptions. Load through the legacy conversion before export. Current RuleOptions is the single XML authority; keep compatibility output semantics and regression-test old records.

Rule.fConflictHolId

no independent XML leaf; common weekday/conflict options

Historical branch-local setting consumed by ConvertToNewRecurrenceOptions. Load through the legacy conversion before export. Current RuleOptions is the single XML authority; keep compatibility output semantics and regression-test old records.

Rule.fDupIfSat

no independent XML leaf; common weekday/conflict options

Historical branch-local setting consumed by ConvertToNewRecurrenceOptions. Load through the legacy conversion before export. Current RuleOptions is the single XML authority; keep compatibility output semantics and regression-test old records.

Rule.fDupIfSun

no independent XML leaf; common weekday/conflict options

Historical branch-local setting consumed by ConvertToNewRecurrenceOptions. Load through the legacy conversion before export. Current RuleOptions is the single XML authority; keep compatibility output semantics and regression-test old records.

Rule.fDaysAfterIfMonday

no independent XML leaf; common weekday/conflict options

Historical branch-local setting consumed by ConvertToNewRecurrenceOptions. Load through the legacy conversion before export. Current RuleOptions is the single XML authority; keep compatibility output semantics and regression-test old records.

Rule.fDupIfMon

no independent XML leaf; common weekday/conflict options

Historical branch-local setting consumed by ConvertToNewRecurrenceOptions. Load through the legacy conversion before export. Current RuleOptions is the single XML authority; keep compatibility output semantics and regression-test old records.

 

Nth-weekday rule

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.NthDay

rule.nthWeekday.ordinal

First=1, Second=2, Third=3, Fourth=4, Fifth=5, Penultimate=-1, Last=0. Stable symbolic tokens remove the special negative/zero ordinal encoding.

Rule.Weekday

rule.nthWeekday.day

Monday..Sunday ↔ 1..7.

Rule.vMonth

rule.nthWeekday.month

Month 1..12.

Rule.DaysAfter

rule.nthWeekday.relativeOffsetDays

Signed calendar-day offset relative to the selected weekday. Distinct from the common rule.offset.amount; preserve both independently.

Rule.vDaysAfterIfConflict

no independent XML leaf; rule.conflict.*

Historical conflict fields; normalize using the existing options conversion.

Rule.vConflictHolId

no independent XML leaf; rule.conflict.*

Historical conflict fields; normalize using the existing options conversion.

 

Easter rule

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.DaysAfterEaster

rule.easter.offsetDays

Signed calendar days before/after Easter; weeks in the UI are already converted to days.

Rule.isOrthodox

rule.easter.calendar

Western or Orthodox; false/true respectively.

Rule.OmitIfBothEastersSame

rule.easter.omitWhenSameDate

Boolean; preserve even when false.

Rule.eDaysAfterIfConflict

no independent XML leaf; rule.conflict.*

Historical conflict fields; normalize through the existing reader.

Rule.eConflictHolId

no independent XML leaf; rule.conflict.*

Historical conflict fields; normalize through the existing reader.

 

Jewish and Muslim rules

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.jMonth

rule.jewish.month

Existing Jewish month number, not a Gregorian month. Validate against the calendar helper.

Rule.jDay

rule.jewish.day

Existing Jewish day number.

Rule.MoveAdarOnLeapYears

rule.jewish.adarLeapYearPolicy

AdarI=0, AdarII=1, Omit=2, confirmed from DFM order.

Rule.jWeekdayAfter

rule.jewish.weekdaySelection.relation; rule.jewish.weekdaySelection.weekday; rule.jewish.weekdaySelection.occurrence

Use the resolved shared weekday codec documented above.

Rule.MoveSatToSun

no independent XML leaf; rule.weekdays.*

Historical flag: ConvertToNewRecurrenceOptions translates it. Do not duplicate current weekday policy.

Rule.iMonth

rule.muslim.month

Muslim month number.

Rule.iDay

rule.muslim.day

Muslim day number.

Rule.iWeekdayAfter

rule.muslim.weekdaySelection.relation; rule.muslim.weekdaySelection.weekday; rule.muslim.weekdaySelection.occurrence

Use the resolved shared weekday codec documented above.

Rule.iEntireMonth

rule.muslim.entireMonth

Stored boolean but not visibly used in the supplied current calculator/UI path. Preserve provisionally; investigate non-default records before deciding it is obsolete. Do not equate it with HolDuration without evidence.

Rule.iDaysAfterIfFriday

no independent XML leaf; rule.weekdays.*

Historical weekday setting; normalize through ConvertToNewRecurrenceOptions.

Rule.iDaysAfterIfSaturday

no independent XML leaf; rule.weekdays.*

Historical weekday setting; normalize through ConvertToNewRecurrenceOptions.

Rule.iDaysAfterIfSunday

no independent XML leaf; rule.weekdays.*

Historical weekday setting; normalize through ConvertToNewRecurrenceOptions.

Rule.iDupIfFri

no independent XML leaf; rule.weekdays.*

Historical weekday setting; normalize through ConvertToNewRecurrenceOptions.

Rule.iDupifSat

no independent XML leaf; rule.weekdays.*

Historical weekday setting; normalize through ConvertToNewRecurrenceOptions.

Rule.iDupifSun

no independent XML leaf; rule.weekdays.*

Historical weekday setting; normalize through ConvertToNewRecurrenceOptions.

 

Chinese, Korean and Vietnamese rules

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.chChineseType

rule.chinese.mode

Lunar or SolarTerms, using the exact subtype constants in the family table.

Rule.chlMonth

rule.chinese.lunar.month

Lunar subtype only; calendar month number.

Rule.chlDay

rule.chinese.lunar.day

Lunar subtype only; calendar day number.

Rule.chlLeapMonth

rule.chinese.lunar.leapMonth

Lunar subtype only; preserve boolean.

Rule.chsNthTerm

rule.chinese.solarTerm.number

SolarTerms subtype only; positive term ordinal.

Rule.chsMinorOrMajor

rule.chinese.solarTerm.kind

Minor=0; Major=1. SolarTerms subtype only.

Rule.chWeekdayAfter

rule.chinese.weekdaySelection.relation; rule.chinese.weekdaySelection.weekday; rule.chinese.weekdaySelection.occurrence

Use the resolved shared weekday codec; applies to the active subtype.

Rule.chDaysAfterIfFriday

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.chDaysAfterIfSaturday

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.chDaysAfterIfSunday

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.chConflictHolId

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.chDaysAfterIfConflict

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.chTypeDaysIfConflict

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.chDupIfFri

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.chDupifSat

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.chDupifSun

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.koKoreanType

rule.korean.mode

Lunar or SolarTerms, using the exact subtype constants in the family table.

Rule.kolMonth

rule.korean.lunar.month

Lunar subtype only; calendar month number.

Rule.kolDay

rule.korean.lunar.day

Lunar subtype only; calendar day number.

Rule.kolLeapMonth

rule.korean.lunar.leapMonth

Lunar subtype only; preserve boolean.

Rule.kosNthTerm

rule.korean.solarTerm.number

SolarTerms subtype only; positive term ordinal.

Rule.kosMinorOrMajor

rule.korean.solarTerm.kind

Minor=0; Major=1. SolarTerms subtype only.

Rule.koWeekdayAfter

rule.korean.weekdaySelection.relation; rule.korean.weekdaySelection.weekday; rule.korean.weekdaySelection.occurrence

Use the resolved shared weekday codec; applies to the active subtype.

Rule.koDaysAfterIfFriday

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.koDaysAfterIfSaturday

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.koDaysAfterIfSunday

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.koConflictHolId

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.koDaysAfterIfConflict

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.koTypeDaysIfConflict

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.koDupIfFri

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.koDupifSat

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.koDupifSun

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.vieVietnameseType

rule.vietnamese.mode

Lunar or SolarTerms, using the exact subtype constants in the family table.

Rule.vielMonth

rule.vietnamese.lunar.month

Lunar subtype only; calendar month number.

Rule.vielDay

rule.vietnamese.lunar.day

Lunar subtype only; calendar day number.

Rule.vielLeapMonth

rule.vietnamese.lunar.leapMonth

Lunar subtype only; preserve boolean.

Rule.viesNthTerm

rule.vietnamese.solarTerm.number

SolarTerms subtype only; positive term ordinal.

Rule.viesMinorOrMajor

rule.vietnamese.solarTerm.kind

Minor=0; Major=1. SolarTerms subtype only.

Rule.vieWeekdayAfter

rule.vietnamese.weekdaySelection.relation; rule.vietnamese.weekdaySelection.weekday; rule.vietnamese.weekdaySelection.occurrence

Use the resolved shared weekday codec; applies to the active subtype.

Rule.vieDaysAfterIfFriday

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.vieDaysAfterIfSaturday

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.vieDaysAfterIfSunday

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.vieConflictHolId

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.vieDaysAfterIfConflict

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.vieTypeDaysIfConflict

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.vieDupIfFri

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.vieDupifSat

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

Rule.vieDupifSun

no independent XML leaf; common weekday/conflict options

Historical branch-local value; load and normalize before XML export. Avoid a second editable version of the same policy.

 

Egyptian-family and Zoroastrian rules

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.egEgyptianType

rule.egyptian.calendar

Coptic, Ethiopic, Egyptian, Armenian ↔ 80,81,82,83.

Rule.egMonth

rule.egyptian.month

Month 1..13, preserving the editor range.

Rule.egDay

rule.egyptian.day

Day 1..30. Preserve month-13 combinations even when the existing calculator returns no date; do not add calendar occurrence validation to serialization.

Rule.zoZoroastrianType

rule.zoroastrian.calendar

ParsiShenshai maps to hrParsi=250. Kadmi/Fasli are not supported by this codec and are rejected.

Rule.zoMonth

rule.zoroastrian.month

Month 1..13.

Rule.zoDay

rule.zoroastrian.day

Day 1..30; occurrence validity remains the calculator’s responsibility.

 

Hindu rule

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.hiHinduType

rule.hindu.mode

Lunar=110 or Solar=111. Write only the active branch.

Rule.hilMonth

rule.hindu.lunar.month

Existing lunar month number; preserve calendar numbering.

Rule.hilDay

rule.hindu.lunar.tithi

Existing tithi number (DFM has 30 choices).

Rule.hilLeapMonthIfExists

rule.hindu.lunar.preferLeapMonth

Boolean; preserve current meaning in the calendar helper.

Rule.hilLeapDay

rule.hindu.lunar.preferLeapDay

Boolean; preserve current meaning.

Rule.hilJunkField

not serialized

Reserved/junk field; not copied by the current pack/unpack assignments. Initialize to zero.

Rule.hilMoveByIfExpunged

rule.hindu.lunar.expungedOffsetDays

Signed integer; shown in StringFormula. Preserve and verify the calendar helper behaviour; do not discard because the shown UI does not assign it.

Rule.hilTimeInterval

rule.hindu.lunar.time.mode

Stable tokens for all 17 choices listed in the timing table below.

Rule.hilTime1

rule.hindu.lunar.time.muhurta.from / rule.hindu.lunar.time.ghatikas

For Muhurtas use the lower bound; for a GhatikasCovering* mode use the ghatika amount. Other modes have no active numeric parameter; normalize unused slots.

Rule.hilTime2

rule.hindu.lunar.time.muhurta.to

Upper muhurta bound in Muhurtas mode only. Other modes do not emit this leaf.

Rule.hisMonth

rule.hindu.solar.month

Existing solar month number, both solar submodes.

Rule.hisDay

rule.hindu.solar.day

DayNumber solar submode only.

Rule.hisUseNakshatra

rule.hindu.solar.selection

false → DayNumber; true → Nakshatra. This selects the active solar substructure.

Rule.hisNakshatra1

rule.hindu.solar.nakshatra.number

One nakshatra, numbered 1..27 in XML; legacy zero-based value = XML number - 1. The DFM lists 27 choices starting at index 0; the pasted helper uses Nakshatra1 × 360/27 degrees. No interval is calculated.

Rule.hisNakshatra2

not serialized [pending compatibility audit]

The helper accepts Nakshatra2 but does not read it. No interval-end leaf is serialized. The codec rejects a nonzero active hisNakshatra2 pending a compatibility audit of other consumers, including XE3. Fresh XML reads leave the unused slot at zero.

Rule.hisNakshatraCalcTime

rule.hindu.solar.nakshatra.calculationTime

Midnight=0, Sunrise=1, Noon=2, Midday=3. Keep Noon and Midday distinct exactly as the DFM does.

Rule.hisNakshatraLeapIfOccurs

rule.hindu.solar.nakshatra.preferSecondOccurrence

Boolean, Nakshatra submode only. Passed as UseSecondNakshatra. true prefers a later occurrence of the same Nakshatra1 when the helper finds it in the requested solar month under its existing search conditions; otherwise retain the first. It neither selects Nakshatra2 nor adds a second holiday date.

Rule.hisMoveByIfExpunged

rule.hindu.solar.expungedOffsetDays

Signed integer; preserve pending calendar-helper verification.

 

Hindu solar nakshatra — behaviour confirmed from helper

 

The supplied helper computes the solar-month ingress after 1 January for longitude (hisMonth - 1) × 30 degrees, then finds the next lunar longitude crossing at Nakshatra1 × 360/27 degrees. Nakshatra2 is not read anywhere in the function body. The XML therefore describes one nakshatra number, not a from/to interval.

 

The first candidate is converted to a date by CriticalTimeType. If UseSecondNakshatra is true and (dteHoliday1 - dateCurSolarMonth) < 10, the helper searches again after dteHoliday1 + 2 for the SAME Nakshatra1 longitude. It accepts the later candidate only if hisMonthOf(Trunc(dteApxHoliday)) still equals hisMonth. The second candidate is then converted using the same critical-time calculation and overwrites Result[Low(ThiDateOccurences)]. Otherwise the first candidate remains. Preserve this exact gate and pre-conversion month check; preferSecondOccurrence is a preference, not a requirement that a second candidate exist.

 

Critical-time tokens remain Midnight=0, Sunrise=1, Noon=2 and Midday=3. Preserve the supplied formulas. In particular, case 3 adds Sunset(date) - Sunrise(date), the daylight duration, to the event time before truncation. That expression is not the midpoint of sunrise and sunset. Whether this matches the intended Midday definition is a separate calculation review; serialization must not quietly replace it with another formula.

 

The three-element occurrence result has an established purpose: as clarified by the user, dates belonging to Gregorian year Y can come from calculating the corresponding holiday for Y-1, Y or Y+1. Retain that shared return structure and the caller loop. The narrower initialization check concerns this particular nakshatra helper, which assigns only Result[Low(ThiDateOccurences)] and overwrites that same slot if it selects the later occurrence. The other result slots are not explicitly initialized in the shown function. AddMultiDate accepts only positive dates by default, so unused entries should be zero. For the fixed numeric result array implied by the shown code, the recommended correction is to insert Result := Default(ThiDateOccurences); as the first statement of HinduSolarNakshatraInGregorianYear, before dteHoliday1 := 0. Keep both assignments to Result[Low(ThiDateOccurences)] and the three-slot caller loop unchanged. This initializes unused entries without altering how the selected date is calculated. Compile and check first- and second-occurrence cases, confirming unused slots are zero. The correction is recommended, not recorded as applied or tested in Delphi; it remains separate from XML migration.

 

hilTimeInterval

Stable XML token

0

Sunrise

1

Arunodaya

2

Pratah

3

Purvahna

4

Madhyahna

5

Aparahna

6

Sayahna

7

Pradosha

8

ExtendedPradosha

9

Nisitha

10

Purvaviddha

11

Paraviddha

12

Muhurtas

13

GhatikasCoveringSunrise

14

GhatikasCoveringMidday

15

GhatikasCoveringSunset

16

GhatikasCoveringMidnight

 

Burmese rule

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.buBurmeseType

rule.burmese.mode

Lunar=211 or NewYearDay=212.

Rule.burMonth

rule.burmese.lunar.month

Calendar month number; Lunar only.

Rule.burDay

rule.burmese.lunar.day

Calendar day number; Lunar only.

Rule.burLeapMonthIfExists

rule.burmese.lunar.preferLeapMonth

Stored Boolean preserved even though the control is hidden/disabled and the current calculator passes literal true. Serialization does not alter that calculation.

Rule.BurNewYearDay

rule.burmese.newYearDay

Akyo=0, Akya=1, FirstAkyat=2, SecondAkyat=3, Atat=4; NewYearDay only.

 

Tibetan, Bhutanese and Mongolian rules

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.tibTibetanType

rule.tibetan.mode

Lunar or Solar; subtype constants listed in the family table.

Rule.tibMonth

rule.tibetan.lunar.month

Calendar month number. Emit only the active subtype.

Rule.tibDay

rule.tibetan.lunar.day

Calendar day number. Emit only the active subtype.

Rule.tibLeapMonthIfExists

rule.tibetan.lunar.preferLeapMonth

Boolean. Emit only the active subtype.

Rule.tibLeapDayIfExists

rule.tibetan.lunar.preferLeapDay

Boolean. Emit only the active subtype.

Rule.tibMansion

rule.tibetan.solar.mansion.number

Integer 0..26 in the existing editor numbering; Solar subtype only.

Rule.tibMansionFraction

rule.tibetan.solar.mansion.fraction

Integer 0..59 in the stored editor units; no conversion to degrees. Solar subtype only.

Rule.bhuBhutaneseType

rule.bhutanese.mode

Lunar or Solar; subtype constants listed in the family table.

Rule.bhuMonth

rule.bhutanese.lunar.month

Calendar month number. Emit only the active subtype.

Rule.bhuDay

rule.bhutanese.lunar.day

Calendar day number. Emit only the active subtype.

Rule.bhuLeapMonthIfExists

rule.bhutanese.lunar.preferLeapMonth

Boolean. Emit only the active subtype.

Rule.bhuLeapDayIfExists

rule.bhutanese.lunar.preferLeapDay

Boolean. Emit only the active subtype.

Rule.bhuMansion

rule.bhutanese.solar.mansion.number

Integer 0..26 in the existing editor numbering; Solar subtype only.

Rule.bhuMansionFraction

rule.bhutanese.solar.mansion.fraction

Integer 0..59 in the stored editor units; no conversion to degrees. Solar subtype only.

Rule.mogMongolianType

rule.mongolian.mode

Lunar or Solar; subtype constants listed in the family table.

Rule.mogMonth

rule.mongolian.lunar.month

Calendar month number. Emit only the active subtype.

Rule.mogDay

rule.mongolian.lunar.day

Calendar day number. Emit only the active subtype.

Rule.mogLeapMonthIfExists

rule.mongolian.lunar.preferLeapMonth

Boolean. Emit only the active subtype.

Rule.mogLeapDayIfExists

rule.mongolian.lunar.preferLeapDay

Boolean. Emit only the active subtype.

Rule.mogMansion

rule.mongolian.solar.mansion.number

Integer 0..26 in the existing editor numbering; Solar subtype only.

Rule.mogMansionFraction

rule.mongolian.solar.mansion.fraction

Integer 0..59 in the stored editor units; no conversion to degrees. Solar subtype only.

 

Sikh and Persian rules

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.siMonth

rule.sikh.month

Month 1..12.

Rule.siDay

rule.sikh.day

Day 1..31.

Rule.peMonth

rule.persian.month

Month 1..12.

Rule.peDay

rule.persian.day

Day 1..32, matching the stored editor domain.

 

Solar rule

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.suType

rule.solar.event

SolarLongitude ↔ 1 (the only type assigned by this form). Inventory other legacy values before migration; do not invent additional events.

Rule.suLongIndex

rule.solar.longitude.selection

0..11 → Preset000Degrees, Preset030Degrees, ... Preset330Degrees; 12 → Custom.

Rule.suLongitudeTimesThousand

rule.solar.longitude.degrees

Exact fixed-point degrees: 90.000 ↔ 90000. When a preset is selected, require consistency with its degrees; do not let the UI selector and value disagree.

Rule.suDayStartsAtSunrise

rule.solar.dayBoundary

Midnight=0, Sunrise=1, PreviousSunset=2, Poya=3, Sunset=4, Pradosha=5. Names mirror distinct existing options; calculation remains in the existing engine.

Rule.suWeekdayAfter

rule.solar.weekdaySelection.relation; rule.solar.weekdaySelection.weekday; rule.solar.weekdaySelection.occurrence

Use the resolved shared weekday codec documented above.

Rule.suSidereal

rule.solar.zodiac

Tropical=false; Sidereal=true.

 

Lunisolar rule

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.luniWeekdayAfter

rule.lunisolar.weekdaySelection.relation; rule.lunisolar.weekdaySelection.weekday; rule.lunisolar.weekdaySelection.occurrence

Use the resolved shared weekday codec documented above.

Rule.luniNthPhase

rule.lunisolar.phase.occurrence

Integer 1..99, explicitly preserved even when ignored by ClosestToDate.

Rule.luniMoonLongIndex

rule.lunisolar.phase.selection

0..7 → Preset000Degrees, Preset045Degrees, ... Preset315Degrees; 8 → Custom.

Rule.luniMoonLongitudeTimesThousand

rule.lunisolar.phase.degrees

Exact fixed-point phase angle /1000; consistency with a preset required.

Rule.luniReferenceType

rule.lunisolar.reference.kind

Ten stable tokens in the following table; choose the corresponding active reference data.

Rule.luniFullCycles

rule.lunisolar.fullCycles

Boolean, explicit even where it does not affect the selected reference mode; preserve current calculator behaviour.

Rule.luniSunLongIndex

rule.lunisolar.reference.solarLongitude.selection

Solar-reference modes only: 0..11 are 30-degree presets; 12 is Custom.

Rule.luniSunLongitudeTimesThousand

rule.lunisolar.reference.solarLongitude.degrees

Solar-reference modes only. Exact fixed-point degrees /1000.

Rule.luniDateMonth

rule.lunisolar.reference.month

Month/date reference modes only. Gregorian month number.

Rule.luniDateDay

rule.lunisolar.reference.day

Date reference modes only. Gregorian day number.

Rule.JUNK_luniDayStartsAtSunrise

not serialized

Obsolete storage slot; not copied by current pack/unpack. Initialize to zero.

Rule.luniDayStartsAtSunrise

rule.lunisolar.dayBoundary

Midnight=0, Sunrise=1, PreviousSunset=2, Poya=3, Sunset=4, Pradosha=5. Preserve the existing lunisolar calculation, which is not assumed identical to the solar code.

Rule.luniSidereal

rule.lunisolar.zodiac

Tropical=false; Sidereal=true. Preserve explicit setting where stored; do not silently derive it from visibility of a UI control.

 

luniReferenceType

XML token

Active reference values

0

BeforeSolarLongitude

solar longitude selection + degrees

1

AfterSolarLongitude

solar longitude selection + degrees

2

InOrBeforeMonth

month

3

InMonth

month

4

InOrAfterMonth

month

5

BeforeDate

month + day

6

OnOrBeforeDate

month + day

7

OnOrAfterDate

month + day

8

AfterDate

month + day

9

ClosestToDate

month + day

 

South-East Asian rule

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.saAsianType

rule.southEastAsian.mode

FullMoon=191; SpecialRule=192; Calendrical=193.

Rule.saEra

rule.southEastAsian.era

Confirmed mapping: _cs=1 Chulasakarat; _ms=2 Mahasakarat; _bs=3 Buddhasakarat; _as=4 Anchansakarat; _be=5 BurmeseEra; _th=6 ThaiEra. Form stores ItemIndex+1. Use an explicit fixed conversion table; the pasted display labels establish the correspondence but are not parsed dynamically.

Rule.samNthMoon

rule.southEastAsian.fullMoon.number

FullMoon only; integer 1..12.

Rule.sasRuleNumber

rule.southEastAsian.specialRule

NewYear=1, MakhaBuchha=2, VisakhaBuchha=3, KhaoPansa=4, confirmed by both the calculator switch and pasted SpecialHolidays assignments. Use these stable tokens, not runtime captions. SpecialRule submode only.

Rule.sacMonth

rule.southEastAsian.calendar.month

Calendrical only; calendar month number.

Rule.sacDay

rule.southEastAsian.calendar.day

Calendrical only; calendar day number.

Rule.sacLeap

rule.southEastAsian.calendar.leap

Calendrical only. Preserve: the calculator passes this value although the shown UI does not assign it.

 

South-East Asian era and special-rule tokens — confirmed

 

Legacy era

Pascal constant

Proposed XML token

1

_cs

Chulasakarat

2

_ms

Mahasakarat

3

_bs

Buddhasakarat

4

_as

Anchansakarat

5

_be

BurmeseEra

6

_th

ThaiEra

 

Legacy special rule

Proposed XML token

1

NewYear

2

MakhaBuchha

3

VisakhaBuchha

4

KhaoPansa

 

The commented-out index-0 Undefined Era / Undefined Holiday assignments do not establish active XML choices. The documented domain is eras 1..6 and special rules 1..4; report other stored values rather than guessing a fallback. Keep these mappings explicit in code, preferably referring to the supplied era constants. XML uses fixed tokens even if display captions later change. The specialRule leaf is present only for the SpecialRule subtype.

 

Arbitrary dates and date ranges

 

Source member / storage

Proposed XML element(s)

Value and conversion

Rule.StartDate

not serialized

Unused legacy slot of the hrArbitrary variant; current PackRule/UnpackRule do not transfer it. Actual dates are in ArbitraryDates.

Rule.EndDate

not serialized

Unused legacy slot of the hrArbitrary variant; do not reinterpret as the last arbitrary date.

Rule.ArbitraryEveryYear

rule.arbitraryDates.occursEveryYear

Annual expectation, not repetition of a stored date. In Olivier’s filtered UI, true keeps the rule visible even without dates in or after the filter year. Preserve the legacy EVERY_YEAR / NOT_EVERY_YEAR conversion.

Rule.RangeEveryYear

rule.ranges.occursEveryYear

Annual expectation, not an instruction to repeat a range. Preserve EVERY_YEAR / NOT_EVERY_YEAR conversion.

ArbitraryDates[i] (hrArbitrary)

rule.arbitraryDates.date

Repeated YYYY-MM-DD element under rule.arbitraryDates.dates. No year markers or serial integers in XML. Preserve validated chronological ordering; reject overcapacity instead of truncating.

DateRanges[i].StartDate

rule.range.startDate

YYYY-MM-DD under a repeated rule.range item in rule.ranges.items.

DateRanges[i].Duration

rule.range.durationDays

Positive integer, inclusive end = start + duration - 1. Distinct from common holiday duration. Preserve the current Word bound.

DateRanges[i].Weekdays[d]

rule.range.weekdays.<weekday>.included

Seven explicit booleans, Monday..Sunday mapped to 1..7. The range duration spans calendar days; these flags exclude dates within that span.

Rule.MaxBuffer

not serialized

Variant-record allocation padding, not holiday data.

 

Exceptions: replace a year or omit it

 

For supported recurring families other than ArbitraryDates/DateRanges, rule.exceptions contains repeated rule.exception entries. Each entry has rule.exception.year, rule.exception.action (ReplaceDates or OmitYear), and a rule.exception.dates container of repeated rule.exception.date elements. ReplaceDates requires at least one actual date in the named year; OmitYear requires an empty dates container. A year cannot have contradictory entries.

 

The legacy adapter rebuilds the chronological stream of year markers followed by that year’s replacement dates. OmitYear is a year marker with no following dates before the next marker/end. Reject malformed streams during migration; do not silently associate an orphan date with the wrong year. Check combined marker+date capacity against the 127-word array and its terminating-zero requirements; review existing consumers that access the following slot before choosing the maximum accepted count.

 

<rule.exceptions>
 <rule.exception>
 <rule.exception.year>2027</rule.exception.year>
 <rule.exception.action>ReplaceDates</rule.exception.action>
 <rule.exception.dates>
 <rule.exception.date>2027-05-03</rule.exception.date>
 <rule.exception.date>2027-05-04</rule.exception.date>
 </rule.exception.dates>
 </rule.exception>
 <rule.exception>
 <rule.exception.year>2028</rule.exception.year>
 <rule.exception.action>OmitYear</rule.exception.action>
 <rule.exception.dates/>
 </rule.exception>
</rule.exceptions>

 

Replacement dates replace the normal yearly result; they do not supplement it. Preserve the existing engine’s ordering of exceptions, validity, frequency, offsets and duration. Serialization is not an opportunity to reorder calculation stages based on XML element order or a simplified reading of documentation.

 

Packed-only fields and historical compatibility

 

Packed-only field(s)

Disposition

THolidayRule_packed.RuleType

Maps to rule.type; not duplicated inside the active subtype.

DaysAfterJewishDate

Old-reader compatibility override becomes common rule.offset.amount with units=Days and duration=1; current PackRule writes 0 to this obsolete slot.

DaysAfterMuslimDate

Old-reader compatibility override becomes common offset with units=Days; current PackRule writes 0.

iMoveFriToSat

Legacy packed-only flag; current unpack does not copy it and current pack writes false. Check older conversion requirements if historical records contain it.

iExtraDays

Old-reader compatibility override becomes HolDuration=iExtraDays+1 when positive; current pack writes 0.

TRuleOptions_packed.ObservanceSettings2

Storage extension for logical observance indexes beyond 31. No separate XML element.

ByteBool / LongInt / alignment / padding

Binary format details remain in legacy adapters only. Do not change packed declarations or field sizes as part of this feature.

 

Filter Year: one shared, explicit filtering operation

 

Filter Year is a caller-supplied export/synchronization policy, not an intrinsic holiday property. Normal saving and migration preserve all history. When producing a filtered updater or synchronization destination, filter a temporary copy, then serialize that same copy into both XML and legacy fields. Do not prune the master worldwide database merely because an export uses a cutoff. Do not filter the legacy fields and leave unfiltered XML alongside them.

 

Data

Filter Year Y policy

Source evidence / boundary

Name variations

Remove complete blocks whose year condition cannot match any year >=Y. Keep all other blocks in their original order.

StringNameVariations already checks explicit year/range upper ends and preserves + forms. Shared extraction/filtering helper proposed.

Abbreviation variations

Apply the same condition analysis as names, retaining whole blocks and their exact text.

Proposed shared policy; verify existing synchronization consumers.

Arbitrary dates

Remove stored dates earlier than 1 January Y.

SaveArbitraryDatesToTable(minYear) tests the actual date, not merely whether its rule has future occurrences.

Date ranges

Remove only a range whose inclusive end (start + duration - 1) is earlier than 1 January Y.

SaveDateRangesToTable(minYear). Keep a crossing range whole, including its original start and weekday mask; do not clip or shift it.

Replacement exceptions

Remove complete exception-year entries earlier than Y, including their replacement dates.

Old writer filters year markers and dates; structured XML permits removal as a single unit.

Omitted-year exceptions

Remove omitted-year entries earlier than Y.

An omitted-year entry is still historical exception data even though it contains no dates.

Validity, frequency and reference year

Preserve. Do not rewrite Beginning to Y or change the frequency anchor.

These are recurrence settings, not historical lists.

Occurs-every-year expectations

Preserve even when pruning leaves a list empty.

Changing the expectation would change the meaning of missing dates.

OneTimeDates

Continue the existing separate updater/synchronization filtering.

Outside this recurring-holiday XML structure.

 

For arbitrary-date rules with duration/offset extending into the retained year, the current legacy pruning uses the stored base date, not the final expanded occurrences. Preserve that established policy initially and explicitly review such cross-boundary cases if changing it; do not quietly apply the date-range rule to arbitrary dates. Other interactions with name-year conditions must use the established final-occurrence-year semantics.

 

Condition, Filter Year 2026

Action

2024 or 2024_DUP

Remove

2021..2025 or 2025-

Remove

2025..2028

Retain unchanged

2023+, 2023+L5, 2023+C

Retain unchanged

DUP, MOV, BASE, DEFAULT, ALWAYS, DAY2

Retain unchanged

Unrecognized condition

Report for review; never delete on a guessed interpretation

 

Informational elements and search behaviour

 

The implemented info section writes info.holiday.id, info.holiday.setId, info.holiday.modifiedDate, info.sort.month and info.sort.day, each with info="true". These are not restored from XML: the destination supplies ID, CountryID, DateModified, RN1 and RN2. Additional human-readable descriptions and resolved names remain possible future additions, not current required nodes.

 

A raw SQL/regex change does not immediately regenerate explanatory text or modification metadata. The generation/preparation save refreshes it from validated authoritative settings. Search editable leaves when exact current truth matters. Dates previews, if ever included, must identify the requested year and calculation context and remain info-only; they are not part of version 1 input.

 

Shared read/save service and generation integration

 

Keep serialization separate from database posting. Implemented helpers are SaveHolidayToXML, ReadHolidayFromXML, HolidayXMLIsBlank and ReadHolidayFromStorage; THoliday wrappers call the codec. The storage helper selects NULL/blank legacy input versus nonblank XML and loads a temporary object. Shared paired database persistence, explicit Filter Year application and integration into all callers remain to be completed.

 

Refactor the form-dependent persistence: scalar fields, all BLOBs, RN1/RN2 derivation, timestamp rules, country/set timestamps, change logs and broadcasts must be callable without a form. A save must not depend on a label caption to determine whether a rule changed. Compare logical data, distinguishing storage-only conversion, an unchanged resave, a true change, and intentional historical pruning. Use semantic comparisons rather than raw record memory or XML whitespace.

 

For generation: load each stored rule through the shared loader; if the option is enabled, validate and persist both representations before filtering it out for having no calculated dates. Perform at most the intended save per record, not per occurrence/year. A successful preparation state requires all packaged records to have been processed successfully; an XML error must identify the record and prevent treating a partially synchronized database as ready for the XE3 package. Transaction/chunk boundaries should fit existing NexusDB generation behaviour.

 

For cached datasets, Post alone is not the whole persistence boundary; include the actual apply/update and commit path in the atomic save design. Keep ID assignment after posting in mind: the current AfterPost assigns a new ID when needed, so informational identity must be refreshed after final identity exists. Never overwrite retained operational columns from a partial THoliday instance.

 

Integration audit checklist

 

Area

Required change / evidence

DataHolidays.SaveHoliday

Extract full save/housekeeping service; preserve normal GUI behaviour.

GetHolidayFromTable and other readers

Route authoritative settings through the XML-aware loader; initialize complete objects; explicitly load ProvinceID and required context.

mnuRemoveNameVariationsClick

Currently writes LocalName directly. Change through the shared full load/edit/save service.

mnuRemoveObservanceClick

Currently saves only the legacy rule. Update both representations.

New / duplicate / copy / import

Assign destination identity correctly, remap external references when needed, and regenerate XML.

Holidays synchronization

Filter the selected destination copy and save both formats; handle ancestry/ignored/kept policy explicitly.

Diary generation / worldwide packaging

Use the existing every-rule traversal for optional conversion plus resynchronization; expose a reliable success/error result before packaging.

NexusDB schema and DFM

Add the XML memo to the real schema, persistent fields, SELECT and both update/insert SQL lists; audit schema propagation to updater destinations.

Other database maintenance commands

Search the full project for direct HolRules writes, field assignments, copied BLOBs, SQL modifications and Save*ToTable calls. Supplied files cannot establish full repository coverage.

HOLCALC / STAND_ALONE builds

Check dependencies and conditional compilation. Do not impose new XML/UI dependencies on builds that must continue using the legacy reader.

XE3 validation

After XV4 writes legacy output, load/calculate using the actual XE3 build or equivalent verified reader. XE3 source/build was not supplied here.

 

Validation: completed round trips and remaining checks

 

Error messages should include NoSite/ID_Holiday, rule type, full dotted XML element path, offending value and the reason. Parse/validate into a temporary object first. Missing mandatory nodes never inherit fields from a previously reused THoliday. Keep the original row unchanged after a failure. Reference checks should distinguish a broken ID from a set-default reference; exact database scope must be preserved.

 

Check

Required evidence

Source coverage

Every THoliday public data member, THolidayRule member and TRuleOptions member has a mapping or explicit disposition. Packed-only additions are accounted for separately.

Legacy → XML → new object

Compare semantic fields, active subtype, ordered text blocks, arrays, all observances and references; allow only documented neutralization of unused storage.

XML → legacy → legacy reader

Compare with the authoritative object after normalization; verify that historical compatibility overrides cannot reactivate stale values.

Representative calculations

Cover each active family/subtype, relevant validity boundaries, exception/omitted years, leap cases, durations, offsets and MOV/DUP/BASE behaviour.

Text preservation

Multiline base name, blank lines, embedded >>> away from line start, ordered/duplicate variations, multiline payload retention, <hol>, <DELETE>, & and literal &#007;.

CDATA

Empty text, Unicode, tabs, ]]> splitting, adjacent CDATA concatenation and writer indentation. Test with the supplied Delphi XML unit, not only a standard XML parser.

Filter Year

Old arbitrary dates, crossing date ranges, replacement and omitted exceptions, names and abbreviations; ensure XML and BLOB contents have the same cutoff.

Invalid input

NULL/empty/whitespace XML versus malformed nonblank XML; mismatched closing tags, duplicate fields, missing active branch, bad booleans/enums, out-of-range dates/IDs, overcapacity arrays and forbidden control characters.

Modification metadata

Storage conversion leaves semantic modification dates unchanged. Real XML edits trigger existing holiday/set notifications and correct modification tracking.

Build/runtime

Olivier has compiled and run the database round-trip harness locally. The optional HOLIDAY_XML_SELF_TEST suite is separate; execution of that suite is not claimed here. Native Help+Manual rendering and the end-to-end generation comparison remain to be verified.

 

Open implementation details — tracked, not guessed

 

Item

What is needed

Unused nakshatra slot compatibility

Nakshatra2 is unused in the supplied helper. Confirm other consumers and XE3 also ignore it before finalizing zero normalization; report nonzero legacy values until checked.

Nakshatra calculation review

The three result slots legitimately accommodate neighboring-year candidates. Recommended separately: initialize the fixed result array with Result := Default(ThiDateOccurences); at helper entry, then compile and verify unused slots and selected dates. Application/testing is not yet confirmed. Review CriticalTimeType=3 formula separately; do not change calculation behaviour as an incidental XML conversion.

Calendar-specific units and legacy rows

Calendar codecs now use explicit editor-compatible domains. Mansion values retain number 0..26 and fraction 0..59; longitude values retain exact thousandths. Stored expunged-day offsets and supported legacy flags are preserved. Calendar combinations that produce no occurrence are not rejected merely for producing no date.

Reserved/unsupported settings

Observance index 10 is resolved and preserved. Nonzero active hisNakshatra2 remains a compatibility-review error. Historical inactive rule types, Zoroastrian Kadmi/Fasli and unsupported target encodings remain unsupported; conflict target -1 has an explicit Unresolved representation.

Formatting/control-character domain

Confirm actual legacy Formatting and symbol-text control conventions before declaring full text coverage. The draft maps their fields and reports unexpected forbidden controls.

Defaults for inactive legacy slots

The reader starts with a fresh, initialized temporary holiday and sets inactive variant/reference fields to deterministic defaults. Round-trip equivalence concerns active semantic settings, not unused bytes in a variant record.

XML helper verification

The supplied VerySimple writer and strict MSXML 6 reader have been exercised by Olivier through the database round-trip harness. The reader validates well-formedness without relying on VerySimple closing-tag parsing. This does not claim execution of every optional self-test or production database integration.

Full project write-path audit

Audit all production readers and writers, including synchronization, shortcuts, generation, packaging and the actual database schema. The test harness and RN1/RN2 extraction do not establish that these write paths have all been converted.

 

Current implementation: separate cHolidayXML.pas

 

cHolidayXML.pas contains SaveHolidayToXML, ReadHolidayFromXML, HolidayXMLIsBlank and ReadHolidayFromStorage. THoliday.SaveToXML and THoliday.ReadFromXML delegate to the codec. Serialization and validation are outside DataHolidays. The storage wrapper receives the actual XML TField and a complete legacy-loader callback; no memo-field name is assumed.

 

Implemented scope: complete common settings and all 22 active families: Fixed, NthWeekday, Easter, ArbitraryDates, DateRanges, Jewish, Muslim, Chinese, Korean, Vietnamese, Hindu, Tibetan, Bhutanese, Mongolian, Burmese, Egyptian, Zoroastrian, Persian, Sikh, SouthEastAsian, Sun and Lunisolar. Only active subtype/reference branches are written. Supporting serialization does not enable any previously unimplemented date calculator.

 

SaveHolidayToXML returns a Unicode string and does not post records. ReadHolidayFromXML validates a fresh temporary holiday before committing settings. The reader keeps ID, CountryID, DateModified and sort metadata from destination context, ignores whitelisted informational values, initializes inactive settings, and clears calculated DatesArray only after success. ReadHolidayFromStorage also uses a temporary object for the legacy callback. The existing read routines must finish historical RuleOptions conversion before XML export.

 

Errors still identify unsupported or unrepresentable settings: out-of-range ProvinceID or priority, image modes 4..6, reserved heading flags, unsupported conflict encodings, active Move with unspecified units, nonzero active hisNakshatra2, invalid text controls and malformed/overcapacity exception streams. Supported legacy sentinels, independent NoneSpecified and reversed validity bounds are preserved explicitly; they are not migration errors.

 

Reader syntax checking uses Windows MSXML 6 with DTDs prohibited and external resolution disabled; it copies the validated DOM into TXmlNode objects without invoking VerySimple parsing. The existing VerySimple writer remains unchanged. COM objects are local to the calling thread, with balanced initialization. Free text accepts ordinary escaped XML text or CDATA; CDATA plus non-whitespace ordinary text is rejected to avoid silently dropping content.

 

The unit includes an opt-in HOLIDAY_XML_SELF_TEST block. Define the symbol and call RunHolidayXMLSelfTests in the XV4 build. Tests cover canonical round trips, multiline names/variations, CDATA terminators, literal entity-like text and Unicode, the complete weekday-selector domain, schema errors, unchanged destination after failed reads, and NULL/blank/nonblank memo selection. These Delphi tests have not been executed here. Local checks validate source structure and XML fixtures only; they are not a substitute for Delphi compilation.

 

Next integration work: connect XML-first loading to complete legacy fallback; centralize paired XML/legacy saves and housekeeping; add the XML memo throughout schema/datasets/SQL; connect generation conversion and legacy resynchronization before XE3 packaging. Filter Year remains an explicit caller operation. Validate the full 2023–2055 generation against a legacy baseline before release.

 

// The field name is selected by the caller; no LoadVersion is consulted.
ReadHolidayFromStorage(XMLField, Holiday,
 procedure(LoadedHoliday: THoliday)
 begin
 LoadCompleteLegacyHoliday(LoadedHoliday); // shared application routine to extract
 end);
 
XMLText := SaveHolidayToXML(Holiday); // validate and prepare before database writes

 

MSXML loadXML and well-formedness

 

MSXML ProhibitDTD

 

COM initialization and balancing

 

Decision record and next implementation milestones

 

Status

Decision / milestone

Agreed

XML authority in XV4 when nonblank; NULL/blank uses legacy; paired saves; XE3 remains a legacy reader.

Agreed

Generation already visits every rule, including date-less rules.

Agreed

Full holiday representation, stable symbolic enums, explicit common defaults, dotted disambiguated element names, active variant only.

Agreed

Separate ordered variation nodes; reference name may be multiline; >>> at line start is the variation boundary.

Agreed

Filter Year covers name/abbreviation variations, arbitrary dates, date ranges and exceptions; apply the same result to both saved representations.

Implemented and documented

Complete field inventory and codec for all 22 active families; explicit legacy compatibility decisions and source-based mapping. Remaining integration and validation work is tracked separately.

Resolved during review

Weekday-selection codec, date semantics and UI mappings resolved from pasted frmWeekdaysBeforeAfter.pas and DFM. Occurrence range 1..99; default 1. The DFM dependency is closed.

Resolved during review

TPictureBoxAlignment ordinals 0..6, South-East Asian eras 1..6 and special rules 1..4. The supplied nakshatra helper uses only Nakshatra1; its flag prefers a later occurrence of that same nakshatra.

Resolved during review

Holiday image alignment supports only ordinals 0..3. Shared enum values 4..6 are excluded from holiday XML; use slogans for detailed positioning.

Recommended correction

Initialize the full nakshatra result array at function entry. Keep the three result slots and the second-occurrence replacement; Delphi application and verification remain pending.

Clarified during review

The three candidate-date slots support dates for year Y arising from Y-1, Y or Y+1 calculations. Preserve this return structure; the separate helper review concerns initialization of unused slots only.

Implemented refinement

Hindu solar XML uses nakshatra.number and nakshatra.preferSecondOccurrence. Nonzero active hisNakshatra2 is rejected pending compatibility review; no second nakshatra interval is inferred.

Agreed naming

camelCase within concepts; dots only for ownership/hierarchy. This revision updates the mapping and examples accordingly.

Completed family testing

Olivier reports successful database XML round trips for all 22 active rule types. The final Sun batch contained 162 rules and the final Lunisolar batch 38.

Implemented codec

Separate cHolidayXML.pas supports all active families, complete temporary-object reads and NULL/blank selection without LoadVersion. Tested locally in Delphi through the user database harness.

Pending implementation

Complete shared paired database persistence and explicit filtering, schema migration, and all UI/service integrations. All active family codecs are now present.

Pending validation

Final combined regression; XML-loaded worldwide generation for 2023–2055 compared with approximately 130,000 baseline dates and names; XML-to-legacy/XE3 packaging verification; native Help+Manual review.

Before release

Replace provisional claims with implemented behaviour, record supported schema versions and error messages, and move internal-only implementation notes to reference/maintenance topics if desired.

 

Source references

 

Local source snapshot: cHoliday(1).pas, DataHolidays(1).pas, DataHolidays(1).dfm and Xml.VerySimple.pas supplied in this discussion, plus frmWeekdaysBeforeAfter.pas and its DFM pasted directly into the chat. Later pasted excerpts confirm TPictureBoxAlignment, the six South-East Asian era constants and names, four SpecialHolidays names, and the implementation of HinduSolarNakshatraInGregorianYear. The topic format and project style names are based on the existing HMXP_QppDocumentation_v64.zip, especially xmlref_conventions.xml. The archive is used as a format reference only; no existing topics or table of contents are replaced. This revision also uses cHolidayXML.pas version 24, the supplied cHoliday(2).pas, and Olivier’s subsequent test reports and clarifications of RN1/RN2 and annual expectation.

 

Recurring holidays

 

Holiday graphical elements

 

PictureBox Alignment

 

Recurrence options

 

Arbitrary dates

 

Date ranges

 

Validity and exceptions

 

Synchronization and Filter Year

 

Variable names

 

XML 1.0 syntax and allowed characters

 

Worked XML fragments — illustrative settings

 

These excerpts illustrate selected settings and omit other required sections; they are not complete importable holidays. All active rule families are now supported by the codec. No finalized XSD is supplied. Use a complete SaveToXML result when testing ReadFromXML.

 

02-Regional-Observance.xml — illustrative settings; common sections not shown remain required in a full holiday.

 

<observance>
 <observance.noneSpecified>false</observance.noneSpecified>
 <observance.legacyPreviousEve>false</observance.legacyPreviousEve>
 <observance.municipal>false</observance.municipal>
 <observance.regional>true</observance.regional>
 <observance.banks>false</observance.banks>
 <observance.other>false</observance.other>
 <observance.government>false</observance.government>
 <observance.linguistic>false</observance.linguistic>
 <observance.morning>false</observance.morning>
 <observance.afternoon>false</observance.afternoon>
 <observance.evening>false</observance.evening>
 <observance.bahai>false</observance.bahai>
 <observance.catholic>false</observance.catholic>
 <observance.christian>false</observance.christian>
 <observance.coptic>false</observance.coptic>
 <observance.buddhist>false</observance.buddhist>
 <observance.hindu>false</observance.hindu>
 <observance.jewish>false</observance.jewish>
 <observance.muslim>false</observance.muslim>
 <observance.orthodox>false</observance.orthodox>
 <observance.protestant>false</observance.protestant>
 <observance.sikh>false</observance.sikh>
 <observance.schools>false</observance.schools>
 <observance.armenian>false</observance.armenian>
 <observance.otherReligion>false</observance.otherReligion>
 <observance.webOnly>false</observance.webOnly>
 <observance.tentative>false</observance.tentative>
 <observance.mayDisappear>false</observance.mayDisappear>
 <observance.privateSector>false</observance.privateSector>
 <observance.pontoFacultativo>false</observance.pontoFacultativo>
 <observance.ignore>false</observance.ignore>
</observance>

 

03-Common-Settings.xml — illustrative settings; common sections not shown remain required in a full holiday.

 

<rule.validity>
 <rule.validity.mode>Beginning</rule.validity.mode>
 <rule.validity.fromYear>2023</rule.validity.fromYear>
 <rule.validity.toYear>None</rule.validity.toYear>
</rule.validity>
<rule.frequency>
 <rule.frequency.intervalYears>1</rule.frequency.intervalYears>
 <rule.frequency.referenceYear>2000</rule.frequency.referenceYear>
</rule.frequency>
<rule.offset>
 <rule.offset.amount>0</rule.offset.amount>
 <rule.offset.units>Days</rule.offset.units>
</rule.offset>
<rule.duration>
 <rule.duration.amount>1</rule.duration.amount>
 <rule.duration.units>Days</rule.duration.units>
</rule.duration>

 

04-Arbitrary-Dates.xml — illustrative settings; common sections not shown remain required in a full holiday.

 

<rule.type>ArbitraryDates</rule.type>
<rule.arbitraryDates>
 <rule.arbitraryDates.occursEveryYear>false</rule.arbitraryDates.occursEveryYear>
 <rule.arbitraryDates.dates>
 <rule.arbitraryDates.date>2025-11-14</rule.arbitraryDates.date>
 <rule.arbitraryDates.date>2026-11-20</rule.arbitraryDates.date>
 </rule.arbitraryDates.dates>
</rule.arbitraryDates>

 

05-Crossing-Date-Range.xml — illustrative settings; common sections not shown remain required in a full holiday.

 

<rule.type>DateRanges</rule.type>
<rule.ranges>
 <rule.ranges.occursEveryYear>false</rule.ranges.occursEveryYear>
 <rule.ranges.items>
 <rule.range>
 <rule.range.startDate>2025-12-29</rule.range.startDate>
 <rule.range.durationDays>7</rule.range.durationDays>
 <rule.range.weekdays>
 <rule.range.weekdays.monday.included>true</rule.range.weekdays.monday.included>
 <rule.range.weekdays.tuesday.included>true</rule.range.weekdays.tuesday.included>
 <rule.range.weekdays.wednesday.included>true</rule.range.weekdays.wednesday.included>
 <rule.range.weekdays.thursday.included>true</rule.range.weekdays.thursday.included>
 <rule.range.weekdays.friday.included>true</rule.range.weekdays.friday.included>
 <rule.range.weekdays.saturday.included>false</rule.range.weekdays.saturday.included>
 <rule.range.weekdays.sunday.included>false</rule.range.weekdays.sunday.included>
 </rule.range.weekdays>
 </rule.range>
 </rule.ranges.items>
</rule.ranges>

 

07-Hindu-Lunar.xml — illustrative settings; common sections not shown remain required in a full holiday.

 

<rule.type>Hindu</rule.type>
<rule.hindu>
 <rule.hindu.mode>Lunar</rule.hindu.mode>
 <rule.hindu.lunar>
 <rule.hindu.lunar.month>1</rule.hindu.lunar.month>
 <rule.hindu.lunar.tithi>1</rule.hindu.lunar.tithi>
 <rule.hindu.lunar.preferLeapMonth>false</rule.hindu.lunar.preferLeapMonth>
 <rule.hindu.lunar.preferLeapDay>false</rule.hindu.lunar.preferLeapDay>
 <rule.hindu.lunar.expungedOffsetDays>0</rule.hindu.lunar.expungedOffsetDays>
 <rule.hindu.lunar.time>
 <rule.hindu.lunar.time.mode>Sunrise</rule.hindu.lunar.time.mode>
 </rule.hindu.lunar.time>
 </rule.hindu.lunar>
</rule.hindu>

 

08-Lunisolar-Reference.xml — illustrative settings; common sections not shown remain required in a full holiday.

 

<rule.type>Lunisolar</rule.type>
<rule.lunisolar>
 <rule.lunisolar.phase>
 <rule.lunisolar.phase.occurrence>1</rule.lunisolar.phase.occurrence>
 <rule.lunisolar.phase.selection>Preset180Degrees</rule.lunisolar.phase.selection>
 <rule.lunisolar.phase.degrees>180.000</rule.lunisolar.phase.degrees>
 </rule.lunisolar.phase>
 <rule.lunisolar.reference>
 <rule.lunisolar.reference.kind>OnOrAfterDate</rule.lunisolar.reference.kind>
 <rule.lunisolar.reference.month>3</rule.lunisolar.reference.month>
 <rule.lunisolar.reference.day>21</rule.lunisolar.reference.day>
 </rule.lunisolar.reference>
 <rule.lunisolar.fullCycles>false</rule.lunisolar.fullCycles>
 <rule.lunisolar.dayBoundary>Midnight</rule.lunisolar.dayBoundary>
 <rule.lunisolar.zodiac>Tropical</rule.lunisolar.zodiac>
 <rule.lunisolar.weekdaySelection>
 <rule.lunisolar.weekdaySelection.relation>None</rule.lunisolar.weekdaySelection.relation>
 <rule.lunisolar.weekdaySelection.weekday>None</rule.lunisolar.weekdaySelection.weekday>
 <rule.lunisolar.weekdaySelection.occurrence>1</rule.lunisolar.weekdaySelection.occurrence>
 </rule.lunisolar.weekdaySelection>
</rule.lunisolar>

 

For Filter Year 2026, example 04 loses only its 2025 date. Example 05 retains the entire range: its inclusive end is 4 January 2026, even though it starts in 2025. Examples 07 and 08 are schematic calendar settings, not claims about a particular real-world holiday.

 

Verification performed for this draft

 

A Python model checked all 2,773 canonical weekday-selector states in the confirmed editor domain (None plus four relations × seven weekdays × 99 occurrences), and compared the documented semantics against a direct translation of the pasted WeekdayAfterToDate for 16,632 date cases. Thirty-five encodings outside the proposed XML domain were rejected, including all 28 combinations for occurrence 100. This verifies the proposed formulas against the supplied logic; it is not a Delphi compile or execution test. XML examples, table dimensions, field coverage and representative multiline text round trips were checked separately.

 

Mechanical field-coverage check

 

The original inventory accounted for all 24 public THoliday data members, 178 THolidayRule members and 14 TRuleOptions members: 216 dispositions, including historical/padding fields. All 22 active family codecs are now implemented. Inventory coverage and successful round trips complement, but do not replace, the pending end-to-end calculation and database compatibility tests.