From 49e7f7e4f61add2693b87b05c51e95d807c5f2bc Mon Sep 17 00:00:00 2001
From: David Grudl <david@grudl.com>
Date: Sat, 21 Jan 2017 01:05:07 +0100
Subject: [PATCH 1/6] DateTime::createFromFormat() returns NULL instead of
 FALSE on error (BC break)

---
 src/Utils/DateTime.php                     | 4 ++--
 tests/Utils/DateTime.createFromFormat.phpt | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/Utils/DateTime.php b/src/Utils/DateTime.php
index 60f102a3e..345dce490 100644
--- a/src/Utils/DateTime.php
+++ b/src/Utils/DateTime.php
@@ -117,7 +117,7 @@ public function getTimestamp()
 	 * @param  string The format the $time parameter should be in
 	 * @param  string String representing the time
 	 * @param  string|\DateTimeZone desired timezone (default timezone is used if NULL is passed)
-	 * @return static|FALSE
+	 * @return static|NULL
 	 */
 	public static function createFromFormat($format, $time, $timezone = NULL)
 	{
@@ -132,7 +132,7 @@ public static function createFromFormat($format, $time, $timezone = NULL)
 		}
 
 		$date = parent::createFromFormat($format, $time, $timezone);
-		return $date ? static::from($date) : FALSE;
+		return $date ? static::from($date) : NULL;
 	}
 
 
diff --git a/tests/Utils/DateTime.createFromFormat.phpt b/tests/Utils/DateTime.createFromFormat.phpt
index 2ae02822f..55d650e40 100644
--- a/tests/Utils/DateTime.createFromFormat.phpt
+++ b/tests/Utils/DateTime.createFromFormat.phpt
@@ -26,4 +26,4 @@ Assert::error(function () {
 	DateTime::createFromFormat('Y-m-d H:i:s', '2050-08-13 11:40:00', 5);
 }, Nette\InvalidArgumentException::class, 'Invalid timezone given');
 
-Assert::false(DateTime::createFromFormat('Y-m-d', '2014-10'));
+Assert::null(DateTime::createFromFormat('Y-m-d', '2014-10'));

From e820ea02ebe30604344f31d127dcb41077e4f777 Mon Sep 17 00:00:00 2001
From: David Grudl <david@grudl.com>
Date: Sat, 21 Jan 2017 01:07:09 +0100
Subject: [PATCH 2/6] Strings::before(), after(), indexOf() and pos() return
 NULL instead of FALSE if the needle was not found (BC break)

---
 src/Utils/Strings.php              | 24 ++++++++++++------------
 tests/Utils/Strings.after().phpt   |  8 ++++----
 tests/Utils/Strings.before().phpt  |  8 ++++----
 tests/Utils/Strings.indexOf().phpt |  8 ++++----
 4 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/src/Utils/Strings.php b/src/Utils/Strings.php
index c25919a79..d66f66f38 100644
--- a/src/Utils/Strings.php
+++ b/src/Utils/Strings.php
@@ -353,51 +353,51 @@ public static function reverse(string $s): string
 
 	/**
 	 * Returns part of $haystack before $nth occurence of $needle (negative value means searching from the end).
-	 * @return string|FALSE  returns FALSE if the needle was not found
+	 * @return string|NULL  returns NULL if the needle was not found
 	 */
 	public static function before(string $haystack, string $needle, int $nth = 1)
 	{
 		$pos = self::pos($haystack, $needle, $nth);
-		return $pos === FALSE
-			? FALSE
+		return $pos === NULL
+			? NULL
 			: substr($haystack, 0, $pos);
 	}
 
 
 	/**
 	 * Returns part of $haystack after $nth occurence of $needle (negative value means searching from the end).
-	 * @return string|FALSE  returns FALSE if the needle was not found
+	 * @return string|NULL  returns NULL if the needle was not found
 	 */
 	public static function after(string $haystack, string $needle, int $nth = 1)
 	{
 		$pos = self::pos($haystack, $needle, $nth);
-		return $pos === FALSE
-			? FALSE
+		return $pos === NULL
+			? NULL
 			: substr($haystack, $pos + strlen($needle));
 	}
 
 
 	/**
 	 * Returns position of $nth occurence of $needle in $haystack (negative value means searching from the end).
-	 * @return int|FALSE  offset in characters or FALSE if the needle was not found
+	 * @return int|NULL  offset in characters or NULL if the needle was not found
 	 */
 	public static function indexOf(string $haystack, string $needle, int $nth = 1)
 	{
 		$pos = self::pos($haystack, $needle, $nth);
-		return $pos === FALSE
-			? FALSE
+		return $pos === NULL
+			? NULL
 			: self::length(substr($haystack, 0, $pos));
 	}
 
 
 	/**
 	 * Returns position of $nth occurence of $needle in $haystack.
-	 * @return int|FALSE  offset in bytes or FALSE if the needle was not found
+	 * @return int|NULL  offset in bytes or NULL if the needle was not found
 	 */
 	private static function pos(string $haystack, string $needle, int $nth = 1)
 	{
 		if (!$nth) {
-			return FALSE;
+			return NULL;
 		} elseif ($nth > 0) {
 			if (strlen($needle) === 0) {
 				return 0;
@@ -416,7 +416,7 @@ private static function pos(string $haystack, string $needle, int $nth = 1)
 				$pos--;
 			}
 		}
-		return $pos;
+		return $pos === FALSE ? NULL : $pos;
 	}
 
 
diff --git a/tests/Utils/Strings.after().phpt b/tests/Utils/Strings.after().phpt
index e333ed5ce..eae7621da 100644
--- a/tests/Utils/Strings.after().phpt
+++ b/tests/Utils/Strings.after().phpt
@@ -27,10 +27,10 @@ test(function () {
 	Assert::same('c', Strings::after($foo, '789', 3));
 	Assert::same('a123456789b123456789c', Strings::after($foo, '9', -3));
 	Assert::same('a123456789b123456789c', Strings::after($foo, '789', -3));
-	Assert::false(Strings::after($foo, '9', 0));
-	Assert::false(Strings::after($foo, 'not-in-string'));
-	Assert::false(Strings::after($foo, 'b', -2));
-	Assert::false(Strings::after($foo, 'b', 2));
+	Assert::null(Strings::after($foo, '9', 0));
+	Assert::null(Strings::after($foo, 'not-in-string'));
+	Assert::null(Strings::after($foo, 'b', -2));
+	Assert::null(Strings::after($foo, 'b', 2));
 });
 
 
diff --git a/tests/Utils/Strings.before().phpt b/tests/Utils/Strings.before().phpt
index 1866dcfa2..68eff6cfc 100644
--- a/tests/Utils/Strings.before().phpt
+++ b/tests/Utils/Strings.before().phpt
@@ -26,10 +26,10 @@ test(function () {
 	Assert::same('0123456789a123456789b123456', Strings::before($foo, '789', 3));
 	Assert::same('012345678', Strings::before($foo, '9', -3));
 	Assert::same('0123456', Strings::before($foo, '789', -3));
-	Assert::false(Strings::before($foo, '9', 0));
-	Assert::false(Strings::before($foo, 'not-in-string'));
-	Assert::false(Strings::before($foo, 'b', -2));
-	Assert::false(Strings::before($foo, 'b', 2));
+	Assert::null(Strings::before($foo, '9', 0));
+	Assert::null(Strings::before($foo, 'not-in-string'));
+	Assert::null(Strings::before($foo, 'b', -2));
+	Assert::null(Strings::before($foo, 'b', 2));
 });
 
 
diff --git a/tests/Utils/Strings.indexOf().phpt b/tests/Utils/Strings.indexOf().phpt
index eceae79d9..8738b0386 100644
--- a/tests/Utils/Strings.indexOf().phpt
+++ b/tests/Utils/Strings.indexOf().phpt
@@ -27,10 +27,10 @@ test(function () {
 	Assert::same(27, Strings::indexOf($foo, '789', 3));
 	Assert::same(9, Strings::indexOf($foo, '9', -3));
 	Assert::same(7, Strings::indexOf($foo, '789', -3));
-	Assert::false(Strings::indexOf($foo, '9', 0));
-	Assert::false(Strings::indexOf($foo, 'not-in-string'));
-	Assert::false(Strings::indexOf($foo, 'b', -2));
-	Assert::false(Strings::indexOf($foo, 'b', 2));
+	Assert::null(Strings::indexOf($foo, '9', 0));
+	Assert::null(Strings::indexOf($foo, 'not-in-string'));
+	Assert::null(Strings::indexOf($foo, 'b', -2));
+	Assert::null(Strings::indexOf($foo, 'b', 2));
 });
 
 

From 8a9b344247644271b7e915a61d85d5cad793d5c0 Mon Sep 17 00:00:00 2001
From: David Grudl <david@grudl.com>
Date: Wed, 1 Feb 2017 15:16:47 +0100
Subject: [PATCH 3/6] typo

---
 tests/Utils/DateTime.createFromFormat.phpt | 6 +++---
 tests/Utils/DateTime.from.phpt             | 2 +-
 tests/Utils/DateTime.modifyClone.phpt      | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/tests/Utils/DateTime.createFromFormat.phpt b/tests/Utils/DateTime.createFromFormat.phpt
index 55d650e40..9a2ddc845 100644
--- a/tests/Utils/DateTime.createFromFormat.phpt
+++ b/tests/Utils/DateTime.createFromFormat.phpt
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * Test: Nette\DateTime::createFromFormat().
+ * Test: Nette\Utils\DateTime::createFromFormat().
  */
 
 declare(strict_types=1);
@@ -14,8 +14,8 @@ require __DIR__ . '/../bootstrap.php';
 
 date_default_timezone_set('Europe/Prague');
 
-Assert::type(Nette\Utils\DateTime::class, DateTime::createFromFormat('Y-m-d H:i:s', '2050-08-13 11:40:00'));
-Assert::type(Nette\Utils\DateTime::class, DateTime::createFromFormat('Y-m-d H:i:s', '2050-08-13 11:40:00', new DateTimeZone('Europe/Prague')));
+Assert::type(DateTime::class, DateTime::createFromFormat('Y-m-d H:i:s', '2050-08-13 11:40:00'));
+Assert::type(DateTime::class, DateTime::createFromFormat('Y-m-d H:i:s', '2050-08-13 11:40:00', new DateTimeZone('Europe/Prague')));
 
 Assert::same('2050-08-13 11:40:00.123450', DateTime::createFromFormat('Y-m-d H:i:s.u', '2050-08-13 11:40:00.12345')->format('Y-m-d H:i:s.u'));
 
diff --git a/tests/Utils/DateTime.from.phpt b/tests/Utils/DateTime.from.phpt
index ff8f31b19..4dbf1ff83 100644
--- a/tests/Utils/DateTime.from.phpt
+++ b/tests/Utils/DateTime.from.phpt
@@ -24,6 +24,6 @@ Assert::same(is_int(2544000000) ? 2544000000 : '2544000000', DateTime::from(2544
 
 Assert::same('1978-05-05 00:00:00', (string) DateTime::from('1978-05-05'));
 
-Assert::type('DateTime', DateTime::from(new DateTime('1978-05-05')));
+Assert::type(DateTime::class, DateTime::from(new \DateTime('1978-05-05')));
 
 Assert::same('1978-05-05 12:00:00.123450', DateTime::from(new DateTime('1978-05-05 12:00:00.12345'))->format('Y-m-d H:i:s.u'));
diff --git a/tests/Utils/DateTime.modifyClone.phpt b/tests/Utils/DateTime.modifyClone.phpt
index b3c3f7d07..7bdd14d80 100644
--- a/tests/Utils/DateTime.modifyClone.phpt
+++ b/tests/Utils/DateTime.modifyClone.phpt
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * Test: Nette\DateTime::modifyClone().
+ * Test: Nette\Utils\DateTime::modifyClone().
  */
 
 declare(strict_types=1);

From 61c58f0d48d1057efe256497c7494196b3c29d73 Mon Sep 17 00:00:00 2001
From: David Grudl <david@grudl.com>
Date: Wed, 1 Feb 2017 16:13:29 +0100
Subject: [PATCH 4/6] DateTime::from() deprecated relative timestamps

---
 src/Utils/DateTime.php | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/Utils/DateTime.php b/src/Utils/DateTime.php
index 345dce490..c6e6a4bea 100644
--- a/src/Utils/DateTime.php
+++ b/src/Utils/DateTime.php
@@ -28,13 +28,13 @@ class DateTime extends \DateTime implements \JsonSerializable
 	/** day in seconds */
 	const DAY = 24 * self::HOUR;
 
-	/** week in seconds */
+	/** @deprecated */
 	const WEEK = 7 * self::DAY;
 
-	/** average month in seconds */
+	/** @deprecated */
 	const MONTH = 2629800;
 
-	/** average year in seconds */
+	/** @deprecated */
 	const YEAR = 31557600;
 
 
@@ -50,6 +50,7 @@ public static function from($time)
 
 		} elseif (is_numeric($time)) {
 			if ($time <= self::YEAR) {
+				trigger_error(__METHOD__ . '() and relative timestamp is deprecated.', E_USER_DEPRECATED);
 				$time += time();
 			}
 			return (new static('@' . $time))->setTimeZone(new \DateTimeZone(date_default_timezone_get()));

From 9da5ed52315f33af58f08d2d24c9722f76f33374 Mon Sep 17 00:00:00 2001
From: David Grudl <david@grudl.com>
Date: Wed, 1 Feb 2017 16:08:55 +0100
Subject: [PATCH 5/6] DateTime is immutable (BC break!)

---
 src/Utils/DateTime.php                    | 58 ++++++++++++++++++++++-
 tests/Utils/DateTime.immutable.check.phpt | 26 ++++++++++
 2 files changed, 83 insertions(+), 1 deletion(-)
 create mode 100644 tests/Utils/DateTime.immutable.check.phpt

diff --git a/src/Utils/DateTime.php b/src/Utils/DateTime.php
index c6e6a4bea..af41c52d2 100644
--- a/src/Utils/DateTime.php
+++ b/src/Utils/DateTime.php
@@ -15,7 +15,7 @@
 /**
  * DateTime.
  */
-class DateTime extends \DateTime implements \JsonSerializable
+class DateTime extends \DateTimeImmutable implements \JsonSerializable
 {
 	use Nette\SmartObject;
 
@@ -145,4 +145,60 @@ public function jsonSerialize(): string
 		return $this->format('c');
 	}
 
+
+	/********************* immutable usage detector ****************d*g**/
+
+
+	public function __destruct()
+	{
+		$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+		if (isset($trace[0]['file'], $trace[1]['function']) && $trace[0]['file'] === __FILE__ && $trace[1]['function'] !== 'from') {
+			trigger_error(__CLASS__ . ' is immutable now, check how it is used in ' . $trace[1]['file'] . ':' . $trace[1]['line'], E_USER_WARNING);
+		}
+	}
+
+
+	public function add($interval)
+	{
+		return parent::add($interval);
+	}
+
+
+	public function modify($modify)
+	{
+		return parent::modify($modify);
+	}
+
+
+	public function setDate($year, $month, $day)
+	{
+		return parent::setDate($year, $month, $day);
+	}
+
+
+	public function setISODate($year, $week, $day = 1)
+	{
+		return parent::setISODate($year, $week, $day);
+	}
+
+
+	public function setTime($hour, $minute, $second = 0, $micro = 0)
+	{
+		return PHP_VERSION_ID < 70100
+			? parent::setTime($hour, $minute, $second)
+			: parent::setTime($hour, $minute, $second, $micro);
+	}
+
+
+	public function setTimezone($timezone)
+	{
+		return parent::setTimezone($timezone);
+	}
+
+
+	public function sub($interval)
+	{
+		return parent::sub($interval);
+	}
+
 }
diff --git a/tests/Utils/DateTime.immutable.check.phpt b/tests/Utils/DateTime.immutable.check.phpt
new file mode 100644
index 000000000..a56d3fa9b
--- /dev/null
+++ b/tests/Utils/DateTime.immutable.check.phpt
@@ -0,0 +1,26 @@
+<?php
+
+declare(strict_types=1);
+
+use Tester\Assert;
+use Nette\Utils\DateTime;
+
+require __DIR__ . '/../bootstrap.php';
+
+Assert::noError(function () {
+	$x = DateTime::from(254400000);
+});
+
+Assert::noError(function () {
+	$x = DateTime::from(254400000);
+	$x = $x->setTimestamp(254400000);
+});
+
+Assert::noError(function () {
+	$x = DateTime::from(254400000)->format('U');
+});
+
+Assert::error(function () {
+	$x = DateTime::from(254400000);
+	$x->setTimestamp(254400000);
+}, E_USER_WARNING, 'Nette\Utils\DateTime is immutable now, check how it is used in ' . __FILE__ . ':' . (__LINE__ - 1));

From 207ea60a6d3df8eb8d674f5eaac7c833518c5270 Mon Sep 17 00:00:00 2001
From: David Grudl <david@grudl.com>
Date: Wed, 1 Feb 2017 16:10:17 +0100
Subject: [PATCH 6/6] DateTime::modifyClone() is deprecated (BC break)

---
 src/Utils/DateTime.php                |  1 +
 tests/Utils/DateTime.modifyClone.phpt | 27 ---------------------------
 2 files changed, 1 insertion(+), 27 deletions(-)
 delete mode 100644 tests/Utils/DateTime.modifyClone.phpt

diff --git a/src/Utils/DateTime.php b/src/Utils/DateTime.php
index af41c52d2..876ca6122 100644
--- a/src/Utils/DateTime.php
+++ b/src/Utils/DateTime.php
@@ -86,6 +86,7 @@ public function __toString(): string
 	 */
 	public function modifyClone(string $modify = '')
 	{
+		trigger_error(__METHOD__ . '() is deprecated, use modify()', E_USER_DEPRECATED);
 		$dolly = clone $this;
 		return $modify ? $dolly->modify($modify) : $dolly;
 	}
diff --git a/tests/Utils/DateTime.modifyClone.phpt b/tests/Utils/DateTime.modifyClone.phpt
deleted file mode 100644
index 7bdd14d80..000000000
--- a/tests/Utils/DateTime.modifyClone.phpt
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-/**
- * Test: Nette\Utils\DateTime::modifyClone().
- */
-
-declare(strict_types=1);
-
-use Tester\Assert;
-use Nette\Utils\DateTime;
-
-require __DIR__ . '/../bootstrap.php';
-
-
-date_default_timezone_set('Europe/Prague');
-
-$date = DateTime::from(254400000);
-$dolly = $date->modifyClone();
-Assert::type(DateTime::class, $dolly);
-Assert::notSame($date, $dolly);
-Assert::same((string) $date, (string) $dolly);
-
-
-$dolly2 = $date->modifyClone('+1 hour');
-Assert::type(DateTime::class, $dolly2);
-Assert::notSame($date, $dolly2);
-Assert::notSame((string) $date, (string) $dolly2);