Skip to content

Instantly share code, notes, and snippets.

@Martyr2
Created October 7, 2017 23:00
Show Gist options
  • Save Martyr2/b2a05e559b4c8f8ab224f7243e9a6214 to your computer and use it in GitHub Desktop.
Save Martyr2/b2a05e559b4c8f8ab224f7243e9a6214 to your computer and use it in GitHub Desktop.
Compare versions semantically and will tell you if the newer version is in fact newer than the current version. For more information refer to http://semver.org
/// <summary>
/// Determines if the new version is semantically newer than the current version.
/// </summary>
/// <param name="currentVersion">Current version number or build</param>
/// <param name="newVersion">New version to compare to the current version</param>
/// <returns>True if the newer version is semantically newer than the current version. False if not.</returns>
/// <remarks>Semantic versioning details at http://semver.org</remarks>
private bool IsSemanticallyNewerVersion(string currentVersion, string newVersion) {
if (!IsVersionSemanticallyValid(currentVersion)) {
throw new ArgumentException("Current version is not semantically valid. Cannot compare.");
}
if (!IsVersionSemanticallyValid(newVersion)) {
throw new ArgumentException("New version is not semantically valid. Cannot compare.");
}
// First check if they are the same.
if (currentVersion.Equals(newVersion, StringComparison.InvariantCultureIgnoreCase)) {
return false;
}
// First remove any build data from string
string cvCleanedData = RemoveBuildData(currentVersion);
string nvCleanedData = RemoveBuildData(newVersion);
string cvPreRelease = string.Empty;
string nvPreRelease = string.Empty;
// Determine if there is a pre-release version specified
// If there is a pre-release, split it off
if (cvCleanedData.IndexOf('-') > -1) {
cvPreRelease = cvCleanedData.Substring(cvCleanedData.IndexOf('-') + 1);
cvCleanedData = cvCleanedData.Substring(0, cvCleanedData.IndexOf('-'));
}
if (nvCleanedData.IndexOf('-') > -1) {
nvPreRelease = nvCleanedData.Substring(nvCleanedData.IndexOf('-') + 1);
nvCleanedData = nvCleanedData.Substring(0, nvCleanedData.IndexOf('-'));
}
// Compare major, minor and patch first
string[] cvSplitVersion = cvCleanedData.Split(new char[] { '.' });
string[] nvSplitVersion = nvCleanedData.Split(new char[] { '.' });
for (int i = 0; i < cvSplitVersion.Length; i++) {
if (Convert.ToInt64(nvSplitVersion[i]) > Convert.ToInt64(cvSplitVersion[i])) {
return true;
} else if (Convert.ToInt64(nvSplitVersion[i]) < Convert.ToInt64(cvSplitVersion[i])) {
return false;
}
}
// If the same, determine if one has a pre-release and other does not. If both don't, they are the same version.
if (!String.IsNullOrEmpty(cvPreRelease) && String.IsNullOrEmpty(nvPreRelease)) {
return true;
} else if (String.IsNullOrEmpty(cvPreRelease) && !String.IsNullOrEmpty(nvPreRelease)) {
return false;
} else if (String.IsNullOrEmpty(cvPreRelease) && String.IsNullOrEmpty(nvPreRelease)) {
return false;
}
// If both have pre-release, split the pre-release on periods
string[] cvSplitPreRelease = cvPreRelease.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
string[] nvSplitPreRelease = nvPreRelease.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < cvSplitPreRelease.Length; i++) {
if (i < nvSplitPreRelease.Length) {
if (!cvSplitPreRelease[i].Equals(nvSplitPreRelease[i], StringComparison.InvariantCultureIgnoreCase)) {
// Both identifiers numbers, compare numerically
bool cvIsNumeric = int.TryParse(cvSplitPreRelease[i], out int c);
bool nvIsNumeric = int.TryParse(nvSplitPreRelease[i], out int n);
// Numbers are less than letters (1.0.0-alpha.1 < 1.0.0-alpha.beta)
if (cvIsNumeric && nvIsNumeric) {
return (n > c);
} else if (cvIsNumeric && !nvIsNumeric) {
return true;
} else if (nvIsNumeric && !cvIsNumeric) {
return false;
} else {
// Both identifiers are letters, compare lexically
return (string.Compare(nvSplitPreRelease[i], cvSplitPreRelease[i]) > 0);
}
}
} else {
return false;
}
}
// If one is equal but then has more identifiers it is greater (eg 1.0.0-beta < 1.0.0-beta.1)
return (nvSplitPreRelease.Length > cvSplitPreRelease.Length);
}
/// <summary>
/// Removes any found build data from a semantic version string.
/// </summary>
/// <param name="version">semantic version string</param>
/// <returns>String minus any build value.</returns>
private string RemoveBuildData(string version) {
if (version.IndexOf('+') > -1) {
return version.Substring(0, version.IndexOf('+'));
}
return version;
}
/// <summary>
/// Determines if the supplied version is a semantically valid version
/// </summary>
/// <param name="version">The version to evaluate if semantic</param>
/// <returns>True if the version is semantically valid, false otherwise.</returns>
private bool IsVersionSemanticallyValid(string version) {
Regex exp = new Regex(@"^(\d+\.){2}\d+(-([0-9A-Za-z-]\.?)+)?(\+([0-9A-Za-z-]\.?)+)?$");
try {
return exp.IsMatch(version);
} catch (RegexMatchTimeoutException) {
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment