91

I am writing an application that receives data with items and version numbers. The numbers are formatted like "1.0.1" or "1.2.5". How can I compare these version numbers? I think they have to be formatted as a string first, no? What options do I have to determine that "1.2.5" comes after "1.0.1"?

Cœur
  • 37,241
  • 25
  • 195
  • 267
mlecho
  • 1,149
  • 2
  • 10
  • 14
  • 3
    It helps to clarify precisely what the versioning scheme is. Some may have formats requiring additional logic. – uchuugaka May 22 '13 at 01:52
  • I wrote that small library to easily compare 2 versions Strings in Obj-C. Typically in iOS. Have examples and codes on the [GitHub page](https://github.com/nicolasembleton/NEVersionCompare) – nembleton Mar 21 '12 at 06:04

14 Answers14

249

This is the simplest way to compare versions, keeping in mind that "1" < "1.0" < "1.0.0":

NSString* requiredVersion = @"1.2.0";
NSString* actualVersion = @"1.1.5";

if ([requiredVersion compare:actualVersion options:NSNumericSearch] == NSOrderedDescending) {
  // actualVersion is lower than the requiredVersion
}
Nathan de Vries
  • 15,481
  • 4
  • 49
  • 55
  • 6
    I was using this method, and I recently found that it returns (what I consider) wrong results when comparing i.e: 2.4.06 with 2.4.4. I believe that 2.4.06 should be lower than 2.4.4, but maybe I'm wrong... any thoughts? – Omer Nov 26 '12 at 16:06
  • @Omer: What probably happens is that 2.4.06 is converted to 2406 in the background. 2.4.4 is converted to 244. 244 < 2406. You would have to change the version number to 2.4.40 for that to work. – Scott Berrevoets Dec 03 '12 at 20:07
  • 9
    @Omer: Why 06 and not 6? I think most developers would consider 2.4.06 to be a higher version than 2.4.4. – Stephen Melvin Dec 04 '12 at 20:31
  • 4
    This is nice and simple but relies on a very simple version scheme. – uchuugaka May 22 '13 at 01:53
  • 11
    @ScottBerrevoets I would certainly hope that's not how it works, as that would mean "1.2.3" is less than "1.1.12" (123 < 1112)! As Apple carefully states, "[Numbers _within_ strings are compared using numeric value](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html#//apple_ref/c/econst/NSNumericSearch)". That is, _sets of numbers_ within strings will each be compared (essentially, the `componentsSeparatedByString` approach). You can test this yourself with `@"1.8"` vs `@"1.7.2.3.55"` and see that 1.8 comes out ahead. – dooleyo Mar 25 '14 at 18:31
  • 4
    NSNumericSearch thinks "1.0" is less than "1.0.0". Not quite flexible enough for my purposes. – bobics Mar 28 '14 at 05:46
  • 1
    @bobics using a simple NSString category to drop unnecessary .0's from a version number string beforehand makes this still a great approach. See my answer [below](http://stackoverflow.com/a/24811200/159758) for the category and how I used it. – DonnaLea Jul 17 '14 at 18:48
  • 1
    NSNumericSearch Description : "Numbers within strings are compared using numeric value, that is, Name2.txt < Name7.txt < Name25.txt." So if we compare 1.44 and 1.5, 1.44 comes first. – jithinroy Oct 28 '14 at 10:47
  • 1
    Yes, jithinroy is right. This implementation is appealing, but ultimately poor. Most would agree that 7.5 is a higher version than 7.4.1, but this returns the opposite. – Tres Dec 01 '16 at 17:04
  • Omer If we are talking about Semantic Versioning then 2.4.06 isn't valid. https://github.com/mojombo/semver/issues/112 – Steve Moser Aug 30 '17 at 18:24
  • @Tres jithinroy is saying the exact opposite of that. "1.44 comes first" as in 1.44 is less than 1.5. Same with your example: 7.4.1 comes before 7.5 i.e. 7.5 is a _higher_ version – Max Feb 23 '18 at 15:33
  • This will not work in all case, eg. 1.9.10 will be higher than 1.10.0 when it supposed to be lower. And those version number are completely match semantic versioning system. – kientux Apr 09 '18 at 02:27
  • I have confirmed that this will not work as intended. Using the code in the answer, if you set `requiredVersion` to `1.5.0` and `actualVersion` to `1.44.0`, then `1.44.0` is viewed as a _lower_ version number than `1.5.0`, whereas `1.44.0` is considered a higher version (at least, in semantic versioning). Seems weird to me that the numbers are compared in that way, but that's how the code runs. – David Gay Jan 02 '19 at 18:01
  • @DavidGay '1.5.0' compare to '1.44.0' will get a NSOrderedAscending result. It means '1.5.0' < '1.44.0'. I think this answer's logic is right. The only problem is '1' < '1.0' < '1.0.0' – Harrison Xi Feb 26 '19 at 10:10
  • comparison steps examples: https://raw.githubusercontent.com/HarrisonXi/HarrisonXi.github.io/master/2019/02/28-B.png – Harrison Xi Feb 28 '19 at 09:33
  • Thanks for the solution. It has fixed my issue. – Varun P V Feb 21 '20 at 11:42
18

I'll add my method, which compares strictly numeric versions (no a, b, RC etc.) with any number of components.

+ (NSComparisonResult)compareVersion:(NSString*)versionOne toVersion:(NSString*)versionTwo {
    NSArray* versionOneComp = [versionOne componentsSeparatedByString:@"."];
    NSArray* versionTwoComp = [versionTwo componentsSeparatedByString:@"."];

    NSInteger pos = 0;

    while ([versionOneComp count] > pos || [versionTwoComp count] > pos) {
        NSInteger v1 = [versionOneComp count] > pos ? [[versionOneComp objectAtIndex:pos] integerValue] : 0;
        NSInteger v2 = [versionTwoComp count] > pos ? [[versionTwoComp objectAtIndex:pos] integerValue] : 0;
        if (v1 < v2) {
            return NSOrderedAscending;
        }
        else if (v1 > v2) {
            return NSOrderedDescending;
        }
        pos++;
    }

    return NSOrderedSame;
}
nikkiauburger
  • 321
  • 2
  • 4
14

This is an expansion to Nathan de Vries answer to address the problem of 1 < 1.0 < 1.0.0 etc.

First off we can address the problem of extra ".0"'s on our version string with an NSString category:

@implementation NSString (VersionNumbers)
- (NSString *)shortenedVersionNumberString {
    static NSString *const unnecessaryVersionSuffix = @".0";
    NSString *shortenedVersionNumber = self;

    while ([shortenedVersionNumber hasSuffix:unnecessaryVersionSuffix]) {
        shortenedVersionNumber = [shortenedVersionNumber substringToIndex:shortenedVersionNumber.length - unnecessaryVersionSuffix.length];
    }

    return shortenedVersionNumber;
}
@end

With the above NSString category we can shorten our version numbers to drop the unnecessary .0's

NSString* requiredVersion = @"1.2.0";
NSString* actualVersion = @"1.1.5";

requiredVersion = [requiredVersion shortenedVersionNumberString]; // now 1.2
actualVersion = [actualVersion shortenedVersionNumberString]; // still 1.1.5

Now we can still use the beautifully simple approach proposed by Nathan de Vries:

if ([requiredVersion compare:actualVersion options:NSNumericSearch] == NSOrderedDescending) {
  // actualVersion is lower than the requiredVersion
}
DonnaLea
  • 8,643
  • 4
  • 32
  • 32
10

I made it myself,use Category..

Source..

@implementation NSString (VersionComparison)
- (NSComparisonResult)compareVersion:(NSString *)version{
    NSArray *version1 = [self componentsSeparatedByString:@"."];
    NSArray *version2 = [version componentsSeparatedByString:@"."];
    for(int i = 0 ; i < version1.count || i < version2.count; i++){
        NSInteger value1 = 0;
        NSInteger value2 = 0;
        if(i < version1.count){
            value1 = [version1[i] integerValue];
        }
        if(i < version2.count){
            value2 = [version2[i] integerValue];
        }
        if(value1  == value2){
            continue;
        }else{
            if(value1 > value2){
                return NSOrderedDescending;
            }else{
                return NSOrderedAscending;
            }
        }
    }
    return NSOrderedSame;
}

Test..

NSString *version1 = @"3.3.1";
NSString *version2 = @"3.12.1";
NSComparisonResult result = [version1 compareVersion:version2];
switch (result) {
    case NSOrderedAscending:
    case NSOrderedDescending:
    case NSOrderedSame:
         break;
    }
Peter
  • 126
  • 1
  • 4
  • Awesome! This is the only example using NSComparisonResult on this thread that compares 7.28.2 & 7.28 correctly. – CokePokes Jul 30 '18 at 15:19
8

Sparkle (the most popular software update framework for MacOS) has a SUStandardVersionComparator class that does this, and also takes into account build numbers and beta markers. I.e. it correctly compares 1.0.5 > 1.0.5b7 or 2.0 (2345) > 2.0 (2100). The code only uses Foundation, so should work fine on iOS as well.

orkoden
  • 18,946
  • 4
  • 59
  • 50
uliwitness
  • 8,532
  • 36
  • 58
7

Check out my NSString category that implements easy version checking on github; https://github.com/stijnster/NSString-compareToVersion

[@"1.2.2.4" compareToVersion:@"1.2.2.5"];

This will return a NSComparisonResult which is more accurate then using;

[@"1.2.2" compare:@"1.2.2.5" options:NSNumericSearch]

Helpers are also added;

[@"1.2.2.4" isOlderThanVersion:@"1.2.2.5"];
[@"1.2.2.4" isNewerThanVersion:@"1.2.2.5"];
[@"1.2.2.4" isEqualToVersion:@"1.2.2.5"];
[@"1.2.2.4" isEqualOrOlderThanVersion:@"1.2.2.5"];
[@"1.2.2.4" isEqualOrNewerThanVersion:@"1.2.2.5"];
Stijnster
  • 209
  • 2
  • 7
4

Swift 2.2 Version :

let currentStoreAppVersion = "1.10.2"
let minimumAppVersionRequired = "1.2.2"
if currentStoreAppVersion.compare(minimumAppVersionRequired, options: NSStringCompareOptions.NumericSearch) ==
            NSComparisonResult.OrderedDescending {
            print("Current Store version is higher")
        } else {
            print("Latest New version is higher")
        }

Swift 3 Version :

let currentStoreVersion = "1.1.0.2"
let latestMinimumAppVersionRequired = "1.1.1"
if currentStoreVersion.compare(latestMinimumAppVersionRequired, options: NSString.CompareOptions.numeric) == ComparisonResult.orderedDescending {
print("Current version is higher")
} else {
print("Latest version is higher")
}
Daniel
  • 8,794
  • 4
  • 48
  • 71
ioopl
  • 1,735
  • 19
  • 19
3

My iOS library AppUpdateTracker contains an NSString category to perform this sort of comparison. (Implementation is based off DonnaLea's answer.)

Usage would be as follows:

[@"1.4" isGreaterThanVersionString:@"1.3"]; // YES
[@"1.4" isLessThanOrEqualToVersionString:@"1.3"]; // NO

Additionally, you can use it to keep track of your app's installation/update status:

[AppUpdateTracker registerForAppUpdatesWithBlock:^(NSString *previousVersion, NSString *currentVersion) {
    NSLog(@"app updated from: %@ to: %@", previousVersion, currentVersion);
}];
[AppUpdateTracker registerForFirstInstallWithBlock:^(NSTimeInterval installTimeSinceEpoch, NSUInteger installCount) {
    NSLog(@"first install detected at: %f amount of times app was (re)installed: %lu", installTimeSinceEpoch, (unsigned long)installCount);
}];
[AppUpdateTracker registerForIncrementedUseCountWithBlock:^(NSUInteger useCount) {
    NSLog(@"incremented use count to: %lu", (unsigned long)useCount);
}];
Stunner
  • 12,025
  • 12
  • 86
  • 145
  • is v4.21 < 4.3 ? if ([thisVersion isGreaterThanOrEqualToVersionString:@"4.3"]) – johndpope May 26 '15 at 10:09
  • No, 4.21 is considered greater than 4.3 as 21 > 3. In order to satisfy your equality comparison, you would want to compare 4.21 with 4.30. Please see the discussion in the comments of [Nathan de Vries's answer](http://stackoverflow.com/questions/1978456/compare-version-numbers-in-objective-c/28064096?noredirect=1#comment18590891_1990854). – Stunner May 26 '15 at 10:29
3

Here is the swift 4.0 + code for version comparison

 let currentVersion = "1.2.0"

 let oldVersion = "1.1.1"

 if currentVersion.compare(oldVersion, options: NSString.CompareOptions.numeric) == ComparisonResult.orderedDescending {
        print("Higher")
    } else {
        print("Lower")
    }
Matloob Hasnain
  • 1,025
  • 6
  • 21
3

I thought I'd just share a function I pulled together for this. It is not perfect at all. Please take a look that the examples and results. But if you are checking your own version numbers (which I have to do to manage things like database migrations) then this may help a little.

(also, remove the log statements in the method, of course. those are there to help you see what it does is all)

Tests:

[self isVersion:@"1.0" higherThan:@"0.1"];
[self isVersion:@"1.0" higherThan:@"0.9.5"];
[self isVersion:@"1.0" higherThan:@"0.9.5.1"];
[self isVersion:@"1.0.1" higherThan:@"1.0"];
[self isVersion:@"1.0.0" higherThan:@"1.0.1"];
[self isVersion:@"1.0.0" higherThan:@"1.0.0"];

// alpha tests
[self isVersion:@"1.0b" higherThan:@"1.0a"];
[self isVersion:@"1.0a" higherThan:@"1.0b"];
[self isVersion:@"1.0a" higherThan:@"1.0a"];
[self isVersion:@"1.0" higherThan:@"1.0RC1"];
[self isVersion:@"1.0.1" higherThan:@"1.0RC1"];

Results:

1.0 > 0.1
1.0 > 0.9.5
1.0 > 0.9.5.1
1.0.1 > 1.0
1.0.0 < 1.0.1
1.0.0 == 1.0.0
1.0b > 1.0a
1.0a < 1.0b
1.0a == 1.0a
1.0 < 1.0RC1       <-- FAILURE
1.0.1 < 1.0RC1     <-- FAILURE

notice that alpha works but you have to be very careful with it. once you go alpha at some point you cannot extend that by changing any other minor numbers behind it.

Code:

- (BOOL) isVersion:(NSString *)thisVersionString higherThan:(NSString *)thatVersionString {

// LOWER
if ([thisVersionString compare:thatVersionString options:NSNumericSearch] == NSOrderedAscending) {
    NSLog(@"%@ < %@", thisVersionString, thatVersionString);
    return NO;
}

// EQUAL
if ([thisVersionString compare:thatVersionString options:NSNumericSearch] == NSOrderedSame) {
    NSLog(@"%@ == %@", thisVersionString, thatVersionString);
    return NO;
}

NSLog(@"%@ > %@", thisVersionString, thatVersionString);
// HIGHER
return YES;
}
bladnman
  • 2,591
  • 1
  • 25
  • 20
0

If you know each version number will have exactly 3 integers separated by dots, you can parse them (e.g. using sscanf(3)) and compare them:

const char *version1str = "1.0.1";
const char *version2str = "1.2.5";
int major1, minor1, patch1;
int major2, minor2, patch2;
if(sscanf(version1str, "%d.%d.%d", &major1, &minor1, &patch1) == 3 &&
   sscanf(version2str, "%d.%d.%d", &major2, &minor2, &patch2) == 3)
{
    // Parsing succeeded, now compare the integers
    if(major1 > major2 ||
      (major1 == major2 && (minor1 > minor2 ||
                           (minor1 == minor2 && patch1 > patch2))))
    {
        // version1 > version2
    }
    else if(major1 == major2 && minor1 == minor2 && patch1 == patch2)
    {
        // version1 == version2
    }
    else
    {
        // version1 < version2
    }
}
else
{
    // Handle error, parsing failed
}
James Webster
  • 31,873
  • 11
  • 70
  • 114
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
0

Glibc has a function strverscmp and versionsort… unfortunately, not portable to the iPhone, but you can write your own fairly easily. This (untested) re-implementation comes from just reading the documented behavior, and not from reading Glibc's source code.

int strverscmp(const char *s1, const char *s2) {
    const char *b1 = s1, *b2 = s2, *e1, *e2;
    long n1, n2;
    size_t z1, z2;
    while (*b1 && *b1 == *b2) b1++, b2++;
    if (!*b1 && !*b2) return 0;
    e1 = b1, e2 = b2;
    while (b1 > s1 && isdigit(b1[-1])) b1--;
    while (b2 > s2 && isdigit(b2[-1])) b2--;
    n1 = strtol(b1, &e1, 10);
    n2 = strtol(b2, &e2, 10);
    if (b1 == e1 || b2 == e2) return strcmp(s1, s2);
    if (n1 < n2) return -1;
    if (n1 > n2) return 1;
    z1 = strspn(b1, "0"), z2 = strspn(b2, "0");
    if (z1 > z2) return -1;
    if (z1 < z2) return 1;
    return 0;
}
ephemient
  • 198,619
  • 38
  • 280
  • 391
  • 2
    this looks just horrible. One of the things that I like the most about Objective-C is that for the most part I don't have to deal with plain C anymore. – Lukas Petr Sep 14 '16 at 16:21
0

To check the version in swift you can use following

switch newVersion.compare(currentversion, options: NSStringCompareOptions.NumericSearch) {
    case .OrderedDescending:
        println("NewVersion available  ")
        // Show Alert Here

    case .OrderedAscending:
        println("NewVersion Not available  ")
    default:
        println("default")
    }

Hope it might be helpful.

PatientC
  • 273
  • 4
  • 9
0

Here is a recursive function that do the works with multiple version formatting of any length. It also works for @"1.0" and @"1.0.0"

static inline NSComparisonResult versioncmp(const NSString * a, const NSString * b)
{
    if ([a isEqualToString:@""] && [b isEqualToString:@""]) {
        return NSOrderedSame;
    }

    if ([a isEqualToString:@""]) {
        a = @"0";
    }

    if ([b isEqualToString:@""]) {
        b = @"0";
    }

    NSArray<NSString*> * aComponents = [a componentsSeparatedByString:@"."];
    NSArray<NSString*> * bComponents = [b componentsSeparatedByString:@"."];
    NSComparisonResult r = [aComponents[0] compare:bComponents[0] options:NSNumericSearch];

    if(r != NSOrderedSame) {
        return r;
    } else {
        NSString* newA = (a.length == aComponents[0].length) ? @"" : [a substringFromIndex:aComponents[0].length+1];
        NSString* newB = (b.length == bComponents[0].length) ? @"" : [b substringFromIndex:bComponents[0].length+1];
        return versioncmp(newA, newB);
    }

}

Test samples :

versioncmp(@"11.5", @"8.2.3");
versioncmp(@"1.5", @"8.2.3");
versioncmp(@"1.0", @"1.0.0");
versioncmp(@"11.5.3.4.1.2", @"11.5.3.4.1.2");
Neimsz
  • 1,554
  • 18
  • 22