9 min read

Choosing a Versioning Scheme

Table of Contents

I came across the release notes for the Odin programming language and stopped on something worth thinking about: Odin does not use semantic versioning. Its releases are dated β€” dev-2026-07, dev-2026-06 β€” one a month, containing whatever landed that month, with a letter appended when two land in the same month.

It would be easy to file that as a rejection of SemVer, and that is roughly how I first heard it. It is not. Odin moved away from v0.13.1-style numbers for the run-up to 1.0 and intends to adopt proper semantic versioning once 1.0 arrives. The complaint in the original proposal is not about SemVer as a specification β€” it is that the old numbers did not map onto how the language was actually being developed.

Consider what v0.13.1 implies: that someone decided this release was a patch rather than a minor, and that the distinction carried information. For a pre-1.0 language where the whole surface is still moving, that call is arbitrary. The number performs precision it does not have. dev-2026-07 makes a smaller claim and keeps it β€” this is the July build β€” and a build system can assign it without anyone arguing about whether a change was major.

So they did not reject SemVer. They rejected using it before it could mean anything. Both of my ventures are pre-1.0, and I have been putting off the same decision.

What a Version Number Promises

A version number is the most compressed piece of documentation you ship β€” often the only thing a prospective user reads before deciding whether to bother, and the only thing an existing user reads before deciding whether to upgrade.

So the real question is not which scheme looks tidiest. It is what am I promising, and to whom. Each one answers a different question: SemVer answers will this break me?, CalVer answers how old is this?, a marketing name answers is this the new one?, a commit hash answers exactly which code is this?

Pick the scheme that answers the question your users are actually asking and you have a useful version number. Pick one that answers a question you cannot answer honestly and you have a lie with a decimal point in it.

What I Need From a Scheme

Requirement Rationale
Honest about stability The number must not imply a compatibility guarantee I am not yet in a position to keep. Pre-1.0, I am not.
Actionable for the reader Someone should be able to look at it and conclude something useful β€” how far behind they are, or whether the project is still alive.
Unambiguously ordered Newer must sort after older, both by eye and by tooling. No cleverness that requires explanation.
Fully automatable The release pipeline should assign the version. Every scheme that needs me to judge the size of a change is a scheme I will eventually apply inconsistently.
Works across components Both ventures ship as several parts rather than one artifact. I do not want to reconcile a handful of independent counters to describe a single release.
Absorbs bursty cadence I ship in bursts β€” sometimes several releases in a single day, sometimes nothing for weeks. The scheme has to handle both extremes without becoming misleading or running out of room.

The Options

Scheme Assessment
SemVer (2.4.1) MAJOR.MINOR.PATCH, where the parts carry defined meaning about compatibility. The right answer for anything with a machine-consumed interface, and genuinely valuable when honored. But its value is entirely in the promise, and the promise is expensive: it requires a stable public surface you have decided not to break casually. Applied to software that is still finding its shape, it degrades into three numbers that go up.
CalVer (2026.07) The release is stamped with its date. Ubuntu, JetBrains, and Odin’s pre-1.0 builds all use a variant. Says nothing about compatibility β€” which is a feature when you have nothing to say. Automatable, trivially ordered, and self-documenting about staleness: a user can tell at a glance that they are eighteen months behind, which no SemVer number reveals. Weakness: the calendar is not a counter, so releases landing inside the same period need a suffix β€” which makes the choice of period the decision that actually matters.
ZeroVer (0.x forever) Staying below 1.0 indefinitely, which a surprising amount of widely-depended-upon software does. Under SemVer, 0.x exempts you from compatibility promises entirely β€” honest in a sense, but it becomes a permanent excuse that says only that the author is hedging.
Rolling integer (142) One number, incremented per release β€” Chrome and Firefox are effectively here. Unambiguous and impossible to argue about, but it conveys nothing beyond sequence: not age, not compatibility.
Marketing names (Jaguar, XP) Memorable and good for launches, useless for engineering: no ordering, no compatibility signal, and a permanent translation table between the name and what shipped.
Commit hash (a3f9c21) Precise identity, zero human meaning. Essential as build metadata and indispensable for debugging, but it cannot be a release version β€” nobody can tell which of two hashes is newer.
Year editions (2027 Edition) How standards do it: ISO/IEC/IEEE 12207:2017 is the 2017 edition. Suited to documents that are revised wholesale and cited precisely, rather than to software with a dependency graph. Effectively CalVer at annual resolution, with the connotation of a deliberate, settled revision rather than a rolling build.

Decision

Date-based releases now, semantic versioning at 1.0. Odin’s path, for Odin’s reason.

Honesty decided it. I cannot promise compatibility across releases of either venture yet, because both are still shaping their interfaces β€” and I would rather change them well than freeze them early. A SemVer number there is not just uninformative but misleading: it invites people to reason about MINOR versus PATCH distinctions I am making arbitrarily. A date makes a claim I can keep every time.

The format is YYYY.MM.DD, zero-padded so that string and numeric sorting agree, with YYYY.MM.DD.N when more than one release lands on the same day.

Month resolution was my first instinct, copying Odin, and it is wrong for how I work. I lean on Boyd’s Law of Iteration β€” speed of iteration beats quality of iteration, from his dogfight research β€” so I would rather ship five small things today than one considered thing next week. On YYYY.MM that collapses within a fortnight: 2026.07.31, where the date contributes nothing and the counter does all the work. It is also where Odin’s letter suffix stops working for me rather than for them, since a monthly cadence never runs out of letters. Day resolution keeps the calendar meaningful, reserves the counter for shipping twice before dinner, and spares me making judgement calls about change size dozens of times a week. A timestamp cannot be got wrong.

It falls out neatly across the ventures, which was a consequence rather than a reason. Both ship as several parts rather than one artifact, and dating the whole set at once avoids maintaining a compatibility matrix between independently numbered components:

  • For the Digital Solution Life Cycle Model, the model itself is closer to a specification than a program β€” and specifications are versioned by edition, the standards it now aligns itself with being stamped :2017 and :2023. A dated release is the native convention rather than a compromise, and it keeps the model and the tooling that implements it from drifting apart.
  • For the Retail Systematic Trading Platform, the parts are deployed together by whoever runs it. One number for the distribution is the only one an operator needs to know.

What 1.0 will mean: a public surface I am willing to defend, where breaking it costs a major version and a migration path. That is a commitment, not a milestone β€” which is why it should not be claimed by default at the first release.

The Cost I Accept

Switching schemes at 1.0 has an ugly consequence worth stating plainly: 1.0.0 sorts below 2026.07.28. To anything comparing version strings, the switch looks like a catastrophic downgrade. It is survivable only because these releases are not published to a package registry that resolves version ranges β€” they are downloads and tags, so nothing automatically decides which is newer. The cost is an odd-looking tag list and a changelog note. If any of it lands in a package manager before 1.0, I would take SemVer earlier rather than fight the ordering.

The format is a cost of its own. A strict SemVer parser rejects 2026.07.28.2 twice over: four numeric fields instead of three, and leading zeroes, which the specification forbids β€” disqualifying plain 2026.07.28 too. The zero-padding I want for sortable tags is exactly what makes these strings illegal as SemVer.

And YYYY.MM.DD tells a user nothing about whether upgrading is safe. Pre-1.0 the truthful answer is possibly not, which belongs in the changelog rather than implied otherwise in the version string.

Which is the whole point. A version scheme is a description of how much you can currently promise, and mine says not much yet. That happens to be true, and it will change when the truth does.


πŸ’¬

Versioning your own projects on a scheme that has held up β€” or one you regret? Leave a comment below, or get in touch β€” I am always interested in where these conventions break down in practice. Follow my RSS feed for more decisions like this as I work through them.