openclaw - 💡(How to fix) Fix cron: one-shot --at broken when using --tz (IANA timezone) [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#78156Fetched 2026-05-06 06:16:30
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
2
Timeline (top)
closed ×1commented ×1cross-referenced ×1

One-shot --at cron jobs produce incorrect (or null) scheduled times when --tz (IANA timezone) is provided.

Root Cause

The offset-less datetime + --tz path in the cron CLI (parseOffsetlessIsoDateTimeInTimeZone / parseAt + getTimeZoneOffsetMs) uses a fragile double-offset correction:

const naiveMs = new Date(`${raw}Z`).getTime();
const resolvedMs = naiveMs - getTimeZoneOffsetMs(
  naiveMs - getTimeZoneOffsetMs(naiveMs, timeZone),
  timeZone
);

This does not reliably convert wall time in the target IANA zone to a correct UTC instant (especially across DST transitions). The resulting at/atMs stored in the job schedule is wrong.

The explicit-offset path (--at "2026-05-06T10:00-05:00") works because it falls through to parseAbsoluteTimeMs + Date.parse. Only the documented --tz path for one-shots is broken.

Code Example

openclaw cron add --name "test-tz" \
  --at "2026-05-06T10:00" \
  --tz "America/Chicago" \
  --system-event "one-shot with tz"

---

const naiveMs = new Date(`${raw}Z`).getTime();
const resolvedMs = naiveMs - getTimeZoneOffsetMs(
  naiveMs - getTimeZoneOffsetMs(naiveMs, timeZone),
  timeZone
);

---

// src/cli/cron-cli/shared.test.ts or new parse-at.test.ts
it("parseAt respects --tz for offset-less datetime (DST)", () => {
  // 10:00 America/Chicago on 2026-05-06 (CDT, UTC-5) → 15:00Z
  const result = parseAt("2026-05-06T10:00", "America/Chicago");
  expect(result).toBe("2026-05-06T15:00:00.000Z");
});

it("parseAt respects --tz for offset-less datetime (standard time)", () => {
  const result = parseAt("2026-01-15T10:00", "America/Chicago");
  expect(result).toBe("2026-01-15T16:00:00.000Z"); // CST = UTC-6
});
RAW_BUFFERClick to expand / collapse

Summary

One-shot --at cron jobs produce incorrect (or null) scheduled times when --tz (IANA timezone) is provided.

Reproduction

openclaw cron add --name "test-tz" \
  --at "2026-05-06T10:00" \
  --tz "America/Chicago" \
  --system-event "one-shot with tz"

Expected: Job scheduled for the correct UTC instant (15:00Z on 2026-05-06 during CDT). Actual: Incorrect at value stored → job never fires or fires at the wrong time.

Root Cause

The offset-less datetime + --tz path in the cron CLI (parseOffsetlessIsoDateTimeInTimeZone / parseAt + getTimeZoneOffsetMs) uses a fragile double-offset correction:

const naiveMs = new Date(`${raw}Z`).getTime();
const resolvedMs = naiveMs - getTimeZoneOffsetMs(
  naiveMs - getTimeZoneOffsetMs(naiveMs, timeZone),
  timeZone
);

This does not reliably convert wall time in the target IANA zone to a correct UTC instant (especially across DST transitions). The resulting at/atMs stored in the job schedule is wrong.

The explicit-offset path (--at "2026-05-06T10:00-05:00") works because it falls through to parseAbsoluteTimeMs + Date.parse. Only the documented --tz path for one-shots is broken.

Affected Code

  • CLI parsing: src/cli/cron-cli/ (parseAt, parseOffsetlessIsoDateTimeInTimeZone, getTimeZoneOffsetMs)
  • Runtime consumption: computeJobNextRunAtMs (jobs module) which trusts the stored at value

Suggested Fix

Replace the naive offset dance with a correct wall-time → UTC conversion (e.g. using Intl.DateTimeFormat parts in reverse or a library like temporal-polyfill). Add unit tests covering:

  • Non-DST zone (America/Chicago in winter)
  • DST zone (America/Chicago in summer)
  • Zones with non-hour offsets (Asia/Kolkata)
  • Edge case near DST transition

Test Case to Add

// src/cli/cron-cli/shared.test.ts or new parse-at.test.ts
it("parseAt respects --tz for offset-less datetime (DST)", () => {
  // 10:00 America/Chicago on 2026-05-06 (CDT, UTC-5) → 15:00Z
  const result = parseAt("2026-05-06T10:00", "America/Chicago");
  expect(result).toBe("2026-05-06T15:00:00.000Z");
});

it("parseAt respects --tz for offset-less datetime (standard time)", () => {
  const result = parseAt("2026-01-15T10:00", "America/Chicago");
  expect(result).toBe("2026-01-15T16:00:00.000Z"); // CST = UTC-6
});

extent analysis

TL;DR

Replace the fragile double-offset correction in parseOffsetlessIsoDateTimeInTimeZone with a correct wall-time → UTC conversion using a library like temporal-polyfill or Intl.DateTimeFormat parts.

Guidance

  • Identify the affected code paths: parseAt, parseOffsetlessIsoDateTimeInTimeZone, and getTimeZoneOffsetMs in src/cli/cron-cli/.
  • Replace the naive offset calculation with a reliable method, such as using temporal-polyfill to handle timezone conversions.
  • Add unit tests to cover various scenarios, including non-DST zones, DST zones, and edge cases near DST transitions.
  • Verify the fix by running the provided test cases and checking the stored at value in the job schedule.

Example

// Using temporal-polyfill
import { Temporal } from '@js-temporal/polyfill';

const parseAt = (raw: string, timeZone: string) => {
  const dt = Temporal.PlainDateTime.from(raw);
  const zdt = dt.toZonedDateTimeISO(timeZone);
  return zdt.toString();
};

Notes

The provided test cases should be added to ensure the fix works correctly for different scenarios. The temporal-polyfill library can be used to handle timezone conversions, but other libraries or methods may also be suitable.

Recommendation

Apply the workaround by replacing the fragile double-offset correction with a correct wall-time → UTC conversion using a library like temporal-polyfill, as it provides a reliable method for handling timezone conversions.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix cron: one-shot --at broken when using --tz (IANA timezone) [1 comments, 2 participants]