How to Compare Version Numbers in JavaScript

on in Methods, Events and Scopes
Last modified on

The basic idea to make this comparison would be to get arrays of parts from the version numbers 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. The parts in each pair are compared numerically, even if we have version strings that are not made up of just digits (e.g. 3.0a or 3.1.3-beta). In this case, the script will still return a value, in this case 0. The script cannot make the difference between 1.0-alpha, 1.0-beta and 1.0-prerelease.
  2. If one version string has more parts than the other, the script will normalize (basically adding a 0 at the end of the shorter version) the versions (1.0 should be considered less than 1.0.1) and then compare them.

The script below compares two version strings. This only considers numeric components to a version – all non-digit components (besides the delimiter – period) will be ignored. Trailing zero components/pieces will be ignored (i.e. 3.1.0 is equivalent to 3.1, but 3.10 is greater than 3.1).

var Versioning = {};

Versioning.DOT_PATTERN = new RegExp("\\.");
Versioning.NON_DIGIT_PATTERN = new RegExp("\\D");

Versioning.compareMajor = function (version1, version2) {
    'use strict';

    return Versioning.compareLevels(version1, version2, 0);
};

Versioning.compareMinor = function (version1, version2) {
    'use strict';

    return Versioning.compareLevels(version1, version2, 1);
};

Versioning.compareRevision = function (version1, version2) {
    'use strict';

    return Versioning.compareLevels(version1, version2, 2);
};

/**
 * Compares two version strings. This only considers numeric components to
 * a version - all non-digit components (besides the delimiter - period)
 * will be ignored. Trailing zero components/pieces will be ignored -
 * i.e. 3.1.0 is equivalent to 3.1, but 3.10 is greater than 3.1
 *
 * @param {String} version1 First version string to compare
 * @param {String} version2 Second version string to compare
 * @param {Integer} index 0-index based number to represent the level
 *
 * @return negative if version1 < version2,
 * zero if version1 == version2,
 * positive if version1 > version2
 */
Versioning.compareLevels = function (version1, version2, index) {
    'use strict';

    var stringLength = index + 1,
        v1 = Versioning.normalize(version1),
        v2 = Versioning.normalize(version2);

    if (v1.length > stringLength) {
        v1.length = stringLength;
    }
    if (v2.length > stringLength) {
        v2.length = stringLength;
    }

    return Versioning.cmp(v1, v2);
};

/**
 * Compares two version strings. This only considers numeric components to
 * a version - all non-digit components (besides the delimiter - period) will
 * be ignored. Trailing zero components/pieces will be ignored -
 * i.e. 3.1.0 is equivalent to 3.1, but 3.10 is greater than 3.1
 *
 * @param {String} version1 First version string to compare
 * @param {String} version2 Second version string to compare
 *
 * @return negative if version1 < version2,
 * zero if version1 == version2,
 * positive if version1 > version2
 */
Versioning.compare = function (version1, version2) {
    'use strict';

    return Versioning.cmp(Versioning.normalize(version1), Versioning.normalize(version2));
};

/**
 * Normalizes a version string
 *
 * @param {String} version
 */
Versioning.normalize = function (version) {
    'use strict';

    var trimmed = version ? version.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1") : '',
        pieces = trimmed.split(Versioning.DOT_PATTERN),
        partsLength,
        parts = [],
        value,
        piece,
        num,
        i;

    for (i = 0; i < pieces.length; i += 1) {
        piece = pieces[i].replace(Versioning.NON_DIGIT_PATTERN, '');
        num = parseInt(piece, 10);

        if (isNaN(num)) {
            num = 0;
        }
        parts.push(num);
    }
    partsLength = parts.length;
    for (i = partsLength - 1; i >= 0; i -= 1) {
        value = parts[i];
        if (value === 0) {
            parts.length -= 1;
        } else {
            break;
        }
    }

    return parts;
};

/**
 * The return value is negative if x < y, zero if x == y and strictly positive if x > y
 *
 * @param {Array} x Array of ints
 * @param {Array} y Array of ints
 */
Versioning.cmp = function (x, y) {
    'use strict';

    var size = Math.min(x.length, y.length),
        i;

    for (i = 0; i < size; i += 1) {
        if (x[i] !== y[i]) {
            return x[i] < y[i] ? -1 : 1;
        }
    }

    if (x.length === y.length) {
        return 0;
    }

    return (x.length < y.length) ? -1 : 1;
};

How to test the script:

console.log(Versioning.compare('3.1', '3.0'));
console.log(Versioning.compare('1.0', '1.0.1'));
console.log(Versioning.compare('2.0.0', '2.0.0'));
console.log(Versioning.compare('1.1.0', '1.1'));
console.log(Versioning.compare('1.1-beta', '1.0-alpha'));

The values returned are 1 for higher version, -1 for lower version and 0 for equal (or undecided) version.

Related posts