PHP Convert any kg amount to a readable metric unit - php

I am trying to create a mathematical function (in PHP) that will take a given number of Kg, and convert them to a readable form or better yet, return the unit best suited for that amount. The input will always be kg. Preferably log.
For example:
5 kg = (5) kg
0.5 kg = (500) gm
1000 kg = (1) tonne
0.001 kg = (1) gm
0.0001 kg = (100) mg
I know there is a way to do it using log or log10 functions but I cannot figure it out.
How could it be done?

I think something like this should work, but I'm sure there are people who can make a much better solution.
function outputWeight($kg)
{
$power = floor(log($kg, 10));
switch($power) {
case 5 :
case 4 :
case 3 : $unit = 'ton';
$power = 3;
break;
case 2 :
case 1 :
case 0 : $unit = 'kilogram';
$power = 0;
break;
case -1 :
case -2 :
case -3 : $unit = 'gram';
$power = -3;
break;
case -4 :
case -5 :
case -6 : $unit = 'milligram';
$power = -6;
break;
default : return 'out of range';
}
return ($kg / pow(10, $power)) . ' ' . $unit;
}
echo outputWeight(0.015) . '<br>';
echo outputWeight(0.15) . '<br>';
echo outputWeight(1.5) . '<br>';
echo outputWeight(15) . '<br>';
echo outputWeight(150) . '<br>';
The idea is that you can easily extend the range. This will output
15 gram
150 gram
1.5 kilogram
15 kilogram
150 kilogram
I did not thoroughly test this code!

After playing with it for a while, here is what I came up with
function readableMetric($kg)
{
$amt = $kg * pow(1000, 3);
$s = array('mcg', 'mg', 'gm', 'kg','tonne');
$e = floor(log10($amt)/log10(1000));
return [
"amount" => $amt/pow(1000, $e),
"unit" => $s[$e]
];
}

The following function internally uses an array with the assignments unit => conversion-factor. This array can easily be expanded or modified to meet your own requirements. A fixed limit of 1000 is used in the function. This means that the output value is always less than 1000, with the exception of tons. With an additional argument which is preset to 2, the number of maximum decimal places can be changed.
function scaleKg(float $kg, int $decimalPlaces = 2) : string {
$scale = [
'micrograms' => 1.E9,
'milligram' => 1.E6,
'gram' => 1.E3,
'kilogram' => 1,
'ton' => 1.E-3,
];
foreach($scale as $unit => $factor){
$mass = $kg * $factor;
if($mass < 1000) {
return round($mass,$decimalPlaces).' '.$unit;
}
}
return round($mass,$decimalPlaces).' '.$unit;
}
Some examples:
$kg = 1.212345;
echo scaleKg($kg); //1.21 kilogram
$kg = 455;
echo scaleKg($kg); //455 kilogram
$kg = 0.0456;
echo scaleKg($kg); //45.6 gram
$kg = 23456;
echo scaleKg($kg); //23.46 ton
$kg = 23489000;
echo scaleKg($kg); //23489 ton
$kg = 167E-6;
echo scaleKg($kg); //167 milligram

I find myself agreeing with Kiko that the shortest code isn't always the best or most readable.
Bearing that in mind bringing log into it seems like unnecessary complication. So I suggest a condensed version of my original code:
function readableMetric($mass)
{
$units = [
-3 => "tonne",
0 => "kg",
3 => "g",
6 => "mg",
];
foreach ($units as $x => $unit) {
if ( ($newMass = $mass * 10 ** $x) >= 1 ) {
return "{$newMass} {$unit}";
}
}
}
This is more extensible (e.g. you could easily add centigram)
The math is intuitive to most (powers of 10)
If you really want to golf it you can shrink it down to a one liner:
function readableMetric($m)
{
foreach([-3=>"tonne",0=>"kg",3=>"g",6 =>"mg"] as $x=>$g)if(($n=$m*10**$x)>=1)return"$n $g";
}
Original Answer
You could just do it with a series of if / else statements?
$values = [
5, 0.5, 1000, 0.001, 0.0001
];
function convertmass($mass) : string
{
if ($mass >= 1000) {
return ($mass / 1000) . " ton";
}
elseif ($mass < 0.001) {
return ($mass * 1000000) . " mg";
}
elseif ($mass < 1) {
return ($mass * 1000 ) . " g";
}
else {
return $mass . " kg";
}
}
foreach ($values as $mass) {
echo convertmass($mass), PHP_EOL;
}
Output:
5 kg
500 g
1 ton
1 g
100 mg
If you want slightly easier/more readable/more user friendly updating to add new measurements in then you can do something like this instead:
$values = [
5, 0.5, 1000, 0.001, 0.0001
];
function convertmass($mass) : string
{
$massLookup = [
[ "unit" => "kg", "min" => 1, "max" => 1000, "multiple" => 1],
[ "unit" => "tonne", "min" => 1000, "max" => 1000000, "multiple" => 0.001],
[ "unit" => "g", "min" => 0.001, "max" => 1, "multiple" => 1000],
[ "unit" => "mg", "min" => 0.000001, "max" => 0.001, "multiple" => 1000000],
];
foreach ($massLookup as $unit) {
if ($mass >= $unit["min"] && $mass < $unit["max"]) {
return ($mass * $unit["multiple"]) . " {$unit["unit"]}";
}
}
return "Measurement {$mass} kg is out of range";
}
foreach ($values as $mass) {
echo convertmass($mass), PHP_EOL;
}

Related

Calculate percentage between two numbers?

I have these two numbers original one is 1000 the new one is 440
I need to calculate the percentage between them as follow:
1000
440
The percentage that getting is 44% and left is 56%
my helper function:
function calculatePercentage($original, $given)
{
$percentChange = (($original - $given) / $original) * 100;
return round(abs($percentChange)) . '%';
}
This gives me the reset percentage!
use this
function calculatePercentage($original, $given)
{
$percentageOfGiven = round(abs( ($given * 100) / $original ));
return [
'getting' => $percentageOfGiven . '%',
'left' => (100 - $percentageOfGiven) . '%'
];
}
The output will be like [ 'getting' => "44%", 'left' => "56%"] .
The principle used is that if the original is 100% then what would be the given value.
Wouldn't it be like
function calculatePercentage($original, $given)
{
$percentChange = ($given / $original) * 100;
return round(abs($percentChange)) . '%';
}
If I am wrong, I probably didn't understand ques enough

How To add shipping rates for weight based shipping in opencart

Actually I want to add shipping rates based on region,I added shipping rates like below,
800:40.00,1250:60.00,1700:80.00,2200:100.00,2700:120.00,99999:40.00
If weight is 800 gms it should take 40 and also it should take from 800-1250gms 40 rs but even 1000gms it is taking 60 rs but it shouldn't take how to resolve this?
Use this code to check in range:
function in_range($number, $min, $max)
{
if (is_int($number) && is_int($min) && is_int($max))
{
return ($number >= $min && $number < $max);
}
return FALSE;
}
Also you have create array like this:
$array = array(
[0] => array(
minw : 1,
maxw : 1249,
rate : 40
),
[1] => array(
minw : 1250,
maxw : 1699,
rate : 60
)
);
After that to get rate use this loop function:
$weight = 500;
function get shipping_rate($array, $weight) {
foreach($array as offset => $arr) {
$minw = $arr['minw'];
$maxw = $arr['maxw'];
$rate = $arr['rate'];
if(in_range($weight, $minw, $maxw)) {
return $rate;
}
}
}

Simple addition and subtraction not working correctly

I've inherited a project written in PHP which has a double entry style cashbook, which requires that the amount going in is equal to the amount going out.
When validating figures entered via a form (to work out if it equals zero), it runs the code below which works fine until there is a decimal number, which seems to make it randomly fall over and return an incorrect value.
In the example below, it returns a really tiny number from 53.87 - 53.87, which should be zero.This has been stripped back to eliminate other things from causing problems, so I've removed a lot of input validation etc.
<?php $inputs = array(
array(
"in" => '',
"out" => '249.6',
),
array(
"in" => '',
"out" => '396',
),
array(
"in" => '554.4',
"out" => ''
),
array(
"in" => '145.07',
"out" => ''
),
array(
"in" => '',
"out" => '53.87',
),
);
$fTotal = 0;
echo "Start at 0: ";
foreach($inputs as $key=>$sRef) {
$itemValid = true;
$aItem = array();
$amountIn = $inputs[$key]['in'];
$amountOut = $inputs[$key]['out'];
if ($itemValid) {
echo "'".$fTotal."'";
$is_in = 0;
if ($amountIn > 0.0) {
$is_in = 1;
echo "+";
$amount = $amountIn;
$fTotal = $fTotal + $amountIn;
} else {
$is_in = 0;
echo "-";
$amount = $amountOut;
$fTotal = $fTotal - $amountOut;
}
echo "'".$amount."'=";
echo "'".$fTotal."' | ";
$aItem["is_in"] = $is_in;
$aItem["amount"] = $amount;
$aItems[] = $aItem;
}
}
You can run this on a sandbox here.
Here is the expected output:
Start at 0: '0'-'249.6'='-249.6' | '-249.6'-'396'='-645.6' | '-645.6'+'554.4'='-91.2' | '-91.2'+'145.07'='53.87' | '53.87'-'53.87'='0' |
Here is the actual output:
Start at 0: '0'-'249.6'='-249.6' | '-249.6'-'396'='-645.6' | '-645.6'+'554.4'='-91.2' | '-91.2'+'145.07'='53.87' | '53.87'-'53.87'='-4.9737991503207E-14' |
What is wrong here?
Update
Following help below, here's the working code for anyone who stumbles on this in the future.
This is happening because of floating point (decimal) maths where computers like binary maths. Sometimes decimal numbers don't have a nice representation in binary so these tiny differences happen.
If you can state that every number can be rounded to 2 decimal points, and based on your limited dataset, wrapping your 'sums' in number_format($fTotal + $amountIn , 2) etc can sort this out for you.
Alternatively for a "more accurate", just wrap the final one eg. echo "'".number_format($fTotal, 2)."' | "; (or 0 or whatever)
Your numbers are being interpreted as strings.
Try this:
<?php
$inputs = array(
array(
"in" => 0,
"out" => 249.6,
),
array(
"in" => 0,
"out" => 396,
),
array(
"in" => 554.4,
"out" => 0
),
array(
"in" => 145.07,
"out" => 0
),
array(
"in" => 0,
"out" => 53.87,
),
);
$fTotal = 0;
echo "Start at 0: ";
foreach($inputs as $key => $sRef) {
$itemValid = true;
$amountIn = $sRef['in'];
$amountOut = $sRef['out'];
if ($itemValid) {
echo $fTotal;
$is_in = 0;
if ($amountIn > 0.0) {
$is_in = 1;
echo "+";
$amount = floatval($amountIn);
$fTotal = floatval($fTotal) + floatval($amountIn);
} else {
$is_in = 0;
echo "-";
$amount = floatval($amountOut);
$fTotal = floatval($fTotal) - floatval($amountIn);
}
echo $amount;
echo ' = ' . $fTotal . " | ";
}
}
The bcsub() function in PHP is an inbuilt function and is used to subtract one arbitrary precision number from another.
Syntax:
string bcsub ( $num_str1, $num_str2, $scaleVal)
Example :
<?php $num_str1 = "8"; $num_str2 = "3"; $res = bcsub($num_str1, $num_str2) echo $res; ?>
Result:
5

Building a Japanese Numeral Reading System on PHP

I faced an interview question which i felt was very good. Couldn't achieve the complete answer, however, felt sharing and asking the right method to code it in PHP.
The question goes as :
Given the Japanese numeral reading system, write a program that converts an integer into the equivalent Japanese reading.
Basic numeral readings:
1: ichi
2: ni
3: san
4: yon
5: go
6: roku
7: nana
8: hachi
9: kyuu
10: juu
20: ni-juu
30: san-juu
100: hyaku
1000 : sen
10,000: man
100,000,000: oku
1,000,000,000,000: chou
10,000,000,000,000,000: kei
Exceptions due to voice rounding in Japanese reading:
300: sanbyaku
600: roppyaku
800: happyaku
3000: sanzen
8000: hassen
1,000,000,000,000: itchou
8,000,000,000,000: hatchou
10,000,000,000,000: jutchou (also applies to multiplies of 10,000,000,000,000)
10,000,000,000,000,000: ikkei
60,000,000,000,000,000: rokkei
80,000,000,000,000,000: hakkei
100,000,000,000,000,000: jukkei (also applies to multiplies of 10,000,000,000,000,000)
1,000,000,000,000,000,000: hyakkei (also applies to multiplies of 1,000,000,000,000,000,000)
Starting at 10,000, numbers begin with ichi if no digit would otherwise precede, e.g. 1,000 is sen but 10,000 is ichi-man.
Examples:
11: juu ichi
17: juu nana
151: hyaku go-juu ichi
302: san-byaku ni
469: yon-hyaku roku-juu kyuu
2025 : ni-sen ni-juu go
10,403: ichi-man yon-byaku san
41,892: yon-juu ichi-man happyaku kyuu-juu ni
80,000,000,000,000: hachi-jutchou
The code that i have tried is :
$inputNumber = 2025;
$inputString = (String)$inputNumber;
$numeralReadings = array(
1 => 'ichi',
2 => 'ni',
3 => 'san',
4 => 'yon',
5 => 'go',
6 => 'roku',
7 => 'nana',
8 => 'hachi',
9 => 'kyuu',
10 => 'juu',
20 => 'ni-juu',
30 => 'san-juu',
100 => 'hyaku',
1000 => 'sen',
10000 => 'man',
100000000 => 'oku',
1000000000000 => 'chou',
10000000000000000 => 'kei'
);
$numeralExceptions = array(
300 => 'sanbyaku',
600 => 'roppyaku',
800 => 'happyaku',
3000 => 'sanzen',
8000 => 'hassen',
1000000000000 => 'itchou',
8000000000000 => 'hatchou',
10000000000000 => 'jutchou',
10000000000000000 => 'ikkei',
60000000000000000 => 'rokkei',
80000000000000000 => 'hakkei',
100000000000000000 => 'jukkei',
1000000000000000000 => 'hyakkei'
);
if ($inputString > 10000) {
$inp1 = floor($inputString / 1000);
$inp = $inputString - ($inp1 * 1000);
if($inp !== 0) {
read($inp1, $numeralReadings, $numeralExceptions, false);
read($inp, $numeralReadings, $numeralExceptions);
} else {
read($inputString, $numeralReadings, $numeralExceptions);
}
} else {
read($inputString, $numeralReadings, $numeralExceptions);
}
function read($inputStr, $numeralReadings, $numeralExceptions, $parse1 = true)
{
$splitString = str_split($inputStr);
$returnString = '';
$appendIchi = false;
$firstNumber = null;
foreach ($splitString as $key => $number) {
if ($firstNumber == null) {
$firstNumber = $number;
}
if ($number !== 0) {
$int = 1;
$a = count($splitString) - 1 - $key;
for ($i = 0; $i < $a; $i++) {
$int = $int * 10;
}
$tempNumber = (int)$number * $int;
if (isset($numeralExceptions[$tempNumber])) {
$returnString .= $numeralExceptions[$tempNumber] . ' ';
continue;
}
if (isset($numeralReadings[$tempNumber])) {
if ($parse1 == false && $tempNumber == 1) {
continue;
}
$returnString .= $numeralReadings[$tempNumber] . ' ';
continue;
}
if (isset($numeralReadings[(int)$number])) {
if ($parse1 == false && $tempNumber == 1) {
continue;
}
$returnString .= $numeralReadings[(int)$number];
if ($int !== 1) {
$returnString .= '-' . $numeralReadings[$int];
}
$returnString .= ' ';
}
}
}
echo $returnString;
}
here is a fiddle that shows the code in running. You might want to try it online. Link
With the code above, i was able to achieve all the examples stated above other than the last 2.
Anyone who can solve this in a better way?
I guess you can simply use array_key_exists() here
function read($inputStr, $numeralReadings, $numeralExceptions)
{
if(array_key_exists($inputStr, $numeralReadings))
{
return $numeralReadings[$inputStr];
}
else if(array_key_exists($inputStr, $numeralExceptions))
{
return $numeralExceptions[$inputStr];
}
else
{
return "not found";
}
}

How to validate a VIN number?

How can I validate a Vehicle Identification Number with PHP? I just need to check if the entered VIN number is correct or not.
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
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));
}
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});
}
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.');
}
}
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.
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'));
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;
}
}

Categories