Billionmail/BillionMail · error
end_time must greater than start_time
Error message
end_time must greater than start_time
What it means
TaskStatService.filterAndPrepareTimeSection validates the chart time window used by GetTaskStatChart. If startTime exceeds endTime it panics with a localized message via public.Lang, because a negative or inverted range would produce meaningless chart buckets. Note the odd guard: endTime is only defaulted to now when endTime < 0, so an unset (0) endTime does not protect against inversion.
Source
Thrown at core/internal/service/batch_mail/stat_service.go:67
return map[string]interface{}{
"dashboard": s.getTaskDashboard(taskId, domain, startTime, endTime),
"mail_providers": s.getTaskMailProviders(taskId, domain, startTime, endTime),
"send_mail_chart": s.getTaskSendMailChart(taskId, domain, startTime, endTime),
"bounce_rate_chart": s.getTaskBounceRateChart(taskId, domain, startTime, endTime),
"open_rate_chart": s.getTaskOpenRateChart(taskId, domain, startTime, endTime),
"click_rate_chart": s.getTaskClickRateChart(taskId, domain, startTime, endTime),
}
}
// filterAndPrepareTimeSection
func (s *TaskStatService) filterAndPrepareTimeSection(startTime, endTime int64) (int64, int64) {
if startTime > 0 && endTime < 0 {
endTime = time.Now().Unix()
}
if startTime > endTime {
panic(public.Lang("end_time must greater than start_time"))
}
return startTime, endTime
}
// prepareChartData
func (s *TaskStatService) prepareChartData(startTime, endTime int64) (string, string) {
columnType := "daily"
secs := endTime - startTime
xAxisField := "EXTRACT(EPOCH FROM date_trunc('day', to_timestamp(sm.log_time_millis / 1000)))::bigint as x"
if secs < 86400 {
columnType = "hourly"
xAxisField = "to_char(to_timestamp(sm.log_time_millis / 1000), 'HH24')::integer as x"
}
return columnType, xAxisField
}View on GitHub (pinned to fc36c76c05)
Solutions
- Fix the caller to pass startTime <= endTime in Unix seconds.
- Normalize units before calling (divide ms timestamps by 1000).
- Validate/swapon the UI side: require an end date and enforce end >= start in the form.
- Harden filterAndPrepareTimeSection to default endTime to now when endTime <= 0, since 0 is the common 'unset' value.
Example fix
// before
if startTime > 0 && endTime < 0 {
endTime = time.Now().Unix()
}
// after
if startTime > 0 && endTime <= 0 {
endTime = time.Now().Unix()
} Defensive patterns
Strategy: validation
Validate before calling
// Go: caller-side guard before GetTaskStatChart
if start > 0 && (end <= 0 || start > end) {
return fmt.Errorf("invalid range: start=%d end=%d", start, end)
} Type guard
func validTimeRange(start, end int64) bool {
return start > 0 && end > 0 && start <= end
} Try / catch
// the service panics, so recover at the handler boundary
defer func() {
if r := recover(); r != nil {
response.Fail(ctx, fmt.Sprintf("%v", r))
}
}()
svc.GetTaskStatChart(ctx, start, end) Prevention
- Convert client timestamps to Unix seconds before calling chart endpoints.
- Default an unset end date to time.Now().Unix() in the handler.
- Enforce end >= start in the date-picker UI.
- Prefer returning an error over panic in new service code.
When it happens
Trigger: Calling GetTaskStatChart with startTime > endTime, e.g. startTime=1700000000, endTime=0 (endTime only auto-set when negative), or the caller swapping the two parameters.
Common situations: Frontend sends start/end in milliseconds while the backend expects Unix seconds (one value dwarfs the other); an empty end-date field submitted as 0; date pickers allowing end before start.
Related errors
- end_time must greater than start_time
- JWT missing group_token claim
- threads must be greater than zero
- threads must be less than 100
- task %d not found
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/cbea25037635d140.
Report an issue: GitHub.