Более надежная реализация
/// <summary>
/// Immutable value type for storing personal numbers
/// </summary>
public struct PersonalNumber : IEquatable<PersonalNumber>
{
private const string YearHundredOpt = @"(19|20)?"; // 19 or 20 only (optional)
private const string YearDecade = @"(\d{2})"; // any two digits
private const string Month = @"(0[1-9]|10|11|12)"; // 01 to 12
private const string Day = @"(0[1-9]|1\d|2\d|30|31)"; // 01 to 31
private const string SeparatorAndLastFourOpt = @"([-\+]?)(\d{4})?"; // optional - or + followed by any four digits (optional)
private const string RegExForValidation =
"^" + YearHundredOpt + YearDecade + Month + Day + SeparatorAndLastFourOpt + "$";
private static readonly Regex personNoRegex = new Regex(RegExForValidation, RegexOptions.Compiled);
private readonly string rawPersonalNumber;
private readonly string personalNumber;
private readonly bool isValid;
private readonly bool isPlus100YearsOld;
public PersonalNumber(string personalNumber)
{
rawPersonalNumber = personalNumber;
var matches = personNoRegex.Matches(personalNumber);
var normalizedYYMMDDXXXC = string.Empty;
isPlus100YearsOld = false;
foreach (Match match in matches)
{
if (match.Success)
{
normalizedYYMMDDXXXC = match.Groups[2].Value + match.Groups[3].Value +
match.Groups[4].Value + match.Groups[6].Value;
if (match.Groups[5].Success)
{
isPlus100YearsOld = match.Groups[5].Value == "+";
}
break;
}
}
if (normalizedYYMMDDXXXC.Length > 6)
{
isValid = personNoRegex.IsMatch(personalNumber) && LuhnAlgorithm.ValidateChecksum(normalizedYYMMDDXXXC);
}
else
{
isValid = personNoRegex.IsMatch(personalNumber);
}
this.personalNumber = string.IsNullOrEmpty(normalizedYYMMDDXXXC) ? rawPersonalNumber : normalizedYYMMDDXXXC;
}
public string Number { get { return personalNumber; } }
public bool IsPlus100YearsOld { get { return isPlus100YearsOld; } }
public bool IsValid { get { return isValid; } }
public bool IsMale { get { return !IsFemale; } }
public bool IsFemale
{
get
{
var genderCharacter = personalNumber.Substring(personalNumber.Length - 2, 1); // second to last character
return int.Parse(genderCharacter) % 2 == 0; // even = female, odd = male
}
}
public override string ToString() { return Number; }
public bool Equals(PersonalNumber other)
{
return Equals(other.personalNumber, personalNumber);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (obj.GetType() != typeof (PersonalNumber)) return false;
return Equals((PersonalNumber) obj);
}
public override int GetHashCode()
{
unchecked
{
int result = (rawPersonalNumber != null ? rawPersonalNumber.GetHashCode() : 0);
result = (result*397) ^ (personalNumber != null ? personalNumber.GetHashCode() : 0);
result = (result*397) ^ isValid.GetHashCode();
return result;
}
}
public static bool operator ==(PersonalNumber left, PersonalNumber right)
{
return left.Equals(right);
}
public static bool operator !=(PersonalNumber left, PersonalNumber right)
{
return !left.Equals(right);
}
}
и некоторые тесты для нее
[TestClass]
public class PersonalNumberFixture
{
[TestMethod]
public void Ctor_PersonalNumber_AsUnformatted()
{
AssertExpectedNumber("7102240475", "7102240475");
AssertExpectedNumber("197102240475", "7102240475");
AssertExpectedNumber("19710224-0475", "7102240475");
AssertExpectedNumber("710224-0475", "7102240475");
}
private static void AssertExpectedNumber(string number, string expectedNumber)
{
Assert.AreEqual(expectedNumber, new PersonalNumber(number).Number);
}
[TestMethod]
public void FiltersAndRemovesPlusAndMinusCharactersCorrectly()
{
Assert.AreEqual("7102240475", new PersonalNumber("710224-0475").Number);
Assert.AreEqual("7102240475", new PersonalNumber("710224+0475").Number);
Assert.AreEqual("7102240475", new PersonalNumber("19710224-0475").Number);
Assert.AreEqual("7102240475", new PersonalNumber("19710224+0475").Number);
}
[TestMethod]
public void PlusAndMinusCharactersOnlyAllowedInCertainPositions()
{
Assert.IsTrue(new PersonalNumber("710224-0475").IsValid);
Assert.IsTrue(new PersonalNumber("710224+0475").IsValid);
Assert.IsFalse(new PersonalNumber("71022404-75").IsValid);
Assert.IsFalse(new PersonalNumber("71022404+75").IsValid);
Assert.IsFalse(new PersonalNumber("710-224-0475").IsValid);
Assert.IsFalse(new PersonalNumber("710224+04+75").IsValid);
}
[TestMethod]
public void MaleAndFemale_CorrectlyIdentified()
{
// female
Assert.IsFalse(new PersonalNumber("551213-7986").IsMale);
Assert.IsTrue(new PersonalNumber("551213-7986").IsFemale);
// male
Assert.IsTrue(new PersonalNumber("710224-0475").IsMale);
Assert.IsFalse(new PersonalNumber("710224-0475").IsFemale);
Assert.IsTrue(new PersonalNumber("19710224-0475").IsMale);
Assert.IsFalse(new PersonalNumber("19710224-0475").IsFemale);
}
[TestMethod]
public void Ctor_ValidPersonalNumbers_ParsedAsValid()
{
TestLoop_ValidPersonalNumbers(new[] { "7102240475", "197102240475", "19710224-0475", "710224-0475", "801231+3535", "630215" });
}
private static void TestLoop_ValidPersonalNumbers(IEnumerable<string> personalNumbers)
{
foreach (var personalNumber in personalNumbers)
{
var pn = new PersonalNumber(personalNumber);
Assert.IsTrue(pn.IsValid, string.Format("Pno = '{0}'", personalNumber));
}
}
[TestMethod]
public void ToString_EqualsNormalizedNumber()
{
Assert.AreEqual(new PersonalNumber("460126").ToString(), new PersonalNumber("460126").Number);
}
[TestMethod]
public void Ctor_InvalidPersonalNumbers_ParsedAsNotValid()
{
TestLoop_InvalidPersonalNumbers(new[] { "127102240475", "19710XY40475", "19710224=0475", "1971", "14532436-45", "556194-7986", "262000-0113", "460531-12" });
}
private static void TestLoop_InvalidPersonalNumbers(IEnumerable<string> personalNumbers)
{
foreach (var personalNumber in personalNumbers)
{
var pn = new PersonalNumber(personalNumber);
Assert.IsFalse(pn.IsValid, string.Format("Pno = '{0}'", personalNumber));
}
}
[TestMethod]
public void TwoNumbers_ConsideredEqual_IfNormalizedNumbersAreEqual()
{
new PersonalNumber("710224-0475");
Assert.IsTrue(new PersonalNumber("800212").Equals(new PersonalNumber("19800212")));
Assert.IsTrue(new PersonalNumber("800212").Equals(new PersonalNumber("800212")));
Assert.IsTrue(new PersonalNumber("8002121234").Equals(new PersonalNumber("19800212-1234")));
Assert.IsFalse(new PersonalNumber("800212").Equals(new PersonalNumber("900212")));
}
[TestMethod]
public void EqualsOperatorOverloaded_AsEquals()
{
Assert.IsTrue(new PersonalNumber("800212") == new PersonalNumber("19800212"));
Assert.IsTrue(new PersonalNumber("800212") == new PersonalNumber("800212"));
Assert.IsTrue(new PersonalNumber("8002121234") == new PersonalNumber("19800212-1234"));
Assert.IsTrue(new PersonalNumber("800212") != new PersonalNumber("900212"));
}
[TestMethod]
public void IsPlus100YearsOld()
{
Assert.IsFalse(new PersonalNumber("800213").IsPlus100YearsOld);
Assert.IsFalse(new PersonalNumber("800213-0123").IsPlus100YearsOld);
Assert.IsTrue(new PersonalNumber("800213+0123").IsPlus100YearsOld);
}
}