jackwener/OpenCLI · error · ArgumentError
--expiration must be a valid calendar date
Error message
--expiration must be a valid calendar date
What it means
After the YYYY-MM-DD format check passes, normalizeExpiration() parses the value as a UTC calendar date and verifies the round-trip (Date -> ISO string) reproduces the input. Values like 2025-02-30 or 2025-13-01 pass the regex but fail this check, so this ArgumentError is thrown for well-formatted but nonexistent calendar dates.
Source
Thrown at clis/barchart/greeks.js:27
const DEFAULT_LIMIT = 10;
const MIN_LIMIT = 1;
const MAX_LIMIT = 100;
function normalizeSymbol(value) {
const symbol = String(value ?? '').trim().toUpperCase();
if (!symbol) throw new ArgumentError('symbol is required');
return symbol;
}
function normalizeExpiration(value) {
const expiration = String(value ?? '').trim();
if (!expiration) return '';
if (!/^\d{4}-\d{2}-\d{2}$/.test(expiration)) {
throw new ArgumentError('--expiration must use YYYY-MM-DD format');
}
const parsed = new Date(`${expiration}T00:00:00Z`);
if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== expiration) {
throw new ArgumentError('--expiration must be a valid calendar date');
}
return expiration;
}
function parseLimit(value) {
if (value === undefined || value === null || value === '') return DEFAULT_LIMIT;
const limit = Number(value);
if (!Number.isInteger(limit) || limit < MIN_LIMIT || limit > MAX_LIMIT) {
throw new ArgumentError(`--limit must be an integer between ${MIN_LIMIT} and ${MAX_LIMIT}`);
}
return limit;
}
function unwrapBrowserResult(value) {
if (value && typeof value === 'object' && 'session' in value && 'data' in value) {
return value.data;
}
return value;View on GitHub (pinned to 49907e53dc)
Solutions
- Correct the date to a real calendar date, e.g. --expiration 2025-02-28.
- Build expiration dates via a Date object (setDate + ISO formatting) instead of manual string concatenation so rollover is handled.
- Validate the date client-side with the same round-trip check before invoking the CLI.
Example fix
// before --expiration 2025-02-30 // after --expiration 2025-02-28
Defensive patterns
Strategy: validation
Validate before calling
function isRealDate(iso) {
if (!/^\d{4}-\d{2}-\d{2}$/.test(iso)) return false;
const parsed = new Date(`${iso}T00:00:00Z`);
return !Number.isNaN(parsed.getTime()) && parsed.toISOString().slice(0, 10) === iso;
}
// call: isRealDate('2025-02-30') => false Type guard
function isValidExpiration(v) {
return typeof v === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v) &&
new Date(`${v}T00:00:00Z`).toISOString().slice(0, 10) === v;
} Try / catch
try {
await greeks({ symbol, expiration });
} catch (e) {
if (e.message.includes('valid calendar date')) {
console.error(`${expiration} is not a real calendar date (e.g. Feb 30)`);
process.exitCode = 2;
return;
}
throw e;
} Prevention
- Build dates with Date objects (setDate/setMonth) rather than manual string assembly
- Run the regex + round-trip date check before invoking the CLI
- Watch for day-of-month overflow when incrementing dates in loops
When it happens
Trigger: Passing --expiration with a syntactically valid but impossible date such as 2025-02-30, 2025-04-31, 2023-02-29 (non-leap year), or 2025-00-10.
Common situations: Generating dates programmatically by incrementing day numbers without month rollover; typos in the day field (e.g. 31 in a 30-day month); constructing dates from user spreadsheets where the day exceeds the month's length.
Related errors
- archive search sort must be one of ${SORT_OPTIONS.join(', ')
- archive search mediatype must be one of ${MEDIATYPES.join(',
- archive search limit must be a positive integer
- archive search limit must be <= 100
- archive search query must not be empty
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/c875fd3d189d8d2a.
Report an issue: GitHub.