Tagline: A deep-dive port mortem on translating Go's canonical cron library (
robfig/cron) to Rust (cron-rs), achieving 100% test parity, zero differential mismatches across 18,000 schedule evaluations, a 3.5x p99 latency reduction, and a 4.6x lower memory footprint.
- π GitHub Repository: github.com/showlook2005/PORTCODE24
- π₯ YouTube Walkthrough & Live Parity Demo: youtu.be/5ilygwmPFYg
β‘ 1. Executive Summary & Key Performance Gains
Porting Goβs standard robfig/cron scheduler to Rust (cron-rs) sounds simple on paper: port bitmask logic, handle time calculations, wrap tasks in Tokio spawn handlers, and run cargo test.
However, making a cron parser compile in Rust takes 5% of the effort. The remaining 95% is spent fighting timezone DST transitions, surviving async Tokio channel deadlocks, and proving bit-for-bit behavioral equivalence against Goβs runtime under concurrent workloads.
π Benchmark & Memory Metrics Summary
| Metric | Go (robfig/cron) |
Rust (cron-rs) |
Result / Gain |
|---|---|---|---|
| p50 Next Tick Calculation | ~120 ns |
~35 ns |
3.4x faster |
| p99 Next Tick Calculation | ~240 ns |
~68 ns |
3.5x faster |
| Throughput | ~4.1M ops/sec |
~14.7M ops/sec |
3.5x higher throughput |
| Heap Memory (1,000 Entries) | ~1.4 MB |
~0.3 MB |
4.6x lower memory footprint |
| Garbage Collection Pauses |
100β500 Β΅s (Go GC) |
0 Β΅s (Deterministic) |
Zero GC pauses |
| Differential Parity Corpus | 18,000 test points | 18,000 test points | 0 mismatches (100% Parity) |
ποΈ 2. System Architecture & Pipeline
+-------------------------------------------------------+
| cron-rs Scheduler Engine |
+-------------------------------------------------------+
|
+-----------------+-----------------+
| |
+------------------+ +--------------------+
| Job Registry | | Async Runner Pool |
| (Sorted Next Exec) | (Tokio Tasks) |
+------------------+ +--------------------+
| |
+-----------------+-----------------+
|
+-------------------------------------------------------+
| Differential Parity Engine |
| (0 Mismatches across 18,000 test points) |
+-------------------------------------------------------+
Key Highlights:
-
Zero-Copy Parsing Engine: Cron expressions parse into
u64bitfields for $O(1)$ bitwise masking. -
Chain Middleware: Native support for
Recover,DelayIfStillRunning, andSkipIfStillRunning. - Async Tokio Pool: Multithreaded job execution without thread-blocking overhead.
π 3. Key Engineering Decisions (DECESSION.md)
1. Passed Date Behavior (Automatic Year Rollover)
-
Symptom: Adding
"0 30 15 2 8 *"on August 3, 2026 sets the next execution to2027-08-02T15:30:00. - Reasoning: Cron specs define recurring patterns without a Year field. If the specified day/time in the current year has passed, it automatically rolls over to the next year.
-
CLI Warning:
cron-clioutputs aβ οΈ Warningalert when a target time has passed for the current year.
2. Timezone Precision & DST (chrono-tz)
-
Default:
Cron::new()uses system timezone (iana-time-zone). -
Custom Timezones: Overridable via
OptionSetter::Location(chrono_tz::Asia::Kolkata). -
DST Safety: Aligned wall-clock normalization order during spring-forward transitions with Go's
time.Location.
3. Explicit User Feedback on Job Removal
- Go Behavior: Removing a non-existent Job ID silently did nothing.
-
cron-rsImprovement:cron-clioutputs an explicit notice:β Job ID <id> does not exist (or was already removed).
π§ͺ 4. Test Parity & Differential Verification
1:1 Ported Unit Test Parity (185/185 Passed)
| Go Test File | Rust Test File | Count | Status |
|---|---|---|---|
spec_test.go |
spec_test.rs |
62 | PASSED |
parser_test.go |
parser_test.rs |
67 | PASSED |
constantdelay_test.go |
constantdelay_test.rs |
13 | PASSED |
option_test.go |
cron_test.rs |
3 | PASSED |
chain_test.go |
chain_test.rs |
12 | PASSED |
cron_test.go |
cron_test.rs |
28 | PASSED |
| TOTAL | 185 Test Cases | 185 | 100% PARITY |
Differential Parity Engine (18,000 Test Points)
- Total test points evaluated: 18,000
- Differential mismatches: 0
- Parity Accuracy: 100.0%
β³ 5. The Edge Case: DST Spring-Forward Gap
Suppose a cron schedule runs daily at 2:30 AM (0 30 2 * * *). On DST spring-forward night, 2:30 AM does not exist.
-
Go:
time.Date()auto-normalizes 2:30 AM forward to 3:30 AM. -
Rust
chrono-tz: ReturnsLocalResult::None.
The Fix:
rust
// Handle DST spring-forward wall-clock gap gracefully
match loc.with_ymd_and_hms(year, month, day, hour, minute, second) {
LocalResult::Single(dt) => dt,
LocalResult::Ambiguous(earliest, _latest) => earliest,
LocalResult::None => {
// Wall clock time skipped by DST transition - advance hour
loc.with_ymd_and_hms(year, month, day, hour + 1, minute, second)
.unwrap()
}
}
# Clone the repository
git clone https://github.com/showlook2005/PORTCODE24
cd PORTCODE24/cron-rs
# 1. Run full unit & integration test suite (185 test cases)
cargo test --all
# 2. Run the 18,000-point differential parity harness
cargo test --test differential
# 3. Run concurrency soak test
cargo test --test soak
# 4. Run Criterion benchmarks
cargo bench
π Conclusion
Porting Go to Rust requires proving equivalence across edge cases, timezones, and concurrent state changes. cron-rs achieves 100% bit-for-bit Go parity while delivering 3.5x lower latency and 4.6x reduced memory overhead.
Check out the repository on GitHub and watch the live parity demo on YouTube!
**Built for Port Mortem / Code Resurrection by Hackathon Raptors**
#PortMortem2026 #HackathonRaptors #OpenSource #SystemsProgramming #Testing #Verification #Fuzzing

Top comments (3)
Differential tests are perfect for this kind of port because they test behavior instead of confidence. Timezone bugs are especially good at hiding behind code that looks clean. I would keep the weird historical DST cases in the corpus forever after finding one.
Spot on, Alex! "Testing behavior instead of confidence" is the exact motto that saved us here.
Youβre 100% right about timezone bugsβthe Rust code compiled cleanly and all unit tests passed, but the differential harness immediately caught the subtle wall-clock gap during spring-forward transitions.
We actually permanently committed those DST edge cases directly into tests/differential.rs as regression fixtures so they run on every CI build. Definitely never taking those out of the corpus! Thanks for reading and for the great feedback! π
That is the perfect use of a differential failure: turn the weird edge case into a permanent fixture. The value is not only catching this bug, but making sure future confidence has to pass the same uncomfortable example.