Jack jdeoel
Jack jdeoel

Reputation: 4584

Convert "1d2h3m" to ["day" => 1, ”hour” => 2,"minutes"=>3]

I am trying to parse a time expression string into an associative array with full-word keys.

My input:

$time = "1d2h3m";

My desired output:

array(
    "day" => 1,
    "hour" => 2,
    "minutes" => 3
)

I have tried to extract the numbers with explode().

$time = "1d2h3m";
$day = explode("d", $time);
var_dump($day); // 0 => string '1' (length=1)
                // 1 => string '2h3m' (length=4)

How can I convert my strictly formatted string into the desired associative array?

Upvotes: 11

Views: 2150

Answers (18)

voodoo417
voodoo417

Reputation: 12101

preg_split returns an array of values split by pattern(#[dhm]#).

list() sets the value for each array element.

$d = [];
list($d['day'],$d['hour'],$d['minutes']) = preg_split('#[dhm]#',"1d2h3m");

Upvotes: 3

Alex Nikulin
Alex Nikulin

Reputation: 8689

One line code:

$result = array_combine(array("day", "hour", "minutes"), preg_split('/[dhm]/', "1d2h3m", -1, PREG_SPLIT_NO_EMPTY));

Upvotes: 0

mickmackusa
mickmackusa

Reputation: 47900

sscanf() is certainly an ideal tool for this job. Unlike preg_match(), this native text parsing function avoids creating a useless fullstring match. The numeric values that are extracted can be cast as integers and directly assigned to their respective keys in the result array.

Code (creates reference variables): (Demo)

$time = "1d2h3m";
sscanf($time, '%dd%dh%dm', $result['day'], $result['hour'], $result['minutes']);
var_export($result);

or if you are processing multiple strings in a loop and want to avoid creating reference variables (or find it ugly to unset() the variables at the end of each iteration), then you can "destructure" the return array into the desired associative structure.

Code: (Demo)

$time = "1d2h3m";
[$result['day'], $result['hour'], $result['minutes']] = sscanf($time, '%dd%dh%dm');
var_export($result);

Output:

array (
  'day' => 1,
  'hour' => 2,
  'minutes' => 3,
)

Upvotes: 0

Mike
Mike

Reputation: 1231

You should go with regex for this case

<?php
 $time = "1d2h3m";
 if(preg_match("/([0-9]+)d([0-9]+)h([0-9]+)m/i",$time,$regx_time)){
    $day = (int) $regx_time[1];
    $hour = (int) $regx_time[2];
    $minute = (int) $regx_time[3];
    var_dump($day);
 }
?>

Explaination :
[0-9] : Match any digits between 0 and 9
[0-9]+ : Means, match digits at least one character
([0-9]+) : Means, match digits at least one character and capture the results
/........../i : Set the case insenstive for the pattern of regex you've setted

Regex is the way better to be a lexer and parse string lexically. And it's good to learn regex. Almost all programming language use regex

Upvotes: 16

Vlad DX
Vlad DX

Reputation: 4730

This answer is not 100% relevant to particular input string format from the question.

But it based on the PHP date parsing mechanism (is not my own date parsing bicycle).

PHP >=5.3.0 has DateInterval class.

You could create DateInterval objects from stings in two formats:

$i = new DateInterval('P1DT12H'); // ISO8601 format
$i = createFromDateString('1 day + 12 hours'); // human format

PHP official docs: http://php.net/manual/en/dateinterval.createfromdatestring.php

In ISO8601 format P stands for "period". Format supports three forms of the periods:

  • PnYnMnDTnHnMnS
  • PnW
  • PT

Capital letters represents the following:

  • P is the duration designator (historically called "period") placed at the start of the duration representation.
  • Y is the year designator that follows the value for the number of years.
  • M is the month designator that follows the value for the number of months.
  • W is the week designator that follows the value for the number of weeks.
  • D is the day designator that follows the value for the number of days.
  • T is the time designator that precedes the time components of the representation.
  • H is the hour designator that follows the value for the number of hours.
  • M is the minute designator that follows the value for the number of minutes.
  • S is the second designator that follows the value for the number of seconds.

For example, "P3Y6M4DT12H30M5S" represents a duration of "three years, six months, four days, twelve hours, thirty minutes, and five seconds".

See https://en.wikipedia.org/wiki/ISO_8601#Durations for details.

Upvotes: 0

Amit.S
Amit.S

Reputation: 441

You can use this fn to get the formatted array which check for the string validation as well :

function getFormatTm($str)
{
    $tm=preg_split("/[a-zA-z]/", $str);

    if(count($tm)!=4) //if not a valid string
       return "not valid string<br>";
    else
       return array("day"=>$tm[0],"hour"=>$tm[1],"minutes"=>$tm[2]);
}
$t=getFormatTm("1d2h3m");
var_dump($t);

Upvotes: 0

Sorin
Sorin

Reputation: 5395

There are a lot of great answers here, but I want to add one more, to show a more generic approach.

function parseUnits($input, $units = array('d'=>'days','h'=>'hours','m' => 'minutes')) {
    $offset = 0;
    $idx = 0;

    $result = array();
    while(preg_match('/(\d+)(\D+)/', $input,$match, PREG_OFFSET_CAPTURE, $offset)) {
        $offset = $match[2][1];

        //ignore spaces
        $unit = trim($match[2][0]);
        if (array_key_exists($unit,$units)) { 
            // Check if the unit was allready found
            if (isset($result[$units[$unit]])) {  
                throw new Exception("duplicate unit $unit");
            }

            // Check for corect order of units
            $new_idx = array_search($unit,array_keys($units));
            if ($new_idx < $idx) {
                throw new Exception("unit $unit out of order");             
            } else {
                $idx = $new_idx;
            }
            $result[$units[trim($match[2][0])]] = $match[1][0];

        } else {
            throw new Exception("unknown unit $unit");
        }
    }
    // add missing units
    foreach (array_keys(array_diff_key(array_flip($units),$result)) as $key) {
        $result[$key] = 0;
    }
    return $result;
}
print_r(parseUnits('1d3m'));
print_r(parseUnits('8h9m'));
print_r(parseUnits('2d8h'));
print_r(parseUnits("3'4\"", array("'" => 'feet', '"' => 'inches')));
print_r(parseUnits("3'", array("'" => 'feet', '"' => 'inches')));
print_r(parseUnits("3m 5 d 5h 1M 10s", array('y' => 'years', 
           'm' => 'months', 'd' =>'days', 'h' => 'hours', 
           'M' => 'minutes', "'" => 'minutes', 's' => 'seconds' )));
print_r(parseUnits("3m 5 d 5h 1' 10s", array('y' => 'years', 
           'm' => 'months', 'd' =>'days', 'h' => 'hours',
           'M' => 'minutes', "'" => 'minutes', 's' => 'seconds' )));

Upvotes: 0

Deepak Mittal
Deepak Mittal

Reputation: 199

You could use one line solution,

$time = "1d2h3m";
$day = array_combine(
           array("day","hour","months")   , 
           preg_split("/[dhm]/", substr($time,0,-1)  )    
       );

Upvotes: 0

Deepak Mittal
Deepak Mittal

Reputation: 199

$time = "1d2h3m";
$split = preg_split("/[dhm]/",$time);
$day = array(
    "day"     => $split[0]
    "hour"    => $split[1]
    "minutes" => $split[2]
);

Upvotes: 0

6339
6339

Reputation: 475

We can also achieve this using str_replace() and explode() function.

$time = "1d2h3m"; 
$time = str_replace(array("d","h","m"), "*", $time);
$exp_time =  explode("*", $time); 
$my_array =  array(  "day"=>(int)$exp_time[0],
                     "hour"=>(int)$exp_time[1],
                     "minutes"=>(int)$exp_time[2] ); 
var_dump($my_array);

Upvotes: 0

ManiMuthuPandi
ManiMuthuPandi

Reputation: 1604

Use this simple implementation with string replace and array combine.

<?php
   $time = "1d2h3m";
   $time=str_replace(array("d","h","m")," " ,$time);
   $time=array_combine(array("day","hour","minute"),explode(" ",$time,3));

   print_r($time);
?>

Upvotes: 5

user2182349
user2182349

Reputation: 9782

This approach uses a regular expression to extract the values and an array to map the letters to the words.

<?php
   // Initialize target array as empty
   $values = [];

   // Use $units to map the letters to the words
   $units = ['d'=>'days','h'=>'hours','m'=>'minutes'];

   // Test value
   $time = '1d2h3m';

   // Extract out all the digits and letters
   $data = preg_match_all('/(\d+)([dhm])/',$time,$matches);

   // The second (index 1) array has the values (digits)
   $unitValues = $matches[1];

   // The third (index 2) array has the keys (letters)
   $unitKeys = $matches[2];

   // Loop through all the values and use the key as an index into the keys
   foreach ($unitValues as $k => $v) {
       $values[$units[$unitKeys[$k]]] = $v;
   }

Upvotes: 4

user3942918
user3942918

Reputation: 26385

A simple sscanf will parse this into an array. Then you array_combine it with the list of keys you want.

Example:

$time = "1d2h3m";

$result = array_combine(
    ['day', 'hour', 'minutes'],
    sscanf($time, '%dd%dh%dm')
);

print_r($result);

Output:

Array
(
    [day] => 1
    [hour] => 2
    [minutes] => 3
)

To break down the sscanf format used:

  • %d - reads a (signed) decimal number, outputs an integer
  • d - matches the literal character "d"
  • %d - (as the first)
  • h - matches the literal character "h"
  • %d - (as the first)
  • m - matches the literal character "m" (optional, as it comes after everything you want to grab)

It'll also work fine with multiple digits and negative values:

$time = "42d-5h3m";
Array
(
    [day] => 42
    [hour] => -5
    [minutes] => 3
)

Upvotes: 24

user3035305
user3035305

Reputation: 295

Can't you just explode it 3 times...

// Define an array
$day = explode("d", $time);
// add it in array with key as "day" and first element as value
$hour= explode("h", <use second value from above explode>);
// add it in array with key as "hour" and first element as value
$minute= explode("m", <use second value from above explode>);
// add it in array with key as "minute" and first element as value

I don't have any working example right now but I think it will work.

Upvotes: 0

Halayem Anis
Halayem Anis

Reputation: 7785

Customizable function, and the most important thing, that you can catch an exception if input is not properly formed

/**
  * @param $inputs string : date time on this format 1d2h3m
  * @return array on this format                 "day"      => 1,
  *                                              "hour"     => 2,
  *                                              "minutes"  => 3        
  * @throws Exception
  *
  */
function dateTimeConverter($inputs) {
    // here you can customize how the function interprets input data and how it should return the result
    // example : you can add "y"    => "year"
    //                       "s"    => "seconds"
    //                       "u"    => "microsecond"
    // key, can be also a string
    // example                "us"  => "microsecond"
    $dateTimeIndex  = array("d" => "day",
                               "h" => "hour",
                               "m" => "minutes");

    $pattern        = "#(([0-9]+)([a-z]+))#";
    $r              = preg_match_all($pattern, $inputs, $matches);
    if ($r === FALSE) {
        throw new Exception("can not parse input data");
    }
    if (count($matches) != 4) {
        throw new Exception("something wrong with input data");
    }
    $datei      = $matches[2]; // contains number
    $dates      = $matches[3]; // contains char or string
    $result    = array();
    for ($i=0 ; $i<count ($dates) ; $i++) {
        if(!array_key_exists($dates[$i], $dateTimeIndex)) {
            throw new Exception ("dateTimeIndex is not configured properly, please add this index : [" . $dates[$i] . "]");
        }
        $result[$dateTimeIndex[$dates[$i]]] = (int)$datei[$i];
    }
    return $result;
}

Upvotes: 13

Gaurang Joshi
Gaurang Joshi

Reputation: 695

You can do using this code.

<?php
$str = "1d2h3m";
list($arr['day'],$arr['day'],$arr['hour'],$arr['hour'],$arr['minute'],$arr['minute']) = $str;
print_r($arr);
?>

OUTPUT

Array ( 
   [minute] => 3
   [hour] => 2
   [day] => 1
)

DEMO

Upvotes: -2

Scuzzy
Scuzzy

Reputation: 12332

Another regex solution

$subject = '1d2h3m';
if(preg_match('/(?P<day>\d+)d(?P<hour>\d+)h(?P<minute>\d+)m/',$subject,$matches))
{
  $result = array_map('intval',array_intersect_key($matches,array_flip(array_filter(array_keys($matches),'is_string'))));
  var_dump($result);
}

Returns

array (size=3)
  'day' => int 1
  'hour' => int 2
  'minute' => int 3

Upvotes: 5

blazerunner44
blazerunner44

Reputation: 657

I think for such a small string, and if the format will always be the same, you could use array_push and substr to get extract the numbers out of the string and put them in an array.

<?php
$time = "1d2h3m";
$array = array();
array_push($array, (int) substr($time, 0, 1));
array_push($array, (int) substr($time, 2, 1));
array_push($array, (int) substr($time, 4, 1));
var_dump($array);
?>

Upvotes: 4

Related Questions