Don't use Dates in Version Numbers

I contend that using dates for version numbers is an anti-pattern. That's pretty rich coming from someone who works for Microsoft, a company which seemingly loves to embed dates, or at least date fragments in their product names - but these version numbers are part of the branding, and not really what I'm talking about here. The kind of version numbers that I am talking about here are binary versions which are often expressed as a multi-part string delimited by a period, for example: 1.4.3.

Version numbers serve a very important purpose in software distribution and a good versioning scheme would ensure the following key requirements are met (in no particular order of importance).

  1. Alerts us to the fact that the software being distributed may behave differently.
  2. Allows us to determine which version of the software is newer than the other.
  3. Helps us to understand the magnitude of differences from version to version.

These all sound good, but they are actually at odds with one another. Let us imagine that we have a versioning scheme that embeds the date that the software was built into the build number (in this case a four part string).

2017.7.22.1
2017.7.23.1
2017.7.23.2

The format of this number could be [year].[month].[day].[hour] or [year].[month].[day].[increment]. The former restricts you to producing one build per hour (not reasonable in most agile teams) and the latter requires that you maintain a log of all builds produced on any particular day. The problem with this versioning scheme is that it fails to satisfy requirement #3 above. It's also dubious on requirement #2 because although 2017.7.23.2 might be newer than 2017.7.23.1 it might in fact be based on an earlier version of the source that was branched off for versioning.

In practice I don't see too many teams using a string like 2017.7.22.1 as their version number, but there are still plenty that hide away a date in their version string. Consider the following version numbers:

1.0.17205.1
1.0.17205.1
1.0.17205.2

The examples above follow the following convention:

[major].[minor].[last two digits of year][day of year number].[increment]

This is better because at least you get some sense of the magnitude of difference from one version to another but still encodes the date in the version number.

A pattern that SemVer 2.0.0 advocates (which I think is a reasonable approach) is stripping this kind of metadata out of the version number itself and instead append optional meta-data to the end of the version string. For example:

1.0.0+build201711220001

The idea behind metadata is that it is for informational purposes only and that another version number that differs only by the text after the plus symbol should be considered the same version. In some package management systems it isn't possible to publish the same version of the software twice. In a world where they observe SemVer 2.0.0 they would ignore the metadata when detecting if a version has already been published.

Still - I don't think that embedding dates in version numbers does much for developers who are trying to establish the origins of a particular package or binary. This is one of the reasons that a lot of package producers have taken to appending a commit ID which corresponds to the source that is associated with produced output.

1.0.0+2836d42e7f93e17c5982c38eda584346f9ac2d23

Of course source code isn't the only input that goes into producing a build of any given binary - but it is a start. Reproducible builds is a topic for another day.

In the meantime, if you want a great way to produce version numbers based on the contents of a version control repository then I heartily recommend using GitVersion. It is a great little tool that analyzes the commit log to come up with a multitude of different version formats to fit most scenarios (it also happens to have an extension for VSTS).

Here is the output of GitVersion against a commit of a repository containing the source code for Encode Decode, an extension that I maintain for Visual Studio Code.

{
  "Major":0,
  "Minor":11,
  "Patch":1,
  "PreReleaseTag":"",
  "PreReleaseTagWithDash":"",
  "PreReleaseLabel":"",
  "PreReleaseNumber":"",
  "BuildMetaData":1,
  "BuildMetaDataPadded":"0001",
  "FullBuildMetaData":"1.Branch.master.Sha.89642f6cc2960593aeed5f6e16033b8bf2363216",
  "MajorMinorPatch":"0.11.1",
  "SemVer":"0.11.1",
  "LegacySemVer":"0.11.1",
  "LegacySemVerPadded":"0.11.1",
  "AssemblySemVer":"0.11.1.0",
  "AssemblySemFileVer":"0.11.1.0",
  "FullSemVer":"0.11.1+1",
  "InformationalVersion":"0.11.1+1.Branch.master.Sha.89642f6cc2960593aeed5f6e16033b8bf2363216",
  "BranchName":"master",
  "Sha":"89642f6cc2960593aeed5f6e16033b8bf2363216",
  "NuGetVersionV2":"0.11.1",
  "NuGetVersion":"0.11.1",
  "NuGetPreReleaseTagV2":"",
  "NuGetPreReleaseTag":"",
  "CommitsSinceVersionSource":1,
  "CommitsSinceVersionSourcePadded":"0001",
  "CommitDate":"2017-11-19"
}

I'd encourage you to start paying more attention to the way that you produce your version numbers and what information they are really giving you. Dates and times are certainly informational but aren't an accurate indicator of where something came from.