package Se::PersonNr; use strict; use warnings; our $VERSION = '0.01'; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $self = { year => '', month => '', day => '', serial => '', checksum => '', }; bless ($self, $class); $self->personnr(shift) if (@_); return $self; } sub personnr { my $self = shift; if (@_) { my $number = shift; $self->{checksum} = chop($number); $self->{serial} = chop($number); $self->{serial} = chop($number) . $self->{serial}; $self->{serial} = chop($number) . $self->{serial}; if (length($number) < 8) { $self->{year} = substr($number, 0, 2); $number = substr($number, 2); } else { $self->{year} = substr($number, 0, 4); $number = substr($number, 4); } $self->{month} = substr($number, 0, 2); $self->{day} = substr($number, 2, 2); my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = gmtime; $year += 1900; if (length($self->{year}) < 4) { if ($self->{year} > substr($year, 2, 2)) { $self->{year} = (substr($year, 0, 2) - 1) . $self->{year}; } else { $self->{year} = substr($year, 0, 2) . $self->{year}; } if (length($number) == 5) { if (substr($number, 4, 1) eq '+') { $self->{year} = (substr($self->{year}, 0, 2) - 1) . substr($self->{year}, 2, 2); } } } } my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = gmtime; $year += 1900; my $yearsign = '-'; if ($year > ($self->{year} + 100)) { $yearsign = '+'; } return substr($self->{year}, 2, 2) . $self->{month} . $self->{day} . $yearsign . $self->{serial} . $self->{checksum}; } sub year { my $self = shift; $self->{year} = shift if (@_); return $self->{year}; } sub month { my $self = shift; $self->{month} = shift if (@_); return $self->{month}; } sub day { my $self = shift; $self->{day} = shift if (@_); return $self->{day}; } sub serial { my $self = shift; $self->{serial} = shift if (@_); return $self->{serial}; } sub checksum { my $self = shift; $self->{checksum} = shift if (@_); return $self->{checksum}; } sub is_valid { my $self = shift; if ($self->{checksum} == $self->get_valid(substr($self->{year}, 2, 2) . $self->{month} . $self->{day} . $self->{serial})) { return 1; } return 0; } sub get_valid { my $self = shift; my $stn; if (@_) { $stn = shift; } else { $stn = substr($self->{year}, 2, 2) . $self->{month} . $self->{day} . $self->{serial}; } my $digits; my $thismod = 2; # go through each digit and multiply with 2 and 1 alternated while (length($stn) > 0) { $digits .= chop($stn) * $thismod; if ($thismod > 1) { $thismod = 1; } else { $thismod = 2; } } # get the sum of all digits $stn = 0; while (length($digits) > 0) { $stn += int(chop($digits)); } # subtract the rightmost digit from 10 to get checksum $digits = 10 - substr($stn, -1, 1); return $digits; } 1; __END__ =head1 NAME Se::PersonNr - Module for validating and generating a Swedish personnummer. =head1 SYNOPSIS use Se::PersonNr; my $pnr = new Se::PersonNr('511013-3815'); # faked, but valid print "It's fake! Checksum should be: " . $pnr->get_valid() unless ($pnr->is_valid()); =head1 DESCRIPTION Se::PersonNr is a module that is used to validate a Swedish personnummer. A personnummer is a unique number generated by the government for each indivual in Sweden. They are also used as registration numbers for corporations and other business entities. A personnummer consists of six digits representing date of birth follow by three serial number digits and a checksum digit. The date of birth and serial number is split by a plus or minus sign. (The minus sign is replaced by a plus sign when he or she turn 100.) The date part is two digits for the year followed by two for the month and another two for the day of month. Example: A man born on the 13th of October 1951 with serial number 381 gets the personnummer 511013-3815. The module will accept personnummer in the formats yymmdd-sssc, yymmddsssc, yyyymmdd-sssc and yyyymmddsssc but only return the first format. The first format may also use the plus sign. More information about personnummer can be found at: http://www.rsv.se/pdf/70407.pdf (it's in Swedish). =head1 METHODS =over 4 =item $pnr = Se::PersonNr->new($personnummer) Create a new PersonNr object. Optionally you may pass the personummer in the constructor directly. =item $personnummer = $pnr->personnr($newpersonnummer); Returns the personummer in the object and sets a new one if supplied. =item $year = $pnr->year($newyear); Returns the year of birth for the person and sets a new one if supplied. =item $month = $pnr->month($newmonth); Returns the month of birth for the person and sets a new one if supplied. =item $day = $pnr->day($newday); Returns the day of birth for the person and sets a new one if supplied. =item $serial = $pnr->serial($newserial); Returns the serial number for the person and sets a new one if supplied. =item $checksum = $pnr->checksum($newchecksum); Returns the checksum digit for the person and sets a new one if supplied. =item $validity = $pnr->is_valid(); Returns 1 if the checksum digit is valid. No other validation is made. Returns 0 if fake. =item $correctchecksum = $pnr->get_valid($incomplete); Returns the correct checksum digit for the incomplete personnummer supplied. The personnummer passed should have the format yymmddsss. If no incomplete personnummer is passed, it uses the one in the object. =back =head1 TODO =over 4 =item Date validation Check to make sure days and months actually are valid. =item Organisationsnummer Detect corporate and government identities. These numbers have the same format as personnummer. =item Sex Decide if the person is male or female. =item Sammordningsnummer A personnummer registered for a different purpose. =back =head1 AUTHOR Erik Bosrup, erik@bosrup.com Copyright (c) 2001 Erik Bosrup. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO perl(1). =cut