Repository: briannesbitt/Carbon (php)

Issue:
Globally set CarbonInterval::toStringFormat()
<!--
    🛑 DON'T REMOVE ME.
    This issue template apply to all
      - bug reports,
      - feature proposals,
      - and documentation requests

    Having all those informations will allow us to know exactly
    what you expect and answer you faster and precisely (answer
    that matches your Carbon version, PHP version and usage).
    
    Note: Comments between <!- - and - -> won't appear in the final
    issue (See [Preview] tab).
-->
Hello,

Is there a way to globally change the `CarbonInterval::__toString()` format? I'm using `CarbonInterval` in a generic class which type juggle to string when an attribute is `Stringable`.

Since [`PHP 8`](https://www.php.net/manual/en/language.oop5.magic.php#object.tostring), `CarbonInterval` is `Stringable`, so will be converted to string in my class, which currently defaults to `$this->forHuman()`. I would like to set globally something like:

```php
CarbonInterval::setToStringFormat(function ($interval) {
    return $interval->spec();
});

// OR

CarbonInterval::settings([
    'toStringFormat' => function ($interval) {
        return $interval->spec();
    }
]);
```

Is there a way to do that please?

Carbon version: **2.62.0**

PHP version: **8.1.0**

<!--
    Run the command `composer show nesbot/carbon`
    to get "versions"
    Use `echo phpversion();`
    to get PHP version.

    Some issues can depends on your context, settings,
    macros, timezone, language. So to be sure the code
    you give is enough to reproduce your bug, try it
    first in:
    https://try-carbon.herokuapp.com/?theme=xcode&export&embed

    You can use the [Options] button to change the version
    then when you get the bug with this editor, you can use
    the [Export] button, copy the link of the opened tab,
    then paste it in the issue. Then we can immediatly get
    your issue.
-->

Thanks!

@kylekatarnls edit: replacing `CarbonInterface` occurrences with `CarbonInterval` as it sounds it was the intended meaning.

Pull request description:
Harmonize toStringFormat config
Use the same possibility and precedence for `->settings(['toStringFormat' => ...])`, `::setToStringFormat()` then default cast method for all `Carbon`, `CarbonImmutable`, `CarbonInterval` and `CarbonPeriod`.

`::setToStringFormat()` remains discouraged for all classes and `->settings(['toStringFormat' => ...])` (with or without factory) is still the safest option.

But for consistency, until 3.0, they'll be aligned across the different classes.

Resolves #2656

Patch:
diff --git a/src/Carbon/CarbonInterval.php b/src/Carbon/CarbonInterval.php
index 54c64d41..ba89829b 100644
--- a/src/Carbon/CarbonInterval.php
+++ b/src/Carbon/CarbonInterval.php
@@ -24,6 +24,7 @@ use Carbon\Traits\IntervalRounding;
 use Carbon\Traits\IntervalStep;
 use Carbon\Traits\Mixin;
 use Carbon\Traits\Options;
+use Carbon\Traits\ToStringFormat;
 use Closure;
 use DateInterval;
 use DateTimeInterface;
@@ -188,6 +189,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
         Mixin::mixin as baseMixin;
     }
     use Options;
+    use ToStringFormat;
 
     /**
      * Interval spec period designators
@@ -1821,7 +1823,7 @@ class CarbonInterval extends DateInterval implements CarbonConverterInterface
      */
     public function __toString()
     {
-        $format = $this->localToStringFormat;
+        $format = $this->localToStringFormat ?? static::$toStringFormat;
 
         if (!$format) {
             return $this->forHumans();
diff --git a/src/Carbon/CarbonPeriod.php b/src/Carbon/CarbonPeriod.php
index e2be2cda..dbf5eb1b 100644
--- a/src/Carbon/CarbonPeriod.php
+++ b/src/Carbon/CarbonPeriod.php
@@ -24,6 +24,7 @@ use Carbon\Exceptions\UnreachableException;
 use Carbon\Traits\IntervalRounding;
 use Carbon\Traits\Mixin;
 use Carbon\Traits\Options;
+use Carbon\Traits\ToStringFormat;
 use Closure;
 use Countable;
 use DateInterval;
@@ -175,6 +176,7 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable
         Mixin::mixin as baseMixin;
     }
     use Options;
+    use ToStringFormat;
 
     /**
      * Built-in filter for limit by recurrences.
@@ -1459,13 +1461,21 @@ class CarbonPeriod implements Iterator, Countable, JsonSerializable
      */
     public function toString()
     {
+        $format = $this->localToStringFormat ?? static::$toStringFormat;
+
+        if ($format instanceof Closure) {
+            return $format($this);
+        }
+
         $translator = ([$this->dateClass, 'getTranslator'])();
 
         $parts = [];
 
-        $format = !$this->startDate->isStartOfDay() || ($this->endDate && !$this->endDate->isStartOfDay())
-            ? 'Y-m-d H:i:s'
-            : 'Y-m-d';
+        $format = $format ?? (
+            !$this->startDate->isStartOfDay() || ($this->endDate && !$this->endDate->isStartOfDay())
+                ? 'Y-m-d H:i:s'
+                : 'Y-m-d'
+        );
 
         if ($this->recurrences !== null) {
             $parts[] = $this->translate('period_recurrences', [], $this->recurrences, $translator);
diff --git a/src/Carbon/Traits/Converter.php b/src/Carbon/Traits/Converter.php
index d6691b2e..05598d57 100644
--- a/src/Carbon/Traits/Converter.php
+++ b/src/Carbon/Traits/Converter.php
@@ -34,39 +34,7 @@ use ReturnTypeWillChange;
  */
 trait Converter
 {
-    /**
-     * Format to use for __toString method when type juggling occurs.
-     *
-     * @var string|Closure|null
-     */
-    protected static $toStringFormat;
-
-    /**
-     * Reset the format used to the default when type juggling a Carbon instance to a string
-     *
-     * @return void
-     */
-    public static function resetToStringFormat()
-    {
-        static::setToStringFormat(null);
-    }
-
-    /**
-     * @deprecated To avoid conflict between different third-party libraries, static setters should not be used.
-     *             You should rather let Carbon object being casted to string with DEFAULT_TO_STRING_FORMAT, and
-     *             use other method or custom format passed to format() method if you need to dump an other string
-     *             format.
-     *
-     * Set the default format used when type juggling a Carbon instance to a string
-     *
-     * @param string|Closure|null $format
-     *
-     * @return void
-     */
-    public static function setToStringFormat($format)
-    {
-        static::$toStringFormat = $format;
-    }
+    use ToStringFormat;
 
     /**
      * Returns the formatted date string on success or FALSE on failure.
@@ -110,7 +78,7 @@ trait Converter
      *
      * @example
      * ```
-     * echo Carbon::now(); // Carbon instances can be casted to string
+     * echo Carbon::now(); // Carbon instances can be cast to string
      * ```
      *
      * @return string
diff --git a/src/Carbon/Traits/ToStringFormat.php b/src/Carbon/Traits/ToStringFormat.php
new file mode 100644
index 00000000..38cb2406
--- /dev/null
+++ b/src/Carbon/Traits/ToStringFormat.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * This file is part of the Carbon package.
+ *
+ * (c) Brian Nesbitt <brian@nesbot.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Carbon\Traits;
+
+use Closure;
+
+/**
+ * Trait ToStringFormat.
+ *
+ * Handle global format customization for string cast of the object.
+ */
+trait ToStringFormat
+{
+    /**
+     * Format to use for __toString method when type juggling occurs.
+     *
+     * @var string|Closure|null
+     */
+    protected static $toStringFormat;
+
+    /**
+     * Reset the format used to the default when type juggling a Carbon instance to a string
+     *
+     * @return void
+     */
+    public static function resetToStringFormat()
+    {
+        static::setToStringFormat(null);
+    }
+
+    /**
+     * @param string|Closure|null $format
+     *
+     * @return void
+     *
+     * @deprecated To avoid conflict between different third-party libraries, static setters should not be used.
+     *             You should rather let Carbon object being cast to string with DEFAULT_TO_STRING_FORMAT, and
+     *             use other method or custom format passed to format() method if you need to dump another string
+     *             format.
+     *
+     * Set the default format used when type juggling a Carbon instance to a string.
+     */
+    public static function setToStringFormat($format)
+    {
+        static::$toStringFormat = $format;
+    }
+}


Test patch:
diff --git a/tests/CarbonInterval/ToStringTest.php b/tests/CarbonInterval/ToStringTest.php
index 25ebda72..fb7af633 100644
--- a/tests/CarbonInterval/ToStringTest.php
+++ b/tests/CarbonInterval/ToStringTest.php
@@ -56,9 +56,14 @@ class ToStringTest extends AbstractTestCase
     public function testClosure()
     {
         $ci = CarbonInterval::create(11);
+        $this->assertSame('11 years:abc', $ci.':abc');
+        CarbonInterval::setToStringFormat('%Y');
+        $this->assertSame('11:abc', $ci.':abc');
         $ci->settings(['toStringFormat' => static function (CarbonInterval $interval) {
             return 'Y'.($interval->years * 2);
         }]);
         $this->assertSame('Y22:abc', $ci.':abc');
+
+        CarbonInterval::resetToStringFormat();
     }
 }
diff --git a/tests/CarbonPeriod/ToStringTest.php b/tests/CarbonPeriod/ToStringTest.php
index 5969752e..d0b60a19 100644
--- a/tests/CarbonPeriod/ToStringTest.php
+++ b/tests/CarbonPeriod/ToStringTest.php
@@ -169,4 +169,34 @@ class ToStringTest extends AbstractTestCase
             $period[0]->toImmutable()->startOfWeek()->format('Y-m-d H:i:s')
         );
     }
+
+    public function testToStringCustomization()
+    {
+        $sunday = CarbonImmutable::parse('2019-12-01');
+
+        $period = CarbonPeriod::create($sunday->startOfWeek(), '1 week', $sunday->endOfWeek());
+
+        $this->assertSame(
+            'Every 1 week from 2019-11-25 00:00:00 to 2019-12-01 23:59:59!!',
+            $period.'!!'
+        );
+
+        CarbonPeriod::setToStringFormat('m/d');
+
+        $this->assertSame(
+            'Every 1 week from 11/25 to 12/01!!',
+            $period.'!!'
+        );
+
+        $period->settings(['toStringFormat' => static function (CarbonPeriod $period) {
+            return $period->toIso8601String();
+        }]);
+
+        $this->assertSame(
+            '2019-11-25T00:00:00-05:00/P7D/2019-12-01T23:59:59-05:00!!',
+            $period.'!!'
+        );
+
+        CarbonPeriod::resetToStringFormat();
+    }
 }