Validating a CPR number

A Danish CPR number (Person Identification Number) is date of birth followed by 4 digits (the last one being a control digit): DDMMYY-XXXX

The final digit validates the CPR number using Modulo 11 by satisfying the following equation:


where the enter image description here are the ten digits of the complete ID number, and the coefficients (4, 3, 2, 7, …) are all nonzero in the finite field of order 11.


A CPR number formatted like this DDMMYY-XXXX where YY indicates a year within the last 100 years (between 1917 and 2016).


Indication whether CPR number is valid (1 or true) or invalid (0 or false)


  290215-2353 --> false
  140585-2981 --> true
  311217-6852 --> true
  121200-0004 --> false
  140322-5166 --> false
  111111-1118 --> true


This is - fewest bytes win!

Date validation

All years refer to a period within the last 100 years. A valid date is a date that exists within it's calendar year.

290215 is not a valid date because 29/2 wasn't a date in 2015.

301216 is a valid date because 30/12 is a date in 2016.

  • 1
    \$\begingroup\$ Can I receive the input as an array of the ten digits? \$\endgroup\$
    – Leaky Nun
    Jul 20, 2016 at 17:09
  • 1
    \$\begingroup\$ Could you show how the first one evaluates to be false? \$\endgroup\$
    – Leaky Nun
    Jul 20, 2016 at 17:11
  • 1
    \$\begingroup\$ @LeakyNun: Look at the date :) \$\endgroup\$
    – Daniel
    Jul 20, 2016 at 17:13
  • 4
    \$\begingroup\$ What kind of date validation needs to be done? Do we have to check for leap years? Calendar changes? Check if the birthday is more recent than today? \$\endgroup\$ Jul 20, 2016 at 17:23
  • 1
    \$\begingroup\$ I'm guessing that the symbol 0 represents the digit with decimal value 10, but that could be clearer. \$\endgroup\$ Jul 20, 2016 at 21:33

4 Answers 4


Python (2), 167 156 155 142 141 bytes

from time import*
def f(s):
 try:return 1>sum(int(i)*(ord(j)-48)for i,j in zip("43276504321",s))%11;strptime(s[:6],"%d%m%y")
 except:return 0

tried to do the date validation without the library, but it's 168 bytes:

lambda s:sum(int(i)*(ord(j)-48)for i,j in zip("43276504321",s))%11==0 and int(s[0:2])<([0,32,(29,30)[int(s[4:6])%4==0],32]+[31,32]*2+[32,31]*2+[32]+[0]*99)[int(s[2:4])]

-12 bytes thanks to @GáborFekete

-14 bytes thanks to @RootTwo

  • 2
    \$\begingroup\$ You can golf some bytes by using s[:6] instead of s.split("-")[0] and also by removing the space before the for. \$\endgroup\$ Jul 20, 2016 at 21:37
  • \$\begingroup\$ thank you, now i just gotta save 1 byte somewhere to beat the php solution despite the horrendous date validation in Python. \$\endgroup\$
    – KarlKastor
    Jul 20, 2016 at 21:52
  • \$\begingroup\$ Move the last line after the line starting with try using a semicolon. \$\endgroup\$ Jul 20, 2016 at 21:53
  • \$\begingroup\$ Does that save characters? (you lose a " " but add a ";"). However, I had a similar idea before I read your comment (see above) \$\endgroup\$
    – KarlKastor
    Jul 20, 2016 at 22:01
  • \$\begingroup\$ Great answer! When golfing, it is a good idea to keep your old byte counts by surrounding them with <s> and <\s> in the markdown editor, which adds a strikethrough. \$\endgroup\$ Jul 20, 2016 at 22:07

Ruby, 145 113 112 103 + 8 (-nrdate flags) = 111 bytes

Returns a date object (truthy) if it's a valid CPR, false if it fails the modulo test, and nil (falsy) if it fails the date test. Input is STDIN.

-33 bytes from @Jordan

p s%11<1&&Date.strptime($_,"%d%m%y")rescue p p

Version that returns literal true on valid CPR for +1 byte:

p Date.strptime($_,"%d%m%y")&&s%11<1 rescue p p

Version that returns an error on invalid date instead of nil, for -10 bytes but probably invalid:

p Date.strptime($_,"%d%m%y")&&s%11<1
  • \$\begingroup\$ Might I suggest Date.strptime("%d%m%y", $_)? \$\endgroup\$
    – Jordan
    Jul 21, 2016 at 21:08
  • \$\begingroup\$ @Jordan I was about to say that it would cause errors for "121118" giving the year as 2018, but then I remembered that the leap years are all the same (except for 1900 which is out of the year scope anyways). Also, the arguments are in the reverse order so I fixed that. \$\endgroup\$
    – Value Ink
    Jul 21, 2016 at 21:38

Mathematica, 130 bytes


Anonymous function. Takes a string as input and returns True or False as output. The Unicode character is U+2223 DIVIDES, representing \[Divides].


Ruby + GNU date, 112 111 103 bytes

-8 bytes from Kevin Lau.

s%11<1&&`date -d#$3#$2#$1`>""}

Note that this prints e.g. "date: invalid date ‘150229’" to stderr if the date portion is invalid, but it still returns true or false (it doesn't raise an exception). If that's a disqualifier, though, I can add ⎵2>/dev/null for a 12-byte penalty.


v = ->(cpr) {
  sum = 0

  cpr.size.times do |idx|
    sum += 12340567234 / 10**idx % 10 * cpr[idx].to_i

  cpr =~ /#{"(..)" * 3}/
  sum % 11 < 1 && `date -d#$3#$2#$1` > ""
  • \$\begingroup\$ Errors are allowed? If so that cuts a lot of bytes off of my own Ruby solution :o \$\endgroup\$
    – Value Ink
    Jul 21, 2016 at 21:40
  • \$\begingroup\$ @KevinLau-notKenny I think that's up to Daniel, hence the note. \$\endgroup\$
    – Jordan
    Jul 21, 2016 at 21:44
  • \$\begingroup\$ FWIW this still returns true or false, even when the date is invalid. It doesn't raise an exception. \$\endgroup\$
    – Jordan
    Jul 21, 2016 at 21:46
  • \$\begingroup\$ Hmm, that's true. I'm tempted to use your zip trick with the block but it feels like cheating and I'd probably outpace yours by using it... \$\endgroup\$
    – Value Ink
    Jul 21, 2016 at 21:54
  • 1
    \$\begingroup\$ It's OK, I managed to tie you with an alternate solution (turns out the zip trick wasn't enough bytes saved). And now you can use my technique and make yours even shorter! \$\endgroup\$
    – Value Ink
    Jul 21, 2016 at 22:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.