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());
+    }
+}