Compare version numbers in Objective-C
Asked Answered
J

14

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"?

Juniorjuniority answered 30/12, 2009 at 4:40 Comment(2)
I wrote that small library to easily compare 2 versions Strings in Obj-C. Typically in iOS. Have examples and codes on the GitHub pageRefection
It helps to clarify precisely what the versioning scheme is. Some may have formats requiring additional logic.Ane
E
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
}
Epicycle answered 2/1, 2010 at 7:11 Comment(15)
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?Hexagonal
@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.Kekkonen
@Omer: Why 06 and not 6? I think most developers would consider 2.4.06 to be a higher version than 2.4.4.Narco
This is nice and simple but relies on a very simple version scheme.Ane
@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". 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.Cutworm
NSNumericSearch thinks "1.0" is less than "1.0.0". Not quite flexible enough for my purposes.Naples
@Naples 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 for the category and how I used it.Wink
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.Styrax
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.Demakis
Omer If we are talking about Semantic Versioning then 2.4.06 isn't valid. github.com/mojombo/semver/issues/112Parrett
@Demakis 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 versionSobriety
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.Octogenarian
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.Barcus
@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'Mcgregor
comparison steps examples: raw.githubusercontent.com/HarrisonXi/HarrisonXi.github.io/…Mcgregor
O
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;
}
Orthoscope answered 21/5, 2013 at 22:5 Comment(0)
W
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
}
Wink answered 17/7, 2014 at 18:43 Comment(3)
This coupled with Nathan de Vries solution is the best and most elegant answer.Hujsak
Wouldn't this still say that 7.4.2 is a higher version than 7.5?Demakis
@Demakis no. Because NSNumericSearch is passed in as the option, the strings are compared as numbers, thus 7.4.2 < 7.5Wink
V
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;
    }
Venenose answered 4/9, 2014 at 7:55 Comment(1)
Awesome! This is the only example using NSComparisonResult on this thread that compares 7.28.2 & 7.28 correctly.Lumpkin
W
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.

Washko answered 3/1, 2010 at 15:54 Comment(0)
E
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"];
Earthshaker answered 1/9, 2014 at 18:32 Comment(0)
B
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")
}
Bithynia answered 15/11, 2016 at 12:58 Comment(0)
P
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;
}
Pert answered 15/3, 2012 at 16:27 Comment(0)
S
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);
}];
Spathose answered 21/1, 2015 at 9:51 Comment(2)
is v4.21 < 4.3 ? if ([thisVersion isGreaterThanOrEqualToVersionString:@"4.3"])Windsucking
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.Spathose
M
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")
    }
Monopoly answered 14/1, 2020 at 12:39 Comment(0)
H
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
}
Headforemost answered 30/12, 2009 at 5:15 Comment(0)
G
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;
}
Granny answered 30/12, 2009 at 5:47 Comment(1)
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.Sought
P
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.

Phenocryst answered 24/3, 2015 at 10:30 Comment(0)
A
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");
Acinaciform answered 1/3, 2019 at 14:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.