Command line reference
Every flag of both commands, both exit code tables, and how to read what comes back. The narrative version, with the reasoning, is on the CLI and CI page.
Two commands
chrono run starts an application at a different date and time.
chrono calc works out a date and tells you what makes it interesting.
They share one step grammar, so a shift written for one means the same in the other.
chrono version answers a third and much smaller question: which build this
is, which of the two cores the executable is, and the protocol version it speaks. One
line on stdout, so a script can capture it.
chrono license answers the fourth: the licence this build is distributed under,
the warranty disclaimer, and the third-party components linked into it. It also says where
the full licence text and the third-party notices are on disk, or admits it when the copy
you are running does not have them beside it. --components lists every bundled
component with its version instead of a summary.
chrono presets lists the preset catalogue the way --preset reads
it: every preset in the folder, written in the words the calculator's grammar reads, and
every file it would refuse, with the reason. --json writes
chronomock.presets/1 and --dir names another folder.
chrono run <target> [options] chrono calc [options] chrono version # chrono 0.4.0 (x64, protocol 1) chrono license # GPL-3.0-only, and what is bundled chrono presets # the catalogue, as --preset reads it chrono --help # usage for all of them
chrono run - options
| Flag | What it does |
|---|---|
--at <moment> |
The moment to start at, in the session zone. Absolute
(2038-01-19T03:14:07) or relative with a leading sign
(+1d, -2h). Units: s m h d w mo q y -
m is minutes and mo is months. Without
this flag the session clock starts at the real current time, so --mode xN
on its own simply runs the target faster. |
--zone +HH:MM |
The session zone: the zone the moment is written in, and the one the target's local-time calls report. A fixed offset, with no daylight saving. Without it, the host's own zone. |
--mode flow|frozen|xN |
flow runs at real speed, frozen holds the clock still,
xN runs it N times faster (x60 turns an hour into a
minute, x1440 a day). |
--scale-duration |
Also speed up the duration axis: tick counts, the interrupt time, sleeps and timers. Object waits are counted and keep their real length. Off by default, because a wall clock and a stopwatch are different questions. A timer or a delay set for a date and time follows the session clock with or without it. |
--scale-qpc |
Also scale the high-resolution counter, which is where Python 3.13+
monotonic, .NET Stopwatch and Java nanoTime
read elapsed time. Off by default: an application that times its rendering off that
counter can look distorted. |
--ticks N |
End the session after N heartbeats, one real second each, and report. Without it the tool stays attached until the target, and every program it started that runs on the session clock, has exited - a launcher that ends at once leaves the session to the application it started. |
--timeout <s> |
Give up after this many seconds and exit 6, for a pipeline that must not hang. Independently of this flag, a core that says nothing for 15 seconds is stopped and also exits 6 - in both cases the run is cut short, so it carries no verdict. |
--args "..." |
Arguments for the target, as one string, split by the same rule Windows uses - so
an argument containing spaces survives. For a Chromium target,
--user-data-dir and --remote-debugging-port are refused
rather than overridden: the session needs its own isolated profile, and quietly
running on your real browser profile is the one thing that must not happen. |
--cwd <dir> |
Start the target in that directory. Without it the target inherits ours, which is
what every run did before this flag existed. An empty value is a usage error rather
than "no directory" - absent and empty are different things, and only absent is
something Windows can be given. A directory that does not exist stops the session
with target.cwd_missing before anything is launched, instead of
surfacing later as a target that would not start. |
--preset <id> |
Take the moment and the speed from presets/<id>.json. Exclusive
with --at, --mode and --scale-duration, since
the preset is then the single source of both. |
--param id=value |
Fill a preset parameter. Repeatable, requires --preset. In
run only, a trial start date defaults to the target executable's own
creation date, so a trial preset counts from the day the application was installed
without you typing it. |
--set-after T:M |
At heartbeat T, change the speed to M. In flight the multiplier is at least 1 - freezing mid-session is not offered here. |
--jump-after T:moment |
At heartbeat T, jump the clock to that moment, absolute or relative, in the session zone. |
--force |
Keep the session even when the opening verdict says the substitution did not take effect - by default the target is stopped instead, because a session that proved nothing is worse than no session. The verdict does not change, and neither does the exit code. This only decides whether the target keeps running. |
--no-embedded |
Leave the web pages inside the application (WebView2, Qt WebEngine) on the real clock and open no debugging port. Without it the session asks such an engine to open a local port and puts its pages on the session clock through it - while the port is open, any other program on this computer can use it to reach those pages. |
--elevated-embedded |
For an application that runs as administrator, from a Chrono Mock that runs as
administrator too. WebView2 ignores the setting the session reaches its pages through
for such an application, so the session writes one value to the machine registry
(HKLM, named after the application's .exe) for its duration and removes it at the end.
While the application runs, any other program on this computer can reach its pages
through the port, and the application has administrator rights. Not with
--no-embedded. |
--keep-background-timers |
Start the web engine - the browser of a Chromium or Electron application, or the
engine inside an application - with its slowdown of timers in hidden windows switched
off, so a minimised window keeps the session speed. The application then also works at
full pace while it is hidden. An application started as administrator through
--elevated-embedded does not get it. |
--dry-run |
Print what the session would be and start nothing: the resolved moment and zone and
where they came from, what a preset filled each parameter with, the arguments after
splitting, and which of the two mechanisms the target would take. No process is
started and no file is written, including the one --report names. It
exits 0 for a workable plan and 2 for a target that is not there, which is the code a
real run gives for the same fact. A plan carries no verdict and says so - only a real
session has one. With --json the plan is data, including
session.elevated_embedded and
session.elevated_embedded_value, the file name the registry value would
be named after when the option applies to an .exe target, null otherwise.
A name there is not a promise of a write: the session writes the value only when
Chrono Mock itself runs as administrator. |
--report <path> |
Write the human report to a file. A verdict other than works leads that file with an unreliable-evidence banner, because evidence that hides its own doubt is worse than none. A works session gets this banner too when the application read none of the clocks the session replaces, or when it is a Go program, whose wall clock Chrono Mock cannot reach. The file also names the run itself: the version, when it started, the target, its folder and the flags. |
--report-args |
Together with --report, also write the target's arguments into that file, word for word. Off by default, because an argument can carry a password - the file then says only how many there were. |
--json |
Emit the session as machine-readable records instead of the report - the same stream the window itself consumes. A console application's own output goes to standard error, so standard output holds the records and nothing else. An application still running when the session ends keeps writing there, so whatever reads standard error to its end waits for it, and the run says so. |
chrono run - exit codes
The exit code is the session verdict, not what the application returned. The application's own exit code is reported separately, in the report and in the session data - the CLI and CI page explains why round that way.
| Code | Meaning |
|---|---|
| 0 | works - every process that read time saw the session clock |
| 10 | works partially - some channels covered, some not |
| 11 | does not work - the substitution did not reach the key channels |
| 4 | undetermined - coverage could not be established. An honest "cannot tell", never a pretended pass |
| 12 | the target vanished right after injection and left nothing running on the session clock, which suggests a single-instance application handing over to a copy already running, or a hand-off to a program the session could not enter, usually one of the other bitness. A target that starts the application and ends is a launcher, and the session goes on for the application |
| 1 | usage error - a bad flag, a bad expression, or a preset this command will not accept |
| 2 | the target could not be launched or injected |
| 3 | internal error - the engine could not start, hit an unexpected state, or stopped before it closed the session (whatever its own exit code). Such a run carries no verdict |
| 5 | the operation is not built in this release - for example a preset whose moment needs a calendar, which run cannot load yet. Deliberately distinct from a usage error: you asked for something that does not exist yet, not for something wrong |
| 6 | the run was cut short - --timeout expired, or the engine stopped answering for 15 seconds and was stopped. This is not a verdict: the session was interrupted, so it proves nothing about the target either way |
| 7 | another session is already running - one session at a time. Trying again once it has ended is enough |
| 8 | the session reached a verdict, but its evidence is not in the file --report names - the disk was full, or another program held the old file open, which then stays as it was. The verdict is still in the report the command prints |
Reading the report
Without --json the session ends in a plain report. Every line is either a
fact the session observed or absent - nothing is filled in with a plausible default.
Chrono Mock - session report
target: .\build\test.exe
verdict: WORKS - time substitution took effect (processes: 1)
every process that read time saw the session clock
session: fake clock reached 2038-01-19T03:14:08
real elapsed 1.3s, fake elapsed 1.3s
exited: the target closed itself with code 3
covered channels (substituted, with call counts, 10 calls in all):
- pid 21240: GetSystemTimeAsFileTime (5 calls)
- pid 21240: GetLocalTime (5 calls)
observed channels (hooked but left real):
- pid 21240: NtCreateUserProcess (0 calls)
warnings:
- the target opened a network connection ...
- verdict - the family verdict over the parent and every child process, with the process count. This is the number the exit code carries.
- exited - the application's own exit code, and only when it
closed itself. A run bounded by
--ticksleaves the target running, a Chromium session is one the tool closes itself, and a session that never started has no such code - the line is then absent rather than showing a made-up zero. A crash code arrives as a large negative number, so it also gets its hexadecimal form. - covered - channels the substitution reached, with how many times each was actually called, per process. Counts are never summed across processes: one process's audit must not borrow another's numbers. A count stops at 1048576 and then reads at least that many.
- observed - channels hooked but deliberately left real, such as object waits and multimedia timers. Their own bucket, so they are never read as substituted.
- uncovered - channels the application asked for and the session did not cover. This is what turns a verdict into partially.
- not fully cleaned up - anything teardown could not remove. Absent on a clean end, which is the normal case.
chrono calc - options
No natural language: each flag is one step, and the order of the flags is the order of the steps. The result after every step is printed, so a wrong step is visible rather than buried.
| Flag | What it does |
|---|---|
--base today|now|<moment> |
Where to start, default today. Resolved in the session zone. An
impossible day such as 2025-02-31 is refused, never quietly
normalised into the next month. |
--base-utc <moment> |
Like --base, but the start is an instant in UTC, with an optional
trailing Z, instead of a wall-clock reading in the session zone. |
--shift ±N<unit> |
Repeatable. The sign is required. Units: s m h d w mo q y bd, or
their full names. m is minutes, mo is
months. Months, quarters and years fold onto the calendar date with a
clamp, so a month after 31 January is the end of February, not a fixed number of
ticks. bd (business days) needs --calendar. |
--set-time HH:MM:SS | Set the time of day on the current result. |
--snap <target> |
Jump to a period boundary: som eom soq eoq soy eoy (start or end of
month, quarter, year). A start is the first day at 00:00:00, an end the last day at
23:59:59. |
--nearest nbd|pbd |
The nearest business day forward or back, the current day included when it already
is one. Needs --calendar. |
--to-zone +HH:MM |
A step: re-express the same instant in another fixed offset. The instant
is preserved, so the epoch value does not move and only the civil fields do. Not to
be confused with --zone. |
--zone +HH:MM |
The session zone, used to resolve today and now and to
render the result. Without it, the host's zone. |
--calendar us-banking|us-federal|pl |
Load a holiday calendar. It adds business day and holiday to the metadata and
enables bd and --nearest. The two American calendars differ
in how they observe a holiday falling at a weekend, which is exactly the kind of
off-by-one day a test is meant to find. Each calendar answers for a range of years:
pl from 2002, us-federal from 1960 and
us-banking from 2008. A business-day step that reaches a day before it is
refused with exit 1, and a date before it is shown as not judged. When a calendar's
holidays were last checked against the law more than a year ago, a later day it judged
is marked calendar_outdated, because a newer holiday may be missing. |
--preset <id> --param id=value |
A named moment from presets/<id>.json, with its parameters
filled in. Exclusive with the step flags and with --analyze, and it
composes with --calendar, --zone and
--format. |
--analyze <date> |
Read a pasted date back and say what it is. An ambiguous 04/08 is
shown in both readings, American and Polish, rather than guessed -
that ambiguity is a real source of wrong bug reports. |
--format <mask> |
Print the result in your own mask as well, so it matches the format of the
application under test: yyyy-MM-dd HH:mm:ss. Tokens are case sensitive
in the .NET and Java sense - M is month, m is
minute. |
--json |
Machine output (chronomock.calc/1) for any of the above, so a pipeline
can compute a date and feed it straight back into chrono run. |
# Last business day of the quarter, American banking calendar chrono calc --base today --snap eoq --nearest pbd --calendar us-banking
chrono calc - exit codes
A separate, smaller table: the calculator has no coverage verdict to report.
| Code | Meaning |
|---|---|
| 0 | computed |
| 1 | usage error - a bad flag, an impossible date, a range overflow, or a calendar file with no business day in it |
| 5 | the step or unit is not built in this release, or the step needs a calendar you did not pass. Separate from a usage error on purpose, so a script can tell "not yet" from "wrong" |