{"record":{"id":"c37f527a0c89f1be","repo":"jackwener/OpenCLI","slug":"checkin-must-be-earlier-than-checkout-got-c","errorCode":null,"errorMessage":"--checkin must be earlier than --checkout (got ${checkin} >= ${checkout})","messagePattern":"--checkin must be earlier than --checkout \\(got (.+?) >= (.+?)\\)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"warning","filePath":"clis/ctrip/hotel-search.js","lineNumber":64,"sourceCode":"      const result = detect();\n      if (result) { observer.disconnect(); resolve(result); }\n    });\n    observer.observe(document.documentElement, { childList: true, subtree: true });\n    setTimeout(() => { observer.disconnect(); resolve('timeout'); }, 5000);\n  })\n`;\n\nconst EXTRACT_HOTELS_JS = `\n  (() => {\n    const list = window.__NEXT_DATA__?.props?.pageProps?.initListData?.hotelList;\n    if (!Array.isArray(list)) return null;\n    return list;\n  })()\n`;\n\nfunction assertCheckinBeforeCheckout(checkin, checkout) {\n    if (Date.parse(checkin + 'T00:00:00Z') >= Date.parse(checkout + 'T00:00:00Z')) {\n        throw new ArgumentError(`--checkin must be earlier than --checkout (got ${checkin} >= ${checkout})`);\n    }\n}\n\ncli({\n    site: 'ctrip',\n    name: 'hotel-search',\n    access: 'read',\n    description: '搜索携程酒店列表（按城市 + 入住/离店日期）',\n    domain: 'hotels.ctrip.com',\n    strategy: Strategy.COOKIE,\n    browser: true,\n    navigateBefore: false,\n    args: [\n        { name: 'city', required: true, positional: true, help: 'Numeric Ctrip city ID (use `ctrip search` or `ctrip hotel-suggest` to discover)' },\n        { name: 'checkin', required: true, help: 'Check-in date (YYYY-MM-DD)' },\n        { name: 'checkout', required: true, help: 'Check-out date (YYYY-MM-DD)' },\n        { name: 'limit', default: DEFAULT_LIMIT, help: `Number of hotels (${MIN_LIMIT}-${MAX_LIMIT}); SSR first page returns ~13 entries` },\n    ],","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/hotel-search.js#L46-L82","documentation":"A client-side ArgumentError from assertCheckinBeforeCheckout: the `ctrip hotel-search` command requires --checkin to be strictly earlier than --checkout. Both are parsed as ISO dates and compared at UTC midnight; if checkin >= checkout (equal dates included), the command refuses to build the hotels.ctrip.com URL, since Ctrip's list endpoint cannot serve a zero/negative-length stay.","triggerScenarios":"Calling `ctrip hotel-search <city> --checkin 2026-09-01 --checkout 2026-09-01` (same-day) or with checkout earlier than checkin (swapped order, e.g. --checkin 2026-09-05 --checkout 2026-09-01).","commonSituations":"Swapping the two arguments in scripts; generating a single-night stay using the same date for both fields; date arithmetic off-by-one producing checkout <= checkin; timezone-shifted strings where the author assumed a different comparison basis.","solutions":["Pass checkout strictly after checkin, e.g. --checkin 2026-09-01 --checkout 2026-09-02 for one night.","Validate the pair in your calling code before invoking the command.","If dates come from user input, normalize and sort them (earlier date = checkin) before calling."],"exampleFix":"// before\nawait hotelSearch({ city: '1', checkin: '2026-09-05', checkout: '2026-09-01' });\n\n// after: normalize order first\nif (checkin >= checkout) [checkin, checkout] = [checkout, checkin];\nawait hotelSearch({ city: '1', checkin, checkout });","handlingStrategy":"validation","validationCode":"function validateStay(checkin, checkout) {\n  const ci = Date.parse(checkin + 'T00:00:00Z');\n  const co = Date.parse(checkout + 'T00:00:00Z');\n  if (!(co > ci)) throw new Error(`checkout must be after checkin (got ${checkin} >= ${checkout})`);\n}\nvalidateStay(checkin, checkout);","typeGuard":null,"tryCatchPattern":"try {\n  const rows = await ctripHotelSearch({ city, checkin, checkout });\n} catch (e) {\n  if (e instanceof ArgumentError && e.message.includes('earlier than')) {\n    console.error(`Bad date range: ${e.message}`); process.exitCode = 2; return [];\n  }\n  throw e;\n}","preventionTips":["Always compare checkin < checkout in calling code before invoking.","Auto-swap or reject equal dates; a stay needs at least one night.","Validate dates from user input or computed date arithmetic (off-by-one bugs).","Keep dates in ISO YYYY-MM-DD and compare with UTC-normalized parsing."],"tags":["argument-validation","date-validation","cli-input"],"backgroundTag":"invalid-date-range","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}