Skip to content

Port datetime and calc to Windows - #714

Merged
jgarzik merged 8 commits into
mainfrom
updates
Oct 3, 2026
Merged

jgarzik merged 8 commits into
mainfrom
updates

Conversation

@jgarzik

@jgarzik jgarzik commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

No description provided.

jgarzik and others added 8 commits October 3, 2026 02:43
… N)"

bc formatted its I/O errors with Rust's Display, which appends the errno
in parentheses: a write to a full disk read "cannot write output: No space
left on device (os error 28)", and a failed flush the same. Every other
utility reports plib::diag::io_error_text, the system's own message.

The interpreter's From<io::Error>, every flush failure (new report_io),
the thread-spawn failure and rustyline's errors (diag::error_text, which
also replaces a {:?} dump) now go through it.

Test: test_bc_write_error_is_reported asserts no "(os error" in stderr;
it failed before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows has no localtime_r or usable strftime, so plib::locale::strftime
was Unix-only and cal could not build there.

New Windows-only plib::timefmt: the C runtime's localtime_s/gmtime_s give
the broken-down time, honouring TZ in the runtime's forms (UTC0, EST5EDT)
or the system zone; the offset is the local fields' distance from the
instant, and the zone name the runtime's own strftime("%Z") (mingw's
msvcrt import library has no _get_tzname). chrono, already a workspace
dependency and now a Windows-only one of plib, formats the rest in the
POSIX locale's names, LC_TIME having no Windows meaning. The format is
rewritten first: %Z becomes the zone name, an E or O modifier is dropped,
and an unsupported conversion is copied literally as glibc does, so
chrono never meets an item it cannot format. UTC names its zone GMT, as
glibc's gmtime_r does.

locale::strftime on Windows wraps timefmt::format_time; the Unix one is
unchanged.

Tests (timefmt, run under Wine): every POSIX conversion at a fixed
instant, an ISO week year that differs from the calendar year, modifiers
and unknown conversions, a zone name containing '%'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each platform difference is one cfg'd pair of helpers; the Unix code
paths are the existing ones, moved, not changed.

- date: format_time is the C library's strftime with its sentinel buffer
  loop on Unix, plib::timefmt on Windows; set_clock is clock_settime on
  Unix, SetSystemTime (kernel32; needs the system-time privilege) on
  Windows. show_time keeps the shared reporting.
- time: CpuStart snapshots CPU times before the utility runs and reports
  the user/system seconds used after it is waited for: times(2) and its
  children's fields on Unix as before; on Windows, which keeps no
  children's totals, GetProcessTimes of this process plus that of the
  waited-for child's handle. exit_code is code-or-128+signal on Unix and
  the exit code on Windows.
- sleep: ignoring SIGALRM is Unix-only; Windows has no such signal.
- cal: unchanged, now that plib::locale::strftime builds on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
expr read its operands as raw bytes through Unix's OsStrExt::as_bytes.
OsStr::as_encoded_bytes is the portable spelling: on Unix it is the same
bytes, so nothing changes there; on Windows a UTF-16 argument arrives as
UTF-8. bc already built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- time: the tests timed `ls -l` and `sh -c ...`, which Windows lacks.
  They now time this crate's own binaries: `date -u` (a hyphenated
  utility argument, as the option-parsing regression needs), `sleep abc`
  for a propagated exit status (2), and, for the CPU-bound child, this
  test binary re-run with `--ignored --exact time::busy_child`, an
  ignored test that spins for 300 ms. The non-executable file for the
  126 case is written with std::fs::write instead of a Unix mode.
- date: three tests are Unix-only, each saying why: a zoneinfo TZ name
  (the Windows C runtime reads no tz database), the 64 KiB strftime
  buffer limit (the C library's; and a Windows command line cannot hold
  such a format), and the unprivileged set form (Windows CI runs as an
  administrator and would really set the clock).

Under Wine 9 three tests fail that pass on Windows: its msvcrt parses
TZ=UTC as zone "UT" (test_tz_utc, test_default_format_utc), and its
GetProcessTimes answers the caller's own times for another process
(cpu_bound_child_reports_nonzero_cpu_time).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- bc: plib::tmp is Unix-only; the three tests that need a directory use
  a small TestDir (a pid-named directory under the temp dir, removed on
  drop). The missing-file and full-device diagnostics are compared with
  the system's own text, from calc-tests.rs's open_error and
  full_device_error, instead of glibc's spelled out.
- expr: the non-UTF-8 operand cases are Unix-only (a Windows command
  line is UTF-16 and cannot carry them); the capture inside a multibyte
  character uses valid UTF-8 operands and becomes its own test that runs
  everywhere. The full-device diagnostic is compared as bc's is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both crates join WINDOWS_CRATES, so the windows job tests them with MSVC,
the lint job runs their Windows clippy, and the msrv job checks them for
Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e and calc

README listed each ported crate's utilities and every Windows difference,
a section that grew with each port. It now says only that some crates
build on Windows and points to WINDOWS.md, and is not edited per crate.

WINDOWS.md opens with "Using the utilities on Windows": a crate/utility
table and build command, and the behaviour list moved from README, adding
datetime and calc: POSIX-locale month and day names whatever LC_TIME
says, TZ in the C runtime's forms only, date setting the clock with the
system-time privilege, time counting the utility's CPU and its own, and
expr operands as text. The porting guide follows; its table gains
localtime_r/gmtime_r/TZ, strftime/LC_TIME, clock_settime, times() and
argv-as-bytes rows, step 6 lists a crate in WINDOWS.md rather than
README, and "Wine's limits" records the three datetime tests Wine 9 fails
and why, and that Wine maps /dev/full to the Linux device.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jgarzik
jgarzik requested a balanced review from Copilot October 3, 2026 04:46
@jgarzik jgarzik self-assigned this Oct 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

It adds substantial new cross-platform unsafe Windows FFI (strftime/tzset, GetProcessTimes, SetSystemTime) and a shared plib formatting module whose correctness cannot be verified by execution here, warranting human sign-off.

Review effort: Balanced
Findings: None

What changed in this PR

This PR extends the project's native Windows (MSVC) support from the xform/text crates to also cover datetime (cal, date, sleep, time) and calc (bc, expr). It adds a new plib::timefmt module that reimplements strftime(3) on Windows (via the C runtime for zone/TZ resolution plus chrono for POSIX-locale field formatting), wires platform-specific implementations for CPU timing and clock-setting, routes user-facing errors through the locale-aware diag::io_error_text/error_text helpers, and updates docs/CI to list the newly portable crates.

Changes:

  • New plib/src/timefmt.rs (+chrono as a Windows-only plib dep) providing Windows strftime, used by plib::locale::strftime's new #[cfg(windows)] branch.
  • datetime date/time/sleep split into #[cfg(unix)]/#[cfg(windows)] helpers (format_time, set_clock, CpuStart, exit_code); tests made portable (self-exec CPU-bound child, date/sleep instead of sh, cfg-gated Unix-only cases).
  • calc bc/expr switch error rendering to diag::io_error_text/error_text, use OsStr::as_encoded_bytes and a local TestDir; CI WINDOWS_CRATES and README/WINDOWS.md updated.
File Description
plib/​src/​timefmt.rs New Windows strftime (CRT zone + chrono formatting) with unit tests.
plib/​src/​locale.rs Adds #[cfg(windows)] strftime delegating to timefmt.
plib/​src/​lib.rs /​ plib/​Cargo.toml /​ Cargo.lock Register timefmt module and Windows-only chrono dependency.
datetime/​date.rs Refactors formatting into format_time and clock-setting into set_clock, per-platform.
datetime/​time.rs Replaces times() logic with per-platform CpuStart/exit_code.
datetime/​sleep.rs Guards SIGALRM ignore under #[cfg(unix)].
datetime/​tests/​{date,time}/​mod.rs Portable timing/exit tests; Unix-only cases gated.
calc/​bc.rs, expr.rs, bc_util/​interpreter.rs Locale-aware error text; portable argv byte handling.
calc/​tests/​* Local TestDir, system-derived error strings, split non-UTF-8 test.
.github/​workflows/​TestingCI.yml Adds datetime/calc to WINDOWS_CRATES.
README.md, WINDOWS.md Reorganize Windows docs; list newly portable crates.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@jgarzik
jgarzik merged commit 54d7d5b into main Oct 3, 2026
21 checks passed
@jgarzik
jgarzik deleted the updates branch October 3, 2026 05:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants