koala73/worldmonitor · error
Use a valid date from 1900 through 9998.
Error message
Use a valid date from 1900 through 9998.
What it means
The date() helper in operational-balance.ts validates startDate/deliveryDate as strict ISO calendar dates: the string must match /^\d{4}-\d{2}-\d{2}$/, parse as a finite Date, round-trip through toISOString() unchanged, and fall between 1900-01-01 and 9998-12-31. Any other value throws 'Use a valid date from 1900 through 9998.'
Solutions
- Convert to a canonical calendar string: new Date(v).toISOString().slice(0, 10), ensuring the local date maps correctly.
- Use <input type="date"> whose value is already 'YYYY-MM-DD'.
- Pre-validate with the same regex and range check before calling the parser.
Example fix
// before
parseOperationalInput({ ..., startDate: '01/05/2026' });
// after
parseOperationalInput({ ..., startDate: '2026-01-05' }); Defensive patterns
Strategy: validation
Validate before calling
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
function isValidWorksheetDate(v: unknown): v is string {
return typeof v === 'string' && ISO_DATE.test(v) &&
Number.isFinite(Date.parse(v)) &&
new Date(v).toISOString().slice(0, 10) === v &&
v >= '1900-01-01' && v <= '9998-12-31';
} Type guard
const isIsoCalendarDate = (v: unknown): v is string =>
typeof v === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v) &&
Number.isFinite(Date.parse(v)) && new Date(v).toISOString().slice(0, 10) === v; Try / catch
try {
const input = parseOperationalInput(raw);
} catch (e) {
if (e instanceof Error && e.message.includes('valid date from 1900')) {
console.error('Date must be a plain YYYY-MM-DD calendar string:', e.message);
} else throw e;
} Prevention
- Use <input type="date"> whose value is already YYYY-MM-DD.
- Strip time components: new Date(v).toISOString().slice(0, 10).
- Never pass Date objects, timestamps, or locale-formatted strings to the parser.
- Pre-validate dates with the regex plus range check before submission.
When it happens
Trigger: Passing '2026-13-40' (invalid month/day), '2026-1-1' (no zero padding), a full timestamp '2026-01-01T00:00:00Z', '01/01/2026', or a Date object instead of a 'YYYY-MM-DD' string.
Common situations: US-format dates from locale pickers; datetime-local inputs submitted with a time component; date inputs bound to Date objects rather than strings; epoch timestamps passed as numbers.
Related errors
- Custom scorecard bloc members must be uppercase ISO-2 codes.
- Expected a six-character hexadecimal ICAO address
- Expected an alphanumeric callsign of at most eight character
- Invalid imagery datetime
- Enter one quantity unit of 1-24 characters.
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/b0f6c2215dd37630.
Report an issue: GitHub.
Appendix: source
Thrown at src/utils/operational-balance.ts:23
export const MAX_OPERATIONAL_JSON_BYTES = 65_536;
const DAY_MS = 86_400_000;
const round = (value: number) => Math.round(value * 1e6) / 1e6;
const dateAt = (start: string, offset: number) => new Date(Date.parse(start) + offset * DAY_MS).toISOString().slice(0, 10);
function record(value: unknown): Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Expected a worksheet object.');
return value as Record<string, unknown>;
}
function quantity(value: unknown, label: string): number {
if (value === '' || value === null || value === undefined) throw new Error(`${label} is required.`);
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1e6 || round(value) !== value) {
throw new Error(`${label} must be between 0 and 1 million with at most 6 decimal places.`);
}
return value;
}
function date(value: unknown): string {
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value) || !Number.isFinite(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value || value < '1900-01-01' || value > '9998-12-31') {
throw new Error('Use a valid date from 1900 through 9998.');
}
return value;
}
export function parseOperationalInput(value: unknown): OperationalInput {
const input = record(value);
if (typeof input.operation !== 'string' || !input.operation.trim() || input.operation.length > 100) throw new Error('Enter an operation name of 1-100 characters.');
if (typeof input.unit !== 'string' || !/^[\p{L}\p{N} %./-]{1,24}$/u.test(input.unit) || !input.unit.trim()) throw new Error('Enter one quantity unit of 1-24 characters.');
const unit = input.unit.trim();
const startDate = date(input.startDate);
if (!Number.isInteger(input.horizonDays) || (input.horizonDays as number) < 1 || (input.horizonDays as number) > MAX_OPERATIONAL_DAYS) throw new Error('Horizon must be 1-90 whole days.');
const horizonDays = input.horizonDays as number;
if (input.basis !== 'example' && input.basis !== 'user') throw new Error('Input basis must be example or user.');
const deliveries = (value: unknown): OperationalDelivery[] => {
if (!Array.isArray(value) || value.length > MAX_OPERATIONAL_DELIVERIES) throw new Error('Use at most 30 deliveries per list.');
return value.map(value => {
const row = record(value);
const deliveryDate = date(row.date);View on GitHub (pinned to 7d06c8633d)