{"record":{"id":"8106bba48d7efd3d","repo":"we-promise/sure","slug":"unable-to-parse-transaction-date","errorCode":null,"errorMessage":"Unable to parse transaction date","messagePattern":"Unable to parse transaction date","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"app/models/akahu_entry/processor.rb","lineNumber":192,"sourceCode":"      when String\n        if value.include?(\"T\") || value.include?(\":\")\n          Time.parse(value).in_time_zone(account&.family&.timezone).to_date\n        else\n          Date.parse(value)\n        end\n      when Integer, Float\n        Time.at(value).in_time_zone(account&.family&.timezone).to_date\n      when Time, DateTime\n        value.in_time_zone(account&.family&.timezone).to_date\n      when Date\n        value\n      else\n        Rails.logger.error(\"Akahu transaction has invalid date value\")\n        raise ArgumentError, \"Invalid date format\"\n      end\n    rescue ArgumentError, TypeError => e\n      Rails.logger.error(\"Failed to parse Akahu transaction date: #{e.class}\")\n      raise ArgumentError, \"Unable to parse transaction date\"\n    end\n\n    def extra_metadata\n      {\n        \"akahu\" => {\n          \"pending\" => pending?,\n          \"type\" => data[:type],\n          \"category\" => category_data[:name],\n          \"category_id\" => category_data[:_id].presence || category_data[:id],\n          \"category_group\" => category_group_name,\n          \"reference\" => meta_data[:reference],\n          \"particulars\" => meta_data[:particulars],\n          \"code\" => meta_data[:code],\n          \"other_account\" => meta_data[:other_account]\n        }.compact\n      }\n    end\n","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/akahu_entry/processor.rb#L174-L210","documentation":"AkahuEntry::Processor#date converts data[:date] to a Date by type: ISO-ish strings go through Time.parse then in_time_zone(family timezone); epoch Integer/Float through Time.at; Time/DateTime re-zoned; Date passed through. Two failure layers produce the same user-visible ArgumentError(\"Unable to parse transaction date\"): (a) an unparseable String raises inside Time.parse; (b) an unknown type hits the internal raise \"Invalid date format\". Both are rescued (ArgumentError, TypeError) — TypeError covers nil-adjacent arithmetic like Time.at(nil) — logged, and re-raised with the stable message. Timezone lookup failures (family.timezone nil/bogus) also surface here since in_time_zone is inside the rescue.","triggerScenarios":"Akahu sends date as \"31/02/2024\" (impossible date) or \"FY24-Q1\" (garbage); a schema change delivers date as a Hash; the account's family has no timezone set making in_time_zone raise; epoch passed as a String containing digits (String branch -> Time.parse(\"1690000000\") succeeds but a String \"abc123\" fails); VCR cassette hand-edited with a malformed date field.","commonSituations":"Bank/feed data glitches producing one corrupt row; API version changes flipping date from ISO 8601 to another format; sandbox accounts with placeholder data; multi-timezone setups where family.timezone is nil because the family record predates the timezone column.","solutions":["Look at the logged line \"Failed to parse Akahu transaction date\" plus the raw transaction (log data[:date].inspect) to identify whether it's the value or the type","Ensure the account's family has a valid timezone (account&.family&.timezone) — a nil timezone poisons every date conversion for that family","If the feed genuinely changed format, extend the String branch to try Date.parse or explicit formats before raising","Quarantine the malformed transaction and let the sync continue — one bad date shouldn't block the whole import"],"exampleFix":"# before\ndef date\n  value = data[:date]\n  case value\n  when String\n    Time.parse(value).in_time_zone(account&.family&.timezone).to_date # \"garbage\" -> ArgumentError\n  ...\n\n# after\ndef date\n  value = data[:date]\n  case value\n  when String\n    tz = account&.family&.timezone || \"UTC\"\n    (Time.parse(value) rescue (return nil)).in_time_zone(tz).to_date\n  when Integer, Float\n    Time.at(value).in_time_zone(account&.family&.timezone || \"UTC\").to_date\n  ...\nend\n# caller: skip/save-without-date when nil is returned","handlingStrategy":"validation","validationCode":"# Before processing a transaction\ndef valid_date_payload?(value)\n  case value\n  when Date, Time, DateTime then true\n  when Integer, Float then true\n  when String\n    begin; !!Time.parse(value); rescue ArgumentError; false; end\n  else false\n  end\nend\nabort_row unless valid_date_payload?(data[:date]) && account&.family&.timezone.present?","typeGuard":"def akahu_date_convertible?(value)\n  case value\n  when Date, Time, DateTime, Integer, Float then true\n  when String then (Time.parse(value) rescue false) ? true : false\n  else false\n  end\nend","tryCatchPattern":"rescue ArgumentError => e\n  if e.message == \"Unable to parse transaction date\"\n    # skip/quarantine the row; log value + family timezone; continue sync\n    next\n  else\n    raise\n  end\nend","preventionTips":["Ensure every family has a timezone set before importing (nil timezone poisons all dates)","Validate feed dates at the boundary (ISO 8601 preferred) and quarantine bad rows","Log data[:date].inspect in the rescue so failures are diagnosable","Cover date-type variants (string/epoch/date) in processor tests, including one bad row"],"tags":["ruby","akahu","date-parsing","timezone","data-import"],"backgroundTag":"date-parsing-failed","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}