{"record":{"id":"3ad2fac75c5b2150","repo":"Billionmail/BillionMail","slug":"start-time-must-be-greater-or-equal-to-end-time","errorCode":null,"errorMessage":"start time must be greater or equal to end time","messagePattern":"start time must be greater or equal to end time","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"core/internal/controller/overview/overview_v1_failed_list.go","lineNumber":22,"sourceCode":"\t\"billionmail-core/internal/service/maillog_stat\"\n\t\"billionmail-core/internal/service/public\"\n\t\"context\"\n\t\"fmt\"\n\t\"github.com/gogf/gf/v2/util/gconv\"\n\t\"time\"\n\n\t\"billionmail-core/api/overview/v1\"\n)\n\nfunc (c *ControllerV1) FailedList(ctx context.Context, req *v1.FailedListReq) (res *v1.FailedListRes, err error) {\n\tres = &v1.FailedListRes{}\n\n\tif req.EndTime == 0 {\n\t\treq.EndTime = time.Now().Unix()\n\t}\n\n\tif req.EndTime < req.StartTime {\n\t\terr = fmt.Errorf(\"start time must be greater or equal to end time\")\n\t\treturn\n\t}\n\n\toverview := maillog_stat.NewOverview()\n\tfailedList := overview.FailedList(req.CampaignId, req.Domain, req.StartTime, req.EndTime)\n\n\terr = gconv.Struct(failedList, &res.Data)\n\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed to convert overview data: %v\", err)\n\t\treturn\n\t}\n\n\tres.SetSuccess(public.LangCtx(ctx, \"Success\"))\n\n\treturn\n}\n","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/controller/overview/overview_v1_failed_list.go#L4-L40","documentation":"FailedList validates the requested time window and defaults EndTime to now when zero. If EndTime is earlier than StartTime, the query would return nonsense, so the handler rejects it with this error. Despite the wording, the requirement is end >= start.","triggerScenarios":"Calling the failed-list API with StartTime greater than EndTime, e.g. start=now, end=yesterday; forgetting that EndTime defaults to now while passing a StartTime in the future (e.g. wrong unit — milliseconds instead of seconds); clock-skewed clients sending future start times.","commonSituations":"Client sends timestamps in milliseconds (13-digit) while server expects Unix seconds, making start appear far in the future; reversed arguments when constructing the request; dashboard 'last 24h' logic computing start after an end timestamp captured earlier.","solutions":["Ensure StartTime <= EndTime in Unix seconds before calling the API","Convert timestamps to seconds (divide ms by 1000) if sending millisecond precision","Check NTP/clock sync if times come from a skewed machine","Swap the values if they were accidentally reversed"],"exampleFix":"// before\nreq.StartTime = time.Now().Add(24 * time.Hour).Unix()\nreq.EndTime = time.Now().Unix()\n// after\nreq.EndTime = time.Now().Unix()\nreq.StartTime = req.EndTime - 24*3600","handlingStrategy":"validation","validationCode":"if (typeof startTime !== 'number' || typeof endTime !== 'number') throw new Error('timestamps must be Unix seconds');\nif (startTime > endTime) throw new Error('start time must be <= end time');","typeGuard":"function isUnixSeconds(n: unknown): n is number {\n  return typeof n === 'number' && Number.isInteger(n) && n < 1e11; // excludes ms timestamps\n}","tryCatchPattern":"try {\n  const data = await api.failedList({ start_time: s, end_time: e });\n} catch (err) {\n  if (String(err.message).includes('greater or equal to end time')) {\n    [s, e] = [Math.min(s, e), Math.max(s, e)]; // or fix units\n  }\n}","preventionTips":["Always send Unix seconds, never milliseconds","Clamp end_time to now and start_time = end_time - window","Compute start before end in time-window helpers"],"tags":["validation","timestamp","overview"],"backgroundTag":"invalid-date-range","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}