{"record":{"id":"8526c2e2be3d6fcb","repo":"instructure/canvas-lms","slug":"invalid-datetime-format-for-attribute","errorCode":null,"errorMessage":"Invalid datetime format for %{attribute}","messagePattern":"Invalid datetime format for %(.+?)","errorType":"validation","errorClass":"PeerReview::InvalidDatesError","httpStatus":null,"severity":"error","filePath":"app/services/peer_review/validations.rb","lineNumber":77,"sourceCode":"  def validate_peer_review_sub_assignment_not_exist(assignment)\n    if assignment.peer_review_sub_assignment.present?\n      raise PeerReview::SubAssignmentExistsError, I18n.t(\"Peer review sub assignment exists\")\n    end\n  end\n\n  # Validates that peer review dates follow the restriction below\n  # peer review unlock_at < peer review due_at <= peer review lock_at\n  def validate_peer_review_dates(peer_review_dates)\n    parsed_dates = {}\n\n    %w[due_at unlock_at lock_at].each do |date_field|\n      date_value = peer_review_dates.fetch(date_field.to_sym, nil)\n      next unless date_value.present?\n\n      # Accept both Time objects and ISO8601 strings for API compatibility\n      if date_value.is_a?(String)\n        unless Api::ISO8601_REGEX.match?(date_value)\n          raise PeerReview::InvalidDatesError, I18n.t(\"Invalid datetime format for %{attribute}\", attribute: date_field)\n        end\n\n        parsed_dates[date_field.to_sym] = Time.zone.parse(date_value)\n      else\n        parsed_dates[date_field.to_sym] = date_value\n      end\n    end\n\n    due_at = parsed_dates[:due_at]\n    unlock_at = parsed_dates[:unlock_at]\n    lock_at = parsed_dates[:lock_at]\n\n    if due_at && unlock_at && due_at < unlock_at\n      raise PeerReview::InvalidDatesError, I18n.t(\"Due date cannot be before available from date\")\n    end\n\n    if due_at && lock_at && due_at > lock_at\n      raise PeerReview::InvalidDatesError, I18n.t(\"Due date cannot be after until date\")","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/peer_review/validations.rb#L59-L95","documentation":"PeerReview::Validations#validate_peer_review_dates raises PeerReview::InvalidDatesError when a provided date field (unlock_at/due_at/lock_at) is a String that does not match Api::ISO8601_REGEX. The method accepts Time objects or ISO8601 strings; anything else in string form is rejected with the offending attribute named in the message.","triggerScenarios":"Passing dates like \"2024-03-01 10:00\" (space instead of T), \"03/01/2024\", or \"2024-03-01T10:00:00\" with an offset format the regex rejects to peer_review_dates; localizing date strings before sending them.","commonSituations":"API clients using strftime with human-readable formats; form params passed through unprocessed; JS Date.toString() output sent directly to the endpoint.","solutions":["Convert strings to ISO8601 (e.g. time.iso8601 or date.to_time.utc.iso8601) before passing.","Pass Time/DateTime objects instead of strings to skip string parsing entirely.","Pre-validate with Api::ISO8601_REGEX.match?(value) client-side and reject bad input early."],"exampleFix":"// before\nPeerReview::Validator.validate_peer_review_dates({ due_at: \"03/01/2024 5pm\" })\n// after\nPeerReview::Validator.validate_peer_review_dates({ due_at: Time.zone.parse(\"2024-03-01 17:00\").iso8601 })","handlingStrategy":"validation","validationCode":"def valid_peer_review_date?(v)\n  v.respond_to?(:zone) || (v.is_a?(String) && Api::ISO8601_REGEX.match?(v))\nend","typeGuard":"def coerce_to_time(v) = v.is_a?(String) ? Time.zone.parse(v) : v","tryCatchPattern":"begin\n  validate_peer_review_dates(dates)\nrescue PeerReview::InvalidDatesError => e\n  render json: { error: e.message }, status: :bad_request\nend","preventionTips":["Always serialize dates with .iso8601","Pass Time objects instead of strings when possible","Pre-validate user date input at the form/API boundary"],"tags":["peer-review","dates","iso8601","validation"],"backgroundTag":"invalid-date-format","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}