jackwener/OpenCLI · error · ArgumentError

date "${value}" is not a real calendar date

Error message

date "${value}" is not a real calendar date

What it means

validateDate() second stage: the string matched YYYY-MM-DD but does not represent a real calendar date. It constructs a UTC Date from the parts and checks for rollover — e.g. 2026-02-30 rolls to March 2, which fails the round-trip check — and throws ArgumentError.

Source

Thrown at clis/12306/utils.js:99

    }
    const lower = trimmed.toLowerCase();
    const exactName = stations.find((s) => s.name === trimmed);
    if (exactName) return exactName;
    const exactPinyin = stations.find((s) => s.pinyin === lower);
    if (exactPinyin) return exactPinyin;
    const exactAbbr = stations.find((s) => s.abbr === lower || s.short === lower);
    if (exactAbbr) return exactAbbr;
    throw new ArgumentError(`Unknown 12306 station "${trimmed}"`, 'Try the Chinese name (上海虹桥), the 3-4 letter telecode (AOH), or full pinyin (shanghaihongqiao).');
}

export function validateDate(value) {
    if (!DATE_RE.test(String(value ?? ''))) {
        throw new ArgumentError(`date must be YYYY-MM-DD, got "${value}"`);
    }
    const [y, m, d] = value.split('-').map(Number);
    const date = new Date(Date.UTC(y, m - 1, d));
    if (date.getUTCFullYear() !== y || date.getUTCMonth() !== m - 1 || date.getUTCDate() !== d) {
        throw new ArgumentError(`date "${value}" is not a real calendar date`);
    }
    return value;
}

export function normalizeLimit(value, defaultValue, max) {
    if (value === undefined || value === null || value === '') return defaultValue;
    const n = Number(value);
    if (!Number.isInteger(n) || n < 1) {
        throw new ArgumentError(`limit must be a positive integer (1-${max})`);
    }
    if (n > max) {
        throw new ArgumentError(`limit must be <= ${max}`);
    }
    return n;
}

/** Extract Set-Cookie header values into a single `Cookie:` header string. */
export function buildCookieHeader(setCookieHeaders) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Fix the date value to a real calendar date, checking month lengths and leap years
  2. Generate dates programmatically (new Date(y, m-1, d).toISOString().slice(0,10)) instead of string concatenation
  3. Add client-side validation with the same round-trip check before calling the library

Example fix

// before
await query({ date: '2026-02-30' });
// after
await query({ date: '2026-02-28' }); // real calendar date
Defensive patterns

Strategy: validation

Validate before calling

function isRealDate(v) {
  const s = String(v ?? '');
  if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return false;
  const [y, m, d] = s.split('-').map(Number);
  const dt = new Date(Date.UTC(y, m - 1, d));
  return dt.getUTCFullYear() === y && dt.getUTCMonth() === m - 1 && dt.getUTCDate() === d;
}
if (!isRealDate('2026-02-30')) throw new Error('not a real calendar date');

Try / catch

try {
  await query({ date });
} catch (e) {
  if (e instanceof ArgumentError && e.message.includes('not a real calendar date')) {
    console.error(`"${date}" does not exist on the calendar; check month lengths and leap years.`);
  } else throw e;
}

Prevention

When it happens

Trigger: Passing syntactically valid but impossible dates: '2026-02-30', '2025-02-29' (non-leap year), '2026-13-01' if the regex permits months >12, or day 31 in a 30-day month.

Common situations: Naive date arithmetic that produced invalid dates (adding 30 days by incrementing the day field), manual string assembly from separate year/month/day inputs, or user typos like 02-30.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/5a4ad158449c99e7. Report an issue: GitHub.