<!doctype html><htmllang=endir=ltrclass="blog-wrapper blog-post-page plugin-blog plugin-id-default"data-has-hydrated=false><metacharset=UTF-8><metaname=generatorcontent="Docusaurus v3.6.1"><titledata-rh=true>What I learned porting dateutil to Rust | The Old Speice Guy</title><metadata-rh=truename=viewportcontent="width=device-width,initial-scale=1.0"><metadata-rh=truename=twitter:cardcontent=summary_large_image><metadata-rh=trueproperty=og:urlcontent=https://speice.io/2018/06/dateutil-parser-to-rust><metadata-rh=trueproperty=og:localecontent=en><metadata-rh=truename=docusaurus_localecontent=en><metadata-rh=truename=docusaurus_tagcontent=default><metadata-rh=truename=docsearch:languagecontent=en><metadata-rh=truename=docsearch:docusaurus_tagcontent=default><metadata-rh=trueproperty=og:titlecontent="What I learned porting dateutil to Rust | The Old Speice Guy"><metadata-rh=truename=descriptioncontent="I've mostly been a lurker in Rust for a while, making a couple small contributions here and there."><metadata-rh=trueproperty=og:descriptioncontent="I've mostly been a lurker in Rust for a while, making a couple small contributions here and there."><metadata-rh=trueproperty=og:typecontent=article><metadata-rh=trueproperty=article:published_timecontent=2018-06-25T12:00:00.000Z><linkdata-rh=truerel=iconhref=/img/favicon.ico><linkdata-rh=truerel=canonicalhref=https://speice.io/2018/06/dateutil-parser-to-rust><linkdata-rh=truerel=alternatehref=https://speice.io/2018/06/dateutil-parser-to-rusthreflang=en><linkdata-rh=truerel=alternatehref=https://speice.io/2018/06/dateutil-parser-to-rusthreflang=x-default><scriptdata-rh=truetype=application/ld+json>{"@context":"https://schema.org","@id":"https://speice.io/2018/06/dateutil-parser-to-rust","@type":"BlogPosting","author":{"@type":"Person","name":"Bradlee Speice"},"dateModified":"2024-11-10T01:23:31.000Z","datePublished":"2018-06-25T12:00:00.000Z","description":"I've mostly been a lurker in Rust for a while, making a couple small contributions here and there.","headline":"What I learned porting dateutil to Rust","isPartOf":{"@id":"https://speice.io/","@type":"Blog","name":"Blog"},"keywords":[],"mainEntityOfPage":"https://speice.io/2018/06/dateutil-parser-to-rust","name":"What I learned porting dateutil to Rust","url":"https://speice.io/2018/06/dateutil-parser-to-rust"}</script><linkrel=alternatetype=application/rss+xmlhref=/rss.xmltitle="The Old Speice Guy RSS Feed"><linkrel=alternatetype=application/atom+xmlhref=/atom.xmltitle="The Old Speice Guy Atom Feed"><linkrel=stylesheethref=/katex/katex.min.css><linkrel=stylesheethref=/assets/css/styles.16c3428d.css><scriptsrc=/assets/js/runtime~main.29a27dcf.jsdefer></script><scriptsrc=/assets/js/main.d461af80.jsdefer></script><bodyclass=navigation-with-keyboard><script>!function(){vart,e=function(){try{returnnewURLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}}()||function(){try{returnwindow.localStorage.getItem("theme")}catch(t){}}();t=null!==e?e:"light",document.documentElement.setAttribute("data-theme",t)}(),function(){try{for(var[t,e]ofnewURLSearchParams(window.location.search).entries())if(t.startsWith("docusaurus-data-")){vara=t.replace("docusaurus-data-","data-");document.documentElement.setAttribute(a,e)}}catch(t){}}()</script><divid=__docusaurus><divrole=regionaria-label="Skip to main content"><aclass=skipToContent_fXgnhref=#__docusaurus_skipToContent_fallback>Skip to main content</a></div><navaria-label=Mainclass="navbar navbar--fixed-top"><divclass=navbar__inner><divclass=navbar__items><buttonaria-label="Toggle navigation bar"aria-expanded=falseclass="navbar__toggle clean-btn"type=button><svgwidth=30height=30viewBox="0 0 30 30"aria-hidden=true><pathstroke=currentColorstroke-linecap=roundstroke-miterlimit=10stroke-width=2d="M4 7h22M4 15h22M4 23h22"/></svg></button><aclass=navbar__brandhref=/><divclass=navbar__logo><imgsrc=/img/logo.svgalt="Sierpinski Gasket"class="themedComponent_mlkZthemedCo
So launching <ahref=https://github.com/bspeice/dtparsetarget=_blankrel="noopener noreferrer">dtparse</a> feels like nice step towards becoming a
functioning member of society. But not too much, because then you know people start asking you to
pay bills, and ain't nobody got time for that.</p>
<p>But I built dtparse, and you can read about my thoughts on the process. Or don't. I won't tell you
what to do with your life (but you should totally keep reading).</p>
<h2class="anchor anchorWithStickyNavbar_LWe7"id=slow-down-what>Slow down, what?<ahref=#slow-down-whatclass=hash-linkaria-label="Direct link to Slow down, what?"title="Direct link to Slow down, what?"></a></h2>
<p>OK, fine, I guess I should start with <em>why</em> someone would do this.</p>
<p><ahref=https://github.com/dateutil/dateutiltarget=_blankrel="noopener noreferrer">Dateutil</a> is a Python library for handling dates. The
standard library support for time in Python is kinda dope, but there are a lot of extras that go
into making it useful beyond just the <ahref=https://docs.python.org/3.6/library/datetime.htmltarget=_blankrel="noopener noreferrer">datetime</a>
module. <code>dateutil.parser</code> specifically is code to take all the super-weird time formats people come
up with and turn them into something actually useful.</p>
<p>Date/time parsing, it turns out, is just like everything else involving
<ahref=https://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-timetarget=_blankrel="noopener noreferrer">computers</a> and
<ahref=https://infiniteundo.com/post/25509354022/more-falsehoods-programmers-believe-about-timetarget=_blankrel="noopener noreferrer">time</a>: it
feels like it shouldn't be that difficult to do, until you try to do it, and you realize that people
suck and this is why
<ahref=https://zachholman.com/talk/utc-is-enough-for-everyone-righttarget=_blankrel="noopener noreferrer">we can't we have nice things</a>. But
alas, we'll try and make contemporary art out of the rubble and give it a pretentious name like
It takes in the time as a string, and gives you back a reasonable "look, this is the best anyone can
possibly do to make sense of your input" value. It doesn't expect much of you.</p>
<p><ahref=https://github.com/bspeice/dtparse/blob/7d565d3a78876dbebd9711c9720364fe9eba7915/src/lib.rs#L1332target=_blankrel="noopener noreferrer">And now it's in Rust.</a></p>
<h2class="anchor anchorWithStickyNavbar_LWe7"id=lost-in-translation>Lost in Translation<ahref=#lost-in-translationclass=hash-linkaria-label="Direct link to Lost in Translation"title="Direct link to Lost in Translation"></a></h2>
<p>Having worked at a bulge-bracket bank watching Java programmers try to be Python programmers, I'm
admittedly hesitant to publish Python code that's trying to be Rust. Interestingly, Rust code can
actually do a great job of mimicking Python. It's certainly not idiomatic Rust, but I've had better
who attempted the same thing for D. These are the actual take-aways:</p>
<p>When transcribing code, <strong>stay as close to the original library as possible</strong>. I'm talking about
using the same variable names, same access patterns, the whole shebang. It's way too easy to make a
couple of typos, and all of a sudden your code blows up in new and exciting ways. Having a reference
manual for verbatim what your code should be means that you don't spend that long debugging
complicated logic, you're more looking for typos.</p>
<p>Also, <strong>don't use nice Rust things like enums</strong>. While
<ahref=https://github.com/bspeice/dtparse/blob/7d565d3a78876dbebd9711c9720364fe9eba7915/src/lib.rs#L88-L94target=_blankrel="noopener noreferrer">one time it worked out OK for me</a>,
I also managed to shoot myself in the foot a couple times because <code>dateutil</code> stores AM/PM as a
boolean and I mixed up which was true, and which was false (side note: AM is false, PM is true). In
general, writing nice code <em>should not be a first-pass priority</em> when you're just trying to recreate
the same functionality.</p>
<p><strong>Exceptions are a pain.</strong> Make peace with it. Python code is just allowed to skip stack frames. So
when a co-worker told me "Rust is getting try-catch syntax" I properly freaked out. Turns out
<ahref=https://github.com/rust-lang/rfcs/pull/243target=_blankrel="noopener noreferrer">he's not quite right</a>, and I'm OK with that. And while
<code>dateutil</code> is pretty well-behaved about not skipping multiple stack frames,
I used to think that Python's whitespace was just there to get you to format your code correctly. I
think that no longer. It's way too easy to close a block too early and have incredibly weird issues
in the logic. Make sure you use an editor that displays indentation levels so you can keep things
straight.</p>
<p><strong>Rust macros are not free.</strong> I originally had the
<ahref=https://github.com/bspeice/dtparse/blob/b0e737f088eca8e83ab4244c6621a2797d247697/tests/compat.rs#L63-L217target=_blankrel="noopener noreferrer">main test body</a>
wrapped up in a macro using <ahref=https://github.com/PyO3/PyO3target=_blankrel="noopener noreferrer">pyo3</a>. It took two minutes to compile.
After
<ahref=https://github.com/bspeice/dtparse/blob/e017018295c670e4b6c6ee1cfff00dbb233db47d/tests/compat.rs#L76-L205target=_blankrel="noopener noreferrer">moving things to a function</a>
compile times dropped down to ~5 seconds. Turns out 150 lines * 100 tests = a lot of redundant code
to be compiled. My new rule of thumb is that any macros longer than 10-15 lines are actually
functions that need to be liberated, man.</p>
<p>Finally, <strong>I really miss list comprehensions and dictionary comprehensions.</strong> As a quick comparison,
<ahref=https://github.com/bspeice/dtparse/blob/7d565d3a78876dbebd9711c9720364fe9eba7915/src/lib.rs#L619-L629target=_blankrel="noopener noreferrer">the implementation in Rust</a>.
I probably wrote it wrong, and I'm sorry. Ultimately though, I hope that these comprehensions can be
added through macros or syntax extensions. Either way, they're expressive, save typing, and are
super-readable. Let's get more of that.</p>
<h2class="anchor anchorWithStickyNavbar_LWe7"id=using-a-young-language>Using a young language<ahref=#using-a-young-languageclass=hash-linkaria-label="Direct link to Using a young language"title="Direct link to Using a young language"></a></h2>
<p>Now, Rust is exciting and new, which means that there's opportunity to make a substantive impact. On
more than one occasion though, I've had issues navigating the Rust ecosystem.</p>
<p>What I'll call the "canonical library" is still being built. In Python, if you need datetime
parsing, you use <code>dateutil</code>. If you want <code>decimal</code> types, it's already in the
<ahref=https://docs.python.org/3.6/library/decimal.htmltarget=_blankrel="noopener noreferrer">standard library</a>. While I might've gotten away
with <code>f64</code>, <code>dateutil</code> uses decimals, and I wanted to follow the principle of <strong>staying as close to
the original library as possible</strong>. Thus began my quest to find a decimal library in Rust. What I
quickly found was summarized in a comment:</p>
<blockquote>
<p>Writing a BigDecimal is easy. Writing a <em>good</em> BigDecimal is hard.</p>
<ahref=https://github.com/rust-num/num/issues/8target=_blankrel="noopener noreferrer">threads</a> to figure out if the library I'm look at is dead
or just stable.</p>
<p>And even when the "canonical library" exists, there's no guarantees that it will be well-maintained.
<ahref=https://github.com/chronotope/chronotarget=_blankrel="noopener noreferrer">Chrono</a> is the <em>de facto</em> date/time library in Rust, and just
released version 0.4.4 like two days ago. Meanwhile,
<ahref=https://github.com/chronotope/chrono-tztarget=_blankrel="noopener noreferrer">chrono-tz</a> appears to be dead in the water even though
<ahref=https://github.com/chronotope/chrono-tz/issues/19target=_blankrel="noopener noreferrer">there are people happy to help maintain it</a>. I
know relatively little about it, but it appears that most of the release process is automated;
keeping that up to date should be a no-brainer.</p>
<h2class="anchor anchorWithStickyNavbar_LWe7"id=trial-maintenance-policy>Trial Maintenance Policy<ahref=#trial-maintenance-policyclass=hash-linkaria-label="Direct link to Trial Maintenance Policy"title="Direct link to Trial Maintenance Policy"></a></h2>
issue, I'm going to try out the following policy to keep things moving on <code>dtparse</code>:</p>
<ol>
<li>
<p>Issues/PRs needing <em>maintainer</em> feedback will be updated at least weekly. I want to make sure
nobody's blocking on me.</p>
</li>
<li>
<p>To keep issues/PRs needing <em>contributor</em> feedback moving, I'm going to (kindly) ask the
contributor to check in after two weeks, and close the issue without resolution if I hear nothing
back after a month.</p>
</li>
</ol>
<p>The second point I think has the potential to be a bit controversial, so I'm happy to receive
feedback on that. And if a contributor responds with "hey, still working on it, had a kid and I'm
running on 30 seconds of sleep a night," then first: congratulations on sustaining human life. And
second: I don't mind keeping those requests going indefinitely. I just want to try and balance
keeping things moving with giving people the necessary time they need.</p>
<p>I should also note that I'm still getting some best practices in place - CONTRIBUTING and
CONTRIBUTORS files need to be added, as well as issue/PR templates. In progress. None of us are
perfect.</p>
<h2class="anchor anchorWithStickyNavbar_LWe7"id=roadmap-and-conclusion>Roadmap and Conclusion<ahref=#roadmap-and-conclusionclass=hash-linkaria-label="Direct link to Roadmap and Conclusion"title="Direct link to Roadmap and Conclusion"></a></h2>
<p>So if I've now built a <code>dateutil</code>-compatible parser, we're done, right? Of course not! That's not
nearly ambitious enough.</p>
<p>Ultimately, I'd love to have a library that's capable of parsing everything the Linux <code>date</code> command
can do (and not <code>date</code> on OSX, because seriously, BSD coreutils are the worst). I know Rust has a
coreutils rewrite going on, and <code>dtparse</code> would potentially be an interesting candidate since it
doesn't bring in a lot of extra dependencies. <ahref=https://crates.io/crates/humantimetarget=_blankrel="noopener noreferrer"><code>humantime</code></a>
could help pick up some of the (current) slack in dtparse, so maybe we can share and care with each
other?</p>
<p>All in all, I'm mostly hoping that nobody's already done this and I haven't spent a bit over a month
on redundant code. So if it exists, tell me. I need to know, but be nice about it, because I'm going