Files
accounted/lib/bookkeeping/validate-period-duration.ts
T
Jakob WennbergandClaude Opus 4.6 99bc462315 fix: prevent Chrome auto-translate from crashing onboarding (#65)
* fix: prevent Chrome auto-translate from crashing React during onboarding

Chrome auto-translate modifies DOM text nodes when it detects a Swedish
page (lang="sv") in a browser set to English. React does not expect
external DOM mutations and throws, crashing the entire component tree
into global-error.tsx on every step transition.

Add translate="no" and <meta name="google" content="notranslate"> to
suppress browser translation. Also fix timezone-unsafe date parsing in
fiscal period validation (new Date("YYYY-MM-DD") + getDate() returns
local-timezone values, shifting dates by -1 day in Western timezones).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add notranslate meta tag to global-error.tsx for consistency

Per review feedback — global-error.tsx renders its own <html> document,
so it needs the same <meta name="google" content="notranslate"> tag as
layout.tsx to fully suppress Chrome translation on error pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 15:11:44 +01:00

61 lines
2.0 KiB
TypeScript

/**
* Validates fiscal period duration per BFL 3 kap.
* Maximum 18 months for any fiscal period (first year may be extended).
* Normal ongoing periods are 12 months.
*/
/**
* Parse a YYYY-MM-DD string into numeric parts without timezone issues.
* Using new Date(dateStr) is unsafe because it creates UTC midnight,
* but getDate()/getMonth()/getFullYear() return local-timezone values —
* shifting the date by -1 day in Western timezones.
*/
export function parseDateParts(dateStr: string): { year: number; month: number; day: number } {
const [year, month, day] = dateStr.split('-').map(Number)
return { year, month, day }
}
/**
* Calculate the number of months between two dates (inclusive of partial months).
* Assumes start is 1st of month and end is last of month.
*/
export function monthsBetween(start: string, end: string): number {
const s = parseDateParts(start)
const e = parseDateParts(end)
return (e.year - s.year) * 12 + (e.month - s.month) + 1
}
/**
* Validate a fiscal period's duration and date constraints.
* Returns null if valid, or an error message string if invalid.
*/
export function validatePeriodDuration(start: string, end: string): string | null {
const startParts = parseDateParts(start)
const endParts = parseDateParts(end)
// end must be after start (YYYY-MM-DD strings are lexicographically orderable)
if (end <= start) {
return 'Period end must be after period start'
}
// start must be 1st of month
if (startParts.day !== 1) {
return 'Period start must be the 1st of a month'
}
// end must be last day of month
// new Date(year, 1-indexed-month, 0) gives the last day of that month
const lastDayOfEndMonth = new Date(endParts.year, endParts.month, 0).getDate()
if (endParts.day !== lastDayOfEndMonth) {
return 'Period end must be the last day of a month'
}
// Max 18 months per BFL 3 kap.
const months = monthsBetween(start, end)
if (months > 18) {
return `Period duration ${months} months exceeds maximum 18 months (BFL 3 kap.)`
}
return null
}