73 comments

[ 3.5 ms ] story [ 66.1 ms ] thread
I like this, I've already started versioning with the (2 digit lol) year as major version so 22.19.73 is the current year/release/build, but a standardized thing like this would be great. When versions gets old the version numbers start to add up you might as well use the year, ie Iphone 14, you might as well call it 22.
Isn’t the “semantic” part of semantic versioning that you can tell whether there could be breaking changes in a new release.
Exactly. Just having a date and some commit hash doesn't tell me anything.
But you dont get the most important part: Will installing the 2022.05.03-37492af update break my build or not?

Semantic versioning is about compatibility and amount of changes, sort of. I know that going from 3.0.1 to 3.1.0 wont break the API.

Not once have I been asked when 3.0.1 was released - and if Im asked, I can look at the release posts, or anything else.

Cute idea, but appending a git commit hash to the end of a semantic version may be more useful than any of this.

Plus, if your main issue with semantic versioning is that you cant find your changelog, make it part of your git repo. At BeamMP (a multiplayer game project) we have a changelog.md file right in the server repo. Go to any release tag and youll automatically also have the relevant changelogs.
I agree. Additionally, some binaries like .NET assemblies have "informational" versions that can be set to arbitrary strings. I like to version my assemblies something like 1.2.3.4-rc.1+githash to communicate not only the semantic version but also the commit hash used during the build.
We use this. Caveat: date is not the day of release if you are doing stuff in the “easy way” and aren’t full CD to prod. So your release on the 23rd could be something packaged the 21st

This basically doesn’t matter though, and is easy, and avoids most ordering questions as well that come with CI.

Highly recommend, and at least think semver is silly for versioning applications in general (libs are different of course)

No, don't do this. You're concerned about information loss, but without semver the consumer doesn't know if a major change occurred that would break their builds.

Software vendors and consumers do NOT want automatic major version bumps on Jan 1st. They DO want control over when a major version is released and when they update to it. Using 2022-06-11 as your version, do you hold all your major breaking changes for Jan 31st? That's impossible.

You get all those "Pros" by working within Semver and appending the date to the patch part: major.minor.patchyearmonthday

Edit: To support version parsers always expecting only 3 version numbers I removed the dashes in the example.

Appending date breaks a ton of naive version parsers.
Then just use: major.minor.patchyearmonthday which would work for naive version parsers that always expect 3 numbers separated by dots.
(comment deleted)
Note that if you release something like 2021.x.x on an App Store you’ll never be able to revert back to a number like 3.x.x. So make sure that’s the direction you want to go.
Strong disagree with this post. Semantic versioning means: MAJOR version when you make incompatible API changes, MINOR version when you add functionality in a backwards compatible manner, and PATCH version when you make backwards compatible bug fixes.

The purpose is to enable APIs and build tools to work well, such as package manager project files that specify a version.

To answer the author's questions:

"When was the last time you could tell someone the difference between version 1.2.1 and 1.2.3 quickly?" I can tell it's a patch upgrade, so my team's projects can do this upgrade automatically, with tests of course. Also, I can look at the source git repo, or changelog, or equivalent.

"Can you tell me when version 1.2.1 was released?" Yes, by looking at the source git repo, or git tags, or changelog, or equivalent.

"What is your company/team's meaning behind a major, minor, and patch increment?" Semver specifies how to do this in the first paragraph of www.semver.org.

"Who decides when a new major version is released?" The person who is responsible for the API breaking change; depending on the team and goals, this person could be the developer, or project manager, or release manager, etc.

"What happens to the version where the manager or developer forgot to put together a change log?" We test for it in our git pre-commit hook and in our continuous integration. If your team isn't using those, or equivalents, then simply add a changelog and push a patch version.

The author seems to be focusing on applications. If you’re releasing an app, service or anything else that doesn’t expose a public API, there is no point in having those compatibility semantics.
If you're releasing an app that our teams are using, then yes please use semver, because we manage apps via system package managers (e.g. macOS brew, Windows wpm, Ubuntu apt, etc.) and containers (e.g. Docker, Podman, etc.), and infrastructure as code (e.g. Ansible, Packer, etc.).

This enables our systems to automate our application updates. We do PATCH updates continuously (e.g each night), MINOR updates during system downtime (e.g. each weekend), and MAJOR updates with manual scheduling when we can look at the changelogs.

If you're not managing your systems' applications with package managers or containers or infrastructure as code, then IMHO these are well worth a look.

Is it a breaking change if a button is moved by a few pixels in a graphical application?
No, because the functionality is the same. Just because a test may be so brittle that it breaks the test does not change the meaning (it just may mean you have to manually inspect the now failing test).

The point is that most patch upgrades can just be run through a test suite and accepted. That may still lead to breakages, something that seemed minor and non-breaking to the developer could instead be breaking for a given user, but the point of semver is to convey actionable meaning and intent, which it does.

Yes. Also several weeks of beta are required before releasing the change, and a few blog posts about how a talented team at $COMPANY deals with challenges of UI design.
If the application documentation promises a stable screenshot, but an update moves a button, then yes this is a breaking change.

This comes up in the real world sometimes such as with automatic testing scripts that use absolute cursor positioning, as well as in some kinds of compliance where any graphic change requires a compliance verification of the new UI.

The first two responses to you were a solid yes and a solid no. I think that just demonstrates how the concept does not apply to application versioning.
One of the "Yes" responses is clearly sarcastic; the other gives an "only if you promise an unchanging UI".
The yes I saw at the time has been deleted or edited I believe
It most certainly applies, the previous poster gave good examples of config files syntax and semantics, db compatibility, etc, etc.

On the UI front it really depends, you need to make decisions and let users know to avoid surprises.

At Sun the process was that every possible interface that something or someone can depend on is acknowledged to be an interface. Is GUI pixel positioning an interface? Well yes, clearly it is as someone could write tools that rely on that position.

But do you as the author want to give users an assurance that you won't break this except in major releases? Maybe not. So interfaces were classified on the axis of public/private and stable/unstable (it was a bit more complex but that's the gist). Most applications used to classify the GUI elements as an unstable interface. Then you document that so consumers of the application know they shouldn't rely on GUI elements not moving.

I was specifically trying to contrast programming interfaces against the large universe of applications that don’t expose any. You bring up an interesting point about external dependencies such as db, however I would say the vast majority of these applications I refer to don’t rely on users to set these up.
What do backwards compatibility and breaking changes mean in the context of an application?
Couple of quick examples off the top of my head:

- changes in configuration statements

- changes in configuration defaults

- removal of functionality

- file format changes

- major architectural overhaul (maybe the functionality is still the same, but you still want to verify that yourself before deploying)

User acceptance testing is not just for software manufacturing, it can be done wherever the users are, and some companies take this very seriously.

Even if you do have an API, sometimes you have versioning built into that API anyway, e.g. in openapi and gRPC (depending on how you structure your protobuf files). At that point, a date-based version starts looking more reasonable.
Agreed. And even more so if you're developing a SaaS product or otherwise hosted app which is continuously updated.

The closer you get to continuous delivery, the more date-based versioning can make a bit more sense as opposed to "traditional" semantic versioning (though it's still often a subjective decision).

It sounds like you want to move slow and not break things. How quaint.
No, they still want to move quick, and you can do that by relying on the change signaling of your vendor: if you know the vendor is trustworthy, you can deploy minor updates with only minimal testing. If the vendor is unreliable or doesn't signal their changes accurately, every "minor" update becomes a liability, and that's where it gets slow.

Companies that want to move quickly should not use unreliable vendors.

It is possible to move fast and NOT break things.

Breaking things is bad. Despite what Mark says. When you break things companies lose customers, they lose money, they lose market share, and people get fired or die.

> backwards compatible bug fixes

What even is a 'backwards compatible bug fix'? Unless it's performance, isn't a bug-fix by definition not going to be backwards compatible?

Any bugfix could be seen as backwards incompatible in the strict sense "somewhere, someone might actually depend on this buggy behavior", yes. That’s not a particularly good definition of backwards incompatibility when it comes to bugfixes, however. A more reasonable definition is "requires changes to downstream code" as in the bug cannot be fixed without changing public API as well. That said, sometimes there definitely exist bugs that a lot of users factually rely on, and changing such behaviors should not be done in patch releases.
Which is why you provide patch notes, and why the more conservative consumers won't even auto-accept patch updates. The point is that a bug fix that doesn't change the API, and which only changes behavior to match intent and documentation, is something that requires minimal testing by the user.

I.e., "fixed possible race condition where..." is a patch.

It may be that a consumer relied on a particular bug when developing, yes, and that fixing it now 'breaks' their application, but that's a definite minority, and why you communicate out the fixes (and for auto-adopting patch releases, you ideally still have automatic tests to validate).

> What even is a 'backwards compatible bug fix'? Unless it's performance, isn't a bug-fix by definition not going to be backwards compatible?

It's a fix that makes the code work as documented, which it didn't do before, ergo had a bug.

To pick a random example, the man page for strlen on Linux reads:

"The strlen() function calculates the length of the string s, excluding the terminating null byte ('\0')."

If it behaves differently (let's say it counts the terminating null) then strlen has a bug. The fix to that can go into a patch release because it simply fixes the interface to work as documented.

You could go down a rathole of saying well maybe someone depends on the broken functionality. Sure, but that's not what the documented contract was. The documented functionality is the contract the API needs to meet.

That's too-specific an example. Often the bug is a corner case which wasn't considered, so that the documentation didn't say what was happening, and people were implying on the old relying behaviour.
"MINOR version when you add functionality in a backwards compatible manner"

Impossible to guarantee and thus impossible to do SemVer.

It's OK if systems are imperfect, they are still helpful even though exceptions may occur. Intent matters. You may even see backwards incompatible changes treated as bugs fixed in subsequent releases.
Almost all code is aspirational in terms of matching a spec, being backwards compatible, not having bugs, etc. Its meaningless to talk about what is guaranteed with respect to practically every piece of software in existence. What is important to a developer is "what is the risk that upgrading this software will break my deployment, and what do I gain from upgrading it", which semver relays to the developer to the best of the library author's ability.
Functionality can always be added in a backward compatible manner, that is indeed the purpose of a minor release.

If libfoo has two public interfaces, sing() and dance(), I can add a third interface walk() and publish a binary-compatible libfoo.so which your consuming code can link to without any concern, as your code isn't calling walk() before it existed.

The moment you allow bugs, you can't ensure backwards compatible changes.

That makes semver as good as any other scheme, including GitDate.

> The moment you allow bugs, you can't ensure backwards compatible changes.

I'm not sure what this even means, unless you're talking of the joke of "someone depended on the bug"?

A bug, by its very definition, means code that doesn't behave as the documented interface description states. Fixing bugs means correcting mistakes in the code so that now it does behave as documented. That's what patch releases do.

No, but if you say you don't break API in minor, but you have a bug that breaks API, then minor broke API.
Are you referring to the possibility of a minor release introducing a bug?

Yes, of course that's possible, nobody is pretending humans are infallible. When it happens, just issue a patch release to that minor release to fix the bug. Versioning is about communicating intent, it's not a guarantee that nobody can ever make a mistake.

But if you're constantly introducing bugs in minor releases, that's a sign of sloppy test infrastructure and it will motivate me to migrate away from such a library. But the occasional bug is normal, fix it quick and move on.

Strongly agree with your strong disagreement. The blog author doesn't seem to have any experience releasing software that others need to actually rely on.

This dovetails nicely with the discussion yesterday on using dependencies (https://news.ycombinator.com/item?id=31692606).

I would not want to have to depend on any library this blog author releases. From that list of random meanigless dates, how would tools know which release is a compatible bugfix and which ones carries breaking changes?

The assumption there seems to be that release date is the one and only thing consumers of the release care about. But as a consumer of some library, I can tell that I mostly don't care at all about the release date. But I must know what the release contains in terms of change disruption, which is the purpose of versioning.

TBH the OP post seems to be highlighting that "when we forget history, we're doomed to repeat it."

Not trying to be ageist but a younger dev wouldn't have context on a time when the industry had timestamps as versions and the core reason why SemVer became preferred, as you describe above.

i’ll answer some questions from the article based on my own experience:

> When was the last time you could tell someone the difference between version 1.2.1 and 1.2.3 quickly?

patches mean small bug fixes or improvements that don’t break the api.

> Can you tell me when version 1.2.1 was released? Was it yesterday? Last year? Ten years ago?

for this we’ll have to check the repository. does it matter thou? if the package is stable then it doesn’t. it also depends on the platform. my golang projects rarely get updates. my javascript projects get updates once a week.

> What is your company/team's meaning behind a major, minor, and patch increment?

patch - small changes, no api changes

minor - medium changes, no api changes

major - big changes, api changes

> Who decides when a new major version is released? Is it a person? Maybe it's based on the size of an architectural change?

no, it’s if the api changes.

> What happens to the version where the manager or developer forgot to put together a change log?

we use git’s history for this.

So CalVer with a git code basically?

https://calver.org/

But please don’t use CalVer’s so-called “standard terminology”, because having “MM” be 1–12 and “0M” 01–12 (and the same for D and mostly Y) is ridiculous, flying in the face of the extremely well-established society-wide convention that “MM” means 01–12, and “M” 1–12.

(Because of this folly, I firmly decline to cite CalVer in anything about date-based version numbers. It’s an obvious enough scheme anyway and doesn’t need a fancy name or specificationy sort of thing.)

Don’t have enough red pen to mark this, but how do you even sort versions chronologically if two or more are on the same date?

Also how do you tell between types of change? Major? Minor? Patch?

"When was the last time you could tell someone the difference between version 1.2.1 and 1.2.3 quickly?"

Especially if you're using something like fzf that can easily search your command history, "every time someone has asked":

    git log <tag 1>..<tag 2> --pretty=oneline
I think this is ok for software with linear releases, for example closed-source SaaS applications. You can easily see the date of the release and compare 2 versions.

However a lot of software, especially in open source, does not follow linear releases. Let's say I use SemVer and I fix a bug in versions 1.2.0 and 2.3.0 of my library. I release Release 1.2.1 is created after 2.3.0, but I can easily immediately see it is not a continuation of 2.x branch. With GitDate, my bug fix will be called 2022.06.11-123abc, and it's hard to see that it's not a continuation of 2022.06.09-abc123 release, but an old 2021.11.12-def456 version.

This kind of sounds like "I choose semantic versioning but actually I didn't really need semantic versions, so now I just have a dated build number"

It's fine, but its not very useful in many different kind of situations. Like for instance managing firmware versions where you have a version 5 which is the last version compatible with a certain level of hardware and you need to hot fix it, while the most "modern" version is version 9.

But if you just keep releasing something like a website, then yeah, all you need is a build number, the date might be helpful, but most likely you don't care too much about that either.

> Possible Cons:

> - Multiple builds on the same day? You would need access to the git repo to figure out which came first.

> - Customers now have visibility into the last time their software was updated.

- If corporations like Apple don't support it, the whole thing goes down the drain.

- I know this seems crazy, but I have met people that don't use Git for version control.

I have a secret heresy, in that I still prefer older systems like Perforce and even svn or, horror of horrors, cvs. I’m a bit of a dinosaur I guess.
(comment deleted)
Not all version storage mechanisms support non-numeric components. For example, Windows executables' file version values can only be up to four dotted number components whose values cannot exceed 65535.
If the package manager for the system lacks sufficient information regarding any system component, that would seem grounds for switching to a different OS.
"What do you mean builds aren't temporally linear?"
No, this is no good. Imagine trying to discuss the version with a colleague. We can all remember 2.1.3 but we can't remember the git short code and date.

If you're working on the project then you'll have a link to the repo or docs so it should be trivial to look up what changed based on the semVer change.

I prefer build number and git hash. You don’t need a date, you just need to know which one comes first without having to check git.
Date versioning is great. Just look at all the success of windows 95, 98, 2000 … oh wait. Let’s just give them cute names like xp, vista … oh wait. Everybody understands increasing version numbers. And semver provides more information at a glance than date+hash - that’s probably fine for nightly test builds but not for production releases.
Many of the disagreements in the comments seem to be overly focussed on versioning for installable software and dependencies (for example packages) where, I would agree, semantic versioning is still useful.

Not all software is a dependency though, and not all software is installed by end users. The approach in this article is fine for hosted apps, or anything that's at the top of the dependency chain.

EDIT: That said, this scheme is missing a sortable differentiator for multiple releases on a given date. This means there is no way to calculate the order of precedence between multiple releases on the same day, which is often important (though may not be in the author's specific case). I recently switched to using a hybrid version number which has elements of date based and semantic versioning (though not really "semantic" in the traditional sense):

  2022.45.11+344.9c88541

  2022     = Current year
  45       = Release number in the current year
  11       = Changes in this release (this is a count of Git commits on the release branch since the last tagged release)
Metadata:

  344      = Unique build number
  9c88541  = Git hash
This is completely automatically generated and each version is tagged automatically in the repo on deployment. So I never have to think about versioning at all.

I only use this for top-level applications (SaaS/hosted) which are continuously delivered (often with multiple deployments per day) and have no downstream dependancies.

I used to use Semver 2.0, so in my new hybrid scheme I wanted my version numbers to still have the same number of elements and still be sortable in a list with my older Semver 2 version numbers.

Given that my major versions never got anywhere close to 2022, this format gave me a nice seamless switchover, where order of all version numbers across both schemes was still maintained and my version numbers still had a consistent number of elements (important for my CI tooling):

Eg. versions, in order of most recent, still sortable across versioning format change:

  2022.45.11+344.9c88541
  2022.44.3+333.8b77462
  2.1.0+303.1a65a73
  2.0.3+297.3c878d2
Or without metadata:

  2022.45.11
  2022.44.3
  2.1.0
  2.0.3
It could be useful, I am working on a project now I think this would be a better fit. I don’t think it would replace semantic versioning for all projects though.
How does one release/run two parallel branches at once?

If I'm running version X.y, and (X+1).0 is released, but it's new and has a whole bunch of changes, I may not want to move to it right away. I can do that with SemVer since two parallel streams are possible.