How to validate a VIN number?
Asked Answered
D

8

22

How can I validate a Vehicle Identification Number with PHP? I just need to check if the entered VIN number is correct or not.

Dickenson answered 30/9, 2010 at 14:51 Comment(6)
Vehicle Identification Number ?Dripstone
Please define "VIN". This is an international community, and acronyms are seldom unique in their meaning across the globeWinegrower
It depends on exactly which VIN you are talking about. There are ISO and European versions; there are also versions for quantities of <500 and >=500, all of which have different validation procedures.Shingles
en.wikipedia.org/wiki/… gives a fairly sufficient step by step procedure to calculate a VIN checkdigit. This should give you enough info to write a validate procedure.Buine
In case anyone comes across this (as I did today), vpic.nhtsa.dot.gov/api supplies vin lookups free.Monostich
VIN number y redundant, VIN means Vehicle Identification Number as is.Isaacs
B
28

Here's something I wrote up real quick using the example in the wikipedia article.

Not guaranteed perfect or bug free or super efficient, but should provide you with a solid starting point:

Note: I included the edits provided by Confluence below, making the procedure slightly more succinct.

function validate_vin($vin) {

    $vin = strtolower($vin);
    if (!preg_match('/^[^\Wioq]{17}$/', $vin)) { 
        return false; 
    }

    $weights = array(8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2);

    $transliterations = array(
        "a" => 1, "b" => 2, "c" => 3, "d" => 4,
        "e" => 5, "f" => 6, "g" => 7, "h" => 8,
        "j" => 1, "k" => 2, "l" => 3, "m" => 4,
        "n" => 5, "p" => 7, "r" => 9, "s" => 2,
        "t" => 3, "u" => 4, "v" => 5, "w" => 6,
        "x" => 7, "y" => 8, "z" => 9
    );

    $sum = 0;

    for($i = 0 ; $i < strlen($vin) ; $i++ ) { // loop through characters of VIN
        // add transliterations * weight of their positions to get the sum
        if(!is_numeric($vin{$i})) {
            $sum += $transliterations[$vin{$i}] * $weights[$i];
        } else {
            $sum += $vin{$i} * $weights[$i];
        }
    }

    // find checkdigit by taking the mod of the sum

    $checkdigit = $sum % 11;

    if($checkdigit == 10) { // checkdigit of 10 is represented by "X"
        $checkdigit = "x";
    }

    return ($checkdigit == $vin{8});
}

Note: there is a small percent error with verifying VINs because of the nature of the checksum:

...a match does not prove the VIN is correct, because there is still a 1 in 11 chance of any two distinct VINs having a matching check digit.

Also note: 11111111111111111 will validate against the procedure above. Whether or not you want to check for that is up to you:

Straight-ones (seventeen consecutive '1's) will suffice the check-digit. This is because a value of one, multiplied against 89 (sum of weights), is still 89. And 89 % 11 is 1, the check digit. This is an easy way to test a VIN-check algorithm.

reference: http://en.wikipedia.org/wiki/Vehicle_identification_number#Check_digit_calculation

Buine answered 30/9, 2010 at 15:22 Comment(4)
respect for going as far as to whip something up to this extent. +5 if I could.Deuterogamy
I have a Discrete Math/Algorithms exam in about 30 minutes, figured it wouldn't hurt to get my mind rolling. : )Buine
@Confluence the exam was far simpler than I had anticipated: mostly just analyzing time complexity of various algorithms... and thanks for the edit! Programming before noon has never been my strong suit...Buine
Take into consideration that some other old vehicles have different VIN length. This are the vehicles produced prior to model year 1981. Source dataoneIsaacs
I
7

Here's a version of the code by jordan ported to Javascript, hope it's helpful to someone...

function validate_vin(vin)
{
  function isnumeric(mixed_var) {
    return (typeof(mixed_var) === 'number' || typeof(mixed_var) === 'string') && mixed_var !== '' && !isNaN(mixed_var);
  }
  var pattern = /^[^\Wioq]{17}$/;
  var weights = Array(8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2);
  var transliterations = {
    "a" : 1, "b" : 2, "c" : 3, "d" : 4,
    "e" : 5, "f" : 6, "g" : 7, "h" : 8,
    "j" : 1, "k" : 2, "l" : 3, "m" : 4,
    "n" : 5, "p" : 7, "r" : 9, "s" : 2,
    "t" : 3, "u" : 4, "v" : 5, "w" : 6,
    "x" : 7, "y" : 8, "z" : 9
  };

  vin = vin.toLowerCase();
  if(!vin.match(pattern)) { return false; }

  var sum = 0;
  for(var i=0; i<vin.length; i++) {
    if(!isnumeric(vin.charAt(i))) {
      sum += transliterations[vin.charAt(i)] * weights[i];
    } else {
      sum += parseInt(vin.charAt(i)) * weights[i];
    }  
  }

  var checkdigit = sum % 11;
  if(checkdigit == 10) { // check digit of 10 represented by X
    checkdigit = 'x';
  }

  return (checkdigit == vin.charAt(8));
}
Impeachable answered 12/11, 2011 at 4:23 Comment(0)
K
3

It's "VIN." "VIN Number" = "Vehicle Identification Number Number," which doesn't make sense.

You can see a definition of the structure of VINs here:
http://en.wikipedia.org/wiki/Vehicle_identification_number

And you can work from there, or you can grab this script here:
http://www.geekpedia.com/code29_Check-if-VIN-number-is-valid.html


Here is an improved version of the function posted by jordan:

$vin = "1M8GDM9AXKP042788";

function validate_vin($vin) {

    $vin = strtolower($vin);
    if (!preg_match('/^[^\Wioq]{17}$/', $vin)) { 
        return false; 
    }

    $weights = array(8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2);

    $transliterations = array(
        "a" => 1, "b" => 2, "c" => 3, "d" => 4,
        "e" => 5, "f" => 6, "g" => 7, "h" => 8,
        "j" => 1, "k" => 2, "l" => 3, "m" => 4,
        "n" => 5, "p" => 7, "r" => 9, "s" => 2,
        "t" => 3, "u" => 4, "v" => 5, "w" => 6,
        "x" => 7, "y" => 8, "z" => 9
    );

    $sum = 0;

    for($i = 0 ; $i < strlen($vin) ; $i++ ) { // loop through characters of VIN
        // add transliterations * weight of their positions to get the sum
        if(!is_numeric($vin{$i})) {
            $sum += $transliterations[$vin{$i}] * $weights[$i];
        } else {
            $sum += $vin{$i} * $weights[$i];
        }
    }

    // find checkdigit by taking the mod of the sum

    $checkdigit = $sum % 11;

    if($checkdigit == 10) { // checkdigit of 10 is represented by "X"
        $checkdigit = "x";
    }

    return ($checkdigit == $vin{8});
}
Kilah answered 30/9, 2010 at 14:54 Comment(4)
Apologies, I meant Vehicle Identification number.Dickenson
Nissan has provided us with a vin number (JN1GANR35U0100001) which fails this validation. Is there a different calculation for US or EUROPE cars?Dickenson
Even though "VIN Number" is a redundancy (and technically incorrect), it's still common to refer to them as "VIN numbers" in both speech and writing. It's the same with "PIN Number" or "ATM Machine".Imperceptible
That's called "pleonasm", also known as "RAS syndrome".Katzen
T
2

I recently had to write a VIN validation class with PHP. I posted my class for everyone to use at: http://dev.strategystar.net/2012/05/validate-vin-checksum-with-php/

class VIN
{
    public static $transliteration = array(
        'A'=>1, 'B'=>2, 'C'=>3, 'D'=>4, 'E'=>5, 'F'=>6, 'G'=>7, 'H'=>8, 
        'J'=>1, 'K'=>2, 'L'=>3, 'M'=>4, 'N'=>5, 'P'=>7, 'R'=>9,
        'S'=>2, 'T'=>3, 'U'=>4, 'V'=>5, 'W'=>6, 'X'=>7, 'Y'=>8, 'Z'=>9,
    );

    public static $weights = array(8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2);

    /***
     * The checksum method is used to validate whether or not a VIN is valid
     * It will return an array with two keys: status and message
     * The "status" will either be boolean TRUE or FALSE
     * The "message" will be a string describing the status
     */
    public static function checksum($vin)
    {
        $vin = strtoupper($vin);
        $length = strlen($vin);
        $sum = 0;

        if($length != 17)
        {
            return array('status'=>false, 'message'=>'VIN is not the right length');
        }

        for($x=0; $x<$length; $x++)
        {
            $char = substr($vin, $x, 1);

            if(is_numeric($char))
            {
                $sum += $char * self::$weights[$x];
            }
            else
            {
                if(!isset(self::$transliteration[$char]))
                {
                    return array('status'=>false, 'message'=>'VIN contains an invalid character.');
                }

                $sum += self::$transliteration[$char] * self::$weights[$x];
            }
        }

        $remainder = $sum % 11;
        $checkdigit = $remainder == 10 ? 'X' : $remainder;

        if(substr($vin, 8, 1) != $checkdigit)
        {
            return array('status'=>false, 'message'=>'The VIN is not valid.');
        }

        return array('status'=>true, 'message'=>'The VIN is valid.');
    }
}
Thoma answered 9/5, 2012 at 19:7 Comment(0)
S
1

Note that the wiki says: "A check-digit validation is used for all road vehicles sold in the United States and Canada."

So if you're working with other countries you might want to loosen the check-digit validation

https://www.olschimke.eu/2012/08/02/dealing-with-vehicle-identification-numbers-vin-data-quality/ had some good tips.

Suwannee answered 2/6, 2021 at 13:25 Comment(1)
is there a way to validate a VIN from say... Mexico?Howitzer
R
0

Thanks to all for the algorithm etc. which I see is on Wikipedia. This is the version I put together based on the comments above. Note, there are problems with the versions above, for ex this 00000000000354888 returns OK for a vin. I took what was above and added an option to check year first if <1980, assume it isn't a 17digit real vin (have to), and also if there are sequences of a single character, assume invalid. This is good enough for my needs as I am comparing against values that are filled with 0s if not 17 in length. Also I know the year value so if I check that I can speed up the code by skipping the vin check (yes I could have put that in before the function) lol bye!.

  <?php
  /*
  =======================================================================================
  PURPOSE: VIN Validation (Check-digit validation is compulsory for all road vehicles sold in North America.)
  DETAILS: Validates 17 digit VINs by checking their formatting
  USAGE:  returns boolean or returns an array with a detailed message
  COMMENTS: This could be made more robust by checking the country codes etc..
  MORE INFO: https://en.wikipedia.org/wiki/Vehicle_identification_number
  =======================================================================================
  */

  class vinValidation {

public static $transliteration = array(
    'A'=>1, 'B'=>2, 'C'=>3, 'D'=>4, 'E'=>5, 'F'=>6, 'G'=>7, 'H'=>8,
    'J'=>1, 'K'=>2, 'L'=>3, 'M'=>4, 'N'=>5, 'P'=>7, 'R'=>9,
    'S'=>2, 'T'=>3, 'U'=>4, 'V'=>5, 'W'=>6, 'X'=>7, 'Y'=>8, 'Z'=>9,
);

public static $weights = array(8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2);

public function validateVIN($vin, $ret_array_status = false, $year = null) {

    //validates US/NA 1980>= VINs, if before 1980, no vin standards, this returns false
    if (!empty($year) && preg_match("/^[0-9]{4}/", $year)) {
        if ($year < 1980) return ($ret_array_status ? array('status' => false, 'message' => 'Unable to check VIN, pre-dates 1980.') : false);
    }

    $vin_length = 17; // US vin requirements >= 1980
    $vin = strtoupper(trim($vin));
    $sum = 0;

     //if (!preg_match('/^[^\Wioq]{17}$/', $vin))
    //return ($ret_array_status ? array('status'=>false, 'message'=>'VIN is not valid, not the right length.') : false);

    if (!preg_match('/^[A-HJ-NPR-Z0-9]{17}$/', $vin))
    return ($ret_array_status ? array('status'=>false, 'message'=>'VIN is not valid, VIN formatting is incorrect.') : false);

    if (preg_match('/(\w)\1{5,}/', $vin))
    return ($ret_array_status ? array('status'=>false, 'message'=>'VIN contains invalid repeating character sequence.') : false);

    for($x=0; $x < $vin_length; $x++) {
        $char = substr($vin, $x, 1);
        if(is_numeric($char)) {
            $sum += $char * self::$weights[$x];
        } else {
            if(!isset(self::$transliteration[$char]))
            return ($ret_array_status ? array('status'=>false, 'message'=>'VIN contains an invalid character.') : false);
            $sum += self::$transliteration[$char] * self::$weights[$x];
        }
    }
    $remainder = $sum % 11;
    $checkdigit = $remainder == 10 ? 'X' : $remainder;

    //echo " sum:".$sum." remain:".$remainder." check dig:".$checkdigit."\n";

    if(substr($vin, 8, 1) != $checkdigit)
    return ($ret_array_status ? array('status'=>false, 'message'=>'The VIN is not valid, failed checksum.') : false);

    // all is good return true or a value and status.
    return ($ret_array_status ? array('status'=>true, 'message'=>'The VIN is valid, passed checksum.') : true);
}


  }

TESTING :

  $vinClass = new vinValidation();

  // not long enough not val
  var_dump($vinClass->validateVIN('1I345678123456789', false, 2000));
  var_dump($vinClass->validateVIN('1I345678123456789'));

  echo "-----------------------------------------------------------\n";
  // not valid
  var_dump($vinClass->validateVIN('00000000012870842', true));
  var_dump($vinClass->validateVIN('00000000012870842', 1968)); //assumes faulty by year
  var_dump($vinClass->validateVIN('00000000012870842'));

  echo "-----------------------------------------------------------\n";
  // not valid
  var_dump($vinClass->validateVIN('00000000000354888', true));
  var_dump($vinClass->validateVIN('00000000000354888'));

  echo "-----------------------------------------------------------\n";
  // Fails Checksum test
  var_dump($vinClass->validateVIN('368TU79MXH4763452',false,2000));
  var_dump($vinClass->validateVIN('368TU79MXH4763452'));

  echo "-----------------------------------------------------------\n";
  // yachtzee, (returns true or array) !
  var_dump($vinClass->validateVIN('WP1AF2A56GLB91679',true));
  var_dump($vinClass->validateVIN('WP1AF2A56GLB91679'));
Restrain answered 5/12, 2016 at 18:0 Comment(0)
J
0

Here is the JavaScript class version as posted by Mike Q:

class VIN {
    static transliteration = {
        'A':1, 'B':2, 'C':3, 'D':4, 'E':5, 'F':6, 'G':7, 'H':8, 'J':1, 'K':2, 'L':3, 'M':4, 'N':5, 'P':7, 'R':9, 'S':2, 'T':3, 'U':4, 'V':5, 'W':6, 'X':7, 'Y':8, 'Z':9
    }

    static weights = [8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2];

    validateVIN(vin, retArrayStatus = false, year = null) {

        if (year != null && year.match(/^[0-9]{4}/)) {
            if (year < 1980)
                return retArrayStatus ? {'status': false, 'message': 'Unable to check VIN, pre-dates 1980.'} : false;
        }

        let vinLength = 17;
        vin = vin.trim();
        let sum = 0;

        if (!vin.match(/^[A-HJ-NPR-Z0-9]{17}$/))
            return retArrayStatus ? {'status': false, 'message': 'VIN is not valid, VIN formatting is incorrect [i, o, q].'} : false;

        //if (!vin.match(/(\w)\1{5,}/))
        //    return retArrayStatus ? {'status': false, 'message': 'VIN contains invalid repeating character sequence.'} : false;

        for (let x = 0; x < vinLength; x++) {
            let char = vin.substr(x, 1);
            if (!isNaN(char)) {
                sum += char * VIN.weights[x];
            }
            else {
                if (VIN.transliteration[char] == '')
                    return retArrayStatus ? {'status': false, 'message': 'VIN contains an invalid character.'} : false;
                sum += VIN.transliteration[char] * VIN.weights[x];
            }
        }
        let reminder = sum % 11;
        let checkdigit = reminder == 10 ? 'X' : reminder;

        if (vin.substr(8, 1) != checkdigit)
            return retArrayStatus ? {'status': false, 'message': 'The VIN is not valid, failed checksum.'} : false;

        return retArrayStatus ? {'status': true, 'message': 'The VIN is valid, passed checksum.'} : true;
    }
}
Jar answered 12/2, 2021 at 11:40 Comment(0)
L
0

Really liked bimbom22 code but after php 8.1.18 update throws this error: PHP Fatal error: Array and string offset access syntax with curly braces is no longer supported

Here is the updated syntax to address the Fatal error at php 8.1.18

function validate_vin($vin) {
    $vin = strtolower($vin);
    if (!preg_match('/^[^\Wioq]{17}$/', $vin)) { 
        return false; 
    }
    $weights = array(8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2);
    $transliterations = array(
        "a" => 1, "b" => 2, "c" => 3, "d" => 4,
        "e" => 5, "f" => 6, "g" => 7, "h" => 8,
        "j" => 1, "k" => 2, "l" => 3, "m" => 4,
        "n" => 5, "p" => 7, "r" => 9, "s" => 2,
        "t" => 3, "u" => 4, "v" => 5, "w" => 6,
        "x" => 7, "y" => 8, "z" => 9
    );
    $sum = 0;
    for($i = 0 ; $i < strlen($vin) ; $i++ ) { // loop through characters of VIN
        // add transliterations * weight of their positions to get the sum
        $check_char = substr($vin, $i, 1);
        if(!is_numeric($check_char)) {
            $sum += $transliterations[$check_char] * $weights[$i];
        } else {
            $sum += $check_char * $weights[$i];
        }
    }

    // find checkdigit by taking the mod of the sum
    $checkdigit = $sum % 11;
    if($checkdigit == 10) { // checkdigit of 10 is represented by "X"
        $checkdigit = "x";
    }
    $actual_checkdigit = substr($vin, 8, 1);
    return ($checkdigit == $actual_checkdigit);
}
Lawana answered 28/4, 2023 at 20:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.