{"record":{"id":"e93ba4cd16825132","repo":"nautechsystems/nautilus_trader","slug":"empty-expiry-string","errorCode":null,"errorMessage":"Empty expiry string","messagePattern":"Empty expiry string","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/interactive_brokers/src/providers/parse.rs","lineNumber":73,"sourceCode":"        parts[1].len().min(8) as u8\n    } else {\n        0\n    }\n}\n\n/// Convert timestamp string to UnixNanos.\n///\n/// Handles formats like \"20230101\" or \"20230101 00:00:00 UTC\".\n///\n/// # Errors\n///\n/// Returns an error if the timestamp cannot be parsed.\npub fn expiry_timestring_to_unix_nanos(\n    expiry: &str,\n    details: Option<&ibapi::contracts::ContractDetails>,\n) -> anyhow::Result<UnixNanos> {\n    if expiry.is_empty() {\n        anyhow::bail!(\"Empty expiry string\");\n    }\n\n    // Parse timestamp string - Most contract expirations are %Y%m%d format\n    // Some exchanges have expirations in %Y%m%d %H:%M:%S %Z\n    let dt = if expiry.len() == 8 {\n        // Format: YYYYMMDD\n        let year = &expiry[0..4];\n        let month = &expiry[4..6];\n        let day = &expiry[6..8];\n        let date = time::Date::from_calendar_date(\n            year.parse()?,\n            time::Month::try_from(month.parse::<u8>()?)?,\n            day.parse()?,\n        )?;\n\n        // If we have trading hours, try to extract the last trade time\n        // Trading hours format: \"20240411:0000-20240411:1800;...\"\n        let mut expiry_time = time::Time::MIDNIGHT;","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/interactive_brokers/src/providers/parse.rs#L55-L91","documentation":"expiry_timestring_to_unix_nanos converts an IB contract expiry string (e.g. a futures or option last-trade date) into a UnixNanos timestamp. IB expiry strings can legitimately be empty for some contracts, but a parseable expiry is required to build the instrument, so an empty string is rejected immediately.","triggerScenarios":"parse_futures_contract or parse_option_contract calls expiry_timestring_to_unix_nanos with contract details whose last_trade_date_or_contract_month (or local expiry field) is empty.","commonSituations":"Continuous/index-style contracts with no discrete expiry, incomplete contract details returned from IB, or manually built ContractDetails omitting the expiry field.","solutions":["Provide an expiry string (YYYYMMDD, optionally with time) in the contract details before parsing.","For perpetual/index instruments that genuinely have no expiry, route to the appropriate parser (e.g. index/continuous future) rather than the futures/options parser.","Check IB contract details are complete — re-request contractDetails if fields came back empty.","Guard: skip contracts with empty expiry instead of failing the whole batch."],"exampleFix":"// before: empty expiry\nlet details = ContractDetails { last_trade_date_or_contract_month: \"\".into(), .. };\n// after: expiry supplied\nlet details = ContractDetails { last_trade_date_or_contract_month: \"20260619\".into(), .. };","handlingStrategy":"validation","validationCode":"fn has_expiry(details: &ContractDetails) -> bool {\n    !details.last_trade_date_or_contract_month.is_empty()\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Skip or re-fetch contracts with empty expiry fields before parsing","Use the index/continuous-future parser for expiry-less instruments","Validate contractDetails completeness after IB responses"],"tags":["interactive-brokers","parsing","expiry","contract"],"backgroundTag":"empty-required-field","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}