From cef981295ee19c9078ccbebbf4170e00bde15ba1 Mon Sep 17 00:00:00 2001
From: Craig Smith <craigsmith@Craigs-MBP-2.localdomain>
Date: Tue, 14 Mar 2023 16:28:10 +1300
Subject: [PATCH] feat: enhanced date functionality

Breaking-change: removed cdcarbondate facade, use the class directly instead
---
 .lando.yml                          | 26 ++++++++++++-
 composer.json                       |  4 +-
 src/CdCarbonDate.php                | 58 ++++++++++++-----------------
 src/CdCarbonMixin.php               |  8 ++--
 src/Exceptions/RuntimeException.php | 44 ++++++++++++++++++++++
 src/Facades/CdCarbonDate.php        | 23 ------------
 src/ServiceProvider.php             |  6 +--
 tests/DateHandlingTest.php          | 36 +++++++++++++-----
 8 files changed, 128 insertions(+), 77 deletions(-)
 create mode 100644 src/Exceptions/RuntimeException.php
 delete mode 100644 src/Facades/CdCarbonDate.php

diff --git a/.lando.yml b/.lando.yml
index 3fd188f..f172fe6 100644
--- a/.lando.yml
+++ b/.lando.yml
@@ -57,15 +57,37 @@ tooling:
       - composer phpstan
 
   all81:
-    service: appserver
+    service: php81
     cmd:
       - rm -rf vendor composer.lock && composer install
       - composer phpunit
       - composer phpstan
 
   all80:
-    service: appserver
+    service: php80
     cmd:
       - rm -rf vendor composer.lock && composer install
       - composer phpunit
       - composer phpstan
+
+  lara8:
+    service: php81
+    cmd:
+      - rm -rf vendor composer.lock
+      - composer require laravel/laravel:^8
+      - composer install
+      - composer phpunit
+      - composer phpstan
+      # this is for removing a specific version
+      - composer remove laravel/laravel
+
+  lara9:
+    service: php81
+    cmd:
+      - rm -rf vendor composer.lock
+      - composer require laravel/laravel:^9
+      - composer install
+      - composer phpunit
+      - composer phpstan
+      # this is for removing a specific version
+      - composer remove laravel/laravel
diff --git a/composer.json b/composer.json
index f7f1fc4..4febb9b 100644
--- a/composer.json
+++ b/composer.json
@@ -19,8 +19,8 @@
   ],
   "require": {
     "php": "^8.0",
-    "illuminate/support": "^8.5|^9.0|^10",
-    "illuminate/notifications": "^8.5|^9.0|^10.0"
+    "illuminate/notifications": "^8.5|^9.0|^10.0",
+    "illuminate/support": "^8.5|^9.0|^10"
   },
   "require-dev": {
     "orchestra/testbench": "^6.0|^7.0|^8.0",
diff --git a/src/CdCarbonDate.php b/src/CdCarbonDate.php
index c8a1213..2622e85 100644
--- a/src/CdCarbonDate.php
+++ b/src/CdCarbonDate.php
@@ -1,60 +1,48 @@
 <?php
 namespace CustomD\LaravelHelpers;
 
+use DateTimeInterface;
+use Carbon\CarbonImmutable;
 use \Illuminate\Support\Carbon;
+use Carbon\Carbon as CarbonCarbon;
+use Carbon\CarbonInterface;
 use Illuminate\Support\Facades\Date;
-use DateTimeInterface;
 
 class CdCarbonDate
 {
     protected string $userTimezone;
     protected string $systemTimezone;
+    protected CarbonInterface $carbon;
 
     public function __construct(?string $userTimezone = null, ?string $systemTimezone = null)
     {
         $this->userTimezone = $userTimezone ?? config('request.user.timezone') ?? config('app.user_timezone') ?? config('app.timezone'); // @phpstan-ignore-line -- will be string
         $this->systemTimezone = $systemTimezone ?? config('app.timezone'); //@phpstan-ignore-line - timezone is string
-    }
-
-    public function setUserTimezone(string $userTimezone): static
-    {
-        $this->userTimezone = $userTimezone;
-        return $this;
-    }
-
-    public function getUserTimezone(): string
-    {
-        return $this->userTimezone;
-    }
 
-    public function setSystemTimezone(string $systemTimezone): static
-    {
-        $this->systemTimezone = $systemTimezone;
-        return $this;
-    }
-
-    public function getSystemTimezone(): string
-    {
-        return $this->systemTimezone;
-    }
+        $this->carbon = (new CarbonImmutable());
 
-    public function usersStartOfDay(DateTimeInterface|string|null $date = null): Carbon
-    {
-        return Date::parse($date ?? now(), $this->userTimezone)->setTimezone($this->userTimezone)->startOfDay()->setTimezone($this->systemTimezone);
-    }
-
-    public function usersEndOfDay(DateTimeInterface|string|null $date = null): Carbon
-    {
-        return Date::parse($date ?? now(), $this->userTimezone)->setTimezone($this->userTimezone)->endOfDay()->setTimezone($this->systemTimezone);
+        $this->carbon->setUserTimezone(strval($this->userTimezone))
+        ->setSystemTimezone(strval($this->systemTimezone));
     }
 
-    public function toUsersTimezone(DateTimeInterface|string|null $date = null): Carbon
+    /**
+     * @param string $name
+     * @param array<mixed,mixed> $arguments
+     * @return mixed
+     */
+    public function __call(string $name, array $arguments): mixed
     {
-        return Date::parse($date ?? now(), $this->systemTimezone)->setTimezone($this->userTimezone);
+        return $this->carbon->{$name}(...$arguments);
     }
 
-    public function toSystemTimezone(DateTimeInterface|string|null $date = null): Carbon
+    /**
+     * @param string $name
+     * @param array<mixed,mixed> $arguments
+     * @return mixed
+     */
+    public static function __callStatic(string $name, array $arguments): mixed
     {
-        return Date::parse($date ?? now(), $this->systemTimezone)->setTimezone($this->systemTimezone);
+        $instance = new static(); //@phpstan-ignore-line
+        return $instance->{$name}(...$arguments);
     }
 }
diff --git a/src/CdCarbonMixin.php b/src/CdCarbonMixin.php
index 14be9ce..9a32469 100644
--- a/src/CdCarbonMixin.php
+++ b/src/CdCarbonMixin.php
@@ -24,8 +24,9 @@ class CdCarbonMixin
     {
         $mixin = $this;
 
-        return static function (string $timezone) use ($mixin) {
+        return function (string $timezone) use ($mixin) {
             $mixin->userTimezone = $timezone;
+            return $this;
         };
     }
 
@@ -42,8 +43,9 @@ class CdCarbonMixin
     {
         $mixin = $this;
 
-        return static function (string $timezone) use ($mixin) {
+        return function (string $timezone) use ($mixin) {
             $mixin->systemTimezone = $timezone;
+            return $this;
         };
     }
 
@@ -180,7 +182,7 @@ class CdCarbonMixin
         return static function ($time) {
              /** @var Carbon $date */
             $date = self::this();
-            return self::parse($time, $date->getUserTimezone());
+            return $date->parse($time, $date->getUserTimezone());
         };
     }
 }
diff --git a/src/Exceptions/RuntimeException.php b/src/Exceptions/RuntimeException.php
new file mode 100644
index 0000000..f42bff9
--- /dev/null
+++ b/src/Exceptions/RuntimeException.php
@@ -0,0 +1,44 @@
+<?php
+namespace CustomD\LaravelHelpers\Exceptions;
+
+use Illuminate\Http\JsonResponse;
+use Throwable;
+use RuntimeException as CoreException;
+
+class RuntimeException extends CoreException
+{
+    public int $statusCode = 500;
+
+    public string $extra = '';
+
+
+    public function __construct(string $message = "", string $extra = '', int $statusCode = 500, int $code = 0, Throwable $previous = null)
+    {
+        parent::__construct($message, $code, $previous);
+        $this->extra = $extra;
+        $this->statusCode = $statusCode;
+    }
+
+
+    public static function err404(string $message = "Not Found", string $extra = ''): static
+    {
+        //@phpstan-ignore-next-line
+        return new static($message, $extra, 404);
+    }
+
+
+    public static function err500(string $message = "Internal Server Error", string $extra = ''): static
+    {
+        //@phpstan-ignore-next-line
+        return new static($message, $extra, 500);
+    }
+
+    public function render(): JsonResponse
+    {
+        return new JsonResponse([
+            'message' => $this->message,
+            'extra'   => $this->extra,
+            'type'    => __CLASS__,
+        ], $this->statusCode);
+    }
+}
diff --git a/src/Facades/CdCarbonDate.php b/src/Facades/CdCarbonDate.php
deleted file mode 100644
index e60831e..0000000
--- a/src/Facades/CdCarbonDate.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-namespace CustomD\LaravelHelpers\Facades;
-
-use Illuminate\Support\Facades\Facade;
-
-/**
- * @method static \CustomD\LaravelHelpers\CdCarbonDate setUserTimezone(string $userTimezone)
- * @method static string getUserTimezone()
- * @method static \CustomD\LaravelHelpers\CdCarbonDate setSystemTimezone(string $systemTimezone)
- * @method static string getSystemTimezone()
- * @method static \Illuminate\Support\Carbon usersStartOfDay()
- * @method static \Illuminate\Support\Carbon usersEndOfDay()
- * @method static \Illuminate\Support\Carbon toUsersTimezone()
- * @method static \Illuminate\Support\Carbon toSystemTimezone()
- */
-class CdCarbonDate extends Facade
-{
-
-    protected static function getFacadeAccessor()
-    {
-        return 'cd-carbon-date';
-    }
-}
diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php
index 457bf3a..d83b508 100644
--- a/src/ServiceProvider.php
+++ b/src/ServiceProvider.php
@@ -4,6 +4,7 @@ namespace CustomD\LaravelHelpers;
 
 use Closure;
 use Carbon\Carbon;
+use Carbon\CarbonImmutable;
 use Illuminate\Support\Str;
 use Illuminate\Database\Query\Builder;
 use Illuminate\Database\Eloquent\Model;
@@ -18,10 +19,9 @@ class ServiceProvider extends \Illuminate\Support\ServiceProvider
 
     public function register()
     {
-        $this->app->bind('cd-carbon-date', function ($app) {
-            return new CdCarbonDate();
-        });
+
         Carbon::mixin(new CdCarbonMixin());
+        CarbonImmutable::mixin(new CdCarbonMixin());
     }
 
 
diff --git a/tests/DateHandlingTest.php b/tests/DateHandlingTest.php
index 592002f..6a3298e 100644
--- a/tests/DateHandlingTest.php
+++ b/tests/DateHandlingTest.php
@@ -3,7 +3,8 @@
 namespace CustomD\LaravelHelpers\Tests;
 
 use Carbon\Carbon;
-use CustomD\LaravelHelpers\Facades\CdCarbonDate;
+use Carbon\CarbonImmutable;
+use CustomD\LaravelHelpers\CdCarbonDate;
 use Mockery;
 use Mockery\MockInterface;
 use Illuminate\Http\Request;
@@ -13,6 +14,7 @@ use CustomD\LaravelHelpers\ServiceProvider;
 use CustomD\LaravelHelpers\Facades\LaravelHelpers;
 use CustomD\LaravelHelpers\Http\Middleware\UserTimeZone;
 use CustomD\LaravelHelpers\Tests\ExecutableAction;
+use Illuminate\Support\Carbon as SupportCarbon;
 
 class DateHandlingTest extends TestCase
 {
@@ -35,23 +37,39 @@ class DateHandlingTest extends TestCase
         $nzDate = '2023-01-05T22:04:00.000Z'; //equiv to 2023-01-06 11:04:00 AM NZ
 
         Carbon::setTestNow($nzDate);
+        CarbonImmutable::setTestNow($nzDate);
 
+        //asserting carbon dates
         $this->assertEquals("Thu Jan 05 2023 22:04:00 GMT+0000", now()->toString());
-        $this->assertEquals("Fri Jan 06 2023 11:04:00 GMT+1300", CdCarbonDate::toUsersTimezone(now())->toString());
         $this->assertEquals("Fri Jan 06 2023 11:04:00 GMT+1300", now()->toUsersTimezone()->toString());
         $this->assertEquals("Fri Jan 06 2023 11:04:00 GMT+1300", Carbon::toUsersTimezone()->toString());
-
-        $this->assertEquals("Thu Jan 05 2023 11:00:00 GMT+0000", CdCarbonDate::usersStartOfDay()->toString());
         $this->assertEquals("Thu Jan 05 2023 11:00:00 GMT+0000", Carbon::usersStartOfDay()->toString());
         $this->assertEquals("Thu Jan 05 2023 11:00:00 GMT+0000", now()->usersStartOfDay()->toString());
-
-        $this->assertEquals("Sat Jan 07 2023 10:59:59 GMT+0000", CdCarbonDate::usersEndOfDay('2023-01-07 23:55:00')->toString());
         $this->assertEquals("Sat Jan 07 2023 10:59:59 GMT+0000", Carbon::parseWithTz('2023-01-07 23:55:00')->usersEndOfDay()->toString());
-
-        $this->assertEquals("Sat Dec 31 2022 11:00:00 GMT+0000", CdCarbonDate::usersStartOfDay('2023-01-01T02:04:00.000Z')->toString());
         $this->assertEquals("Sat Dec 31 2022 11:00:00 GMT+0000", Carbon::parseWithTz('2023-01-01T02:04:00.000Z')->usersStartOfDay()->toString());
-
         $this->assertEquals("Fri Jan 06 2023 10:59:59 GMT+0000", now()->usersEndOfDay()->toString());
+
+        //make sure the factory returns a new instance each time
+        $customdDate1 = CdCarbonDate::toUsersTimezone(now()->toString());
+        $customdDate2 = CdCarbonDate::setUserTimezone('Africa/Johannesburg')->toUsersTimezone(now()->toString());
+
+        $this->assertNotEquals($customdDate1->toString(), $customdDate2->toString());
+
+        $this->assertEquals("Fri Jan 06 2023 11:04:00 GMT+1300", CdCarbonDate::toUsersTimezone(now())->toString());
+        $this->assertEquals("Thu Jan 05 2023 11:00:00 GMT+0000", CdCarbonDate::usersStartOfDay()->toString());
+        $this->assertEquals("Tue Jan 10 2023 10:59:59 GMT+0000", CdCarbonDate::parse('2023-01-09 23:55:00')->usersEndOfDay()->toString());
+        $this->assertEquals("Sat Dec 31 2022 11:00:00 GMT+0000", CdCarbonDate::parse('2023-01-01T02:04:00.000Z')->usersStartOfDay()->toString());
+    }
+
+    public function testMultipleCarbonInstances()
+    {
+        Config::set('app.user_timezone', 'Pacific/Auckland');
+        $nzDate = '2023-01-05T22:04:00.000Z'; //equiv to 2023-01-06 11:04:00 AM NZ
+        Carbon::setTestNow($nzDate);
+
+        $custom = CdCarbonDate::setUserTimezone('Africa/Johannesburg');
+
+        $this->assertNotEquals(now()->toUsersTimezone()->toString(), $custom->toUsersTimezone(now())->toString());
     }
 
     public function testSetsViaMiddleware()
-- 
GitLab