larksuite/cli · error
set_calendar: event_end must be after event_start
Error message
set_calendar: event_end must be after event_start
What it means
This is a semantic validation, not a format error: both event_start and event_end parsed successfully, but the parser requires end to be strictly after start (end.After(start)). An end equal to start (zero-duration event) or before start is rejected before any API call is made.
Source
Thrown at shortcuts/mail/draft/model.go:362
case "remove_signature":
// No required fields.
case "set_calendar":
if strings.TrimSpace(op.EventSummary) == "" {
return fmt.Errorf("set_calendar requires event_summary")
}
if strings.TrimSpace(op.EventStart) == "" || strings.TrimSpace(op.EventEnd) == "" {
return fmt.Errorf("set_calendar requires event_start and event_end")
}
start, err := parseISO8601(op.EventStart)
if err != nil {
return fmt.Errorf("set_calendar: event_start must be a valid ISO 8601 timestamp")
}
end, err := parseISO8601(op.EventEnd)
if err != nil {
return fmt.Errorf("set_calendar: event_end must be a valid ISO 8601 timestamp")
}
if !end.After(start) {
return fmt.Errorf("set_calendar: event_end must be after event_start")
}
case "remove_calendar":
// No required fields.
default:
return fmt.Errorf("unsupported op %q", op.Op)
}
return nil
}
func isRecipientField(field string) bool {
switch strings.ToLower(strings.TrimSpace(field)) {
case "to", "cc", "bcc":
return true
default:
return false
}
}
View on GitHub (pinned to 7fd6ef3c07)
Solutions
- Ensure event_end > event_start as absolute instants, e.g. end = start.Add(30 * time.Minute)
- Check for swapped arguments — if your values look reversed, exchange them
- Compare parsed instants (time.Parse both values and check end.After(start)) rather than string comparison, since offsets like +08:00 vs Z change the ordering
- If a zero-length event is intended, extend it to a minimal duration (e.g. 1 minute) since the parser rejects equality
Example fix
// before start := "2026-09-04T10:00:00+08:00"; end := "2026-09-04T02:00:00Z" // same instant -> rejected // after end := startTime.Add(30 * time.Minute).Format(time.RFC3339)
Defensive patterns
Strategy: validation
Validate before calling
start, err := time.Parse(time.RFC3339, op.EventStart)
end, err2 := time.Parse(time.RFC3339, op.EventEnd)
if err != nil || err2 != nil || !end.After(start) {
return errors.New("event_end must be a later instant than event_start")
} Prevention
- Compute end as start.Add(duration) so ordering is guaranteed by construction
- Compare parsed instants, never wall-clock strings, especially across different timezones
- Guard against zero-length events — the parser rejects end == start; use a minimal duration instead
- Unit-test any duration arithmetic that could produce negative or zero intervals
When it happens
Trigger: set_calendar op where event_end <= event_start after parsing: identical timestamps, swapped start/end values, end computed as start + 0 duration, or a negative duration applied to the end.
Common situations: Swapping arguments when constructing the op (end passed as start); timezone confusion where end is formatted in a different offset than start so wall-clock strings look ordered but instants are not; setting end = start for an 'instant' event; interval math producing zero or negative length.
Related errors
- set_calendar: event_start must be a valid ISO 8601 timestamp
- set_calendar: event_end must be a valid ISO 8601 timestamp
- unsupported op %q
- unsupported patch op %q
- recipient field must be one of to/cc/bcc
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/7cf76d2c3ead2003.
Report an issue: GitHub.