Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Retterer committed Feb 11, 2016
0 parents commit 1fa460f
Show file tree
Hide file tree
Showing 8 changed files with 394 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/vendor
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# PHP ISO8601 Duration Converter
Easily converts ISO 8601 Durations to Seconds and Seconds to ISO 8601 Durations

## Installation
```sh
composer require bretterer/iso_duration_converter
```

## Usage
```php
$converter = new \Bretterer\IsoDurationConverter\DurationParser();
$converter->parse('PT8S'); // Returns 8
$converter->parse('PT5M'); // Returns 300
$converter->parse('PT20H'); // Returns 72000
$converter->parse('PT6M4S'); // Returns 364

$converter->compose(8); // Returns PT8S
$converter->composer(300); // Returns PT5M
$converter->composer(7200); // Returns PT20H
$converter->compose(364); //Returns PT6M4S

$converter->parse('P5W'); // Returns 3024000
// To Returns Weeks, The second argument should be true
$converter->compose(3024000, true); // Returns P5W
$converter->compose(3024000); // Returns P35D

$converter->parse('Hello World'); // Throws 'Invalid Argument Exception' with Message 'Invalid Duration'
$converter->parse('P10Y10M10D'); // Throws 'Invalid Argument Exception' with Message 'Ambiguous Duration'
```

## Years and Months
If years are passed into the `parse` method, an `invalid argument exception` will be thrown.

If you are wanting to convert seconds into months, pass true as the second argument in the `compose` method

## License
MIT
22 changes: 22 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "bretterer/iso_duration_converter",
"description": "Easily convert ISO_8601 Duration time format to seconds and convert seconds to ISO-8601 Duration",
"license": "MIT",
"authors": [
{
"name": "Brian Retterer",
"email": "[email protected]"
}
],
"autoload": {
"psr-4": {
"Bretterer\\IsoDurationConverter\\": "src/"
}
},
"autoload-dev": {
"classmap": [
"tests/TestCase.php"
]
},
"require": {}
}
17 changes: 17 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/Bootstrap.php"
colors="true"
backupGlobals="false">

<testsuites>
<testsuite name="Unit Tests">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>

<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>
129 changes: 129 additions & 0 deletions src/DurationParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

namespace Bretterer\IsoDurationConverter;

class DurationParser
{
const WEEK = '/^P([0-9]+W)$/';
const DATE_TIME = '/^P(([0-9]+Y)?([0-9]+M)?([0-9]+D)?)?(T([0-9]+H)?([0-9]+M)?([0-9]+S)?)?$/';

private $totalTime;
private $composedDuration;

public function parse($duration)
{
$m = [];
preg_match(self::WEEK, $duration, $m);

if ($m) {
return $this->parsePart('week', $m[1]);
}

preg_match(self::DATE_TIME, $duration, $m);

if ($m) {
return (
$this->parsePart('date', isset($m[2]) ? $m[2] : null) +
$this->parsePart('date', isset($m[3]) ? $m[3] : null) +
$this->parsePart('date', isset($m[4]) ? $m[4] : null) +
$this->parsePart('time', isset($m[6]) ? $m[6] : null) +
$this->parsePart('time', isset($m[7]) ? $m[7] : null) +
$this->parsePart('time', isset($m[8]) ? $m[8] : null)
);
}

throw new \InvalidArgumentException('Invalid duration');

}

public function compose($time, $weekMode = false)
{
$this->totalTime = $time;
$this->composedDuration = 'P';

if($weekMode) {
$this->composePart('week W');
return $this->composedDuration;
}

$this->composePart('date D');
$this->composePart('time H');
$this->composePart('time M');
$this->composePart('time S');

return $this->composedDuration;
}

private function parsePart($mode, $string)
{
if(!$string) return 0;

$n = $this->extractInt($string);
$id = $mode . ' ' . $string[strlen($string)-1];

if ($n === 0) return 0;

switch($id) {
case 'time S':
return $n * 1;
case 'time M':
return $n * 60;
case 'time H':
return $n * 3600;
case 'date D':
return $n * 86400;
case 'week W':
return $n * 604800;
}
throw new \InvalidArgumentException('Ambiguous duration');
}

private function extractInt($string)
{
return (int) substr($string, 0, strlen($string)-1);
}

private function composePart($mode)
{
$time = true;

switch($mode) {
case 'time S':
$string = max((int)floor($this->totalTime / 1), 0);
$this->totalTime -= $string*1;
$string = $string > 0 ? $string.'S' : '';
break;
case 'time M':
$string = max((int)floor($this->totalTime / 60), 0);
$this->totalTime -= $string*60;
$string = $string > 0 ? $string.'M' : '';
break;
case 'time H':
$string = max((int)floor($this->totalTime / 3600), 0);
$this->totalTime -= $string*3600;
$string = $string > 0 ? $string.'H' : '';
break;
case 'date D':
$string = max((int)floor($this->totalTime / 86400), 0);
$this->totalTime -= $string*86400;
$string = $string > 0 ? $string.'D' : '';
$time = false;
break;
case 'week W':
$string = max((int)floor($this->totalTime / 604800), 0) . 'W';
$this->totalTime -= $string*604800;
$time = false;
break;
}

if(($time && !strpos($this->composedDuration, 'T')) && $string != '') {
$this->composedDuration .= 'T';
}



$this->composedDuration .= $string;

}

}
7 changes: 7 additions & 0 deletions tests/Bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

// Include the composer autoloader
require_once dirname(__DIR__) . '/vendor/autoload.php';


date_default_timezone_set('UTC');
175 changes: 175 additions & 0 deletions tests/DurationParserTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<?php

class DurationParserTest extends TestCase
{
private $parser;

public function __construct()
{
$this->parser = new \Bretterer\IsoDurationConverter\DurationParser();
}

/** @test */
public function it_can_parse_week_durations()
{
$this->assertEquals($this->parser->parse('P0W'), 0);
$this->assertEquals($this->parser->parse('P1W'), 1 * 604800);
$this->assertEquals($this->parser->parse('P5W'), 5 * 604800);
$this->assertEquals($this->parser->parse('P10W'), 10 * 604800);
}

/** @test */
public function it_can_compose_week_durations()
{
$this->assertEquals($this->parser->compose(0, true), 'P0W');
$this->assertEquals($this->parser->compose(1 * 604800, true), 'P1W');
$this->assertEquals($this->parser->compose(5 * 604800, true), 'P5W');
$this->assertEquals($this->parser->compose(10 * 604800, true), 'P10W');
}


/** @test */
public function it_can_parse_second_durations()
{
$this->assertEquals($this->parser->parse('PT0S'), 0);
$this->assertEquals($this->parser->parse('PT1S'), 1 * 1);
$this->assertEquals($this->parser->parse('PT5S'), 5 * 1);
$this->assertEquals($this->parser->parse('PT10S'), 10 * 1);
}

/** @test */
public function it_can_compose_second_durations()
{
$this->assertEquals($this->parser->compose(1 * 1), 'PT1S');
$this->assertEquals($this->parser->compose(5 * 1), 'PT5S');
$this->assertEquals($this->parser->compose(10 * 1), 'PT10S');
}

/** @test */
public function it_can_parse_minute_durations()
{
$this->assertEquals($this->parser->parse('PT0M'), 0);
$this->assertEquals($this->parser->parse('PT1M'), 1 * 60);
$this->assertEquals($this->parser->parse('PT5M'), 5 * 60);
$this->assertEquals($this->parser->parse('PT10M'), 10 * 60);
}

/** @test */
public function it_can_compose_minute_durations()
{
$this->assertEquals($this->parser->compose(1 * 60), 'PT1M');
$this->assertEquals($this->parser->compose(5 * 60), 'PT5M');
$this->assertEquals($this->parser->compose(10 * 60), 'PT10M');
}

/** @test */
public function it_can_parse_hour_durations()
{
$this->assertEquals($this->parser->parse('PT0H'), 0);
$this->assertEquals($this->parser->parse('PT1H'), 1 * 3600);
$this->assertEquals($this->parser->parse('PT5H'), 5 * 3600);
$this->assertEquals($this->parser->parse('PT10H'), 10 * 3600);
}

/** @test */
public function it_can_compose_hour_durations()
{
$this->assertEquals($this->parser->compose(1 * 3600), 'PT1H');
$this->assertEquals($this->parser->compose(5 * 3600), 'PT5H');
$this->assertEquals($this->parser->compose(10 * 3600), 'PT10H');
}

/** @test */
public function it_can_parse_full_time_durations()
{
$this->assertEquals($this->parser->parse('PT0H2M'), 2 * 60);
$this->assertEquals($this->parser->parse('PT1H5M'), 1 * 3600 + 5 * 60);
$this->assertEquals($this->parser->parse('PT5H10S'), 5 * 3600 + 10 * 1);
$this->assertEquals($this->parser->parse('PT10H40M23S'), 10 * 3600 + 40 * 60 + 23 * 1);

}

/** @test */
public function it_can_compose_full_time_durations()
{
$this->assertEquals($this->parser->compose(10 * 3600 + 2 * 60), 'PT10H2M');
$this->assertEquals($this->parser->compose(1 * 3600 + 5 * 60), 'PT1H5M');
$this->assertEquals($this->parser->compose(5 * 3600 + 10 * 1), 'PT5H10S');
$this->assertEquals($this->parser->compose(10 * 3600 + 40 * 60 + 23 * 1), 'PT10H40M23S');

}

/** @test */
public function it_can_parse_day_durations()
{
$this->assertEquals($this->parser->parse('P0D'), 0 * 86400);
$this->assertEquals($this->parser->parse('P1D'), 1 * 86400);
$this->assertEquals($this->parser->parse('P5D'), 5 * 86400);
$this->assertEquals($this->parser->parse('P10D'), 10 * 86400);

}

/** @test */
public function it_can_compose_day_durations()
{
$this->assertEquals($this->parser->compose(1 * 86400), 'P1D');
$this->assertEquals($this->parser->compose(5 * 86400), 'P5D');
$this->assertEquals($this->parser->compose(10 * 86400), 'P10D');

}

/** @test */
public function it_can_parse_full_date_time_durations()
{
$this->assertEquals($this->parser->parse('P0DT30M'), 0 * 86400 + 30 * 60);
$this->assertEquals($this->parser->parse('P10DT30S'), 10 * 86400 + 30 * 1);
$this->assertEquals($this->parser->parse('P12DT28M'), 12 * 86400 + 28 * 60);
$this->assertEquals($this->parser->parse('P14DT26H'), 14 * 86400 + 26 * 3600);
}

/** @test */
public function it_can_compose_full_date_time_durations()
{
$this->assertEquals($this->parser->compose(1 * 86400 + 30 * 60), 'P1DT30M');
$this->assertEquals($this->parser->compose(10 * 86400 + 30 * 1), 'P10DT30S');
$this->assertEquals($this->parser->compose(12 * 86400 + 28 * 60), 'P12DT28M');
$this->assertEquals($this->parser->compose(14 * 86400 + 26 * 3600), 'P15DT2H');
}

/** @test */
public function it_should_throw_invalid_argument_exception_when_garbage_is_used()
{
$this->setExpectedException('\InvalidArgumentException', 'Invalid duration');
$this->parser->parse('PT123123');
}

/** @test */
public function it_should_throw_invalid_argument_exception_for_ambiguous_durations()
{
$this->setExpectedException('\InvalidArgumentException', 'Ambiguous duration');
$this->parser->parse('P8Y20M10D');
}

/** @test */
public function examples_work()
{
$this->assertEquals(8, $this->parser->parse('PT8S'));
$this->assertEquals(300, $this->parser->parse('PT5M'));
$this->assertEquals(72000, $this->parser->parse('PT20H'));
$this->assertEquals(364, $this->parser->parse('PT6M4S'));

$this->assertEquals('PT8S', $this->parser->compose(8));
$this->assertEquals('PT5M', $this->parser->compose(300));
$this->assertEquals('PT20H', $this->parser->compose(72000));
$this->assertEquals('PT6M4S', $this->parser->compose(364));

$this->assertEquals(3024000, $this->parser->parse('P5W'));
$this->assertEquals('P5W', $this->parser->compose(3024000, true));
$this->assertEquals('P35D', $this->parser->compose(3024000));
}





}
Loading

0 comments on commit 1fa460f

Please sign in to comment.