2026-08-22
You need to test what your code does on Feb 29, 2028. Or verify that a certificate expiration path triggers cleanly. Or reproduce the bug that only happened at 03:14:07 UTC on January 19, 2038. The naive fix — sudo date -s ... — reaches out and yanks the wall clock for every process, breaks systemd timers, confuses your SSH session's Kerberos ticket, and if you're on a laptop, chrony will happily undo it 30 seconds later. There's a better way that's been sitting in Debian since 2007.
libfaketime is an LD_PRELOAD shim that intercepts time(), gettimeofday(), clock_gettime(), and friends — for one process and its children only. The system clock stays untouched. Cousin to eatmydata, same trick, different syscall family.
# Absolute time
faketime '2038-01-19 03:14:07' date
# → Tue Jan 19 03:14:07 UTC 2038
# Relative offsets — d/y/h/m/s
faketime '-3 years' openssl x509 -in cert.pem -noout -dates
faketime '+45 days' ./run_billing_job.sh
# ISO 8601 also works
faketime '2028-02-29T12:00:00' python -c 'import datetime; print(datetime.date.today())'
The real trick is the -f advanced format, which supports rate multipliers. Freeze time entirely, or make it march 10× faster to test a 24-hour cron in under three hours:
# Freeze — every call returns the same instant
faketime -f '2025-06-15 12:00:00 x0' bash
# now `sleep 5; date` returns the same timestamp
# Accelerate 60×: one wall-second = one faked-minute
faketime -f '@2025-01-01 00:00:00 x60' ./long_running_daemon
# Time drifts +1 second per real second, starting an hour ahead
faketime -f '+1h i1.0' ./scheduler_test.py
The killer application is testing TLS chains without waiting years. Every developer who's ever debugged a cert-expiry bug has faked the system clock and regretted it:
# What does our client do the day after this cert expires?
faketime "$(openssl x509 -in server.crt -noout -enddate | cut -d= -f2) + 1 day" \
curl -v https://internal-api.example.com/
# Simulate an already-expired intermediate CA
faketime '2040-01-01' openssl s_client -connect example.com:443 -servername example.com
Point it at a whole test suite — pytest, go test, cargo test, whatever — and the tests see a world where it's already 2038:
FAKETIME='2038-01-19 03:14:08' pytest tests/test_timestamps.py
FAKETIME_NO_CACHE=1 FAKETIME='@1234567890' go test ./...
Environment variable form is usually what you want inside CI, because it propagates to fork()-ed children automatically. Combine with a per-process control file (FAKETIME_TIMESTAMP_FILE) and you can advance time from another shell while the process is running — perfect for testing token-refresh loops.
The fine print, learned the hard way:
LD_PRELOAD. Rebuild with dynamic linkage or use faketime's -m flag which asks the kernel via ptrace instead — slower but works.CLOCK_MONOTONIC is faked by default (set FAKETIME_DONT_FAKE_MONOTONIC=1 if it breaks your event loop)./proc/uptime or asks the kernel directly (via vDSO without the libc wrapper) bypasses it. Rare in practice.now() uses the server's clock — but connect from a faketime'd client and clock_timestamp() at the client library level lies happily).The Debian package is faketime; on Fedora/RHEL it's libfaketime. Zero configuration, zero dependencies, works on anything glibc-based going back 15+ years.
LD_PRELOAD a lie into one process with faketime, including rate multipliers for accelerated or frozen time.
