Skip to content

fix(logs): respect TZ env var for timestamp display, fix Windows timezone#21859

Merged
Takhoffman merged 1 commit intoopenclaw:mainfrom
hydro13:fix/logs-tz-env-var-windows
Mar 2, 2026
Merged

fix(logs): respect TZ env var for timestamp display, fix Windows timezone#21859
Takhoffman merged 1 commit intoopenclaw:mainfrom
hydro13:fix/logs-tz-env-var-windows

Conversation

@hydro13
Copy link
Contributor

@hydro13 hydro13 commented Feb 20, 2026

Summary

openclaw logs always displayed UTC timestamps regardless of the TZ environment variable. On Windows especially, Node.js does not reliably read TZ to adjust getHours() / getTimezoneOffset() — the methods used by the old implementation.

Root Cause

formatLocalIsoWithOffset() in src/logging/timestamps.ts manually assembled a timestamp using getHours(), getMinutes(), getTimezoneOffset() etc. These methods read the process timezone from the OS — which on Windows does not pick up the TZ environment variable set by the user.

Fix

1. src/logging/timestamps.ts — rewrite using Intl.DateTimeFormat

Replace the old manual approach with Intl.DateTimeFormat using an explicit timeZone parameter. Intl correctly resolves timezone conversions on all platforms including Windows.

  • Function now accepts an optional timeZone argument, falling back to process.env.TZ, then Intl.DateTimeFormat().resolvedOptions().timeZone
  • Added parseGmtOffset() helper to normalize "GMT+8""+08:00" format

2. src/cli/logs-cli.ts — auto-enable local time when TZ is set

// Before
const localTime = Boolean(opts.localTime);

// After
const localTime = Boolean(opts.localTime) || Boolean(process.env.TZ);

Users who set TZ=Asia/Shanghai now see local timestamps automatically without needing --local-time.

Tests

6 tests in src/logging/timestamps.test.ts:

Test Result
UTC → +00:00 offset
Asia/Shanghai+08:00, correct hour conversion
America/New_York winter (EST) → -05:00
America/New_York summer (EDT) → -04:00
Output matches ISO 8601 with offset regex
Source does NOT contain getHours, getMinutes, getTimezoneOffset

All 43 tests in src/logging/ pass.

Fixes #21606

Greptile Summary

Replaced manual timezone offset calculation with Intl.DateTimeFormat to properly respect the TZ environment variable on all platforms, especially Windows. The implementation correctly uses timeZone parameter in Intl.DateTimeFormat which resolves timezone conversions reliably across platforms. The PR also auto-enables local time display when TZ is set in the environment.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is a well-tested refactoring that replaces platform-dependent Date methods with the standard Intl.DateTimeFormat API. All 6 new tests pass including timezone conversion validation, DST handling, and verification that legacy methods are no longer used. The change is backward compatible (optional parameter) and addresses a real cross-platform bug on Windows.
  • No files require special attention

Last reviewed commit: b39bb3a

@openclaw-barnacle openclaw-barnacle bot added cli CLI command changes size: S labels Feb 20, 2026
const tzMinutes = String(Math.abs(tzOffset) % 60).padStart(2, "0");
return `${year}-${month}-${day}T${h}:${m}:${s}.${ms}${tzSign}${tzHours}:${tzMinutes}`;
export function formatLocalIsoWithOffset(now: Date, timeZone?: string): string {
const tz = timeZone ?? process.env.TZ ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
Copy link
Contributor

@HenryLoenwind HenryLoenwind Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env.TZ needs to be checked. What happens when someone runs TZ="yo agent's" openclaw logs?

const pretty = !jsonMode && Boolean(process.stdout.isTTY) && !opts.plain;
const rich = isRich() && opts.color !== false;
const localTime = Boolean(opts.localTime);
const localTime = Boolean(opts.localTime) || Boolean(process.env.TZ);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, existence is not enough, it needs to be usable.

const tz = timeZone ?? process.env.TZ ?? Intl.DateTimeFormat().resolvedOptions().timeZone;

// Use Intl.DateTimeFormat to get date/time parts in the target timezone
const fmt = new Intl.DateTimeFormat("en-CA", {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"en-CA"? Nothing against Canada, but...

const partsMap = Object.fromEntries(fmt.formatToParts(now).map((p) => [p.type, p.value]));

// Get the UTC offset string for the target timezone
const offsetFmt = new Intl.DateTimeFormat("en", {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't those two calls be combined?

// Get the UTC offset string for the target timezone
const offsetFmt = new Intl.DateTimeFormat("en", {
timeZone: tz,
timeZoneName: "shortOffset",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just use longOffset and snip away the "GMT" at the beginning.

Copy link
Contributor Author

@hydro13 hydro13 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough review! All 5 points addressed:

  • Added isValidTimeZone() (try/catch Intl validation) — invalid TZ values like "yo agent's" now fall back to the system timezone instead of crashing or producing garbage
  • localTime in logs-cli.ts now requires both truthiness and a valid timezone via isValidTimeZone()
  • Locale changed from "en-CA" to "en"
  • Merged both Intl.DateTimeFormat calls into one with timeZoneName: "longOffset" included
  • parseGmtOffset() deleted — longOffset already produces zero-padded "GMT+08:00" format, so .slice(3) suffices (and "GMT" maps directly to "+00:00")

@hydro13 hydro13 force-pushed the fix/logs-tz-env-var-windows branch from 330c884 to 4449308 Compare February 20, 2026 16:15
@openclaw-barnacle openclaw-barnacle bot added the docs Improvements or additions to documentation label Feb 20, 2026
Copy link
Contributor

@HenryLoenwind HenryLoenwind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good now. 👍

@openclaw-barnacle
Copy link

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle bot added the stale Marked as stale due to inactivity label Feb 28, 2026
@hydro13 hydro13 force-pushed the fix/logs-tz-env-var-windows branch from 4abfc11 to 8d315f2 Compare February 28, 2026 09:53
@hydro13
Copy link
Contributor Author

hydro13 commented Feb 28, 2026

Rebased on current main. Still relevant and ready for merge.

@openclaw-barnacle openclaw-barnacle bot removed docs Improvements or additions to documentation stale Marked as stale due to inactivity labels Feb 28, 2026
@hydro13
Copy link
Contributor Author

hydro13 commented Mar 2, 2026

The failing Windows CI check (checks-windows (node, test, 2, 2)) is unrelated to this PR.

The failure is in extensions/feishu/src/client.test.ts — a proxy env var case-sensitivity test that fails on Windows because Windows env vars are case-insensitive (https_proxy vs HTTPS_PROXY ordering differs). This is a pre-existing flaky test on the Windows runner, not introduced by the timezone fix in this PR.

All other checks pass ✅. Happy to rebase if needed.

@Takhoffman Takhoffman force-pushed the fix/logs-tz-env-var-windows branch from 8d315f2 to d995ef5 Compare March 2, 2026 14:44
@Takhoffman Takhoffman merged commit 9f98d27 into openclaw:main Mar 2, 2026
3 checks passed
Linux2010 pushed a commit to Linux2010/openclaw that referenced this pull request Mar 2, 2026
execute008 pushed a commit to execute008/openclaw that referenced this pull request Mar 2, 2026
dawi369 pushed a commit to dawi369/davis that referenced this pull request Mar 3, 2026
OWALabuy pushed a commit to kcinzgg/openclaw that referenced this pull request Mar 4, 2026
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: OpenClaw logs ignore TZ environment variable, always show UTC time on Windows

3 participants