instructure/canvas-lms · error · PeerReview::InvalidDatesError
Due date cannot be before available from date
Error message
Due date cannot be before available from date
What it means
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.
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.
Example fix
// before
validate_peer_review_dates({ unlock_at: "2024-03-10T00:00:00Z", due_at: "2024-03-01T00:00:00Z" })
// raises
// after
validate_peer_review_dates({ unlock_at: "2024-03-01T00:00:00Z", due_at: "2024-03-10T00:00:00Z" }) Defensive patterns
Strategy: validation
Validate before calling
if dates[:due_at] && dates[:unlock_at] && dates[:due_at] < dates[:unlock_at] raise ArgumentError, "due_at must be after unlock_at" end
Try / catch
begin
validate_peer_review_dates(dates)
rescue PeerReview::InvalidDatesError => e
render json: { error: e.message }, status: :unprocessable_entity
end Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Invalid datetime format for
- Invalid parent assignment
- Must be a group assignment to create group overrides
- Peer review sub assignment does not exist
- Peer review sub assignment exists
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/c27e1da42be41fd4.
Report an issue: GitHub.
Appendix: source
Thrown at app/services/peer_review/validations.rb:91
# Accept both Time objects and ISO8601 strings for API compatibility
if date_value.is_a?(String)
unless Api::ISO8601_REGEX.match?(date_value)
raise PeerReview::InvalidDatesError, I18n.t("Invalid datetime format for %{attribute}", attribute: date_field)
end
parsed_dates[date_field.to_sym] = Time.zone.parse(date_value)
else
parsed_dates[date_field.to_sym] = date_value
end
end
due_at = parsed_dates[:due_at]
unlock_at = parsed_dates[:unlock_at]
lock_at = parsed_dates[:lock_at]
if due_at && unlock_at && due_at < unlock_at
raise PeerReview::InvalidDatesError, I18n.t("Due date cannot be before available from date")
end
if due_at && lock_at && due_at > lock_at
raise PeerReview::InvalidDatesError, I18n.t("Due date cannot be after until date")
end
if unlock_at && lock_at && unlock_at > lock_at
raise PeerReview::InvalidDatesError, I18n.t("Available from date cannot be after until date")
end
end
# Validates that peer review override dates fall within parent assignment override dates
# assignment available from date <= assignment due date <= peer review available from date <= peer review due date <= peer review until date <= assignment until date
def validate_override_dates_against_parent_override(peer_review_override, parent_override)
parent_unlock_at = parent_override.unlock_at_overridden ? parent_override.unlock_at : nil
parent_due_at = parent_override.due_at_overridden ? parent_override.due_at : nil
parent_lock_at = parent_override.lock_at_overridden ? parent_override.lock_at : nil
View on GitHub (pinned to 1c9f0bb801)