views:

900

answers:

5

Here I am faced with an issue that I believe(or at least hope) was solved 1 million times already. What I got as the input is a string that represents a length of an object in imperial units. It can go like this:

$length = "3' 2 1/2\"";

or like this:

$length = "1/2\"";

or in fact in any other way we normally would write it.

In effort to reduce global wheel invention, I wonder if there is some function, class, or regexp-ish thing that will allow me to convert Imperial length into Metric length?

+1  A: 

Perhaps check out the units library? There doesn't seem to be a PHP binding for it, though.

strager
+1  A: 

The regexp would look something like this:

"([0-9]+)'\s*([0-9]+)\""

(where \s represents whitespace - I'm not sure how it works in php). Then you extract the first + second group and do

(int(grp1)*12+int(grp2))*2.54

to convert to centimeters.

Claudiu
+3  A: 

Here is my solution. It uses eval() to evaluate the expression, but don't worry, the regex check at the end makes it completely safe.

function imperial2metric($number) {
    // Get rid of whitespace on both ends of the string.
    $number = trim($number);

    // This results in the number of feet getting multiplied by 12 when eval'd
    // which converts them to inches.
    $number = str_replace("'", '*12', $number);

    // We don't need the double quote.
    $number = str_replace('"', '', $number);

    // Convert other whitespace into a plus sign.
    $number = preg_replace('/\s+/', '+', $number);

    // Make sure they aren't making us eval() evil PHP code.
    if (preg_match('/[^0-9\/\.\+\*\-]/', $number)) {
        return false;
    } else {
        // Evaluate the expression we've built to get the number of inches.
        $inches = eval("return ($number);");

        // This is how you convert inches to meters according to Google calculator.
        $meters = $inches * 0.0254;

        // Returns it in meters. You may then convert to centimeters by
        // multiplying by 100, kilometers by dividing by 1000, etc.
        return $meters;
    }
}

So for example, the string

3' 2 1/2"

gets converted to the expression

3*12+2+1/2

which gets evaluated to

38.5

which finally gets converted to 0.9779 meters.

yjerem
...to centimeters by MULTIPLYING by 100, kilometers by DIVIDING by 1000...
Sani Huttunen
Oh right, I will edit that. Thanks.
yjerem
What about "3 ft 2.5 in"? :DWhat about yards? Chains, rods, perches, poles and so on are a tad esoteric, but furlongs are used in horse racing (as well as the mythical furlongs per fortnight).
Jonathan Leffler
Jeremy Really cool solution. I will honor you by stealing it!Jonathan for "3 ft 2.5 in" you just need a couple for extra regexes to convert "ft" to "*12" and "in" to "".
James Anderson
ehh.... this is really over-kill. the regex grouping solution is 2 lines long and doesn't eval anything, which is a lot more inefficient as php has to parse the expression, form the AST, then evaluate it. -1
Claudiu
@Claudiu: Your regex doesn't handle fractions like '1/2' or mixed fractions like '1 1/2'.
yjerem
@jeremy - ah, true, i didn't see this. if those are given, then yeah, this way is better.
Claudiu
+4  A: 

The Zend Framework has a measurement component for just that purpose. I suggest you check it out - here.

$unit = new Zend_Measure_Length($length,Zend_Measure_Length::YARD);
$unit -> convertTo(Zend_Measure_Length::METER);
Eran Galperin
Wow! nice component... Lacks documentation though, but I'd definitely check it out!
xelurg
A: 

The imperial string values a little bit more complicater, so I used following expression:

string pattern = "(([0-9]+)')*\\s*-*\\s*(([0-9])*\\s*([0-9]/[0-9])*\")*";
Regex regex = new Regex( pattern );
Match match = regex.Match(sourceValue);
if( match.Success ) 
{
    int feet = 0;
    int.TryParse(match.Groups[2].Value, out feet);
    int inch = 0;
    int.TryParse(match.Groups[4].Value, out inch);
    double fracturalInch = 0.0;
    if (match.Groups[5].Value.Length == 3)
         fracturalInch = (double)(match.Groups[5].Value[0] - '0') / (double)(match.Groups[5].Value[2] - '0');

    resultValue = (feet * 12) + inch + fracturalInch;
Thomas