<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Srimoneyshankar Ajith</title>
    <description>The latest articles on DEV Community by Srimoneyshankar Ajith (@moneytosms).</description>
    <link>https://dev.to/moneytosms</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4061763%2F56a34769-a506-41a4-b291-d7b479fd350f.jpg</url>
      <title>DEV Community: Srimoneyshankar Ajith</title>
      <link>https://dev.to/moneytosms</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moneytosms"/>
    <language>en</language>
    <item>
      <title>We Rewrote a Cron Library in Rust and Almost Missed the Deadline Doing It</title>
      <dc:creator>Srimoneyshankar Ajith</dc:creator>
      <pubDate>Sat, 08 Aug 2026 11:46:10 +0000</pubDate>
      <link>https://dev.to/moneytosms/we-rewrote-a-cron-library-in-rust-and-almost-missed-the-deadline-doing-it-3d1j</link>
      <guid>https://dev.to/moneytosms/we-rewrote-a-cron-library-in-rust-and-almost-missed-the-deadline-doing-it-3d1j</guid>
      <description>&lt;p&gt;Ten hours, one 46 nanosecond bug, and a mild existential crisis about my degree.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it all started
&lt;/h3&gt;

&lt;p&gt;Some months ago I read about Anthropic rewriting Bun in Rust, and the part that stuck was not the speed. It was that they had to prove the new thing behaved like the old thing on a codebase nobody could afford to break. That is a much harder problem than writing fast code.&lt;/p&gt;

&lt;p&gt;So when a hackathon turned up whose whole premise was "take a library, rewrite it in another language, prove you did not change its behaviour," I signed up immediately. Then did nothing for weeks. Then opened an empty folder on the final morning with about ten hours left.&lt;/p&gt;

&lt;p&gt;We submitted with roughly five minutes to spare. This was not a plan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjs45dny8g53ptprgzlat.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjs45dny8g53ptprgzlat.png" alt=" " width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What we picked
&lt;/h3&gt;

&lt;p&gt;We rewrote &lt;code&gt;croniter&lt;/code&gt;, a Python library that answers one question: given a schedule like "every 30 minutes between 1am and 3am" and a moment in time, when does it next run, and when did it last run.&lt;/p&gt;

&lt;p&gt;Sounds like an afternoon. It is not, because time is a nightmare. Twice a year the clocks change. In spring an hour vanishes, so a job set for 2:30am has no 2:30am to run at. In autumn 1:30am happens twice, so the same job could run zero times, once, or twice depending on who wrote the library and what mood they were in. On top of that, cron syntax carries fifteen years of accumulated weirdness. A character for "last day of the month." Another for "nearest weekday." A rule about days and weekdays that looks broken and is deliberate, because it has behaved that way since the seventies and a lot of servers quietly depend on it.&lt;/p&gt;

&lt;p&gt;Get any of that subtly wrong and nothing crashes. No stack trace, no alert. Your job just runs at the wrong time, once, in November, and six weeks later someone says the numbers look a bit off and nobody ever connects the two.&lt;/p&gt;

&lt;p&gt;The real reason we picked it, though: this library ships with nearly three times as much test code as library code. That mattered more than anything else.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frz7zut2v8ckzc1p4qfkx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frz7zut2v8ckzc1p4qfkx.png" alt=" " width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The part where I questioned my degree
&lt;/h3&gt;

&lt;p&gt;My harness was Claude Code. I went in wanting to see how far it could actually be pushed, and I set it up properly first: a decent pile of skills and custom instructions to shape the output rather than just typing at it and hoping.&lt;/p&gt;

&lt;p&gt;The first few hours were a rude awakening, in both directions.&lt;/p&gt;

&lt;p&gt;Direction one, which I did not enjoy. It is genuinely unsettling to watch a machine produce, in twenty minutes, work that I would have spent a week on. I am a junior in college. I am currently paying money and time to learn things that were appearing on my screen faster than I could read them. There is a specific feeling that comes with that, and it is not excitement. It is closer to "what exactly am I doing here." I spent a real part of that morning half seriously wondering whether I should be aiming at a different role entirely.&lt;/p&gt;

&lt;p&gt;Direction two, which arrived a few hours later and fixed the first one.&lt;/p&gt;

&lt;p&gt;The output only looked effortless from a distance. Up close it needed constant steering. The model does not remember what it decided forty minutes ago, so it would contradict itself across two files while sounding completely confident in both. It would solve the problem I described rather than the problem I had. It would produce something that compiled, passed, and was quietly the wrong shape. Every one of those was caught because somebody in the room had enough of an idea of what correct looked like to notice.&lt;/p&gt;

&lt;p&gt;That is the actual skill now. Not typing the code. Knowing enough to tell when the answer is wrong, and knowing what to build so that being wrong becomes visible.&lt;/p&gt;

&lt;p&gt;Which is what turned the day around. Instead of reading generated code line by line, we spent the early hours wiring up the original library's own test suite to run directly against our Rust version. After that, every change came back as one number: 248 tests, how many pass. No judgement calls, no reading a diff and deciding it looked fine. A number that went up or did not.&lt;/p&gt;

&lt;p&gt;Once that loop existed the whole thing changed character. Ask, run, read the failure, feed it back. It stopped feeling like an argument and started feeling like a build. The rest of the day was smooth in a way the first three hours absolutely were not.&lt;/p&gt;

&lt;p&gt;The other habit worth stealing: write decisions down the moment you make them. Every time we deliberately did something differently from the original, it went into a file with the reasoning, immediately. That file ended up with 19 entries. It meant that at hour eight, when someone asked why the timezone handling had been pulled out of the search loop, the answer already existed instead of being reconstructed by two tired people and a model with no memory.&lt;/p&gt;

&lt;p&gt;So, no, I am not pivoting. The models keep improving and compute keeps getting cheaper, and I genuinely do not know where that lands in three years. But the useful position right now is being the person who can tell good output from confident nonsense, and that still requires knowing the material. That is what I am aiming at.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proving it, without cheating
&lt;/h3&gt;

&lt;p&gt;The obvious way to prove a rewrite matches the original is to run them side by side. The rules did not allow that, rightly, because the easy version means quietly embedding Python inside your Rust program and calling it a port.&lt;/p&gt;

&lt;p&gt;So we did four other things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recorded everything.&lt;/strong&gt; We ran the original test suite once with a recorder attached, capturing every question it asked the library and every answer it got back. About 15,800 exchanges. Our Rust version replays all of them and checks the answers match, with no Python present at replay time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let the original tests drive our code.&lt;/strong&gt; We wrote a small adapter so that when the Python suite says &lt;code&gt;import croniter&lt;/code&gt;, it gets a thin wrapper that forwards each call to our Rust program and hands the reply back. The tests never know anything changed. Python calling out to a separate program is the direction where you are clearly not smuggling an interpreter in the back door.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Threw random garbage at both.&lt;/strong&gt; A script that generates random schedules and start times, runs them through Python and through us, and shouts if the answers differ. This found the two most interesting problems we hit, neither of which it was built to look for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrote tests that ignore Python entirely.&lt;/strong&gt; Everything above checks that we agree with Python, and none of it catches a mistake we both make. So we also tested things that must be true of any correct scheduler: moving forward always moves forward, forward then backward never overshoots, stepping forward never skips a valid time (checked by probing every minute in between), and a long run of steps never gets stuck, since a scheduler that stalls will hang your service and is invisible to a test that takes one step.&lt;/p&gt;

&lt;p&gt;Those last ones found nothing. I still think they are the best tests in the repo, because they are the only ones that could have.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original Python suite, unedited, run against our Rust version&lt;/td&gt;
&lt;td&gt;248 of 248&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recorded exchanges replayed and matched&lt;/td&gt;
&lt;td&gt;15,827 of 15,827&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Random comparison cases with no disagreement&lt;/td&gt;
&lt;td&gt;14,610&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed, depending on the task&lt;/td&gt;
&lt;td&gt;8x to 185x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Thirteen recorded exchanges are excluded and listed by name. Ten use a feature that deliberately picks random values, so there is no fixed answer to check. Three are calls Rust will not let you write at all. Naming your exclusions costs a rounder number and buys the right to be believed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qv5bn5zkifz1pujnfky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9qv5bn5zkifz1pujnfky.png" alt=" " width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The 46 nanosecond bug, and the worse bug behind it
&lt;/h3&gt;

&lt;p&gt;At some point we noticed our version occasionally reporting that the last run happened a whole day earlier than it actually did. Not a few seconds. A day.&lt;/p&gt;

&lt;p&gt;Python's clock stores time down to the microsecond. The Rust library we used goes finer, to the nanosecond. When the search stepped backwards by one microsecond to look just before the current moment, Python landed exactly on the second and we landed 46 nanoseconds underneath it. Clearing the seconds field then rounded that into the previous minute, and the search went hunting in the wrong day entirely.&lt;/p&gt;

&lt;p&gt;You cannot find that by reading the code. Both versions look right, and in a narrow sense both are, since each does exactly what its own clock library promises. We only caught it because we were comparing against the real thing at full precision. The fix rounds our clock to whole microseconds so both sides agree on which moment is even being discussed.&lt;/p&gt;

&lt;p&gt;The embarrassing part is why it took hours.&lt;/p&gt;

&lt;p&gt;That recorder, the one capturing every question and answer, had a bug of its own. It was dropping the optional settings from most calls it recorded, so roughly 1,500 calls originally made with unusual settings were replayed with defaults.&lt;/p&gt;

&lt;p&gt;Our evidence was making our code look better than it was.&lt;/p&gt;

&lt;p&gt;That is worse than the code being broken, and it took a beat to feel how much worse. Broken code gets fixed because something tells you. A flattering test setup gets trusted. You see the green number, believe it, and go work on something else. The microsecond bug only became visible after we fixed the recorder. It had been sitting there the whole time behind a score that was lying to us.&lt;/p&gt;

&lt;p&gt;Your test setup is code too. Same author, same day, same assumptions as the bug you are hunting. It deserves at least as much suspicion as the thing it tests. Ours got checked last. It should have been first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc8qjdiazjg79z5reapwf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc8qjdiazjg79z5reapwf.png" alt=" " width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Two things the random script found
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Sydney, in the year 2100, off by exactly one hour.&lt;/strong&gt; Exactly one hour is a suspicious number. It usually means you are not lost, you are in the wrong timezone. Both versions produced the same wall clock time and disagreed only on the offset, and ours was wrong for an Australian winter. Neither cron library was at fault. Timezone data lists real daylight saving switches out to about 2037 and then adds a rule saying "keep doing this forever." Python keeps applying that rule indefinitely. The Rust timezone library builds a fixed table and, once past the end of it, holds the last offset forever. They agree until 2099 and part ways in 2100. Nothing to fix, so we pinned the boundary with a test that fails if that library ever extends its table, and capped the random script at 2099 so it stops comparing timezone databases and goes back to comparing cron libraries. Also, if you are scheduling jobs in Australia beyond 2099, your offset may be an hour out. Felt worth mentioning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A function that took 92 seconds to return its first result.&lt;/strong&gt; The original walks through scheduled runs one at a time, so asking for the first three only ever computes three. Ours computed all of them into a list and handed the list back. Identical results, every test passed, and nobody would notice because no sane person writes a test asking for a decade of runs. Then the random script generated a year long range at one second resolution. The answers were never wrong, the shape of the work was. On a wide enough range that stops being slow and becomes a crash. It streams now, pinned by a test that pulls three runs out of a ten year window at one second resolution, roughly 300 million results if you were foolish enough to compute them all, and checks it returns immediately.&lt;/p&gt;

&lt;p&gt;Random input found a design mistake no hand written test would ever have expressed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc6k58ddcug5t3knmmucg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc6k58ddcug5t3knmmucg.png" alt=" " width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Three bugs we found and deliberately did not fix
&lt;/h3&gt;

&lt;p&gt;While checking our work we came across three open bug reports filed against the original library by other people. We tested all three. We reproduce all three exactly, on purpose.&lt;/p&gt;

&lt;p&gt;This feels wrong the first time you type it. You can see the bug, you know the fix, and fixing it is still the wrong move, because the point of the exercise is behaving identically and people downstream may already have worked around it. A rewrite that quietly improves things is a rewrite nobody can safely swap in.&lt;/p&gt;

&lt;p&gt;So all three are locked down by tests. If upstream ever fixes one, our test goes red and tells us to look. A deliberate bug with an alarm on it is a decision. Without the alarm it is a bug you got away with.&lt;/p&gt;

&lt;h3&gt;
  
  
  The receipts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make build          &lt;span class="c"&gt;# builds it&lt;/span&gt;
make &lt;span class="nb"&gt;test&lt;/span&gt;           &lt;span class="c"&gt;# our tests, plus the recorded replay&lt;/span&gt;
make test-original  &lt;span class="c"&gt;# the original Python suite, run against our version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with nothing but Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; croniter-rs &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; croniter-rs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of it re-runs on every push, including a fresh round of random comparison that fails the build on any disagreement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fajsu205m9inrpl9yygk0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fajsu205m9inrpl9yygk0.png" alt=" " width="797" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code and full write-ups: &lt;a href="https://github.com/moneytosms/croniter-rs" rel="noopener noreferrer"&gt;https://github.com/moneytosms/croniter-rs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two files there are worth more than the code. One lists all 19 places we knowingly differ from the original, with reasons. The other lists every bug we found, worst first, plus a security review of the original that came back clean.&lt;/p&gt;

&lt;p&gt;If you liked this project then go give it a like on devfolio and check it out &lt;a href="https://devfolio.co/projects/croniterrs-ed98" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built for &lt;a href="https://coderesurrection.com/2026/" rel="noopener noreferrer"&gt;Port Mortem 2026&lt;/a&gt; by Nidhi Rakesh and Srimoneyshankar Ajith.&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/partnerships_raptors"&gt;@partnerships_raptors&lt;/a&gt; &lt;a href="https://dev.to/raptorsdev"&gt;At&lt;/a&gt;&lt;/p&gt;

</description>
      <category>portmortem</category>
      <category>hackathonraptors</category>
      <category>rust</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
