{"record":{"id":"c27e1da42be41fd4","repo":"instructure/canvas-lms","slug":"due-date-cannot-be-before-available-from-date","errorCode":null,"errorMessage":"Due date cannot be before available from date","messagePattern":"Due date cannot be before available from date","errorType":"validation","errorClass":"PeerReview::InvalidDatesError","httpStatus":null,"severity":"error","filePath":"app/services/peer_review/validations.rb","lineNumber":91,"sourceCode":"\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\")\n    end\n\n    if unlock_at && lock_at && unlock_at > lock_at\n      raise PeerReview::InvalidDatesError, I18n.t(\"Available from date cannot be after until date\")\n    end\n  end\n\n  # Validates that peer review override dates fall within parent assignment override dates\n  # assignment available from date <= assignment due date <= peer review available from date <= peer review due date <= peer review until date <= assignment until date\n  def validate_override_dates_against_parent_override(peer_review_override, parent_override)\n    parent_unlock_at = parent_override.unlock_at_overridden ? parent_override.unlock_at : nil\n    parent_due_at = parent_override.due_at_overridden ? parent_override.due_at : nil\n    parent_lock_at = parent_override.lock_at_overridden ? parent_override.lock_at : nil\n","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/peer_review/validations.rb#L73-L109","documentation":"After parsing, validate_peer_review_dates enforces unlock_at < due_at <= lock_at. It raises PeerReview::InvalidDatesError with 'Due date cannot be before available from date' when due_at is earlier than unlock_at (and both are provided). This mirrors standard Canvas date ordering restrictions for the peer review sub assignment.","triggerScenarios":"Submitting peer_review_dates where due_at < unlock_at, e.g. due_at: Mar 1 with unlock_at: Mar 10; clients that swap the two fields; timezone conversions shifting due_at behind unlock_at.","commonSituations":"Users entering an 'available from' date after the due date in a form; API integrations computing dates from offset epochs; bulk updates applying one assignment's dates to another with a different schedule.","solutions":["Ensure unlock_at is strictly earlier than due_at before calling the validation.","Compare parsed dates client-side: raise/skip when due_at < unlock_at and correct the payload.","If both dates were swapped by mistake, exchange the unlock_at and due_at values."],"exampleFix":"// before\nvalidate_peer_review_dates({ unlock_at: \"2024-03-10T00:00:00Z\", due_at: \"2024-03-01T00:00:00Z\" })\n// raises\n// after\nvalidate_peer_review_dates({ unlock_at: \"2024-03-01T00:00:00Z\", due_at: \"2024-03-10T00:00:00Z\" })","handlingStrategy":"validation","validationCode":"if dates[:due_at] && dates[:unlock_at] && dates[:due_at] < dates[:unlock_at]\n  raise ArgumentError, \"due_at must be after unlock_at\"\nend","typeGuard":null,"tryCatchPattern":"begin\n  validate_peer_review_dates(dates)\nrescue PeerReview::InvalidDatesError => e\n  render json: { error: e.message }, status: :unprocessable_entity\nend","preventionTips":["Enforce unlock_at < due_at in forms with client-side validation","Compare dates in a single timezone (UTC) before submitting","Guard bulk date imports against swapped field order"],"tags":["peer-review","dates","validation","rails"],"backgroundTag":"value-out-of-range","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"}