5

I Have a problem with my regular expression. I have made it possible to valdiate correct swedish social security number to match these criteria.

  • YYMMDDNNNN
  • YYMMDD-NNNN
  • YYYYMMDDNNNN
  • YYYYMMDD-NNNN

But i would also like to reject a user to register if the user is under 18 years old. My reqular expression is looking like this at the moment: Do anyone encountered the same problem with the age range Swedish SSN?

  private const string RegExForValidation =
        @"^(\d{6}|\d{8})[-|(\s)]{0,1}\d{4}$";

UPDATE

 private const string RegExForValidation = @"^(?<date>\d{6}|\d{8})[-\s]?\d{4}$";
        string date = Regex.Match("19970918-1234", RegExForValidation).Groups["date"].Value;
        DateTime dt;

 [Required(ErrorMessage = "Du måste ange personnummer")]
        [RegularExpression(RegExForValidation, ErrorMessage = "Personnummer anges med 10 siffror (yymmddnnnn)")]
        public string PersonalIdentityNumber { get; set; }

Second Update

 public class ValidateOfAge : ValidationAttribute
{
    public bool IsOfAge(DateTime birthdate)
    {
        DateTime today = DateTime.Today;
        int age = today.Year - 18;
        if (birthdate.AddYears(birthdate.Year) < today.AddYears(-age))
            return false;
        else
            return true;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        string RegExForValidation = @"^(?<date>\d{6}|\d{8})[-\s]?\d{4}$";
        string date = Regex.Match((string)value, RegExForValidation).Groups["date"].Value;
        DateTime dt;
        if (DateTime.TryParseExact(date, new[] { "yyMMdd", "yyyyMMdd" }, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt))
            if (IsOfAge(dt))
                return ValidationResult.Success;
        return new ValidationResult("Personnummer anges med 10 siffror (yymmddnnnn)");
    }
}
Andreas Jangefalk
  • 337
  • 1
  • 4
  • 20
  • Just curious: do you realize that `[-|(\s)]` matches a `-`, `|`, `(`, whitespace and `)`? Is it expected? And I guess the language is C#, right? I am asking because what you need is not possible to do with just regex. – Wiktor Stribiżew Sep 17 '15 at 07:48
  • 1
    You're better off doing the check for age in the code. That regex would have to change from day to day, and it's not very good at that. Consider that you need a pattern that takes into account the month and day in addition to the year to calculate exact age. – melwil Sep 17 '15 at 07:50
  • That was not expected so i have to check that one again. Yes the language is C#. Okey i like it very much that you post a solution for it. Thank you! I Have been struggling with this for a bit now so thanks. @stribizhev – Andreas Jangefalk Sep 17 '15 at 08:03
  • If you use the regex to validate the SSN, would you not want a stricter date check? Currently, the SSN of someone born on YYYY-99-99 would be deemed valid, which I assume you don't want. Also, does the SSN have a check digit? If so you should probably also validate that, although that's probably better done after the regex match. – Sigve Kolbeinson Sep 17 '15 at 08:21
  • 1
    Shouldn't swedish SSN's only have 10 digits? Don't think the latter two formats are valid ssn's for Sweden but judging by your name you might know more about this than me, I'm only reading internet information which I used when I wrote our SSN validation, and it said 10 digits. Ah, I see now that 12-digit numbers are starting to be used, OK. Guess it's time to update our SSN validation then. – Lasse V. Karlsen Sep 17 '15 at 09:11

3 Answers3

3

This is a case where I wouldn't use regular expressions, but instead rely on the base class library's built-in DateTime parsing functionality:

public DateTime GetBirthDate(string ssn)
{
    var strippedOfSerial =
        ssn.Substring(0, ssn.Length - 4).TrimEnd('-');
    return DateTime.ParseExact(
        strippedOfSerial,
        new[] { "yyMMdd", "yyyyMMdd" },
        new CultureInfo("se-SE"),
        DateTimeStyles.None);
}

Now you can look at the returned DateTime value and compare it to DateTime.Now in order to figure out if you want to reject it or not.

In my opinion, this is much more readable than relying on regular expressions, and may be safer and more flexible as well. As you can tell, you can e.g. use other cultures etc. to tweak the parsing strategy.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
2

You need to get the birthdate from the SSN, parse to DateTime, and then compare with today's date.

Here is the method checking if a person is of age:

public bool IsOfAge(DateTime birthdate)
{
    DateTime today = DateTime.Today;       // Calculating age...
    int age = today.Year - birthdate.Year;
    if (birthdate > today.AddYears(-age)) 
        age--;
    return age < 18 ? false : true;   // If the age is 18+ > true, else false.
}

And here is how you can use this:

string RegExForValidation = @"^(?<date>\d{6}|\d{8})[-\s]?\d{4}$";
string date = Regex.Match("19970918-1234", RegExForValidation).Groups["date"].Value;
DateTime dt;
if (DateTime.TryParseExact(date, new[] { "yyMMdd", "yyyyMMdd" }, new CultureInfo("sv-SE"), DateTimeStyles.None, out dt))
   Console.WriteLine(IsOfAge(dt));

Note that [-|(\s)] matches a -, |, (, whitespace or ). I am sure you only want to match a hyphen or whitespace.

I added a named capture to the regex and removed unnecessary symbols from the character class. Also, note that {0,1} is the same as ?.

UPDATE

To make it work in an MVC app, you need to implement a custom validator:

[Required(ErrorMessage = "Du måste ange personnummer")]
[ValidateOfAge]  // <---------------------------- HERE
public string PersonalIdentityNumber { get; set; }

And implement this as follows:

public class ValidateOfAge: ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {                 
        string RegExForValidation = @"^(?<date>\d{6}|\d{8})[-\s]?\d{4}$";
        string date = Regex.Match((string)value, RegExForValidation).Groups["date"].Value;
        DateTime dt;
        if (DateTime.TryParseExact(date, new[] { "yyMMdd", "yyyyMMdd" }, new CultureInfo("sv-SE"), DateTimeStyles.None, out dt))
            if (IsOfAge(dt))
                return ValidationResult.Success;
        return new ValidationResult("Personnummer anges med 10 siffror (yymmddnnnn)");
    }
}
Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
  • Okey i see. i get that a have to parse to datetime and then compare it. Im using .NET MVC int this is in my ViewModel so how do you sugest me to implement that. I update my question! – Andreas Jangefalk Sep 17 '15 at 08:19
  • Okej that looks really good. And works for most part. In my case i want the users under 18 to be rejected so if i understand this correctly i could do this public bool IsOfAge(DateTime birthdate) { DateTime today = DateTime.Today; int age = today.Year - 18; if (birthdate.AddYears(birthdate.Year) < today.AddYears(-age)) return false; else return true; } or do you have a better solution to check that? – Andreas Jangefalk Sep 17 '15 at 08:46
  • Oh, sorry, with all that coding I forgot about `18` magic number :) – Wiktor Stribiżew Sep 17 '15 at 08:49
  • Thank you for all the help. It really helped me a lot. And it improve my code significant. Thanks! :) – Andreas Jangefalk Sep 17 '15 at 08:54
  • I finally found a way to check for the 18 age. Please see updated `IsOfAge` method. – Wiktor Stribiżew Sep 17 '15 at 10:44
  • Yes i did something similar. Thank you for the effort of finding a way :) – Andreas Jangefalk Sep 17 '15 at 11:55
0

Instead of doing custom RegEx, I'd suggest looking at a NuGet package available that is able to parse a valid Swedish Personal Identity Number and then extract information such as DateOfBirth, Age, Gender etc that could be used as input to your validation. The method .GetAge() would be the most relevant in your case. Disclaimer: I'm one of the co-authors of that package.

An implementation of ValidateOfAge using SwedishPersonalIdentityNumber could look like:

public class ValidateOfAge : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (SwedishPersonalIdentityNumber.TryParse((string)value, out var personalIdentityNumber))
        {
            if(personalIdentityNumber.GetAge() >= 18) {
                return ValidationResult.Success;
            } else {
                return new ValidationResult("Du måste vara minst 18 år gammal.");
            }
        }

        return new ValidationResult("Ogiltigt personnummer.");
    }
}

SwedishPersonalIdentityNumber supports the patterns you describe, including a few more that is valid (did you know that a PIN can contain a + for example?).

The second one contains a validation attribute for validating a PIN in your model. If the use case of validating age is common enough, we could implement it into that validation attribute. I created an issue to find out about interest and the design.

The API design proposed at the moment is this (for your scenario):

[SwedishPersonalIdentityNumber(MinimumAge = 18)]
public string PersonalIdentityNumber { get; set; }

Please share your thoughts in the issue if this is something you'd like to see implemented.

Peter Örneholm
  • 2,838
  • 20
  • 24