270

Here is the software version number:

"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"

How can I compare this?

Assume the correct order is:

"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"

The idea is simple...: Read the first digit, than, the second, after that the third... But I can't convert the version number to float number... You also can see the version number like this:

"1.0.0.0", "1.0.1.0", "2.0.0.0", "2.0.0.1", "2.0.1.0"

And this is clearer to see what is the idea behind... But, how can I convert it into a computer program?

Penny Liu
  • 15,447
  • 5
  • 79
  • 98
Tattat
  • 15,548
  • 33
  • 87
  • 138
  • 14
    This would be a good fizzbuzz-type interview question. – Steve Claridge Jul 26 '11 at 15:33
  • 4
    This why all software version numbers should be integers like 2001403. When you want to display it in some friendly way like "2.0.14.3" then you format the version number at presentation time. – jarmod May 10 '13 at 21:50
  • 3
    The general problem here is Semantic Version comparisons, and it's non-trivial (see #11 at http://semver.org). Fortunately, there is an official library for that, the [semantic versioner for npm](https://docs.npmjs.com/misc/semver). – Dan Dascalescu Sep 02 '15 at 00:23
  • 4
    Found a [**simple script**](https://github.com/substack/semver-compare/blob/master/index.js) that compares semvers – vsync Jan 05 '17 at 14:16
  • @jarmod so you have `2001403`, is it `2.0.14.3` or `20.1.4.3` or `2.0.1.43`? This approach is limiting if not flawed. – vanowm Oct 26 '20 at 03:11
  • @vsync **It's named `semver-compare`, but it does not support [Semantic Versioning](https://semver.org/).** – Míng May 17 '22 at 07:09

59 Answers59

183

semver

The semantic version parser used by npm.

$ npm install semver
var semver = require('semver');

semver.diff('3.4.5', '4.3.7') //'major'
semver.diff('3.4.5', '3.3.7') //'minor'
semver.gte('3.4.8', '3.4.7') //true
semver.ltr('3.4.8', '3.4.7') //false

semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true

var versions = [ '1.2.3', '3.4.5', '1.0.2' ]
var max = versions.sort(semver.rcompare)[0]
var min = versions.sort(semver.compare)[0]
var max = semver.maxSatisfying(versions, '*')

Semantic Versioning Link :
https://www.npmjs.com/package/semver#prerelease-identifiers

kabirbaidhya
  • 3,264
  • 3
  • 34
  • 59
Mohammed Akdim
  • 2,223
  • 1
  • 13
  • 21
  • 17
    Yes. **This** is the correct answer - comparing versions is non-trivial (see #11 at http://semver.org), and there are production-level libraries that do the job. – Dan Dascalescu Sep 02 '15 at 00:38
  • 11
    technically, it's not the right answers, since node.js and javascript is different. I supposed the original question was targeted more for browser. But google brought me here and luckily i'm using node :) – Lee Gary Nov 18 '15 at 13:48
  • 1
    It's an important point that npm semver is server side. It's no use for a client side JavaScript solution. That there may be a client side semver solution is another matter. The OP didn't state server only, so this is not the correct answer, but would be a valid comment on his question. – tjmoore Apr 13 '16 at 09:33
  • 3
    NodeJS is not only a server-side only solution. Electron framework embed a nodeJS for desktop applications. This is actually the answer i was looking for. – Anthony Raymond Apr 13 '17 at 14:50
  • 5
    semver it's a npm package, it can be used on any JS environment! THIS IS the right answer – neiker Jun 21 '17 at 18:05
  • 1
    Well, it costs you 16 KB (5 KB gzipped) more for jour project. – artuska Mar 09 '18 at 11:11
  • This wins for me. – user1828780 Mar 09 '18 at 23:42
  • 5
    @artuska well then simply go for another package like [semver-compare](https://github.com/substack/semver-compare) - 233B (less than 0.5kB!) gzipped : ) – kano Nov 28 '18 at 10:03
  • Wow ! Didn't know it, quite impressive ! I exactly needed something for managing major or minor update and not a basic number comparaison.... This one works like a charm ! Thank's a lot ! – Mayoul Mar 01 '19 at 10:18
  • 3
    semver only supports A.B.C style versions. If you need to work with A.B.C.D versions (as suggested in the question), you're out of luck. – LubosD Sep 23 '20 at 13:23
  • 1
    It also doesn't support A.B versions which the OP asked for in their example. – AdamG Feb 03 '21 at 23:36
  • @kano **It's named `semver-compare`, but it does not support [Semantic Versioning](https://semver.org/).** – Míng May 17 '22 at 07:09
  • 1
    This approach has worked with React Native as well. Thanks for your answer. – Hamza Hmem Jun 08 '22 at 08:28
  • 1
    Great library, though this would not work in cases of heterogeneous matches, like A.B.C v/s A.B.C.D or A.B v/s A.B.C – DhakkanCoder Jun 13 '22 at 07:20
163

The basic idea to make this comparison would be to use Array.split to get arrays of parts from the input strings and then compare pairs of parts from the two arrays; if the parts are not equal we know which version is smaller.

There are a few of important details to keep in mind:

  1. How should the parts in each pair be compared? The question wants to compare numerically, but what if we have version strings that are not made up of just digits (e.g. "1.0a")?
  2. What should happen if one version string has more parts than the other? Most likely "1.0" should be considered less than "1.0.1", but what about "1.0.0"?

Here's the code for an implementation that you can use directly (gist with documentation):

function versionCompare(v1, v2, options) {
    var lexicographical = options && options.lexicographical,
        zeroExtend = options && options.zeroExtend,
        v1parts = v1.split('.'),
        v2parts = v2.split('.');

    function isValidPart(x) {
        return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
    }

    if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
        return NaN;
    }

    if (zeroExtend) {
        while (v1parts.length < v2parts.length) v1parts.push("0");
        while (v2parts.length < v1parts.length) v2parts.push("0");
    }

    if (!lexicographical) {
        v1parts = v1parts.map(Number);
        v2parts = v2parts.map(Number);
    }

    for (var i = 0; i < v1parts.length; ++i) {
        if (v2parts.length == i) {
            return 1;
        }

        if (v1parts[i] == v2parts[i]) {
            continue;
        }
        else if (v1parts[i] > v2parts[i]) {
            return 1;
        }
        else {
            return -1;
        }
    }

    if (v1parts.length != v2parts.length) {
        return -1;
    }

    return 0;
}

This version compares parts naturally, does not accept character suffixes and considers "1.7" to be smaller than "1.7.0". The comparison mode can be changed to lexicographical and shorter version strings can be automatically zero-padded using the optional third argument.

There is a JSFiddle that runs "unit tests" here; it is a slightly expanded version of ripper234's work (thank you).

Important note: This code uses Array.map and Array.every, which means that it will not run in IE versions earlier than 9. If you need to support those you will have to provide polyfills for the missing methods.

IvanRF
  • 7,115
  • 5
  • 47
  • 71
Jon
  • 428,835
  • 81
  • 738
  • 806
  • I was thinking, when will this case valid: if (v2parts.length == i) { return v1 + " is smaller"; } – Tattat Jul 26 '11 at 22:10
  • @Tattat: Actually it should read "is larger". Fixed now, thanks. – Jon Jul 26 '11 at 22:26
  • but when will this case trigger? – Tattat Jul 26 '11 at 23:16
  • @Tattat: When the `v2parts` array has "run out", and at the same time `v1parts` has not (because we 're still in the `for`), and there was no version difference so far (otherwise the function would have already returned). This means that you have e.g. `v1 = "1.0.1"` and `v2 = "1.0"`. – Jon Jul 26 '11 at 23:34
  • 21
    Here is an improved version with some unit tests: http://jsfiddle.net/ripper234/Xv9WL/28/ – ripper234 Mar 13 '12 at 10:36
  • 3
    Your algorithm isnt't working correctly if we compare '11.1.2' with '3.1.2' for example. You should convert the strings to integer before comparing them. Please fix this ;) – Tamás Pap Sep 08 '12 at 08:01
  • @TamasPap: That is by design; I explicitly mention it in point #3 and at the very last sentence. You have to pick one or the other, for this code I picked strings. You can convert to integers with `Number(str)` if you want the other version. – Jon Sep 08 '12 at 10:25
  • Your JSFiddle claims that 1.7 < 1.7.0. Per definition, are they really different versions? – stafffan Dec 18 '13 at 10:22
  • @stafffan: That's kind of an interesting case. IMO it's not obvious what should happen there -- assuming that you do have a version 1.7.0, then what is 1.7 supposed to refer to? – Jon Dec 18 '13 at 10:32
  • @Jon: Well, in every-day speech, I guess 1.7 could refer to any version within that branch (from 1.7.0 to 1.7.369.3498). But when using this kind of function for testing minimum requirements (as is my case), I would say that 1.7.0 should be interpreted as equal to 1.7. – stafffan Dec 18 '13 at 10:40
  • @stafffan: I guess that would be OK if it's an opt-in feature. Since this answer can be improved and it does get a steady trickle of traffic I 'm now working on doing that. – Jon Dec 18 '13 at 11:11
  • @TamasPap: I have substantially improved the answer; it now works as you describe by default. Sorry for taking this long! :-) – Jon Dec 18 '13 at 12:38
  • 1
    @stafffan: Answer updated. There is now a flag that zero-pads the shorter version string as necessary, making 1.7 equal to 1.7.0.0.0.0.0. – Jon Dec 18 '13 at 12:39
  • semantic versions like `1.0.0-beta` doesn't work. I found a solution for these cases: http://stackoverflow.com/a/26573190/736518 – timaschew Oct 26 '14 at 12:56
  • 6
    Hey All, I've rolled this gist into a gitrepo with tests and everything and put it up on npm and bower so I can include it in my projects more easily. https://github.com/gabe0x02/version_compare – Gabriel Littman Nov 06 '14 at 01:10
  • 2
    @GabrielLittman: Hey, thanks for taking the time to do that! However all code on SO is licensed with [CC-BY-SA](http://creativecommons.org/licenses/by-sa/2.5/) by default. That means you can't have your package be GPL-licensed. I know lawyering is not what anyone is here for, but it would be good if you fixed it. – Jon Nov 06 '14 at 09:53
  • No problem. I literally did a google search for the last restrictive license when it asked me for one. I can update that. – Gabriel Littman Nov 07 '14 at 17:23
  • 2
    @GabrielLittman: GPL is actually very restrictive in the sense that you are forced to GPL-license all code that comes into contact with existing GPL code. Anyway, for future reference: a good and widely used "do whatever you want, no strings attached" license is [MIT](http://en.wikipedia.org/wiki/MIT_License). – Jon Nov 08 '14 at 21:42
  • 1
    @jon I updated the LICENSE file and put 'CC-BY-SA' in the manifests. Thanks! – Gabriel Littman Nov 10 '14 at 20:22
  • My version minor tweaked for JSLint. No functional change, just it raises eyebrows in the loop (literally says, 'strange loop' ;) ). https://gist.github.com/tjmoore/af63996e398d743d659b – tjmoore Apr 14 '15 at 15:51
  • 3
    @GabrielLittman: there are already [established libraries written by seasoned devs](https://github.com/gabe0x02/version_compare/issues/1) that perform semver comparisons. – Dan Dascalescu Sep 01 '15 at 21:33
  • @DanDascalescu let me get this straight: you felt the need to post that comment and mass downvote here because someone somewhere has already solved this problem in a production-level way? Perhaps you missed the "how can I actually implement this idea" aspect of the question? – Jon Sep 02 '15 at 00:11
  • 1
    Not sure what you're talking about re. "mass downvoting". Out of 26 answer here I downvoted only two, and explained each of my downvotes. As for the general reasoning behind recommending *mentioning* production-level solutions, see my presentation [Prior Art](http://slides.com/dandv/prior-art) and the level of complexity that correct semver sorting requires - #11 at http://semver.org/. The OP asked for "numbers only", but the general problem is [semver sorting](https://docs.npmjs.com/misc/semver#comparison) & I feel that's worth mentioning. Maybe not worth downvoting, but I can't retract it. – Dan Dascalescu Sep 02 '15 at 00:19
  • Note also the extended comments, improvements and even test cases proposed on this answer - a relatively futile enterprise of making this snippet production quality, if its purpose is "how can I actually implement this idea". Mentioning that the algorithm here has an academic role and production-quality solutions exist would've prevented reinventing the wheel. – Dan Dascalescu Sep 02 '15 at 00:26
  • @DanDascalescu the downvotes are immaterial (and I admit I didn't try to check all the answers, just those you commented on). Regarding making this production quality: people can choose to solve their practical problem using the best, most reliable and proven tool for the situation, but there will always be people who come to SO, find something that is good enough (for some definition of good enough) and run with it -- that includes me on occasion, and possibly even you. – Jon Sep 02 '15 at 13:44
  • 1
    @DanDascalescu: In retrospect I was put off by what seemed to me the rather lecturing "you should not be doing this" tone whereas I would have appreciated something like "this problem is called semver comparison, and if you are after production quality software that solves it take a look here". Which I now see you have posted as a comment to the question, so please accept my apologies. – Jon Sep 02 '15 at 13:48
  • @Jon: apologies accepted, and an upvote expressing your support of [that comment identifying the problem as Semver comparison](http://stackoverflow.com/questions/6832596/how-to-compare-software-version-number-using-js-only-number/6832721?noredirect=1#comment52560025_6832596) would be appreciated. Thanks either way. – Dan Dascalescu Sep 02 '15 at 20:29
  • @DanDascalescu thanks for letting me know. Obviously I didn't find those a year ago when I set this up. – Gabriel Littman Sep 16 '15 at 21:46
  • I missed all this hubbub earlier, but appreciate being made aware of existing libraries and the spec. However then associating it with not acknowledging Prior Art is a bit much given that the author may be utterly unaware of existing libraries. I have to admit I wasn't. I'm thankful knowing there is now and knowing about the spec. I would add that a SO answer of "use this library" when asking about how to write a solution to this problem is not very SO. As commented, this is quite a fizzbuzz type of question. "use this library" wouldn't be appreciated in an interview ;) – tjmoore Apr 13 '16 at 09:03
  • Noting that the solution is non trivial, I wouldn't expect an interview question of this type to have a perfect solution but I would expect a good quality candidate to have a good stab at it and maybe acknowledge that it's non trivial. It shows an understanding of the problem. Indeed they may even state the library that already exists, but I'd ask them to have a go anyway. – tjmoore Apr 13 '16 at 09:10
  • 2
    @ripper234 It does not work correctly with : `assert(compareVersionNumbers("1.7.9", "1.7.10") < 0);` – Sw0ut Nov 14 '17 at 08:57
  • I tried this code on some version-strings of android packages. For example "11.5.18 (436-170253583)" and "7.13.28.21.arm64". The major version 11 gets put before the major version 7. Which is wrong. Removing the first 2 if statements in the code gave the batch of data a better sorting quality. – user2032300 Mar 01 '18 at 13:43
  • @user2032300 I'm not sure what you mean, the "11" vs "7" issue should be dealt with correctly by default and there is an option to override this if you want the non-intuitive-to-humans behavior. This has been tested and documented. Did you maybe get `NaN` as a result because the first string has parens and a hyphen? – Jon Mar 02 '18 at 16:54
  • As @Sw0ut mentioned, use Jon's `versionCompare` solution and not @ripper234's, as the latter shows wrong results in specific cases. – dabo248 Feb 12 '20 at 12:00
  • 1
    Does not work as expected. it doesnt understand 1.0.90 vs 1.0.100. comparing 1.0.90 vs 1.0.91 works, but once you change 91 to 100 its different results. makes no sense. – tmarois Aug 22 '20 at 18:33
  • @tmarois works fine for me. Considering that it was designed with such cases in mind and that this answer is now in its 10th year of existence, I suspect you might be doing something wrong. If you would like another pair of eyes, please post a fiddle. – Jon Aug 23 '20 at 19:07
  • @Jon I should have been more clear, there are multiple versions of this answer, the one I was actually referring too was the short version using `compareVersions` – tmarois Aug 24 '20 at 16:01
125

The simplest is to use localeCompare :

a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })

This will return:

  • 0: version strings are equal
  • 1: version a is greater than b
  • -1: version b is greater than a
Míng
  • 2,500
  • 6
  • 32
  • 48
Idan
  • 3,604
  • 1
  • 28
  • 33
  • 1
    @JuanMendes This does have a limitation that the version strings must always have the same number of parts. So when passed `1.0` and `1.0.0.0`, `localeCompare` shows that `1.0.0.0` is greater. – Mordred Oct 01 '21 at 22:11
  • 1
    @Mordred That's the behavior I expect, I don't expect those to be treated the same. See https://semver.org/ https://semver.org/#spec-item-11 – Ruan Mendes Oct 02 '21 at 14:44
  • @JuanMendes Most people do (and most of the answers work the same way), but depending on your needs and data sanitization, there could be issues. – Mordred Oct 02 '21 at 19:54
  • 3
    Love it, but unfortunately it can pass this test `1.0.0-alpha < 1.0.0`. See https://semver.org/#spec-item-11 – Míng May 17 '22 at 03:36
  • 2
    A patch to support `1.0.0-alpha < 1.0.0` and more cases: https://gist.github.com/iwill/a83038623ba4fef6abb9efca87ae9ccb – Míng May 17 '22 at 09:19
  • 1
    For the versioning we're handling in our app, this works perfectly. It's so simple and short! – Jesús Vera Mar 13 '23 at 12:36
  • Won't the behavior of this answer depend on the client's locale? (Some locales use commas where en-US uses periods) – Matt Thomas Apr 11 '23 at 20:30
  • 1
    @Idan the suggestion from Ming is important. Could you add a small note in your answer which warns about prerelease version? According to Semver 1.0.1 should be **after** 1.0.0-1 – Falco May 16 '23 at 14:56
68
// Return 1 if a > b
// Return -1 if a < b
// Return 0 if a == b
function compare(a, b) {
    if (a === b) {
       return 0;
    }

    var a_components = a.split(".");
    var b_components = b.split(".");

    var len = Math.min(a_components.length, b_components.length);

    // loop while the components are equal
    for (var i = 0; i < len; i++) {
        // A bigger than B
        if (parseInt(a_components[i]) > parseInt(b_components[i])) {
            return 1;
        }

        // B bigger than A
        if (parseInt(a_components[i]) < parseInt(b_components[i])) {
            return -1;
        }
    }

    // If one's a prefix of the other, the longer one is greater.
    if (a_components.length > b_components.length) {
        return 1;
    }

    if (a_components.length < b_components.length) {
        return -1;
    }

    // Otherwise they are the same.
    return 0;
}

console.log(compare("1", "2"));
console.log(compare("2", "1"));

console.log(compare("1.0", "1.0"));
console.log(compare("2.0", "1.0"));
console.log(compare("1.0", "2.0"));
console.log(compare("1.0.1", "1.0"));
Joe
  • 46,419
  • 33
  • 155
  • 245
  • I think the line: `var len = Math.min(a_components.length, b_components.length);` will cause versions 2.0.1.1 and 2.0.1 to be treated as equal will it? – Jon Egerton Jul 26 '11 at 15:47
  • 1
    No. Look just after the loop! If one string is a prefix of the other (i.e. loop reaches the end), then the longer one is taken as higher. – Joe Jul 26 '11 at 15:51
  • Perhaps you were put off my stumbling over the English language in the comment... – Joe Jul 26 '11 at 16:11
  • @Joe I know is a bit old answer but I was using the function. Testing `a = '7'` and `b = '7.0'` returns `-1` because 7.0 is longer. Got any suggestion for that? ( `console.log(compare("7", "7.0")); //returns -1` ) – RaphaelDDL Sep 17 '13 at 21:24
  • I suppose that comes under the heading of undefined behaviour. If you do have these version numbers then I'm sure you can modify the logic to fit your requirements. – Joe Sep 18 '13 at 07:37
  • I came here to add my implementation and found this was almost exactly the same... ha ha! – Ben Power Jul 19 '16 at 03:39
  • 1
    @RaphaelDDL compare length of both arrays and add 0's to the shortest until lengths are equal. – vanowm May 27 '19 at 01:16
53

This very small, yet very fast compare function takes version numbers of any length and any number size per segment.

Return values:
- a number < 0 if a < b
- a number > 0 if a > b
- 0 if a = b

So you can use it as compare function for Array.sort();

EDIT: Bugfixed Version stripping trailing zeros to recognize "1" and "1.0.0" as equal

function cmpVersions (a, b) {
    var i, diff;
    var regExStrip0 = /(\.0+)+$/;
    var segmentsA = a.replace(regExStrip0, '').split('.');
    var segmentsB = b.replace(regExStrip0, '').split('.');
    var l = Math.min(segmentsA.length, segmentsB.length);

    for (i = 0; i < l; i++) {
        diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
        if (diff) {
            return diff;
        }
    }
    return segmentsA.length - segmentsB.length;
}

// TEST
console.log(
['2.5.10.4159',
 '1.0.0',
 '0.5',
 '0.4.1',
 '1',
 '1.1',
 '0.0.0',
 '2.5.0',
 '2',
 '0.0',
 '2.5.10',
 '10.5',
 '1.25.4',
 '1.2.15'].sort(cmpVersions));
// Result:
// ["0.0.0", "0.0", "0.4.1", "0.5", "1.0.0", "1", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"]
LeJared
  • 2,032
  • 1
  • 17
  • 28
  • Failed with '0.0' and '0.0.0'. See fiddle: https://jsfiddle.net/emragins/9e9pweqg/ – emragins Sep 12 '16 at 18:16
  • 1
    @emragins When would you need to do that? – Skylar Ittner Sep 13 '16 at 18:58
  • 2
    @emragins : I don't see where it fails. It outputs ```["0.0.0", "0.0", "0.4.1", "0.5", "1.0.0", "1", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"] ``` where your code outputs ```["0.0", "0.0.0", "0.4.1", "0.5", "1", "1.0.0", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"] ```, which is perfectly the same, since 0.0 and 0.0.0 are considered to be *equal*, which means it is irrelevant whether '0.0' is before '0.0.0' or vice versa. – LeJared Sep 14 '16 at 07:20
  • I agree this is an usual point. I'm using this with https://github.com/jonmiles/bootstrap-treeview, which tiers nodes in a manner similar to versions, only it's really just parent/child nodes and their indexes. Ex. Parent: 0.0, child: 0.0.0, 0.0.1. See this issue for more details on why I care: https://github.com/jonmiles/bootstrap-treeview/issues/251 – emragins Sep 14 '16 at 19:06
  • Why is `parseInt(num, 10)` needed here instead of `parseInt(num)` – benjaminz Apr 18 '17 at 15:50
  • 1
    See answer here http://stackoverflow.com/questions/6611824/why-do-we-need-to-use-radix. Older browsers used to guess the radix parameter if not specified. A leading zero in a number string like the middle part in "1.09.12" used to be parsed with radix=8 resulting in number 0 instead of expected number 9. – LeJared Apr 19 '17 at 19:13
  • For some reason I feel like 0.0 should come before 0.0.0 so I prefer @Joe's answer. – cprcrack Oct 07 '18 at 03:34
43

Simple and short function:

function isNewerVersion (oldVer, newVer) {
  const oldParts = oldVer.split('.')
  const newParts = newVer.split('.')
  for (var i = 0; i < newParts.length; i++) {
    const a = ~~newParts[i] // parse int
    const b = ~~oldParts[i] // parse int
    if (a > b) return true
    if (a < b) return false
  }
  return false
}

Tests:

isNewerVersion('1.0', '2.0') // true
isNewerVersion('1.0', '1.0.1') // true
isNewerVersion('1.0.1', '1.0.10') // true
isNewerVersion('1.0.1', '1.0.1') // false
isNewerVersion('2.0', '1.0') // false
isNewerVersion('2', '1.0') // false
isNewerVersion('2.0.0.0.0.1', '2.1') // true
isNewerVersion('2.0.0.0.0.1', '2.0') // false
Arthur Ronconi
  • 2,290
  • 25
  • 25
  • 3
    You can simplify it with: **const a = ~~newParts[i];** In fact this is the most efficient way convert a string into an integer, which returns 0 if variable undefined or contains non-numerical characters. – vanowm Nov 20 '18 at 07:10
  • I often need to know if it's newer or the same, so my code can decide whether to hide a feature that is not supported. Isn't that the question that most are interested in? – Ruan Mendes Aug 10 '21 at 16:20
  • 1
    Nice and short, exactly what I was looking for. You could also add `oldVer.replace(/[^0-9.]/g, '').trim()` and `newVer.replace(/[^0-9.]/g, '').trim()` to handle alpha, beta, or release candidate versions that add text like so: `1.0.0-rc' – Blizzardengle Nov 09 '21 at 19:10
16

Taken from http://java.com/js/deployJava.js:

    // return true if 'installed' (considered as a JRE version string) is
    // greater than or equal to 'required' (again, a JRE version string).
    compareVersions: function (installed, required) {

        var a = installed.split('.');
        var b = required.split('.');

        for (var i = 0; i < a.length; ++i) {
            a[i] = Number(a[i]);
        }
        for (var i = 0; i < b.length; ++i) {
            b[i] = Number(b[i]);
        }
        if (a.length == 2) {
            a[2] = 0;
        }

        if (a[0] > b[0]) return true;
        if (a[0] < b[0]) return false;

        if (a[1] > b[1]) return true;
        if (a[1] < b[1]) return false;

        if (a[2] > b[2]) return true;
        if (a[2] < b[2]) return false;

        return true;
    }
user123444555621
  • 148,182
  • 27
  • 114
  • 126
13

Here is another short version that works with any number of sub versions, padded zeros and even numbers with letters (1.0.0b3)

const compareVer = (prep =>
{
  prep = t => ("" + t)
      // treat non-numerical characters as lower version
      // replacing them with a negative number based on charcode of first character
    .replace(/[^\d\.]+/g, c => "." + (c.replace(/[\W_]+/, "").toUpperCase().charCodeAt(0) - 65536) + ".")
      // remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
    .replace(/(?:\.0+)*(\.-\d+(?:\.\d+)?)\.*$/g, "$1")
      // return array
    .split('.')

  return (a, b, c, d, len, res, i = 0) =>
  {
      c = prep(a);
      d = prep(b);
      len = Math.max(c.length, d.length);
      while (!res && i < len)
      {
        //convert every item into integer
        a = ~~c[i];
        b = ~~d[i++];
        res = -(a < b) + (a > b);
      }
      return res;
  }
})();

Function returns:

0 if a = b

1 if a > b

-1 if a < b

1.0         = 1.0.0.0.0.0
1.0         < 1.0.1
1.0b1       < 1.0
1.0b        = 1.0b
1.1         > 1.0.1b
1.1alpha    < 1.1beta
1.1rc1      > 1.1beta
1.1rc1      < 1.1rc2
1.1.0a1     < 1.1a2
1.1.0a10    > 1.1.0a1
1.1.0alpha  = 1.1a
1.1.0alpha2 < 1.1b1
1.0001      > 1.00000.1.0.0.0.01

/*use strict*/
const compareVer = (prep =>
{
  prep = t => ("" + t)
      // treat non-numerical characters as lower version
      // replacing them with a negative number based on charcode of first character
    .replace(/[^\d\.]+/g, c => "." + (c.replace(/[\W_]+/, "").toUpperCase().charCodeAt(0) - 65536) + ".")
      // remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
    .replace(/(?:\.0+)*(\.-\d+(?:\.\d+)?)\.*$/g, "$1")
      // return array
    .split('.')

  return (a, b, c, d, len, res, i = 0) =>
  {
      c = prep(a);
      d = prep(b);
      len = Math.max(c.length, d.length);
      while (!res && i < len)
      {
        //convert every item into integer
        a = ~~c[i];
        b = ~~d[i++];
        res = -(a < b) + (a > b);
      }
      return res;
  }
})();
//examples

let list = [
  ["1.0",         "1.0.0.0.0.0"],
  ["1.0",         "1.0.1"],
  ["1.0b1",       "1.0"],
  ["1.0b",        "1.0b"],
  ["1.1",         "1.0.1b"],
  ["1.1alpha",    "1.1beta"],
  ["1.1rc1",      "1.1beta"],
  ["1.1rc1",      "1.1rc2"],
  ["1.1.0a1",     "1.1a2"],
  ["1.1.0a10",    "1.1.0a1"],
  ["1.1.0alpha",  "1.1a"],
  ["1.1.0alpha2", "1.1b1"],
  ["1.0001",      "1.00000.1.0.0.0.01"]
]
for(let i = 0; i < list.length; i++)
{
  console.log( list[i][0] + " " + "<=>"[compareVer(list[i][0], list[i][1]) + 1] + " " + list[i][1] );
}

https://jsfiddle.net/vanowm/p7uvtbor/

vanowm
  • 9,466
  • 2
  • 21
  • 37
10

Couldn't find a function doing what I wanted here. So I wrote my own. This is my contribution. I hope someone find it useful.

Pros:

  • Handles version strings of arbitrary length. '1' or '1.1.1.1.1'.

  • Defaults each value to 0 if not specified. Just because a string is longer doesn't mean it's a bigger version. ('1' should be the same as '1.0' and '1.0.0.0'.)

  • Compare numbers not strings. ('3'<'21' should be true. Not false.)

  • Don't waste time on useless compares in the loop. (Comparing for ==)

  • You can choose your own comparator.

Cons:

  • It does not handle letters in the version string. (I don't know how that would even work?)

My code, similar to the accepted answer by Jon:

function compareVersions(v1, comparator, v2) {
    "use strict";
    var comparator = comparator == '=' ? '==' : comparator;
    if(['==','===','<','<=','>','>=','!=','!=='].indexOf(comparator) == -1) {
        throw new Error('Invalid comparator. ' + comparator);
    }
    var v1parts = v1.split('.'), v2parts = v2.split('.');
    var maxLen = Math.max(v1parts.length, v2parts.length);
    var part1, part2;
    var cmp = 0;
    for(var i = 0; i < maxLen && !cmp; i++) {
        part1 = parseInt(v1parts[i], 10) || 0;
        part2 = parseInt(v2parts[i], 10) || 0;
        if(part1 < part2)
            cmp = 1;
        if(part1 > part2)
            cmp = -1;
    }
    return eval('0' + comparator + cmp);
}

Examples:

compareVersions('1.2.0', '==', '1.2'); // true
compareVersions('00001', '==', '1.0.0'); // true
compareVersions('1.2.0', '<=', '1.2'); // true
compareVersions('2.2.0', '<=', '1.2'); // false
Viktor
  • 558
  • 5
  • 12
  • this version is in my opinion better than the one in the approved answer! – user3807877 Mar 24 '15 at 16:32
  • 2
    This function is prone to code injection if the comparator parameter is used with unchecked user input! Example: compareVersions('1.2', '==0;alert("cotcha");', '1.2'); – LeJared Feb 15 '16 at 13:49
  • @LeJared True. When I wrote it we were not going to use it with user submitted code though. Should have brought it up as a con probably. I have now updated the code to eliminate that possibility. Now though, when webpack and other node.js bundlers have become prevalent, I would suggest that **Mohammed Akdim**'s answer above, using semver, would almost always be the correct answer to this question. – Viktor Feb 18 '16 at 09:59
8

2017 answer:

v1 = '20.0.12'; 
v2 = '3.123.12';

compareVersions(v1,v2) 
// return positive: v1 > v2, zero:v1 == v2, negative: v1 < v2 
function compareVersions(v1, v2) {
        v1= v1.split('.')
        v2= v2.split('.')
        var len = Math.max(v1.length,v2.length)
        /*default is true*/
        for( let i=0; i < len; i++)
            v1 = Number(v1[i] || 0);
            v2 = Number(v2[i] || 0);
            if (v1 !== v2) return v1 - v2 ;
            i++;
        }
        return 0;
    }

Simplest code for modern browsers:

 function compareVersion2(ver1, ver2) {
      ver1 = ver1.split('.').map( s => s.padStart(10) ).join('.');
      ver2 = ver2.split('.').map( s => s.padStart(10) ).join('.');
      return ver1 <= ver2;
 }

The idea here is to compare numbers but in the form of string. to make the comparison work the two strings must be at the same length. so:

"123" > "99" become "123" > "099"
padding the short number "fix" the comparison

Here I padding each part with zeros to lengths of 10. then just use simple string compare for the answer

Example :

var ver1 = '0.2.10', ver2=`0.10.2`
//become 
ver1 = '0000000000.0000000002.0000000010'
ver2 = '0000000000.0000000010.0000000002'
// then it easy to see that
ver1 <= ver2 // true
pery mimon
  • 7,713
  • 6
  • 52
  • 57
  • would you explain function `compareVersion2` what exactly happen ? – Usman Wali Apr 03 '18 at 13:30
  • Good, then you can use `substring` instead of `padStart`for better compatibility i.e. `var zeros = "0000000000"; '0.2.32'.split('.').map( s => zeros.substring(0, zeros.length-s.length) + s ).join('.') ` will give you `0000000000.0000000002.0000000032` :) – Usman Wali Apr 12 '18 at 11:11
8

You could use String#localeCompare with options

sensitivity

Which differences in the strings should lead to non-zero result values. Possible values are:

  • "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A.
  • "accent": Only strings that differ in base letters or accents and other diacritic marks compare as unequal. Examples: a ≠ b, a ≠ á, a = A.
  • "case": Only strings that differ in base letters or case compare as unequal. Examples: a ≠ b, a = á, a ≠ A.
  • "variant": Strings that differ in base letters, accents and other diacritic marks, or case compare as unequal. Other differences may also be taken into consideration. Examples: a ≠ b, a ≠ á, a ≠ A.

The default is "variant" for usage "sort"; it's locale dependent for usage "search".

numeric

Whether numeric collation should be used, such that "1" < "2" < "10". Possible values are true and false; the default is false. This option can be set through an options property or through a Unicode extension key; if both are provided, the options property takes precedence. Implementations are not required to support this property.

var versions = ["2.0.1", "2.0", "1.0", "1.0.1", "2.0.0.1"];

versions.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));

console.log(versions);
Community
  • 1
  • 1
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
6

Although this question already has a lot of answers, each one promotes their own backyard-brewn solution, whilst we have a whole ecosystem of (battle-)tested libraries for this.

A quick search on NPM, GitHub, X will give us some lovely libs, and I'd want to run through some:

semver-compare is a great lightweight (~230 bytes) lib that's especially useful if you want to sort by version numbers, as the library's exposed method returns -1, 0 or 1 appropriately.

The core of the library:

module.exports = function cmp (a, b) {
    var pa = a.split('.');
    var pb = b.split('.');
    for (var i = 0; i < 3; i++) {
        var na = Number(pa[i]);
        var nb = Number(pb[i]);
        if (na > nb) return 1;
        if (nb > na) return -1;
        if (!isNaN(na) && isNaN(nb)) return 1;
        if (isNaN(na) && !isNaN(nb)) return -1;
    }
    return 0;
};

compare-semver is rather hefty in size (~4.4 kB gzipped), but allows for some nice unique comparisons like to find the minimum/maximum of a stack of versions or to find out if the provided version is unique or less than anything else in a collection of versions.

compare-versions is another small library (~630 bytes gzipped) and follows the spec nicely, meaning you can compare versions with alpha/beta flags and even wildcards (like for minor/patch versions: 1.0.x or 1.0.*)

The point being: there's not always a need to copy-paste code from Stack Overflow, if you can find decent, (unit-)tested versions via your package manager of choice.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
kano
  • 5,626
  • 3
  • 33
  • 48
  • **The first one is named `semver-compare`, but it does not support [Semantic Versioning](https://semver.org/). And, [this answer](https://stackoverflow.com/a/65687141/456536) is much great and lightweight than it.** – Míng May 17 '22 at 07:06
  • @Mr.Míng Semantic versioning is actually without the v.* prefix (https://semver.org/#is-v123-a-semantic-version) so I'd say `semver-compare` does support semantic versioning just fine – kano May 17 '22 at 08:06
  • It might be fine in some cases, but its name is misleading. – Míng May 17 '22 at 09:14
  • How exactly? It does support the semver spec – kano May 19 '22 at 06:36
  • Exactly, `cmp("1.0.0-b", "1.0.0-a")` should returns 1 if supports the semver spec, but it returns 0. See more examples from [Semantic Versioning](https://semver.org/#spec-item-11): 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0. – Míng May 20 '22 at 01:58
6

I faced the similar issue, and I had already created a solution for it. Feel free to give it a try.

It returns 0 for equal, 1 if the version is greater and -1 if it is less

function compareVersion(currentVersion, minVersion) {
  let current = currentVersion.replace(/\./g," .").split(' ').map(x=>parseFloat(x,10))
  let min = minVersion.replace(/\./g," .").split(' ').map(x=>parseFloat(x,10))

  for(let i = 0; i < Math.max(current.length, min.length); i++) {
    if((current[i] || 0) < (min[i] || 0)) {
      return -1
    } else if ((current[i] || 0) > (min[i] || 0)) {
      return 1
    }
  }
  return 0
}


console.log(compareVersion("81.0.1212.121","80.4.1121.121"));
console.log(compareVersion("81.0.1212.121","80.4.9921.121"));
console.log(compareVersion("80.0.1212.121","80.4.9921.121"));
console.log(compareVersion("4.4.0","4.4.1"));
console.log(compareVersion("5.24","5.2"));
console.log(compareVersion("4.1","4.1.2"));
console.log(compareVersion("4.1.2","4.1"));
console.log(compareVersion("4.4.4.4","4.4.4.4.4"));
console.log(compareVersion("4.4.4.4.4.4","4.4.4.4.4"));
console.log(compareVersion("0","1"));
console.log(compareVersion("1","1"));
console.log(compareVersion("1","1.0.00000.0000"));
console.log(compareVersion("","1"));
console.log(compareVersion("10.0.1","10.1"));
Awesome Dev
  • 69
  • 1
  • 4
  • 1
    The regex is unnecessary. You can simply append `.` inside the `map()`: `x=>parseFloat("." + x, 10)` – vanowm Dec 23 '21 at 13:34
5

Forgive me if this idea already been visited in a link I have not seen.

I have had some success with conversion of the parts into a weighted sum like so:

partSum = this.major * Math.Pow(10,9);
partSum += this.minor * Math.Pow(10, 6);
partSum += this.revision * Math.Pow(10, 3);
partSum += this.build * Math.Pow(10, 0);

Which made comparisons very easy (comparing a double). Our version fields are never more than 4 digits.

7.10.2.184  -> 7010002184.0
7.11.0.1385 -> 7011001385.0

I hope this helps someone, as the multiple conditionals seem a bit overkill.

Noxin
  • 51
  • 1
  • 1
5

We can now use Intl.Collator API now to create numeric comparators. Browser support is pretty decent, but not supported in Node.js at the time of writing.

const semverCompare = new Intl.Collator("en", { numeric: true }).compare;

const versions = ['1.0.1', '1.10.2', '1.1.1', '1.10.1', '1.5.10', '2.10.0', '2.0.1'];

console.log(versions.sort(semverCompare))

const example2 = ["1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"];
console.log(example2.sort(semverCompare))
Sid Vishnoi
  • 1,250
  • 1
  • 16
  • 27
5

A dead simple way:

function compareVer(previousVersion, currentVersion) {
 try {
    const [prevMajor, prevMinor = 0, prevPatch = 0] = previousVersion.split('.').map(Number);
    const [curMajor, curMinor = 0, curPatch = 0] = currentVersion.split('.').map(Number);

    if (curMajor > prevMajor) {
      return 'major update';
    }
    if (curMajor < prevMajor) {
      return 'major downgrade';
    }
    if (curMinor > prevMinor) {
      return 'minor update';
    }
    if (curMinor < prevMinor) {
      return 'minor downgrade';
    }
    if (curPatch > prevPatch) {
      return 'patch update';
    }
    if (curPatch < prevPatch) {
      return 'patch downgrade';
    }
    return 'same version';
  } catch (e) {
    return 'invalid format';
  }
}

Output:

compareVer("3.1", "3.1.1") // patch update
compareVer("3.1.1", "3.2") // minor update
compareVer("2.1.1", "1.1.1") // major downgrade
compareVer("1.1.1", "1.1.1") // same version
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
GorvGoyl
  • 42,508
  • 29
  • 229
  • 225
4

My less verbose answer than most of the answers here

/**
 * Compare two semver versions. Returns true if version A is greater than
 * version B
 * @param {string} versionA
 * @param {string} versionB
 * @returns {boolean}
 */
export const semverGreaterThan = function(versionA, versionB){
  var versionsA = versionA.split(/\./g),
    versionsB = versionB.split(/\./g)
  while (versionsA.length || versionsB.length) {
    var a = Number(versionsA.shift()), b = Number(versionsB.shift())
    if (a == b)
      continue
    return (a > b || isNaN(b))
  }
  return false
}
Ally
  • 4,894
  • 8
  • 37
  • 45
  • 1
    you should make it a module and put it on node.js. until then, i'm stealing your code with attribution to you. thank you for this. – r3wt May 11 '19 at 18:05
4

The (most of the time) correct JavaScript answer in 2020

Both Nina Scholz in March 2020 and Sid Vishnoi in April 2020 post the modern answer:

var versions = ["2.0.1", "2.0", "1.0", "1.0.1", "2.0.0.1"];

versions.sort((a, b) => 
   a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
);

console.log(versions);

localCompare has been around for some time

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator

But what about 1.0a and 1.0.1

localCompare doesn't solve that, still returns 1.0.1 , 1.0a

Michael Deal in his (longish &complex) solution already cracked that in 2013

He converts Numbers to another Base, so they can be sorted better

His answer got me thinking...

666 - Don't think in numbers - 999

Sorting is alphanumeric, based on the ASCII values, so let's (ab)use ASCII as the "base"

My solution is to convert 1.0.2.1 to b.a.c.b to bacb , and then sort

This solves 1.1 vs. 1.0.0.0.1 with: bb vs. baaab

And immediately solves the 1.0a and 1.0.1 sorting problem with notation: baa and bab

Conversion is done with:

    const str = s => s.match(/(\d+)|[a-z]/g)
                      .map(c => c == ~~c ? String.fromCharCode(97 + c) : c);

= Calculate ASCII value for 0...999 Numbers, otherwise concat letter

1.0a >>> [ "1" , "0" , "a" ] >>> [ "b" , "a" , "a" ]

For comparison sake there is no need to concatenate it to one string with .join("")

Oneliner

const sortVersions=(x,v=s=>s.match(/(\d+)|[a-z]/g)
                            .map(c=>c==~~c?String.fromCharCode(97+c):c))
                    =>x.sort((a,b)=>v(b)<v(a)?1:-1)

Test snippet:

function log(label,val){
  document.body.append(label,String(val).replace(/,/g," - "),document.createElement("BR"));
}

let v = ["1.90.1", "1.9.1", "1.89", "1.090", "1.2", "1.0a", "1.0.1", "1.10", "1.0.0a"];
log('not sorted input :',v);

v.sort((a, b) => a.localeCompare(b,undefined,{numeric:true,sensitivity:'base'   }));
log(' locale Compare :', v); // 1.0a AFTER 1.0.1

const str = s => s.match(/(\d+)|[a-z]/g)
                  .map(c => c == ~~c ? String.fromCharCode(97 + c) : c);
const versionCompare = (a, b) => {
  a = str(a);
  b = str(b);
  return b < a ? 1 : a == b ? 0 : -1;
}

v.sort(versionCompare);
log('versionCompare:', v);

Note how 1.090 is sorted in both results.

My code will not solve the 001.012.001 notation mentioned in one answer, but the localeCompare gets that part of the challenge right.

You could combine the two methods:

  • sort with .localCompare OR versionCompare when there is a letter involved

Final JavaScript solution

const sortVersions = (
  x,
  v = s => s.match(/[a-z]|\d+/g).map(c => c==~~c ? String.fromCharCode(97 + c) : c)
) => x.sort((a, b) => (a + b).match(/[a-z]/) 
                             ? v(b) < v(a) ? 1 : -1 
                             : a.localeCompare(b, 0, {numeric: true}))

let v=["1.90.1","1.090","1.0a","1.0.1","1.0.0a","1.0.0b","1.0.0.1"];
console.log(sortVersions(v));
Herohtar
  • 5,347
  • 4
  • 31
  • 41
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
4

Check the function version_compare() from the php.js project. It's is similar to PHP's version_compare().

You can simply use it like this:

version_compare('2.0', '2.0.0.1', '<'); 
// returns true
powtac
  • 40,542
  • 28
  • 115
  • 170
4

Few lines of code and good if you don't want to allow letters or symbols. This works if you control the versioning scheme and it's not something a 3rd party provides.

// we presume all versions are of this format "1.4" or "1.10.2.3", without letters
// returns: 1 (bigger), 0 (same), -1 (smaller)
function versionCompare (v1, v2) {
  const v1Parts = v1.split('.')
  const v2Parts = v2.split('.')
  const length = Math.max(v1Parts.length, v2Parts.length)
  for (let i = 0; i < length; i++) {
    const value = (parseInt(v1Parts[i]) || 0) - (parseInt(v2Parts[i]) || 0)
    if (value < 0) return -1
    if (value > 0) return 1
  }
  return 0
}

console.log(versionCompare('1.2.0', '1.2.4') === -1)
console.log(versionCompare('1.2', '1.2.0') === 0)
console.log(versionCompare('1.2', '1') === 1)
console.log(versionCompare('1.2.10', '1.2.1') === 1)
console.log(versionCompare('1.2.134230', '1.2.2') === 1)
console.log(versionCompare('1.2.134230', '1.3.0.1.2.3.1') === -1)
dreamLo
  • 1,612
  • 12
  • 17
3

You can use a JavaScript localeCompare method:

a.localeCompare(b, undefined, { numeric: true })

Here is an example:

"1.1".localeCompare("2.1.1", undefined, { numeric: true }) => -1

"1.0.0".localeCompare("1.0", undefined, { numeric: true }) => 1

"1.0.0".localeCompare("1.0.0", undefined, { numeric: true }) => 0

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Anis
  • 33
  • 4
  • 1
    Here's a slightly better version (takes patches and `-alpha` suffixes into account) https://gist.github.com/iwill/a83038623ba4fef6abb9efca87ae9ccb – kano May 17 '22 at 08:03
2

The idea is to compare two versions and know which is the biggest. We delete "." and we compare each position of the vector with the other.

// Return 1  if a > b
// Return -1 if a < b
// Return 0  if a == b

function compareVersions(a_components, b_components) {

   if (a_components === b_components) {
       return 0;
   }

   var partsNumberA = a_components.split(".");
   var partsNumberB = b_components.split(".");

   for (var i = 0; i < partsNumberA.length; i++) {

      var valueA = parseInt(partsNumberA[i]);
      var valueB = parseInt(partsNumberB[i]);

      // A bigger than B
      if (valueA > valueB || isNaN(valueB)) {
         return 1;
      }

      // B bigger than A
      if (valueA < valueB) {
         return -1;
      }
   }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Marc
  • 37
  • 4
2
// Returns true if v1 is bigger than v2, and false if otherwise.
function isNewerThan(v1, v2) {
      v1=v1.split('.');
      v2=v2.split('.');
      for(var i = 0; i<Math.max(v1.length,v2.length); i++){
        if(v1[i] == undefined) return false; // If there is no digit, v2 is automatically bigger
        if(v2[i] == undefined) return true; // if there is no digit, v1 is automatically bigger
        if(v1[i] > v2[i]) return true;
        if(v1[i] < v2[i]) return false;
      }
      return false; // Returns false if they are equal
    }
Dyllan M
  • 301
  • 2
  • 15
  • 1
    Welcome to SO. This question has a lot of good answers already, please refrain from adding new answers unless you add something new. – ext Nov 26 '17 at 20:50
1

Check out this blog post. This function works for numeric version numbers.

function compVersions(strV1, strV2) {
  var nRes = 0
    , parts1 = strV1.split('.')
    , parts2 = strV2.split('.')
    , nLen = Math.max(parts1.length, parts2.length);

  for (var i = 0; i < nLen; i++) {
    var nP1 = (i < parts1.length) ? parseInt(parts1[i], 10) : 0
      , nP2 = (i < parts2.length) ? parseInt(parts2[i], 10) : 0;

    if (isNaN(nP1)) { nP1 = 0; }
    if (isNaN(nP2)) { nP2 = 0; }

    if (nP1 != nP2) {
      nRes = (nP1 > nP2) ? 1 : -1;
      break;
    }
  }

  return nRes;
};

compVersions('10', '10.0'); // 0
compVersions('10.1', '10.01.0'); // 0
compVersions('10.0.1', '10.0'); // 1
compVersions('10.0.1', '10.1'); // -1
David
  • 11
  • 2
1

If, for example, we want to check if the current jQuery version is less than 1.8, parseFloat($.ui.version) < 1.8 ) would give a wrong result if version is "1.10.1", since parseFloat("1.10.1") returns 1.1. A string compare would also go wrong, since "1.8" < "1.10" evaluates to false.

So we need a test like this

if(versionCompare($.ui.version, "1.8") < 0){
    alert("please update jQuery");
}

The following function handles this correctly:

/** Compare two dotted version strings (like '10.2.3').
 * @returns {Integer} 0: v1 == v2, -1: v1 < v2, 1: v1 > v2
 */
function versionCompare(v1, v2) {
    var v1parts = ("" + v1).split("."),
        v2parts = ("" + v2).split("."),
        minLength = Math.min(v1parts.length, v2parts.length),
        p1, p2, i;
    // Compare tuple pair-by-pair. 
    for(i = 0; i < minLength; i++) {
        // Convert to integer if possible, because "8" > "10".
        p1 = parseInt(v1parts[i], 10);
        p2 = parseInt(v2parts[i], 10);
        if (isNaN(p1)){ p1 = v1parts[i]; } 
        if (isNaN(p2)){ p2 = v2parts[i]; } 
        if (p1 == p2) {
            continue;
        }else if (p1 > p2) {
            return 1;
        }else if (p1 < p2) {
            return -1;
        }
        // one operand is NaN
        return NaN;
    }
    // The longer tuple is always considered 'greater'
    if (v1parts.length === v2parts.length) {
        return 0;
    }
    return (v1parts.length < v2parts.length) ? -1 : 1;
}

Here are some examples:

// compare dotted version strings
console.assert(versionCompare("1.8",      "1.8.1")    <   0);
console.assert(versionCompare("1.8.3",    "1.8.1")    >   0);
console.assert(versionCompare("1.8",      "1.10")     <   0);
console.assert(versionCompare("1.10.1",   "1.10.1")   === 0);
// Longer is considered 'greater'
console.assert(versionCompare("1.10.1.0", "1.10.1")   >   0);
console.assert(versionCompare("1.10.1",   "1.10.1.0") <   0);
// Strings pairs are accepted
console.assert(versionCompare("1.x",      "1.x")      === 0);
// Mixed int/string pairs return NaN
console.assert(isNaN(versionCompare("1.8", "1.x")));
//works with plain numbers
console.assert(versionCompare("4", 3)   >   0);

See here for a live sample and test suite: http://jsfiddle.net/mar10/8KjvP/

mar10
  • 14,320
  • 5
  • 39
  • 64
  • arghh, just noticed that ripper234 had posted a fiddle URL in on eof the comments a few months ago that is quite similar. Anyway, I keep my answer here... – mar10 Jan 26 '13 at 18:49
  • This one will also fail (as most of variants around) in these cases: versionCompare('1.09', '1.1') returns "1", the same way as versionCompare('1.702', '1.8'). – shaman.sir Sep 05 '13 at 00:33
  • The code evaluates "1.09" > "1.1" and "1.702" > "1.8", which I think is correct. If you don't agree: can you point to some resource that backs your opinion? – mar10 Sep 07 '13 at 09:35
  • It depends on your principles — as I know there's no strict rule or something. Regarding resources, wikipedia article for "Software versioning" in "Incrementing sequences" says that 1.81 may be a minor version of 1.8, so 1.8 should read as 1.80. Semantic versioning article http://semver.org/spec/v2.0.0.html also says that 1.9.0 -> 1.10.0 -> 1.11.0, so 1.9.0 is treated as 1.90.0 in comparison like this. So, following this logic, version 1.702 was before version 1.8, which is treated as 1.800. – shaman.sir Sep 30 '13 at 16:26
  • 1
    I see that some rules treat 1.8 < 1.81 < 1.9. But in semver you would use 1.8.1 instead of 1.81. Semver (as I understand it) is defined around the assumption that incrementing a part will always generate a 'later' version, so 1.8 < 1.8.1 < 1.9 < 1.10 < 1.81 < 1.90 < 1.100 . I don't see an indication that this is limted to two digits either. So I would say that my code is fully compliant with semver. – mar10 Oct 01 '13 at 07:14
  • Yes, your example do compliant with semver way of view, sorry me :), I was wrong in a statement that 1.9.0 is treated there as 1.90.0. – shaman.sir Oct 01 '13 at 20:31
1

This is a neat trick. If you are dealing with numeric values, between a specific range of values, you can assign a value to each level of the version object. For instance "largestValue" is set to 0xFF here, which creates a very "IP" sort of look to your versioning.

This also handles alpha-numeric versioning (i.e. 1.2a < 1.2b)

// The version compare function
function compareVersion(data0, data1, levels) {
    function getVersionHash(version) {
        var value = 0;
        version = version.split(".").map(function (a) {
            var n = parseInt(a);
            var letter = a.replace(n, "");
            if (letter) {
                return n + letter[0].charCodeAt() / 0xFF;
            } else {
                return n;
            }
        });
        for (var i = 0; i < version.length; ++i) {
            if (levels === i) break;
            value += version[i] / 0xFF * Math.pow(0xFF, levels - i + 1);
        }
        return value;
    };
    var v1 = getVersionHash(data0);
    var v2 = getVersionHash(data1);
    return v1 === v2 ? -1 : v1 > v2 ? 0 : 1;
};
// Returns 0 or 1, correlating to input A and input B
// Direct match returns -1
var version = compareVersion("1.254.253", "1.254.253a", 3);
Michael Deal
  • 2,948
  • 2
  • 18
  • 8
1

I made this based on Kons idea, and optimized it for Java version "1.7.0_45". It's just a function meant to convert a version string to a float. This is the function:

function parseVersionFloat(versionString) {
    var versionArray = ("" + versionString)
            .replace("_", ".")
            .replace(/[^0-9.]/g, "")
            .split("."),
        sum = 0;
    for (var i = 0; i < versionArray.length; ++i) {
        sum += Number(versionArray[i]) / Math.pow(10, i * 3);
    }
    console.log(versionString + " -> " + sum);
    return sum;
}

String "1.7.0_45" is converted to 1.0070000450000001 and this is good enough for a normal comparison. Error explained here: How to deal with floating point number precision in JavaScript?. If need more then 3 digits on any part you can change the divider Math.pow(10, i * 3);.

Output will look like this:

1.7.0_45         > 1.007000045
ver 1.7.build_45 > 1.007000045
1.234.567.890    > 1.23456789
Community
  • 1
  • 1
Adrian S
  • 11
  • 3
  • This is a great solution. It is also possible in a one-liner: `("" + versionString).replace("_", ".").replace(/[^0-9.]/g, "").split(".").reverse().reduce((accumulator, value) => accumulator/1000 + Number(value), 0)` – Jpsy Feb 12 '22 at 19:45
1

Here's a coffeescript implementation suitable for use with Array.sort inspired by other answers here:

# Returns > 0 if v1 > v2 and < 0 if v1 < v2 and 0 if v1 == v2
compareVersions = (v1, v2) ->
  v1Parts = v1.split('.')
  v2Parts = v2.split('.')
  minLength = Math.min(v1Parts.length, v2Parts.length)
  if minLength > 0
    for idx in [0..minLength - 1]
      diff = Number(v1Parts[idx]) - Number(v2Parts[idx])
      return diff unless diff is 0
  return v1Parts.length - v2Parts.length
LOAS
  • 7,161
  • 2
  • 28
  • 25
1

I wrote a node module for sorting versions, you can find it here: version-sort

Features:

  • no limit of sequences '1.0.1.5.53.54654.114.1.154.45' works
  • no limit of sequence length: '1.1546515465451654654654654138754431574364321353734' works
  • can sort objects by version (see README)
  • stages (like alpha, beta, rc1, rc2)

Do not hesitate to open an issue if you need an other feature.

1

This works for numeric versions of any length separated by a period. It returns true only if myVersion is >= minimumVersion, making the assumption that version 1 is less than 1.0, version 1.1 is less than 1.1.0 and so on. It should be fairly simple to add extra conditions such as accepting numbers (just convert to a string) and hexadecimal or making the delimiter dynamic (just add a delimiter parameter then replace the "." with the param)

function versionCompare(myVersion, minimumVersion) {

    var v1 = myVersion.split("."), v2 = minimumVersion.split("."), minLength;   

    minLength= Math.min(v1.length, v2.length);

    for(i=0; i<minLength; i++) {
        if(Number(v1[i]) > Number(v2[i])) {
            return true;
        }
        if(Number(v1[i]) < Number(v2[i])) {
            return false;
        }           
    }

    return (v1.length >= v2.length);
}

Here are some tests:

console.log(versionCompare("4.4.0","4.4.1"));
console.log(versionCompare("5.24","5.2"));
console.log(versionCompare("4.1","4.1.2"));
console.log(versionCompare("4.1.2","4.1"));
console.log(versionCompare("4.4.4.4","4.4.4.4.4"));
console.log(versionCompare("4.4.4.4.4.4","4.4.4.4.4"));
console.log(versionCompare("0","1"));
console.log(versionCompare("1","1"));
console.log(versionCompare("","1"));
console.log(versionCompare("10.0.1","10.1"));

Alternatively here is a recursive version

function versionCompare(myVersion, minimumVersion) {
  return recursiveCompare(myVersion.split("."),minimumVersion.split("."),Math.min(myVersion.length, minimumVersion.length),0);
}

function recursiveCompare(v1, v2,minLength, index) {
  if(Number(v1[index]) < Number(v2[index])) {
    return false;
  }
  if(Number(v1[i]) < Number(v2[i])) {
    return true;
    }
  if(index === minLength) {
    return (v1.length >= v2.length);
  }
  return recursiveCompare(v1,v2,minLength,index+1);
}
1

The replace() function only replaces the first occurence in the string. So, lets replace the . with ,. Afterwards delete all . and make the , to . again and parse it to float.

for(i=0; i<versions.length; i++) {
    v = versions[i].replace('.', ',');
    v = v.replace(/\./g, '');
    versions[i] = parseFloat(v.replace(',', '.'));
}

finally, sort it:

versions.sort();
Sascha Galley
  • 15,711
  • 5
  • 37
  • 51
1

Based on Idan's awesome answer, the following function semverCompare passed most cases of semantic versioning 2.0.0. See this gist for more.

function semverCompare(a, b) {
    if (a.startsWith(b + "-")) return -1
    if (b.startsWith(a + "-")) return  1
    return a.localeCompare(b, undefined, { numeric: true, sensitivity: "case", caseFirst: "upper" })
}

It returns:

  • -1: a < b
  • 0: a == b
  • 1: a > b
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Míng
  • 2,500
  • 6
  • 32
  • 48
0

I like the version from @mar10, though from my point of view, there is a chance of misusage (seems it is not the case if versions are compatible with Semantic Versioning document, but may be the case if some "build number" is used):

versionCompare( '1.09', '1.1');  // returns 1, which is wrong:  1.09 < 1.1
versionCompare('1.702', '1.8');  // returns 1, which is wrong: 1.702 < 1.8

The problem here is that sub-numbers of version number are, in some cases, written with trailing zeroes cut out (at least as I recently see it while using different software), which is similar to rational part of a number, so:

5.17.2054 > 5.17.2
5.17.2 == 5.17.20 == 5.17.200 == ... 
5.17.2054 > 5.17.20
5.17.2054 > 5.17.200
5.17.2054 > 5.17.2000
5.17.2054 > 5.17.20000
5.17.2054 < 5.17.20001
5.17.2054 < 5.17.3
5.17.2054 < 5.17.30

The first (or both first and second) version sub-number, however, is always treated as an integer value it actually equals to.

If you use this kind of versioning, you may change just a few lines in the example:

// replace this:
p1 = parseInt(v1parts[i], 10);
p2 = parseInt(v2parts[i], 10);
// with this:
p1 = i/* > 0 */ ? parseFloat('0.' + v1parts[i], 10) : parseInt(v1parts[i], 10);
p2 = i/* > 0 */ ? parseFloat('0.' + v2parts[i], 10) : parseInt(v2parts[i], 10);

So every sub-number except the first one will be compared as a float, so 09 and 1 will become 0.09 and 0.1 accordingly and compared properly this way. 2054 and 3 will become 0.2054 and 0.3.

The complete version then, is (credits to @mar10):

/** Compare two dotted version strings (like '10.2.3').
 * @returns {Integer} 0: v1 == v2, -1: v1 < v2, 1: v1 > v2
 */
function versionCompare(v1, v2) {
    var v1parts = ("" + v1).split("."),
        v2parts = ("" + v2).split("."),
        minLength = Math.min(v1parts.length, v2parts.length),
        p1, p2, i;
    // Compare tuple pair-by-pair. 
    for(i = 0; i < minLength; i++) {
        // Convert to integer if possible, because "8" > "10".
        p1 = i/* > 0 */ ? parseFloat('0.' + v1parts[i], 10) : parseInt(v1parts[i], 10);;
        p2 = i/* > 0 */ ? parseFloat('0.' + v2parts[i], 10) : parseInt(v2parts[i], 10);
        if (isNaN(p1)){ p1 = v1parts[i]; } 
        if (isNaN(p2)){ p2 = v2parts[i]; } 
        if (p1 == p2) {
            continue;
        }else if (p1 > p2) {
            return 1;
        }else if (p1 < p2) {
            return -1;
        }
        // one operand is NaN
        return NaN;
    }
    // The longer tuple is always considered 'greater'
    if (v1parts.length === v2parts.length) {
        return 0;
    }
    return (v1parts.length < v2parts.length) ? -1 : 1;
}

P.S. It is slower, but also possible to think on re-using the same comparing function operating the fact that the string is actually the array of characters:

 function cmp_ver(arr1, arr2) {
     // fill the tail of the array with smaller length with zeroes, to make both array have the same length
     while (min_arr.length < max_arr.length) {
         min_arr[min_arr.lentgh] = '0';
     }
     // compare every element in arr1 with corresponding element from arr2, 
     // but pass them into the same function, so string '2054' will act as
     // ['2','0','5','4'] and string '19', in this case, will become ['1', '9', '0', '0']
     for (i: 0 -> max_length) {
         var res = cmp_ver(arr1[i], arr2[i]);
         if (res !== 0) return res;
     }
 }
Community
  • 1
  • 1
shaman.sir
  • 3,198
  • 3
  • 28
  • 36
0

This isn't a quite solution for the question was asked, but it's very similiar.

This sort function is for semantic versions, it handles resolved version, so it doesn't work with wildcards like x or *.

It works with versions where this regular expression matches: /\d+\.\d+\.\d+.*$/. It's very similar to this answer except that it works also with versions like 1.2.3-dev. In comparison with the other answer: I removed some checks that I don't need, but my solution can be combined with the other one.

semVerSort = function(v1, v2) {
  var v1Array = v1.split('.');
  var v2Array = v2.split('.');
  for (var i=0; i<v1Array.length; ++i) {
    var a = v1Array[i];
    var b = v2Array[i];
    var aInt = parseInt(a, 10);
    var bInt = parseInt(b, 10);
    if (aInt === bInt) {
      var aLex = a.substr((""+aInt).length);
      var bLex = b.substr((""+bInt).length);
      if (aLex === '' && bLex !== '') return 1;
      if (aLex !== '' && bLex === '') return -1;
      if (aLex !== '' && bLex !== '') return aLex > bLex ? 1 : -1;
      continue;
    } else if (aInt > bInt) {
      return 1;
    } else {
      return -1;
    }
  }
  return 0;
}

The merged solution is that:

function versionCompare(v1, v2, options) {
    var zeroExtend = options && options.zeroExtend,
        v1parts = v1.split('.'),
        v2parts = v2.split('.');

    if (zeroExtend) {
        while (v1parts.length < v2parts.length) v1parts.push("0");
        while (v2parts.length < v1parts.length) v2parts.push("0");
    }

    for (var i = 0; i < v1parts.length; ++i) {
        if (v2parts.length == i) {
            return 1;
        }
        var v1Int = parseInt(v1parts[i], 10);
        var v2Int = parseInt(v2parts[i], 10);
        if (v1Int == v2Int) {
            var v1Lex = v1parts[i].substr((""+v1Int).length);
            var v2Lex = v2parts[i].substr((""+v2Int).length);
            if (v1Lex === '' && v2Lex !== '') return 1;
            if (v1Lex !== '' && v2Lex === '') return -1;
            if (v1Lex !== '' && v2Lex !== '') return v1Lex > v2Lex ? 1 : -1;
            continue;
        }
        else if (v1Int > v2Int) {
            return 1;
        }
        else {
            return -1;
        }
    }

    if (v1parts.length != v2parts.length) {
        return -1;
    }

    return 0;
}
Community
  • 1
  • 1
timaschew
  • 16,254
  • 6
  • 61
  • 78
0

I had the same problem of version comparison, but with versions possibly containing anything (ie: separators that were not dots, extensions like rc1, rc2...).

I used this, which basically split the version strings into numbers and non-numbers, and tries to compare accordingly to the type.

function versionCompare(a,b) {
  av = a.match(/([0-9]+|[^0-9]+)/g)
  bv = b.match(/([0-9]+|[^0-9]+)/g)
  for (;;) {
    ia = av.shift();
    ib = bv.shift();
    if ( (typeof ia === 'undefined') && (typeof ib === 'undefined') ) { return 0; }
    if (typeof ia === 'undefined') { ia = '' }
    if (typeof ib === 'undefined') { ib = '' }

    ian = parseInt(ia);
    ibn = parseInt(ib);
    if ( isNaN(ian) || isNaN(ibn) ) {
      // non-numeric comparison
      if (ia < ib) { return -1;}
      if (ia > ib) { return 1;}
    } else {
      if (ian < ibn) { return -1;}
      if (ian > ibn) { return 1;}
    }
  }
}

There are some assumptions here for some cases, for example : "1.01" === "1.1", or "1.8" < "1.71". It fails to manage "1.0.0-rc.1" < "1.0.0", as specified by Semantic versionning 2.0.0

Uriel
  • 182
  • 3
  • 10
0

Preprocessing the versions prior to the sort means parseInt isn't called multiple times unnecessarily. Using Array#map similar to Michael Deal's suggestion, here's a sort I use to find the newest version of a standard 3 part semver:

var semvers = ["0.1.0", "1.0.0", "1.1.0", "1.0.5"];

var versions = semvers.map(function(semver) {
    return semver.split(".").map(function(part) {
        return parseInt(part);
    });
});

versions.sort(function(a, b) {
    if (a[0] < b[0]) return 1;
    else if (a[0] > b[0]) return -1;
    else if (a[1] < b[1]) return 1;
    else if (a[1] > b[1]) return -1;
    else if (a[2] < b[2]) return 1;
    else if (a[2] > b[2]) return -1;
    return 0;
});

var newest = versions[0].join(".");
console.log(newest); // "1.1.0"
theGecko
  • 1,033
  • 12
  • 22
0

One more implementation I believe worth sharing as it's short, simple and yet powerful. Please note that it uses digit comparison only. Generally it checks if version2 is later than version1 and returns true if it's the case. Suppose you have version1: 1.1.1 and version2: 1.1.2. It goes through each part of the two versions adding their parts as follows: (1 + 0.1) then (1.1 + 0.01) for version1 and (1 + 0.1) then (1.1 + 0.02) for version2.

function compareVersions(version1, version2) {

    version1 = version1.split('.');
    version2 = version2.split('.');

    var maxSubVersionLength = String(Math.max.apply(undefined, version1.concat(version2))).length;

    var reduce = function(prev, current, index) {

        return parseFloat(prev) + parseFloat('0.' + Array(index + (maxSubVersionLength - String(current).length)).join('0') + current);
    };

    return version1.reduce(reduce) < version2.reduce(reduce);
}

If you want to find the latest version out of list of versions then this might be useful:

function findLatestVersion(versions) {

    if (!(versions instanceof Array)) {
        versions = Array.prototype.slice.apply(arguments, [0]);
    }

    versions = versions.map(function(version) { return version.split('.'); });

    var maxSubVersionLength = String(Math.max.apply(undefined, Array.prototype.concat.apply([], versions))).length;

    var reduce = function(prev, current, index) {

        return parseFloat(prev) + parseFloat('0.' + Array(index + (maxSubVersionLength - String(current).length)).join('0') + current);
    };

    var sums = [];

    for (var i = 0; i < versions.length; i++) {
        sums.push(parseFloat(versions[i].reduce(reduce)));
    }

    return versions[sums.indexOf(Math.max.apply(undefined, sums))].join('.');
}

console.log(findLatestVersion('0.1000000.1', '2.0.0.10', '1.6.10', '1.4.3', '2', '2.0.0.1')); // 2.0.0.10
console.log(findLatestVersion(['0.1000000.1', '2.0.0.10', '1.6.10', '1.4.3', '2', '2.0.0.1'])); // 2.0.0.10
0

Here's another way to do it with a recursive algorithm.

This code just uses Array.shift and recursive, which means that it can run in Internet Explorer 6+. If you have any doubts, you can visit my GitHub page.

(function(root, factory) {
  if (typeof exports === 'object') {
    return module.exports = factory();
  } else if (typeof define === 'function' && define.amd) {
    return define(factory);
  } else {
    return root.compareVer = factory();
  }
})(this, function() {
  'use strict';
  var _compareVer;
  _compareVer = function(newVer, oldVer) {
    var VER_RE, compareNum, isTrue, maxLen, newArr, newLen, newMatch, oldArr, oldLen, oldMatch, zerofill;
    VER_RE = /(\d+\.){1,9}\d+/;
    if (arguments.length !== 2) {
      return -100;
    }
    if (typeof newVer !== 'string') {
      return -2;
    }
    if (typeof oldVer !== 'string') {
      return -3;
    }
    newMatch = newVer.match(VER_RE);
    if (!newMatch || newMatch[0] !== newVer) {
      return -4;
    }
    oldMatch = oldVer.match(VER_RE);
    if (!oldMatch || oldMatch[0] !== oldVer) {
      return -5;
    }
    newVer = newVer.replace(/^0/, '');
    oldVer = oldVer.replace(/^0/, '');
    if (newVer === oldVer) {
      return 0;
    } else {
      newArr = newVer.split('.');
      oldArr = oldVer.split('.');
      newLen = newArr.length;
      oldLen = oldArr.length;
      maxLen = Math.max(newLen, oldLen);
      zerofill = function() {
        newArr.length < maxLen && newArr.push('0');
        oldArr.length < maxLen && oldArr.push('0');
        return newArr.length !== oldArr.length && zerofill();
      };
      newLen !== oldLen && zerofill();
      if (newArr.toString() === oldArr.toString()) {
        if (newLen > oldLen) {
          return 1;
        } else {
          return -1;
        }
      } else {
        isTrue = -1;
        compareNum = function() {
          var _new, _old;
          _new = ~~newArr.shift();
          _old = ~~oldArr.shift();
          _new > _old && (isTrue = 1);
          return _new === _old && newArr.length > 0 && compareNum();
        };
        compareNum();
        return isTrue;
      }
    }
  };
  return _compareVer;
});

Well, I hope this code helps someone.

Here's the testing.

console.log(compareVer("0.0.2","0.0.1"));//1
console.log(compareVer("0.0.10","0.0.1")); //1
console.log(compareVer("0.0.10","0.0.2")); //1
console.log(compareVer("0.9.0","0.9")); //1
console.log(compareVer("0.10.0","0.9.0")); //1
console.log(compareVer("1.7", "1.07")); //1
console.log(compareVer("1.0.07", "1.0.007")); //1

console.log(compareVer("0.3","0.3")); //0
console.log(compareVer("0.0.3","0.0.3")); //0
console.log(compareVer("0.0.3.0","0.0.3.0")); //0
console.log(compareVer("00.3","0.3")); //0
console.log(compareVer("00.3","00.3")); //0
console.log(compareVer("01.0.3","1.0.3")); //0
console.log(compareVer("1.0.3","01.0.3")); //0

console.log(compareVer("0.2.0","1.0.0")); //-1
console.log(compareVer('0.0.2.2.0',"0.0.2.3")); //-1
console.log(compareVer('0.0.2.0',"0.0.2")); //-1
console.log(compareVer('0.0.2',"0.0.2.0")); //-1
console.log(compareVer("1.07", "1.7")); //-1
console.log(compareVer("1.0.007", "1.0.07")); //-1

console.log(compareVer()); //-100
console.log(compareVer("0.0.2")); //-100
console.log(compareVer("0.0.2","0.0.2","0.0.2")); //-100
console.log(compareVer(1212,"0.0.2")); //-2
console.log(compareVer("0.0.2",1212)); //-3
console.log(compareVer('1.abc.2',"1.0.2")); //-4
console.log(compareVer('1.0.2',"1.abc.2")); //-5
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
J.G
  • 1
  • 1
0

I found the simplest way to compare them, but I am not sure if it's what you want.

When I run the below code in the console, it makes sense, and using the sort() method, I could get the sorted array of the versions string. It's based on the alphabetical order.

"1.0" < "1.0.1" //true
var arr = ["1.0.1", "1.0", "3.2.0", "1.3"]
arr.sort();     //["1.0", "1.0.1", "1.3", "3.2.0"]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
inoutwhy
  • 624
  • 1
  • 6
  • 12
0

Here's a version that orders version strings without allocating any sub-strings or arrays. Being that it allocates fewer objects, there's less work for the GC to do.

There is one pair of allocations (to allow reuse of the getVersionPart method), but you could expand that to avoid allocations altogether if you were very performance sensitive.

const compareVersionStrings : (a: string, b: string) => number = (a, b) =>
{
    var ia = {s:a,i:0}, ib = {s:b,i:0};
    while (true)
    {
        var na = getVersionPart(ia), nb = getVersionPart(ib);

        if (na === null && nb === null)
            return 0;
        if (na === null)
            return -1;
        if (nb === null)
            return 1;
        if (na > nb)
            return 1;
        if (na < nb)
            return -1;
    }
};

const zeroCharCode = '0'.charCodeAt(0);

const getVersionPart = (a : {s:string, i:number}) =>
{
    if (a.i >= a.s.length)
        return null;

    var n = 0;
    while (a.i < a.s.length)
    {
        if (a.s[a.i] === '.')
        {
            a.i++;
            break;
        }

        n *= 10;
        n += a.s.charCodeAt(a.i) - zeroCharCode;
        a.i++;
    }
    return n;
}
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
0

this is my solution. it has accepted on leetcode. I met the problem in my interview today. But i did not solve it at that time. I thought about it again. Adding zeros to make the two arrays' length equal. Then comparison.

var compareVersion = function(version1, version2) {
    let arr1 = version1.split('.').map(Number);
    let arr2 = version2.split('.').map(Number);
    let diff = 0;
    if (arr1.length > arr2.length){
        diff = arr1.length - arr2.length;
        while (diff > 0){
            arr2.push(0);
            diff--;
        } 
    }
    else if (arr1.length < arr2.length){
        diff = arr2.length - arr1.length;
        while (diff > 0){
            arr1.push(0);
            diff--;
        }
    }
   
    let i = 0;
    while (i < arr1.length){
        if (arr1[i] > arr2[i]){
           return 1;
       } else if (arr1[i] < arr2[i]){
           return -1;
       }
        i++;
    }
    return 0;
    
};
metrical1
  • 1
  • 1
0

The function will return -1 if the version are equal, 0 if the first version is the latest and 1 to indicate the second version is the latest.

let v1 = '12.0.1.0'
let v2 = '12.0.1'

let temp1 = v1.split('.');
let temp2 = v2.split('.');

console.log(compareVersion(temp1, temp2))


function compareVersion(version1, version2) {
    let flag = false;
    var compareResult;
    let maxLength = Math.max(version1.length, version2.length); 
    let minLength = Math.min(version1.length, version2.length);

    for (let i = 0; i < maxLength; ++i ) {
        let result = version1[i] - version2[i];
        if (result > 0) {
            flag = true;
            compareResult = 0;
            break;
        }
        else if (result < 0) {
            flag = true;
            compareResult = 1;
            break;
        }

        if (i === minLength) {
            if (version1.length > version1.length) {
                compareResult = version1[version1.length-1] > 0 ? '0' : '-1'
            }  else  {
                compareResult = version1[version2.length-1] > 0 ? '1' : '-1'
            }
            break;
        }
    }
    if (flag === false) {
        compareResult = -1;
    }
    return compareResult;
}
leox
  • 1,315
  • 17
  • 26
0

I didn't love any of the solutions so I rewrote it for my coding preferences. Notice the last four checks come out slightly different than the accepted answer. Works for me.

function v_check(version_a, version_b) {
    // compares version_a as it relates to version_b
    // a = b => "same"
    // a > b => "larger"
    // a < b => "smaller"
    // NaN   => "invalid"

    const arr_a = version_a.split('.');
    const arr_b = version_b.split('.');

    let result = "same"; // initialize to same // loop tries to disprove

    // loop through a and check each number against the same position in b
    for (let i = 0; i < arr_a.length; i++) {
        let a = arr_a[i];
        let b = arr_b[i];

        // same up to this point so if a is not there, a is smaller
        if (typeof a === 'undefined') {
            result = "smaller";
            break;

        // same up to this point so if b is not there, a is larger
        } else if (typeof b === 'undefined') {
            result = "larger";
            break;

        // otherwise, compare the two numbers
        } else {

            // non-positive numbers are invalid
            if (a >= 0 && b >= 0) {

                if (a < b) {
                    result = "smaller";
                    break;
                }
                else if (a > b) {
                    result = "larger";
                    break;
                }

            } else {
                result = "invalid";
                break;
            }
        }
    }

    // account for the case where the loop ended but there was still a position in b to evaluate
    if (result == "same" && arr_b.length > arr_a.length) result = "smaller";

    return result;
}


console.log(v_check("1.7.1", "1.7.10"));  // smaller
console.log(v_check("1.6.1", "1.7.10"));  // smaller
console.log(v_check("1.6.20", "1.7.10")); // smaller
console.log(v_check("1.7.1", "1.7.10"));  // smaller
console.log(v_check("1.7", "1.7.0"));     // smaller
console.log(v_check("1.7", "1.8.0"));     // smaller

console.log(v_check("1.7.10", "1.7.1"));  // larger
console.log(v_check("1.7.10", "1.6.1"));  // larger
console.log(v_check("1.7.10", "1.6.20")); // larger
console.log(v_check("1.7.0", "1.7"));     // larger
console.log(v_check("1.8.0", "1.7"));     // larger

console.log(v_check("1.7.10", "1.7.10")); // same
console.log(v_check("1.7", "1.7"));       // same

console.log(v_check("1.7", "1..7")); // larger
console.log(v_check("1.7", "Bad"));  // invalid
console.log(v_check("1..7", "1.7")); // smaller
console.log(v_check("Bad", "1.7"));  // invalid
0

I had to compare the version of my extension, but I did not find a working solution here. Almost all the proposed options break when comparing 1.89 > 1.9 or 1.24.1 == 1.240.1

Here I started from the fact that zeros go down only in the last record 1.1 == 1.10 and 1.10.1 > 1.1.1

compare_version = (new_version, old_version) => {
    new_version = new_version.split('.');
    old_version = old_version.split('.');
    for(let i = 0, m = Math.max(new_version.length, old_version.length); i<m; i++){
        //compare text
        let new_part = (i<m-1?'':'.') + (new_version[i] || 0)
        ,   old_part = (i<m-1?'':'.') + (old_version[i] || 0);
        //compare number (I don’t know what better)
      //let new_part = +((i<m-1?0:'.') + new_version[i]) || 0
      //,   old_part = +((i<m-1?0:'.') + old_version[i]) || 0;
        //console.log(new_part, old_part);
        if(old_part > new_part)return 0;    //change to -1 for sort the array
        if(new_part > old_part)return 1
    }
    return 0
};
compare_version('1.0.240.1','1.0.240.1');   //0
compare_version('1.0.24.1','1.0.240.1');    //0
compare_version('1.0.240.89','1.0.240.9');  //0
compare_version('1.0.24.1','1.0.24');       //1

I'm not a big specialist, but I built simple code to compare 2 versions, change the first return to -1 to sort the array of versions

['1.0.240', '1.0.24', '1.0.240.9', '1.0.240.89'].sort(compare_version)
//results ["1.0.24", "1.0.240", "1.0.240.89", "1.0.240.9"]

and short version for compare full string

c=e=>e.split('.').map((e,i,a)=>e[i<a.length-1?'padStart':'padEnd'](5)).join('');

//results "    1    0  2409    " > "    1    0  24089   "

c('1.0.240.9')>c('1.0.240.89')              //true

If you have comments or improvements, do not hesitate to suggest.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
killovv
  • 51
  • 3
0

I have created this solution, and I hope you find it useful:

https://runkit.com/ecancino/5f3c6c59593d23001485992e


const quantify = max => (n, i) => n * (+max.slice(0, max.length - i))

const add = (a, b) => a + b

const calc = s => s.
    split('.').
    map(quantify('1000000')).
    reduce(add, 0)

const sortVersions = unsortedVersions => unsortedVersions
    .map(version => ({ version, order: calc(version) }))
    .sort((a, b) => a.order - b.order)
    .reverse()
    .map(o => o.version)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

Here is my solution works for any level of depth for either version.

Auto deal with number+dot issue. If not so function exists and console log would give undefined instead of truthy false or true.

Auto deal with trailing zero issue.

Auto relay exists where ever possible.

Auto backward compatible on old browsers.

function checkVersion (vv,vvv){
if(!(/^[0-9.]*$/.test(vv) && /^[0-9.]*$/.test(vvv))) return;
va = vv.toString().split('.');
vb = vvv.toString().split('.');
length = Math.max(va.length, vb.length);
for (i = 0; i < length; i++) {

if ((va[i]|| 0) < (vb[i]|| 0)  ) {return false; } 
  }
  return true;}
  
  console.log(checkVersion('20.0.0.1' , '20.0.0.2'));
  console.log(checkVersion(20.0 , '20.0.0.2'));
  console.log(checkVersion('20.0.0.0.0' , 20));
  console.log(checkVersion('20.0.0.0.1' , 20));
  console.log(checkVersion('20.0.0-0.1' , 20));
Syed
  • 696
  • 1
  • 5
  • 11
0

I would like to advertise for the lightweight library I made to solve this problem: semantic-version

You can use it both object-oriented (OO) and function-oriented. It is available as npm-package and ready-to-use bundle files.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bill
  • 143
  • 1
  • 10
0

const compareAppVersions = (firstVersion, secondVersion) => {
  const firstVersionArray = firstVersion.split(".").map(Number);
  const secondVersionArray = secondVersion.split(".").map(Number);
  const loopLength = Math.max(
    firstVersionArray.length,
    secondVersionArray.length
  );
  for (let i = 0; i < loopLength; ++i) {
    const a = firstVersionArray[i] || 0;
    const b = secondVersionArray[i] || 0;
    if (a !== b) {
      return a > b ? 1 : -1;
    }
  }
  return 0;
};
Eduard K.
  • 1
  • 1
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 01 '21 at 14:17
0
function compare(versionA: string | undefined, versionB: string | undefined, operator: string = '>') {
    if (versionA === undefined || versionB === undefined) {
        return false
    }
    const listA = versionA.split('.')
    const listB = versionB.split('.')
    let a = []
    let b = []
    for (let i = 0; i < listA.length; i++) {
        a.push(parseInt(listA[i].replace(/\D/g, ''), 10))
        b.push(parseInt(listB[i].replace(/\D/g, ''), 10))
    }

    for (let i = 0; i < listA.length; i++) {
        switch (operator) {
            case '>':
            case '>=':
                if (a[i] === b[i]) {
                    continue
                }
                if (a[i] > b[i]) {
                    return true
                }
                if (a[i] < b[i]) {
                    return false
                }
                break
            case '<':
            case '<=':
                if (a[i] === b[i]) {
                    continue
                }
                if (a[i] > b[i]) {
                    return false
                }
                if (a[i] < b[i]) {
                    return true
                }
                break
            case '=':
               if (a[i] > b[i]) {
                   return false
               }
               if (a[i] < b[i]) {
                   return false
               }
               break
        }
    }
    switch (operator) {
        case '>':
            return false
        case '<':
            return false
        case '=':
        case '>=':
        case '<=':
            return true
    }
}
Hakan Saglam
  • 101
  • 1
  • 3
0

Convert it to a number, then compare. Let's say you use no more than 3 digits per major/minor/patch and no labels (like this 1.12.042)

const versionNumber = +versionString
  .split('.')
  .map(v => '000' + v)
  .map(v => v.slice(-3))
  .join('');
0

Two versions comparing

const val = '1.2.3 5.4.3';
const arr = val.split(' ');
let obj = {};
for(let i = 0; i<2; i++) {
    const splitArr = arr[i].split('.')
    const reduced = splitArr.reduce((pre, 
    curr)=>parseInt(pre)+parseInt(curr));
    obj[i] = reduced;
}
if(obj[0]>obj[1]) {
    console.log(arr[0]);
} else {
    console.log(arr[1]);
}
Faiz
  • 1
  • 1
  • 1
  • An explanation would be in order. E.g., what is the idea/gist? From [the Help Center](https://stackoverflow.com/help/promotion): *"...always explain why the solution you're presenting is appropriate and how it works"*. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/73500211/edit), not here in comments (***without*** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Aug 26 '22 at 18:45
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 31 '22 at 05:41
0

Compare function with different conditions:

const compareVer = (ver1, middle, ver2) => {
  const res = new Intl.Collator("en").compare(ver1, ver2)
  let comp

  switch (middle) {
    case "=":
      comp = 0 === res
      break

    case ">":
      comp = 1 === res
      break

    case ">=":
      comp = 1 === res || 0 === res
      break

    case "<":
      comp = -1 === res
      break

    case "<=":
      comp = -1 === res || 0 === res
      break
  }

  return comp
}

console.log(compareVer("1.0.2", "=", "1.0.2")) // true
console.log(compareVer("1.0.3", ">", "1.0.2")) // true
console.log(compareVer("1.0.1", ">=", "1.0.2")) // false
console.log(compareVer("1.0.3", ">=", "1.0.2")) // true
console.log(compareVer("1.0.1", "<", "1.0.2")) // true
console.log(compareVer("1.0.1", "<=", "1.0.2")) // true
wpuzman
  • 340
  • 1
  • 5
  • 14
0

You can compare arrays if they contain numbers. Converted to an array without dots and compared them.

function VersionCompare( version1, version2){
    var regExStrip0 = /(\.0+)+$/;
    var segmentsA = version1.replace(regExStrip0, '').split('.');
    var segmentsB = version2.replace(regExStrip0, '').split('.');

    if(segmentsA > segmentsB){
        return 1
    }else if (segmentsA< segmentsB){
        return -1
    }else{
        return 0
    }
    
}
vasif mammadov
  • 105
  • 1
  • 3
  • Unfortunately, it's not this simple. For example, `['2.3', '1.2', '1.10', '2.11'] .sort (VersionCompare)` yields `["1.10", "1.2", "2.11", "2.3"]`, when it should be `["1.2", "1.10", "2.3", "2.11"]`. – Scott Sauyet Jan 25 '23 at 13:17
0

Here's a clean modular approach with a functional typescript implementation -

semver.ts

import * as Arr from "./arr.js"
import * as Ord from "./ord.js"

export type t_semver = [number, number, number]

export function fromString(s: string): t_semver {
  const [_, major, minor, patch] =
    s.match(/^(\d+)\.(\d+)\.(\d+)$/) ?? []
  if (major == null || minor == null || patch == null)
    throw Error(`invalid version number: ${s}`)
  return [Number(major), Number(minor), Number(patch)]
}

export compare(l: t_semver, r: t_semver): t_ord {
  return Arr.zip(l,r)
    .map(pair => Ord.compare(...pair))
    .reduce(Ord.concat, 0)
} 

ord.ts

export type t_ord = -1 | 0 | 1

export type t_comparable = string | number

export function compare(l: t_comparable, r: t_comparable): t_ord {
  return l < r ? -1 : l > r ? 1 : 0
}

export function concat(l: t_ord, r: t_ord): t_ord {
  return l === 0 ? r : l
}

arr.ts

export function zip<a,b>(l: Array<a>, r: Array<b>): Array<[a,b]> {
  const z = Array(Math.max(l.length, r.length))
  for (let i = 0; i<z.length; i++)
    z[i] = [l[i], r[i]]
  return z
}

usage example

import * as Semver from "./semver.js"

Semver.compare(
  Semver.fromString("1.22.3"),
  Semver.fromString("1.10.33"),
)                                 // => 1

Semver.compare(
  Semver.fromString("1.10.33"),
  Semver.fromString("1.22.3"),
)                                 // => -1

Semver.compare(
  Semver.fromString("1.2.3"),
  Semver.fromString("1.2.3"),
)                                 // => 0
Mulan
  • 129,518
  • 31
  • 228
  • 259
-1
function versionCompare(version1, version2){
                var a = version1.split('.');
                var b = version2.split('.');
                for (var i = 0; i < a.length; ++i) {
                    a[i] = Number(a[i]);
                }
                for (var i = 0; i < b.length; ++i) {
                    b[i] = Number(b[i]);
                }
                var length=a.length;

                for(j=0; j<length; j++){
                    if(typeof b[j]=='undefined')b[j]=0;
                    if (a[j] > b[j]) return true;
                    else if(a[j] < b[j])return false;
                    if(j==length-1 && a[j] >= b[j])return true;
                }             

                return false;
            },
Sakis
  • 821
  • 7
  • 12
-1

couldnt you convert them into numbers and then sort after size? Append 0's to the ones to the numbers that are < 4 in length

played around in console:

$(["1.0.0.0", "1.0.1.0", "2.0.0.0", "2.0.0.1", "2.0.1", "3.0"]).each(function(i,e) {
    var n =   e.replace(/\./g,"");
    while(n.length < 4) n+="0" ; 
    num.push(  +n  )
});

bigger the version, the bigger number. Edit: probably needs adjusting to account for bigger version series

davethegr8
  • 11,323
  • 5
  • 36
  • 61
Contra
  • 2,754
  • 4
  • 20
  • 18
  • That was just an example, as he's got to do some things himself :P Instead of 4, get the amount of numbers the biggest version has, then fill the ones lower than that with 0's – Contra Jul 26 '11 at 16:16
-1

Here's a fun way to do it OO:

    function versionString(str) {
    var parts = str.split('.');
    this.product = parts.length > 0 ? parts[0] * 1 : 0;
    this.major = parts.length > 1 ? parts[1] * 1 : 0;
    this.minor = parts.length > 2 ? parts[2] * 1 : 0;
    this.build = parts.length > 3 ? parts[3] * 1 : 0;

    this.compareTo = function(vStr){
        vStr = this._isVersionString(vStr) ? vStr : new versionString(vStr);
        return this.compare(this, vStr);
    };

    this.toString = function(){
        return this.product + "." + this.major + "." + this.minor + "." + this.build;
    }

    this.compare = function (str1, str2) {
        var vs1 = this._isVersionString(str1) ? str1 : new versionString(str1);
        var vs2 = this._isVersionString(str2) ? str2 : new versionString(str2);

        if (this._compareNumbers(vs1.product, vs2.product) == 0) {
            if (this._compareNumbers(vs1.major, vs2.major) == 0) {
                if (this._compareNumbers(vs1.minor, vs2.minor) == 0) {
                    return this._compareNumbers(vs1.build, vs2.build);
                } else {
                    return this._compareNumbers(vs1.minor, vs2.minor);
                }
            } else {
                return this._compareNumbers(vs1.major, vs2.major);
            }
        } else {
            return this._compareNumbers(vs1.product, vs2.product);
        }
    };

    this._isVersionString = function (str) {
        return str !== undefined && str.build !== undefined;
    };

    this._compareNumbers = function (n1, n2) {
        if (n1 > n2) {
            return 1;
        } else if (n1 < n2) {
            return -1;
        } else {
            return 0;
        }
    };
}

And some tests:

var v1 = new versionString("1.0");
var v2 = new versionString("1.0.1");
var v3 = new versionString("2.0");
var v4 = new versionString("2.0.0.1");
var v5 = new versionString("2.0.1");


alert(v1.compareTo("1.4.2"));
alert(v3.compareTo(v1));
alert(v5.compareTo(v4));
alert(v4.compareTo(v5));
alert(v5.compareTo(v5));
Brian
  • 2,772
  • 15
  • 12
-1

It really depends on the logic behind your versioning system. What does each number represent, and how it is used.

Is each subversion is a numeration for designating development stage? 0 for alpha 1 for beta 2 for release candidate 3 for (final) release

Is it a build version? Are you applying incremental updates?

Once you know how the versioning system works, creating the algorithm becomes easy.

If you don't allow numbers greater than 9 in each subversion, eliminating all the decimals but the first will allow you to do a straight comparison.

If you do allow numbers greater than 9 in any of subversions, there are several ways to compare them. The most obvious is to split the string by the decimals and compare each column.

But without knowing how the versioning system works, implementing a process like the ones above can get harry when version 1.0.2a is released.

Utilitron
  • 402
  • 3
  • 10
-2

You can loop through every period-delimited character and convert it to an int:

var parts = versionString.split('.');

for (var i = 0; i < parts.length; i++) {
  var value = parseInt(parts[i]);
  // do stuffs here.. perhaps build a numeric version variable?
}
Timo Tijhof
  • 10,032
  • 6
  • 34
  • 48
Kon
  • 27,113
  • 11
  • 60
  • 86
  • I like how simple this is and used it to make a semverToFloat method that returns a float representation of a semver string so it can be used for numeric comparison. `semverToFloat('1.11.342.1') === 1.113421; // true` http://jsfiddle.net/dsww1j77/1/ – JP DeVries Jul 15 '15 at 20:26
  • 1
    This wont work because: `semverToFloat('1.11.342.1') === 1.113421;` and `semverToFloat('1.11.34.21') === 1.113421;` – LeJared Feb 15 '16 at 13:59