Examples tested against actual runtime. CI re-verifies continuously. Only documented examples are tested.
How to use
Install via yarn add locutus and import:
import { version_compare } from 'locutus/php/info/version_compare'.
Or with CommonJS: const { version_compare } = require('locutus/php/info/version_compare')
Use a bundler that supports tree-shaking so you only ship the functions you actually use.
Vite,
webpack,
Rollup, and
Parcel
all handle this. For server-side use this is less of a concern.
Examples
These examples are extracted from test cases that automatically verify our functions against their native counterparts.
exportfunctionversion_compare(v1: string, v2: string, operator?: string): number | boolean | null { // discuss at: https://locutus.io/php/version_compare/ // parity verified: PHP 8.3 // original by: Philippe Jausions (https://pear.php.net/user/jausions) // original by: Aidan Lister (https://aidanlister.com/) // reimplemented by: Kankrelune (https://www.webfaktory.info/) // improved by: Brett Zamir (https://brett-zamir.me) // improved by: Scott Baker // improved by: Theriault (https://github.com/Theriault) // example 1: version_compare('8.2.5rc', '8.2.5a') // returns 1: 1 // example 2: version_compare('8.2.50', '8.2.52', '<') // returns 2: true // example 3: version_compare('5.3.0-dev', '5.3.0') // returns 3: -1 // example 4: version_compare('4.1.0.52','4.01.0.51') // returns 4: 1
// Important: compare must be initialized at 0. let compare = 0
// vm maps textual PHP versions to negatives so they're less than 0. // PHP currently defines these as CASE-SENSITIVE. It is important to // leave these as negatives so that they can come before numerical versions // and as if no letters were there to begin with. // (1alpha is < 1 and < 1.1 but > 1dev1) // If a non-numerical value can't be mapped to this table, it receives // -7 as its value. constvm: Record<string, number> = { dev: -6, alpha: -5, a: -5, beta: -4, b: -4, RC: -3, rc: -3, '#': -2, p: 1, pl: 1, }
// This function will be called to prepare each version argument. // It replaces every _, -, and + with a dot. // It surrounds any nonsequence of numbers/dots with dots. // It replaces sequences of dots with a single dot. // version_compare('4..0', '4.0') === 0 // Important: A string of 0 length needs to be converted into a value // even less than an unexisting value in vm (-7), hence [-8]. // It's also important to not strip spaces because of this. // version_compare('', ' ') === 1 const _prepVersion = function (v: string): string[] { let normalized = String(v).replace(/[_\-+]/g, '.') normalized = normalized.replace(/([^.\d]+)/g, '.$1.').replace(/\.{2,}/g, '.') return !normalized.length ? ['-8'] : normalized.split('.') } // This converts a version component to a number. // Empty component becomes 0. // Non-numerical component becomes a negative number. // Numerical component becomes itself as an integer. const _numVersion = function (v: string | undefined): number { if (!v) { return0 } returnisNaN(Number(v)) ? (vm[v] ?? -7) : parseInt(v, 10) }
const leftParts = _prepVersion(v1) const rightParts = _prepVersion(v2) const maxLength = Math.max(leftParts.length, rightParts.length) for (let i = 0; i < maxLength; i++) { if (leftParts[i] === rightParts[i]) { continue } const left = _numVersion(leftParts[i]) const right = _numVersion(rightParts[i]) if (left < right) { compare = -1 break } elseif (left > right) { compare = 1 break } } if (!operator) { return compare }
// Important: operator is CASE-SENSITIVE. // "No operator" seems to be treated as "<." // Any other values seem to make the function return null. switch (operator) { case'>': case'gt': return compare > 0 case'>=': case'ge': return compare >= 0 case'<=': case'le': return compare <= 0 case'===': case'=': case'eq': return compare === 0 case'<>': case'!==': case'ne': return compare !== 0 case'': case'<': case'lt': return compare < 0 default: returnnull } }
// Important: compare must be initialized at 0. let compare = 0
// vm maps textual PHP versions to negatives so they're less than 0. // PHP currently defines these as CASE-SENSITIVE. It is important to // leave these as negatives so that they can come before numerical versions // and as if no letters were there to begin with. // (1alpha is < 1 and < 1.1 but > 1dev1) // If a non-numerical value can't be mapped to this table, it receives // -7 as its value. const vm = { dev: -6, alpha: -5, a: -5, beta: -4, b: -4, RC: -3, rc: -3, '#': -2, p: 1, pl: 1, }
// This function will be called to prepare each version argument. // It replaces every _, -, and + with a dot. // It surrounds any nonsequence of numbers/dots with dots. // It replaces sequences of dots with a single dot. // version_compare('4..0', '4.0') === 0 // Important: A string of 0 length needs to be converted into a value // even less than an unexisting value in vm (-7), hence [-8]. // It's also important to not strip spaces because of this. // version_compare('', ' ') === 1 const _prepVersion = function (v) { let normalized = String(v).replace(/[_\-+]/g, '.') normalized = normalized.replace(/([^.\d]+)/g, '.$1.').replace(/\.{2,}/g, '.') return !normalized.length ? ['-8'] : normalized.split('.') } // This converts a version component to a number. // Empty component becomes 0. // Non-numerical component becomes a negative number. // Numerical component becomes itself as an integer. const _numVersion = function (v) { if (!v) { return0 } returnisNaN(Number(v)) ? (vm[v] ?? -7) : parseInt(v, 10) }
const leftParts = _prepVersion(v1) const rightParts = _prepVersion(v2) const maxLength = Math.max(leftParts.length, rightParts.length) for (let i = 0; i < maxLength; i++) { if (leftParts[i] === rightParts[i]) { continue } const left = _numVersion(leftParts[i]) const right = _numVersion(rightParts[i]) if (left < right) { compare = -1 break } elseif (left > right) { compare = 1 break } } if (!operator) { return compare }
// Important: operator is CASE-SENSITIVE. // "No operator" seems to be treated as "<." // Any other values seem to make the function return null. switch (operator) { case'>': case'gt': return compare > 0 case'>=': case'ge': return compare >= 0 case'<=': case'le': return compare <= 0 case'===': case'=': case'eq': return compare === 0 case'<>': case'!==': case'ne': return compare !== 0 case'': case'<': case'lt': return compare < 0 default: returnnull } }
Improve this function
Locutus is a community effort following
The McDonald's Theory:
we ship first iterations, hoping others will improve them.
If you see something that could be better, we'd love your contribution.