Date objects carry both CLR and JavaScript members

Pitfall By design
A Date in HashJS answers to both the CLR and the JavaScript API on the same instance. The two disagree about the month: .Month is 1-based and .getMonth() is 0-based. .Year and .getFullYear() agree, and .Day and .getDate() agree - which is what makes the month trap easy to miss, because a spot check on the year suggests the two are interchangeable.
Applies to: JavaScriptHash templatesAPI

What you'll see

A date is off by exactly one month, in some code paths and not others. Filters miss records at a month boundary, a generated period label names the wrong month, or a comparison between two dates built in different parts of the file disagrees with itself.

Both spellings look correct in isolation, because both are real members of the same object:

let d = new Date();

d.Year;            // 2026   (CLR)
d.getFullYear();   // 2026   (JS)     - agree

d.Month;           // 8      (CLR, 1-based)
d.getMonth();      // 7      (JS,  0-based)   - do NOT agree

d.Day;             // 20     (CLR)
d.getDate();       // 20     (JS)     - agree

What's actually happening

A Date here is a CLR value surfaced into V8, and it keeps both APIs. The CLR members (.Year, .Month, .Day) and the JavaScript members (.getFullYear(), .getMonth(), .getDate()) sit on the same instance, each carrying its own platform's convention unchanged.

Those conventions differ in exactly one place. .NET numbers months from 1; JavaScript numbers them from 0. Everything else lines up, and that is the whole difficulty: .Year and .getFullYear() returning the same number is not evidence that the two APIs are interchangeable, but it reads like it. Code then picks whichever spelling came to hand, and a calculation that mixes them is wrong by one month in every month of the year.

The failure is quiet because both values are plausible. An off-by-one month is not obviously garbage the way a NaN or an empty render is — it is a real month, just the wrong one, and it only looks wrong to someone who knows what the answer should have been.

What to do

1. Pick one API per expression and stay inside it. Mixing the two in a single calculation is off by one, always:

// consistent - CLR throughout
let label = d.Year + "-" + d.Month;

// consistent - JavaScript throughout
let label = d.getFullYear() + "-" + (d.getMonth() + 1);

// wrong - mixes the two
let label = d.Year + "-" + d.getMonth();

2. Prefer the CLR members when the number is going to be displayed or stored, since they need no + 1 and therefore no comment explaining the + 1. Prefer the JavaScript members when the value feeds other JavaScript that expects 0-based months.

3. Reviewing code you did not write, check the month spellings first when a date is off by one. The bug is almost never in the arithmetic around it.

4. For formatting, do not assemble the string yourself. docly.format handles month names and ordering, and respects the culture — but a request starts with the invariant culture, so set one first. See A request starts with the invariant culture.

5. When in doubt, print both. One throwaway page settles which member you are actually holding — see Use a scratch hash file to test Docly functions.