Repository: briannesbitt/Carbon (php)

Issue:
Locale is now ignored on weekday()
<!--
    🛑 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,

Since upgrading to the latest version of Carbon (2.63.0), I noticed that a few tests we're failing, because the weekday method was returning a different value. By looking at this [pull request](https://github.com/briannesbitt/Carbon/pull/2687/files), it looks like the method is now ignoring the set locale when no value parameter is given.

Code example that since the 2.63.2 update gives a different value in e.g. locale 'nl':
```php
echo Carbon::parse('2022-04-18')->weekday();
```
I expect to get the value `0` with locale 'nl', since in my locale the week starts on a monday.

However, since the locale is now ignored, I receive the value `1`, as it assumes that the week starts on Sunday.


Is this supposed to be the expected behavior and if so, how do I get the weekday in a given locale?

Carbon version: 2.63.0

PHP version: 8.1.12

<!--
    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://play.phpsandbox.io/embed/nesbot/carbon?input=%3C%3Fphp%0A%0Aecho%20Carbon%3A%3Aparse('2018-06-01')-%3EsubDay()-%3Emonth%3B

    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.
-->


<!--
    If you did not succeed to get the same result in
    https://play.phpsandbox.io/embed/nesbot/carbon?input=%3C%3Fphp%0A%0Aecho%20Carbon%3A%3Aparse('2018-06-01')%3B then precise the
    result you get.
-->

Thanks!


Pull request description:
Add get/set DaysFromStartOfWeek methods
Fix #2702

Patch:
diff --git a/src/Carbon/Traits/Date.php b/src/Carbon/Traits/Date.php
index c12ed1eb..750516a0 100644
--- a/src/Carbon/Traits/Date.php
+++ b/src/Carbon/Traits/Date.php
@@ -1380,6 +1380,39 @@ trait Date
         return $value === null ? $dayOfWeekIso : $this->addDays($value - $dayOfWeekIso);
     }
 
+    /**
+     * Return the number of days since the start of the week (using the current locale or the first parameter
+     * if explicitly given).
+     *
+     * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week,
+     *                               if not provided, start of week is inferred from the locale
+     *                               (Sunday for en_US, Monday for de_DE, etc.)
+     *
+     * @return int
+     */
+    public function getDaysFromStartOfWeek(int $weekStartsAt = null): int
+    {
+        $firstDay = (int) ($weekStartsAt ?? $this->getTranslationMessage('first_day_of_week') ?? 0);
+
+        return ($this->dayOfWeek + 7 - $firstDay) % 7;
+    }
+
+    /**
+     * Set the day (keeping the current time) to the start of the week + the number of days passed as the first
+     * parameter. First day of week is driven by the locale unless explicitly set with the second parameter.
+     *
+     * @param int      $numberOfDays number of days to add after the start of the current week
+     * @param int|null $weekStartsAt optional start allow you to specify the day of week to use to start the week,
+     *                               if not provided, start of week is inferred from the locale
+     *                               (Sunday for en_US, Monday for de_DE, etc.)
+     *
+     * @return static
+     */
+    public function setDaysFromStartOfWeek(int $numberOfDays, int $weekStartsAt = null)
+    {
+        return $this->addDays($numberOfDays - $this->getDaysFromStartOfWeek($weekStartsAt));
+    }
+
     /**
      * Set any unit to a new value without overflowing current other unit given.
      *


Test patch:
diff --git a/tests/Carbon/WeekTest.php b/tests/Carbon/WeekTest.php
index e512ba69..2ffb55d4 100644
--- a/tests/Carbon/WeekTest.php
+++ b/tests/Carbon/WeekTest.php
@@ -5437,4 +5437,111 @@ class WeekTest extends AbstractTestCase
         Carbon::setTestNowAndTimezone(Carbon::parse('2022-10-21 16:00:00'), 'Europe/Oslo');
         $this->assertSame('2022-10-18 00:00', Carbon::today()->weekday(CarbonInterface::TUESDAY)->format('Y-m-d H:i'));
     }
+
+    /**
+     * @dataProvider getDaysFromStartOfWeekDataProvider
+     */
+    public function testGetDaysFromStartOfWeek(string $locale, string $date, int $daysCount)
+    {
+        $this->assertSame(
+            $daysCount,
+            Carbon::parse($date)->locale($locale)->getDaysFromStartOfWeek()
+        );
+    }
+
+    public function getDaysFromStartOfWeekDataProvider(): array
+    {
+        return [
+            'Monday en_US' => ['en_US', '2022-11-21', 1],
+            'Monday late en_US' => ['en_US', '2022-11-21 23:59', 1],
+            'Tuesday en_US' => ['en_US', '2022-11-22 00:01', 2],
+            'Thursday en_US' => ['en_US', '2022-11-24', 4],
+            'Saturday en_US' => ['en_US', '2022-11-26 23:59:59.999999', 6],
+            'Sunday en_US' => ['en_US', '2022-11-27 00:00:00', 0],
+            'Monday fr_FR' => ['fr_FR', '2022-11-21', 0],
+            'Monday late fr_FR' => ['fr_FR', '2022-11-21 23:59', 0],
+            'Tuesday fr_FR' => ['fr_FR', '2022-11-22 00:01', 1],
+            'Thursday fr_FR' => ['fr_FR', '2022-11-24', 3],
+            'Saturday fr_FR' => ['fr_FR', '2022-11-26 23:59:59.999999', 5],
+            'Sunday fr_FR' => ['fr_FR', '2022-11-27 00:00:00', 6],
+            'Monday ku' => ['ku', '2022-11-21', 2],
+            'Monday late ku' => ['ku', '2022-11-21 23:59', 2],
+            'Tuesday ku' => ['ku', '2022-11-22 00:01', 3],
+            'Thursday ku' => ['ku', '2022-11-24', 5],
+            'Saturday ku' => ['ku', '2022-11-26 23:59:59.999999', 0],
+            'Sunday ku' => ['ku', '2022-11-27 00:00:00', 1],
+        ];
+    }
+
+    /**
+     * @dataProvider getDaysFromStartOfWeekDataProviderExplicit
+     */
+    public function testGetDaysFromStartOfWeekExplicit(int $start, string $date, int $daysCount)
+    {
+        static $locales = [null, 'pt_BR', 'de_CH', 'ar_MA'];
+        $carbon = Carbon::parse($date);
+        $locale = $locales[array_rand($locales)];
+
+        if ($locale) {
+            $carbon = $carbon->locale($locale);
+        }
+
+        $this->assertSame($daysCount, $carbon->getDaysFromStartOfWeek($start));
+    }
+
+    public function getDaysFromStartOfWeekDataProviderExplicit(): array
+    {
+        return [
+            'Monday 0' => [0, '2022-11-21', 1],
+            'Monday late 0' => [0, '2022-11-21 23:59', 1],
+            'Tuesday 0' => [0, '2022-11-22 00:01', 2],
+            'Thursday 0' => [0, '2022-11-24', 4],
+            'Saturday 0' => [0, '2022-11-26 23:59:59.999999', 6],
+            'Sunday 0' => [0, '2022-11-27 00:00:00', 0],
+            'Monday 1' => [1, '2022-11-21', 0],
+            'Monday late 1' => [1, '2022-11-21 23:59', 0],
+            'Tuesday 1' => [1, '2022-11-22 00:01', 1],
+            'Thursday 1' => [1, '2022-11-24', 3],
+            'Saturday 1' => [1, '2022-11-26 23:59:59.999999', 5],
+            'Sunday 1' => [1, '2022-11-27 00:00:00', 6],
+            'Monday 6' => [6, '2022-11-21', 2],
+            'Monday late 6' => [6, '2022-11-21 23:59', 2],
+            'Tuesday 6' => [6, '2022-11-22 00:01', 3],
+            'Thursday 6' => [6, '2022-11-24', 5],
+            'Saturday 6' => [6, '2022-11-26 23:59:59.999999', 0],
+            'Sunday 6' => [6, '2022-11-27 00:00:00', 1],
+        ];
+    }
+
+    public function testSetDaysFromStartOfWeek()
+    {
+        $this->assertSame(
+            '2022-11-29 23:59:59.999999',
+            Carbon::parse('2022-11-26 23:59:59.999999')
+                ->locale('ar_MA')
+                ->setDaysFromStartOfWeek(3)
+                ->format('Y-m-d H:i:s.u')
+        );
+        $this->assertSame(
+            '2022-11-24 12:34:56.123456',
+            Carbon::parse('2022-11-24 12:34:56.123456')
+                ->locale('fr_FR')
+                ->setDaysFromStartOfWeek(3)
+                ->format('Y-m-d H:i:s.u')
+        );
+        $this->assertSame(
+            '2022-11-23 12:34:56.123456',
+            Carbon::parse('2022-11-24 12:34:56.123456')
+                ->locale('en_US')
+                ->setDaysFromStartOfWeek(3)
+                ->format('Y-m-d H:i:s.u')
+        );
+        $this->assertSame(
+            '2022-11-27 12:34:56.123456',
+            Carbon::parse('2022-11-24 12:34:56.123456')
+                ->locale('en_US')
+                ->setDaysFromStartOfWeek(3, 4)
+                ->format('Y-m-d H:i:s.u')
+        );
+    }
 }
diff --git a/tests/CarbonImmutable/WeekTest.php b/tests/CarbonImmutable/WeekTest.php
index 3814c1f5..001a4761 100644
--- a/tests/CarbonImmutable/WeekTest.php
+++ b/tests/CarbonImmutable/WeekTest.php
@@ -5431,4 +5431,111 @@ class WeekTest extends AbstractTestCase
         $this->assertSame(CarbonInterface::MONDAY, $date->weekday());
         $this->assertSame(CarbonInterface::MONDAY, $date->isoWeekday());
     }
+
+    /**
+     * @dataProvider getDaysFromStartOfWeekDataProvider
+     */
+    public function testGetDaysFromStartOfWeek(string $locale, string $date, int $daysCount)
+    {
+        $this->assertSame(
+            $daysCount,
+            Carbon::parse($date)->locale($locale)->getDaysFromStartOfWeek()
+        );
+    }
+
+    public function getDaysFromStartOfWeekDataProvider(): array
+    {
+        return [
+            'Monday en_US' => ['en_US', '2022-11-21', 1],
+            'Monday late en_US' => ['en_US', '2022-11-21 23:59', 1],
+            'Tuesday en_US' => ['en_US', '2022-11-22 00:01', 2],
+            'Thursday en_US' => ['en_US', '2022-11-24', 4],
+            'Saturday en_US' => ['en_US', '2022-11-26 23:59:59.999999', 6],
+            'Sunday en_US' => ['en_US', '2022-11-27 00:00:00', 0],
+            'Monday fr_FR' => ['fr_FR', '2022-11-21', 0],
+            'Monday late fr_FR' => ['fr_FR', '2022-11-21 23:59', 0],
+            'Tuesday fr_FR' => ['fr_FR', '2022-11-22 00:01', 1],
+            'Thursday fr_FR' => ['fr_FR', '2022-11-24', 3],
+            'Saturday fr_FR' => ['fr_FR', '2022-11-26 23:59:59.999999', 5],
+            'Sunday fr_FR' => ['fr_FR', '2022-11-27 00:00:00', 6],
+            'Monday ku' => ['ku', '2022-11-21', 2],
+            'Monday late ku' => ['ku', '2022-11-21 23:59', 2],
+            'Tuesday ku' => ['ku', '2022-11-22 00:01', 3],
+            'Thursday ku' => ['ku', '2022-11-24', 5],
+            'Saturday ku' => ['ku', '2022-11-26 23:59:59.999999', 0],
+            'Sunday ku' => ['ku', '2022-11-27 00:00:00', 1],
+        ];
+    }
+
+    /**
+     * @dataProvider getDaysFromStartOfWeekDataProviderExplicit
+     */
+    public function testGetDaysFromStartOfWeekExplicit(int $start, string $date, int $daysCount)
+    {
+        static $locales = [null, 'pt_BR', 'de_CH', 'ar_MA'];
+        $carbon = Carbon::parse($date);
+        $locale = $locales[array_rand($locales)];
+
+        if ($locale) {
+            $carbon = $carbon->locale($locale);
+        }
+
+        $this->assertSame($daysCount, $carbon->getDaysFromStartOfWeek($start));
+    }
+
+    public function getDaysFromStartOfWeekDataProviderExplicit(): array
+    {
+        return [
+            'Monday 0' => [0, '2022-11-21', 1],
+            'Monday late 0' => [0, '2022-11-21 23:59', 1],
+            'Tuesday 0' => [0, '2022-11-22 00:01', 2],
+            'Thursday 0' => [0, '2022-11-24', 4],
+            'Saturday 0' => [0, '2022-11-26 23:59:59.999999', 6],
+            'Sunday 0' => [0, '2022-11-27 00:00:00', 0],
+            'Monday 1' => [1, '2022-11-21', 0],
+            'Monday late 1' => [1, '2022-11-21 23:59', 0],
+            'Tuesday 1' => [1, '2022-11-22 00:01', 1],
+            'Thursday 1' => [1, '2022-11-24', 3],
+            'Saturday 1' => [1, '2022-11-26 23:59:59.999999', 5],
+            'Sunday 1' => [1, '2022-11-27 00:00:00', 6],
+            'Monday 6' => [6, '2022-11-21', 2],
+            'Monday late 6' => [6, '2022-11-21 23:59', 2],
+            'Tuesday 6' => [6, '2022-11-22 00:01', 3],
+            'Thursday 6' => [6, '2022-11-24', 5],
+            'Saturday 6' => [6, '2022-11-26 23:59:59.999999', 0],
+            'Sunday 6' => [6, '2022-11-27 00:00:00', 1],
+        ];
+    }
+
+    public function testSetDaysFromStartOfWeek()
+    {
+        $this->assertSame(
+            '2022-11-29 23:59:59.999999',
+            Carbon::parse('2022-11-26 23:59:59.999999')
+                ->locale('ar_MA')
+                ->setDaysFromStartOfWeek(3)
+                ->format('Y-m-d H:i:s.u')
+        );
+        $this->assertSame(
+            '2022-11-24 12:34:56.123456',
+            Carbon::parse('2022-11-24 12:34:56.123456')
+                ->locale('fr_FR')
+                ->setDaysFromStartOfWeek(3)
+                ->format('Y-m-d H:i:s.u')
+        );
+        $this->assertSame(
+            '2022-11-23 12:34:56.123456',
+            Carbon::parse('2022-11-24 12:34:56.123456')
+                ->locale('en_US')
+                ->setDaysFromStartOfWeek(3)
+                ->format('Y-m-d H:i:s.u')
+        );
+        $this->assertSame(
+            '2022-11-27 12:34:56.123456',
+            Carbon::parse('2022-11-24 12:34:56.123456')
+                ->locale('en_US')
+                ->setDaysFromStartOfWeek(3, 4)
+                ->format('Y-m-d H:i:s.u')
+        );
+    }
 }