{"record":{"id":"6d21bc71dd0cc48f","repo":"instructure/canvas-lms","slug":"available-from-date-cannot-be-after-until-date","errorCode":null,"errorMessage":"Available from date cannot be after until date","messagePattern":"Available from date cannot be after until date","errorType":"validation","errorClass":"PeerReview::InvalidDatesError","httpStatus":null,"severity":"error","filePath":"app/services/peer_review/validations.rb","lineNumber":99,"sourceCode":"      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\n    validate_dates_within_parent_boundaries(\n      child_dates: peer_review_override,\n      parent_unlock_at:,\n      parent_due_at:,\n      parent_lock_at:,\n      is_override: true\n    )\n  end","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/peer_review/validations.rb#L81-L117","documentation":"PeerReview::InvalidDatesError raised by validate_peer_review_dates when the peer review sub-assignment's unlock_at (available from) is later than its lock_at (until date). An availability window whose start exceeds its end is meaningless, so Canvas rejects it before persisting. Thrown whenever peer review dates are validated, both standalone and against the parent assignment.","triggerScenarios":"Submitting peer review dates where both unlock_at and lock_at are present and unlock_at > lock_at, e.g. unlock_at: '2026-06-01T00:00:00Z', lock_at: '2026-05-01T00:00:00Z', via the peer review sub assignment create/update API path.","commonSituations":"Bulk date-import scripts that set unlock_at to a new semester start but forget to push lock_at; UI edits that advance the available-from date without re-checking the until date; copying dates from another course with a later start.","solutions":["Ensure unlock_at <= lock_at: raise lock_at or lower unlock_at in the payload","If reviews should be open-ended, drop lock_at (nil) instead of leaving a stale early value","If reviews should not be available yet, drop unlock_at or set it before lock_at","Pre-validate: raise unless Time.parse(unlock_at) <= Time.parse(lock_at)"],"exampleFix":"// before\n{ unlock_at: '2026-06-01T00:00:00Z', lock_at: '2026-05-01T00:00:00Z' }\n// after\n{ unlock_at: '2026-05-01T00:00:00Z', lock_at: '2026-06-01T00:00:00Z' }","handlingStrategy":"validation","validationCode":"def ordered_window?(dates)\n  u = dates[:unlock_at]; l = dates[:lock_at]\n  return true if u.nil? || l.nil?\n  u = Time.zone.parse(u) if u.is_a?(String)\n  l = Time.zone.parse(l) if l.is_a?(String)\n  u <= l\nend","typeGuard":"def parse_iso_date(v)\n  return v unless v.is_a?(String)\n  raise ArgumentError, 'not ISO8601' unless Api::ISO8601_REGEX.match?(v)\n  Time.zone.parse(v)\nend","tryCatchPattern":"begin\n  validator.validate_peer_review_dates(dates)\nrescue PeerReview::InvalidDatesError => e\n  Rails.logger.warn(\"peer review date order rejected: #{e.message}\")\n  flash[:error] = e.message\nend","preventionTips":["Validate the full unlock <= due <= lock chain before any API call","When updating one date, re-check it against the other two (partial updates break ordering)","Beware timezone offsets: two 'same-day' dates in different zones can invert ordering","Write a shared date-order assertion used by all peer review tooling"],"tags":["peer-review","date-validation","rails","canvas-lms"],"backgroundTag":"invalid-argument-value","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"}