diff --git a/tests/PHPUnit/Core/ConfigTest.php b/tests/PHPUnit/Core/ConfigTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bcfa02a4a3a4c39fe530df1726b46b92b4aec14b --- /dev/null +++ b/tests/PHPUnit/Core/ConfigTest.php @@ -0,0 +1,357 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + */ +class ConfigTest extends PHPUnit_Framework_TestCase +{ + /** + * @group Core + * @group Config + */ + public function testUserConfigOverwritesSectionGlobalConfigValue() + { + $userFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/config.ini.php'; + $globalFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/global.ini.php'; + + $config = Piwik_Config::getInstance(); + $config->setTestEnvironment($userFile, $globalFile); + $config->init(); + + $this->assertEquals($config->Category['key1'], "value_overwritten"); + $this->assertEquals($config->Category['key2'], "value2"); + $this->assertEquals($config->GeneralSection['login'], 'tes"t'); + $this->assertEquals($config->CategoryOnlyInGlobalFile['key3'], "value3"); + $this->assertEquals($config->CategoryOnlyInGlobalFile['key4'], "value4"); + + $expectedArray = array('plugin"1', 'plugin2', 'plugin3'); + $array = $config->TestArray; + $this->assertEquals($array['installed'], $expectedArray); + + $expectedArray = array('value1', 'value2'); + $array = $config->TestArrayOnlyInGlobalFile; + $this->assertEquals($array['my_array'], $expectedArray); + } + + /** + * @group Core + * @group Config + */ + public function testWritingConfigWithSpecialCharacters() + { + $userFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/config.written.ini.php'; + $globalFile = PIWIK_INCLUDE_PATH . '/tests/resources/Config/global.ini.php'; + + $config = Piwik_Config::getInstance(); + $config->setTestEnvironment($userFile, $globalFile); + $config->init(); + + $stringWritten = '&6^ geagea\'\'\'";;&'; + $config->Category = array('test' => $stringWritten); + $this->assertEquals($config->Category['test'], $stringWritten); + unset($config); + + $config = Piwik_Config::getInstance(); + $config->setTestEnvironment($userFile, $globalFile); + $config->init(); + + $this->assertEquals($config->Category['test'], $stringWritten); + $config->Category = array( + 'test' => $config->Category['test'], + 'test2' => $stringWritten, + ); + $this->assertEquals($config->Category['test'], $stringWritten); + $this->assertEquals($config->Category['test2'], $stringWritten); + } + + /** + * @group Core + * @group Config + */ + public function testUserConfigOverwritesGlobalConfig() + { + $userFile = PIWIK_PATH_TEST_TO_ROOT . '/tests/resources/Config/config.ini.php'; + $globalFile = PIWIK_PATH_TEST_TO_ROOT . '/tests/resources/Config/global.ini.php'; + + $config = Piwik_Config::getInstance(); + $config->setTestEnvironment($userFile, $globalFile); + + $this->assertEquals($config->Category['key1'], "value_overwritten"); + $this->assertEquals($config->Category['key2'], "value2"); + $this->assertEquals($config->GeneralSection['login'], "tes\"t"); + $this->assertEquals($config->CategoryOnlyInGlobalFile['key3'], "value3"); + $this->assertEquals($config->CategoryOnlyInGlobalFile['key4'], "value4"); + + $expectedArray = array('plugin"1', 'plugin2', 'plugin3'); + $array = $config->TestArray; + $this->assertEquals($array['installed'], $expectedArray); + + $expectedArray = array('value1', 'value2'); + $array = $config->TestArrayOnlyInGlobalFile; + $this->assertEquals($array['my_array'], $expectedArray); + + Piwik_Config::getInstance()->clear(); + } + + /** + * Dateprovider for testCompareElements + */ + public function getCompareElementsData() + { + return array( + array('string = string', array( + 'a', 'a', 0, + )), + array('string > string', array( + 'b', 'a', 1, + )), + array('string < string', array( + 'a', 'b', -1, + )), + array('string vs array', array( + 'a', array('a'), -1, + )), + array('array vs string', array( + array('a'), 'a', 1, + )), + array('array = array', array( + array('a'), array('a'), 0, + )), + array('array > array', array( + array('b'), array('a'), 1, + )), + array('array < array', array( + array('a'), array('b'), -1, + )), + ); + } + + /** + * @group Core + * @group Config + * @dataProvider getCompareElementsData + */ + public function testCompareElements($description, $test) + { + list($a, $b, $expected) = $test; + + $result = Piwik_Config::compareElements($a, $b); + $this->assertEquals($result, $expected, $description); + } + + /** + * Dataprovider for testArrayUnmerge + * @return array + */ + public function getArrayUnmergeData() { + return array( + array('description of test', array( + array(), + array(), + )), + array('override with empty', array( + array('login' => 'root', 'password' => 'b33r'), + array('password' => ''), + )), + array('override with non-empty', array( + array('login' => 'root', 'password' => ''), + array('password' => 'b33r'), + )), + array('add element', array( + array('login' => 'root', 'password' => ''), + array('auth' => 'Login'), + )), + array('override with empty array', array( + array('headers' => ''), + array('headers' => array()), + )), + array('override with array', array( + array('headers' => ''), + array('headers' => array('Content-Length', 'Content-Type')), + )), + array('override an array', array( + array('headers' => array()), + array('headers' => array('Content-Length', 'Content-Type')), + )), + array('override similar arrays', array( + array('headers' => array('Content-Length', 'Set-Cookie')), + array('headers' => array('Content-Length', 'Content-Type')), + )), + array('override dyslexic arrays', array( + array('headers' => array('Content-Type', 'Content-Length')), + array('headers' => array('Content-Length', 'Content-Type')), + )), + ); + } + + /** + * @group Core + * @group Config + * @dataProvider getArrayUnmergeData + */ + public function testArrayUnmerge($description, $test) + { + $configWriter = Piwik_Config::getInstance(); + + list($a, $b) = $test; + + $combined = array_merge($a, $b); + + $diff = $configWriter->array_unmerge($a, $combined); + + // expect $b == $diff + $this->assertEquals(serialize($b), serialize($diff), $description); + } + + /** + * Dataprovider for testDumpConfig + */ + public function getDumpConfigData() + { + $header = <<<END_OF_HEADER +; <?php exit; ?> DO NOT REMOVE THIS LINE +; file automatically generated or modified by Piwik; you can manually override the default values in global.ini.php by redefining them in this file. + +END_OF_HEADER; + + return array( + array('global only, not cached', array( + array(), + array('General' => array('debug' => '1')), + array(), + false, + )), + + array('global only, cached get', array( + array(), + array('General' => array('debug' => '1')), + array('General' => array('debug' => '1')), + false, + )), + + array('global only, cached set', array( + array(), + array('General' => array('debug' => '1')), + array('General' => array('debug' => '2')), + $header . "[General]\ndebug = 2\n\n", + )), + + array('local copy (same), not cached', array( + array('General' => array('debug' => '1')), + array('General' => array('debug' => '1')), + array(), + false, + )), + + array('local copy (same), cached get', array( + array('General' => array('debug' => '1')), + array('General' => array('debug' => '1')), + array('General' => array('debug' => '1')), + false, + )), + + array('local copy (same), cached set', array( + array('General' => array('debug' => '1')), + array('General' => array('debug' => '1')), + array('General' => array('debug' => '2')), + $header . "[General]\ndebug = 2\n\n", + )), + + array('local copy (different), not cached', array( + array('General' => array('debug' => '2')), + array('General' => array('debug' => '1')), + array(), + false, + )), + + array('local copy (different), cached get', array( + array('General' => array('debug' => '2')), + array('General' => array('debug' => '1')), + array('General' => array('debug' => '2')), + false, + )), + + array('local copy (different), cached set', array( + array('General' => array('debug' => '2')), + array('General' => array('debug' => '1')), + array('General' => array('debug' => '3')), + $header . "[General]\ndebug = 3\n\n", + )), + + array('local copy, not cached, new section', array( + array('Tracker' => array('anonymize' => '1')), + array('General' => array('debug' => '1')), + array(), + false, + )), + + array('local copy, cached get, new section', array( + array('Tracker' => array('anonymize' => '1')), + array('General' => array('debug' => '1')), + array('Tracker' => array('anonymize' => '1')), + false, + )), + + array('local copy, cached set local, new section', array( + array('Tracker' => array('anonymize' => '1')), + array('General' => array('debug' => '1')), + array('Tracker' => array('anonymize' => '2')), + $header . "[Tracker]\nanonymize = 2\n\n", + )), + + array('local copy, cached set global, new section', array( + array('Tracker' => array('anonymize' => '1')), + array('General' => array('debug' => '1')), + array('General' => array('debug' => '2')), + $header . "[General]\ndebug = 2\n\n[Tracker]\nanonymize = 1\n\n", + )), + + array('sort, common sections', array( + array('Tracker' => array('anonymize' => '1'), + 'General' => array('debug' => '1')), + array('General' => array('debug' => '0'), + 'Tracker' => array('anonymize' => '0')), + array('Tracker' => array('anonymize' => '2')), + $header . "[General]\ndebug = 1\n\n[Tracker]\nanonymize = 2\n\n", + )), + + array('sort, common sections before new section', array( + array('Tracker' => array('anonymize' => '1'), + 'General' => array('debug' => '1')), + array('General' => array('debug' => '0'), + 'Tracker' => array('anonymize' => '0')), + array('Segment' => array('dimension' => 'foo')), + $header . "[General]\ndebug = 1\n\n[Tracker]\nanonymize = 1\n\n[Segment]\ndimension = \"foo\"\n\n", + )), + + array('change back to default', array( + array('Tracker' => array('anonymize' => '1')), + array('Tracker' => array('anonymize' => '0'), + 'General' => array('debug' => '1')), + array('Tracker' => array('anonymize' => '0')), + $header + )), + ); + + } + + /** + * @group Core + * @group Config + * @dataProvider getDumpConfigData + */ + public function testDumpConfig($description, $test) + { + $config = Piwik_Config::getInstance(); + + list($configLocal, $configGlobal, $configCache, $expected) = $test; + + $output = $config->dumpConfig($configLocal, $configGlobal, $configCache); + + $this->assertEquals($output, $expected, $description); + } +} + diff --git a/tests/PHPUnit/Core/CookieTest.php b/tests/PHPUnit/Core/CookieTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a9f66f980ba547ba11f051e9bd95213c799d7ab4 --- /dev/null +++ b/tests/PHPUnit/Core/CookieTest.php @@ -0,0 +1,159 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + */ +class CookieTest extends PHPUnit_Framework_TestCase +{ + /** + * Dataprovider for testJsonSerialize + */ + public function getJsonSerializeData() + { + return array( + array('null', null), + array('bool false', false), + array('bool true', true), + array('negative int', -42), + array('zero', 0), + array('positive int', 42), + array('float', 1.25), + array('empty string', ''), + array('nul in string', "\0"), + array('carriage return in string', "first line\r\nsecond line"), + array('utf7 in string', 'hello, world'), + array('utf8 in string', '是'), + array('empty array', array()), + array('single element array', array("test")), + array('associative array', array("alpha", 2 => "beta")), + array('mixed keys', array('first' => 'john', 'last' => 'doe', 10 => 'age')), + array('nested arrays', array('top' => array('middle' => 2, array('bottom'), 'last'), 'the end' => true)), + array('array confusion', array('"', "'", '}', ';', ':')), + ); + } + + /** + * @group Core + * @group Cookie + * @dataProvider getJsonSerializeData + */ + public function testJsonSerialize($testData, $id) + { + // @see http://bugs.php.net/38680 + if(PHP_VERSION >= '5.2.0' && PHP_VERSION < '5.2.1') { + $this->markTestSkipped('see http://bugs.php.net/38680'); + } + + $this->assertEquals( json_decode(json_encode($testData), $assoc = true), $testData, $id ); + } + + /** + * Dataprovider for testSafeSerialize + */ + public function getSafeSerializeData() + { + return array( + array('null', null), + array('bool false', false), + array('bool true', true), + array('negative int', -42), + array('zero', 0), + array('positive int', 42), + array('float', 1.25), + array('empty string', ''), + array('nul in string', "\0"), + array('carriage return in string', "first line\r\nsecond line"), + array('utf7 in string', 'hello, world'), + array('utf8 in string', '是'), + array('empty array', array()), + array('single element array', array("test")), + array('associative array', array("alpha", 2 => "beta")), + array('mixed keys', array('first' => 'john', 'last' => 'doe', 10 => 'age')), + array('nested arrays', array('top' => array('middle' => 2, array('bottom'), 'last'), 'the end' => true)), + array('array confusion', array('"', "'", '}', ';', ':')), + ); + } + + /** + * @group Core + * @group Cookie + * @dataProvider getSafeSerializeData + */ + public function testSafeSerialize($id, $testData) + { + $this->assertEquals( safe_serialize($testData), serialize($testData), $id ); + $this->assertEquals( unserialize(safe_serialize($testData)), $testData, $id ); + $this->assertTrue( safe_unserialize(safe_serialize($testData)) === $testData, $id ); + $this->assertTrue( safe_unserialize(serialize($testData)) === $testData, $id ); + } + + /** + * @group Core + * @group Cookie + */ + public function testSafeUnserialize() + { + /* + * serialize() uses its internal maachine representation when floats expressed in E-notation, + * which may vary between php versions, OS, and hardware platforms + */ + $testData = $tests['exp float'] = -5.0E+142; + // intentionally disabled; this doesn't work +// $this->assertEquals( safe_serialize($testData), serialize($testData) ); + $this->assertEquals( unserialize(safe_serialize($testData)), $testData ); + $this->assertTrue( safe_unserialize(safe_serialize($testData)) === $testData) ; + // workaround: cast floats into strings + $this->assertTrue( (string)safe_unserialize(serialize($testData)) === (string)$testData ); + + $unserialized = array( + 'announcement' => true, + 'source' => array( + array( + 'filename' => 'php-5.3.3.tar.bz2', + 'name' => 'PHP 5.3.3 (tar.bz2)', + 'md5' => '21ceeeb232813c10283a5ca1b4c87b48', + 'date' => '22 July 2010', + ), + array( + 'filename' => 'php-5.3.3.tar.gz', + 'name' => 'PHP 5.3.3 (tar.gz)', + 'md5' => '5adf1a537895c2ec933fddd48e78d8a2', + 'date' => '22 July 2010', + ), + ), + 'date' => '22 July 2010', + 'version' => '5.3.3', + ); + $serialized = 'a:4:{s:12:"announcement";b:1;s:6:"source";a:2:{i:0;a:4:{s:8:"filename";s:17:"php-5.3.3.tar.bz2";s:4:"name";s:19:"PHP 5.3.3 (tar.bz2)";s:3:"md5";s:32:"21ceeeb232813c10283a5ca1b4c87b48";s:4:"date";s:12:"22 July 2010";}i:1;a:4:{s:8:"filename";s:16:"php-5.3.3.tar.gz";s:4:"name";s:18:"PHP 5.3.3 (tar.gz)";s:3:"md5";s:32:"5adf1a537895c2ec933fddd48e78d8a2";s:4:"date";s:12:"22 July 2010";}}s:4:"date";s:12:"22 July 2010";s:7:"version";s:5:"5.3.3";}'; + + $this->assertTrue( unserialize($serialized) === $unserialized ); + $this->assertEquals( serialize($unserialized), $serialized ); + + $this->assertTrue( safe_unserialize($serialized) === $unserialized ); + $this->assertEquals( safe_serialize($unserialized), $serialized ); + $this->assertTrue( safe_unserialize(safe_serialize($unserialized)) === $unserialized ); + $this->assertEquals( safe_serialize(safe_unserialize($serialized)), $serialized ); + + $a = 'O:31:"Test_Piwik_Cookie_Phantom_Class":0:{}'; + $this->assertFalse( safe_unserialize($a), "test: unserializing an object where class not (yet) defined" ); + + $a = 'O:28:"Test_Piwik_Cookie_Mock_Class":0:{}'; + $this->assertFalse( safe_unserialize($a), "test: unserializing an object where class is defined" ); + + $a = 'a:1:{i:0;O:28:"Test_Piwik_Cookie_Mock_Class":0:{}}'; + $this->assertFalse( safe_unserialize($a), "test: unserializing nested object where class is defined" ); + + $a = 'a:2:{i:0;s:4:"test";i:1;O:28:"Test_Piwik_Cookie_Mock_Class":0:{}}'; + $this->assertFalse( safe_unserialize($a), "test: unserializing another nested object where class is defined" ); + + $a = 'O:28:"Test_Piwik_Cookie_Mock_Class":1:{s:34:"'."\0".'Test_Piwik_Cookie_Mock_Class'."\0".'name";s:4:"test";}'; + $this->assertFalse( safe_unserialize($a), "test: unserializing object with member where class is defined" ); + + // arrays and objects cannot be used as keys, i.e., generates "Warning: Illegal offset type ..." + $a = 'a:2:{i:0;a:0:{}O:28:"Test_Piwik_Cookie_Mock_Class":0:{}s:4:"test";'; + $this->assertFalse( safe_unserialize($a), "test: unserializing with illegal key" ); + } +} diff --git a/tests/PHPUnit/Core/DateTest.php b/tests/PHPUnit/Core/DateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..64d2d591514a959df7f1dc12b72efdf65989c307 --- /dev/null +++ b/tests/PHPUnit/Core/DateTest.php @@ -0,0 +1,255 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + */ +class DateTest extends PHPUnit_Framework_TestCase +{ + /** + * create today object check that timestamp is correct (midnight) + * + * @group Core + * @group Date + */ + public function testToday() + { + $date = Piwik_Date::today(); + $this->assertEquals( strtotime(date("Y-m-d "). " 00:00:00"), $date->getTimestamp()); + + // test getDatetime() + $this->assertEquals( $date->getDatetime(), $date->getDateStartUTC()); + $date = $date->setTime('12:00:00'); + $this->assertEquals( $date->getDatetime(), date('Y-m-d') . ' 12:00:00'); + } + + /** + * create today object check that timestamp is correct (midnight) + * + * @group Core + * @group Date + */ + public function testYesterday() + { + $date = Piwik_Date::yesterday(); + $this->assertEquals( strtotime(date("Y-m-d",strtotime('-1day')). " 00:00:00"), $date->getTimestamp()); + } + + /** + * @group Core + * @group Date + */ + public function test_invalidDate_throws() + { + try { + $date = Piwik_Date::factory('0001-01-01'); + } catch(Exception $e) { + return; + } + $this->fail('Exception not raised'); + } + + /** + * @group Core + * @group Date + */ + public function testFactoryTimezone() + { + // now in UTC converted to UTC+10 means adding 10 hours + $date = Piwik_Date::factory('now', 'UTC+10'); + $dateExpected = Piwik_Date::now()->addHour(10); + $this->assertEquals($date->getDatetime(), $dateExpected->getDatetime()); + + // Congo is in UTC+1 all year long (no DST) + $date = Piwik_Date::factory('now', 'Africa/Brazzaville'); + $dateExpected = Piwik_Date::factory('now')->addHour(1); + $this->assertEquals($date->getDatetime(), $dateExpected->getDatetime()); + + // yesterday same time in Congo is the same as today in Congo - 24 hours + $date = Piwik_Date::factory('yesterdaySameTime', 'Africa/Brazzaville'); + $dateExpected = Piwik_Date::factory('now', 'Africa/Brazzaville')->subHour(24); + $this->assertEquals($date->getDatetime(), $dateExpected->getDatetime()); + + if(Piwik::isTimezoneSupportEnabled()) + { + // convert to/from local time + $now = time(); + $date = Piwik_Date::factory($now, 'America/New_York'); + $time = $date->getTimestamp(); + $this->assertTrue($time < $now); + + $date = Piwik_Date::factory($time)->setTimezone('America/New_York'); + $time = $date->getTimestamp(); + $this->assertEquals($now, $time); + } + } + + /** + * @group Core + * @group Date + */ + public function testSetTimezoneDayInUTC() + { + $date = Piwik_Date::factory('2010-01-01'); + + $dayStart = '2010-01-01 00:00:00'; + $dayEnd = '2010-01-01 23:59:59'; + $this->assertEquals($date->getDateStartUTC(), $dayStart); + $this->assertEquals($date->getDateEndUTC(), $dayEnd); + + // try with empty timezone + $date = $date->setTimezone(''); + $this->assertEquals($date->getDateStartUTC(), $dayStart); + $this->assertEquals($date->getDateEndUTC(), $dayEnd); + + $date = $date->setTimezone('UTC'); + $this->assertEquals($date->getDateStartUTC(), $dayStart); + $this->assertEquals($date->getDateEndUTC(), $dayEnd); + + if(Piwik::isTimezoneSupportEnabled()) + { + $date = $date->setTimezone('Europe/Paris'); + $utcDayStart = '2009-12-31 23:00:00'; + $utcDayEnd = '2010-01-01 22:59:59'; + $this->assertEquals($date->getDateStartUTC(), $utcDayStart); + $this->assertEquals($date->getDateEndUTC(), $utcDayEnd); + } + + $date = $date->setTimezone('UTC+1'); + $utcDayStart = '2009-12-31 23:00:00'; + $utcDayEnd = '2010-01-01 22:59:59'; + $this->assertEquals($date->getDateStartUTC(), $utcDayStart); + $this->assertEquals($date->getDateEndUTC(), $utcDayEnd); + + $date = $date->setTimezone('UTC-1'); + $utcDayStart = '2010-01-01 01:00:00'; + $utcDayEnd = '2010-01-02 00:59:59'; + $this->assertEquals($date->getDateStartUTC(), $utcDayStart); + $this->assertEquals($date->getDateEndUTC(), $utcDayEnd); + + if(Piwik::isTimezoneSupportEnabled()) + { + $date = $date->setTimezone('America/Vancouver'); + $utcDayStart = '2010-01-01 08:00:00'; + $utcDayEnd = '2010-01-02 07:59:59'; + $this->assertEquals($date->getDateStartUTC(), $utcDayStart); + $this->assertEquals($date->getDateEndUTC(), $utcDayEnd); + } + } + + /** + * @group Core + * @group Date + */ + public function testModifyDateWithTimezone() + { + $date = Piwik_Date::factory('2010-01-01'); + $date = $date->setTimezone('UTC-1'); + + $timestamp = $date->getTimestamp(); + $date = $date->addHour(0)->addHour(0)->addHour(0); + $this->assertEquals($timestamp, $date->getTimestamp()); + + + if(Piwik::isTimezoneSupportEnabled()) + { + $date = Piwik_Date::factory('2010-01-01')->setTimezone('Europe/Paris'); + $dateExpected = clone $date; + $date = $date->addHour(2); + $dateExpected = $dateExpected->addHour(1.1)->addHour(0.9)->addHour(1)->subHour(1); + $this->assertEquals($date->getTimestamp(), $dateExpected->getTimestamp()); + } + } + + /** + * @group Core + * @group Date + */ + public function testGetDateStartUTCEndDuringDstTimezone() + { + if(Piwik::isTimezoneSupportEnabled()) + { + $date = Piwik_Date::factory('2010-03-28'); + + $date = $date->setTimezone('Europe/Paris'); + $utcDayStart = '2010-03-27 23:00:00'; + $utcDayEnd = '2010-03-28 21:59:59'; + + $this->assertEquals($date->getDateStartUTC(), $utcDayStart); + $this->assertEquals($date->getDateEndUTC(), $utcDayEnd); + } + } + + /** + * @group Core + * @group Date + */ + public function testAddHour() + { + // add partial hours less than 1 + $dayStart = '2010-03-28 00:00:00'; + $dayExpected = '2010-03-28 00:18:00'; + $date = Piwik_Date::factory($dayStart)->addHour(0.3); + $this->assertEquals($date->getDatetime(), $dayExpected); + $date = $date->subHour(0.3); + $this->assertEquals($date->getDatetime(), $dayStart); + + // add partial hours + $dayExpected = '2010-03-28 05:45:00'; + $date = Piwik_Date::factory($dayStart)->addHour(5.75); + $this->assertEquals($date->getDatetime(), $dayExpected); + + // remove partial hours + $dayExpected = '2010-03-27 18:15:00'; + $date = Piwik_Date::factory($dayStart)->subHour(5.75); + $this->assertEquals($date->getDatetime(), $dayExpected); + } + + /** + * @group Core + * @group Date + */ + public function testAddHourLongHours() + { + $dateTime = '2010-01-03 11:22:33'; + $expectedTime = '2010-01-05 11:28:33'; + $this->assertEquals(Piwik_Date::factory($dateTime)->addHour(48.1)->getDatetime(), $expectedTime); + $this->assertEquals(Piwik_Date::factory($dateTime)->addHour(48.1)->subHour(48.1)->getDatetime(), $dateTime); + } + + /** + * @group Core + * @group Date + */ + public function testAddPeriod() + { + $date = Piwik_Date::factory('2010-01-01'); + $dateExpected = Piwik_Date::factory('2010-01-06'); + $date = $date->addPeriod(5, 'day'); + $this->assertEquals($date->getTimestamp(), $dateExpected->getTimestamp()); + + $date = Piwik_Date::factory('2010-03-01'); + $dateExpected = Piwik_Date::factory('2010-04-05'); + $date = $date->addPeriod(5, 'week'); + $this->assertEquals($date->getTimestamp(), $dateExpected->getTimestamp()); + } + + /** + * @group Core + * @group Date + */ + public function testSubPeriod() + { + $date = Piwik_Date::factory('2010-03-01'); + $dateExpected = Piwik_Date::factory('2010-02-15'); + $date = $date->subPeriod(2, 'week'); + $this->assertEquals($date->getTimestamp(), $dateExpected->getTimestamp()); + + $date = Piwik_Date::factory('2010-12-15'); + $dateExpected = Piwik_Date::factory('2005-12-15'); + $date = $date->subPeriod(5, 'year'); + $this->assertEquals($date->getTimestamp(), $dateExpected->getTimestamp()); + } +}