Repository: briannesbitt/Carbon (php)

Issue:
diffInWeekdays returns unexpected values
<!--
    🛑 DON'T REMOVE ME.
    This issue template applies 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,

I encountered an issue with the following code. I'd expect only the dates to matter here but it seems that just an hour (or a second, doesn't matter) of difference counts as a weekday and 25 hours counts as two weekdays.
<!--
    ⚠️ Your code below (not the code from inside Carbon library)
    Be sure it's dependency-free and enough to reproduce the issue
    when we run it on:
    https://play.phpsandbox.io/embed/nesbot/carbon
-->
```php
// 0 hour diff
$s1 = Carbon::create(2023, 6, 6, 15, 0, 0);
$e1 = Carbon::create(2023, 6, 6, 15, 0, 0);

echo $s1->diffInWeekdays($e1) . PHP_EOL;

// 1 hour diff
$s2 = Carbon::create(2023, 6, 6, 15, 0, 0);
$e2 = Carbon::create(2023, 6, 6, 16, 0, 0);

echo $s2->diffInWeekdays($e2) . PHP_EOL;

// 23 hour diff
$s3 = Carbon::create(2023, 6, 6, 15, 0, 0);
$e3 = Carbon::create(2023, 6, 7, 14, 0, 0);

echo $s3->diffInWeekdays($e3) . PHP_EOL;

// 24 hour diff
$s4 = Carbon::create(2023, 6, 6, 15, 0, 0);
$e4 = Carbon::create(2023, 6, 7, 15, 0, 0);

echo $s4->diffInWeekdays($e4) . PHP_EOL;

// 25 hour diff
$s5 = Carbon::create(2023, 6, 6, 15, 0, 0);
$e5 = Carbon::create(2023, 6, 7, 16, 0, 0);

echo $s5->diffInWeekdays($e5) . PHP_EOL;
```

Carbon version: **2.67.0**
<!--
    ⚠️ Run the command `composer show nesbot/carbon` to get "versions"
-->

PHP version: **7.4.10**

<!--
    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.
-->


I expected to get:

```
0
0
1
1
1
```
<!--
    Always give your expectations. Each use has their owns.
    You may want daylight saving time to be taken into account,
    someone else want it to be ignored. You may want timezone
    to be used in comparisons, someone else may not, etc.
-->

But I actually get:

```
0
1
1
1
2
```
<!--
    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:
Fix diffInWeekdays and diffInWeekendDays with partial days
Enforce diffInWeekdays and diffInWeekendDays to use perdiod starting and ending at the same hour

Fix #2798

Patch:
diff --git a/readme.md b/readme.md
index 49e0e9e7..efc5811f 100644
--- a/readme.md
+++ b/readme.md
@@ -123,8 +123,8 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
 <a title="#1 Guide To Online Gambling In Canada" href="https://casinohex.org/canada/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank" rel="sponsored"><img alt="CasinoHex Canada" src="https://opencollective-production.s3.us-west-1.amazonaws.com/79fdbcc0-a997-11eb-abbc-25e48b63c6dc.jpg" width="85" height="64"></a>
 <a title="Casino-portugal.pt" href="https://casino-portugal.pt/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank" rel="sponsored"><img alt="Casino-portugal.pt" src="https://logo.clearbit.com/casino-portugal.pt" width="64" height="64"></a>
 <a title="Slots City® ➢ Лучшее лицензионно казино онлайн и оффлайн на гривны в Украине. 【 Более1500 игровых автоматов и слотов】✅ Официально и Безопасно" href="https://slotscity.ua/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank" rel="sponsored"><img alt="Slots City" src="https://opencollective-production.s3.us-west-1.amazonaws.com/d7e298c0-7abe-11ed-8553-230872f5e54d.png" width="90" height="64"></a>
-<a title="Znajdź najlepsze zakłady bukmacherskie w Polsce w 2023 roku. Probukmacher.pl to Twoje kompendium wiedzy na temat bukmacherów!" href="https://www.probukmacher.pl?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank" rel="sponsored"><img alt="Probukmacher" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/caf50271-4560-4ffe-a434-ea15239168db/Screenshot_1.png" width="89" height="64"></a>
 <a title="inkedin" href="https://inkedin.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank" rel="sponsored"><img alt="inkedin" src="https://logo.clearbit.com/inkedin.com" width="64" height="64"></a>
+<a title="Znajdź najlepsze zakłady bukmacherskie w Polsce w 2023 roku. Probukmacher.pl to Twoje kompendium wiedzy na temat bukmacherów!" href="https://www.probukmacher.pl?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank" rel="sponsored"><img alt="Probukmacher" src="https://opencollective-production.s3.us-west-1.amazonaws.com/account-avatar/caf50271-4560-4ffe-a434-ea15239168db/Screenshot_1.png" width="89" height="64"></a>
 <a title="A self-hosted web radio management suite, including turnkey installer tools and an easy-to-use web app to manage your stations. " href="https://azuracast.com?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank" rel="sponsored"><img alt="AzuraCast" src="https://opencollective-production.s3.us-west-1.amazonaws.com/3c12ea10-cdfb-11eb-9cf4-3760b386b76d.png" width="64" height="64"></a>
 <a title="Triplebyte is the first software engineering job platform that is on the developer&#039;s side. Take our coding quiz!" href="https://triplebyte.com/os/opencollective?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank" rel="sponsored"><img alt="Triplebyte" src="https://opencollective-production.s3.us-west-1.amazonaws.com/43e4f9d0-30cd-11ea-9c6b-e1142996e8b2.png" width="64" height="64"></a>
 <a title="Connect your Collective to GitHub Sponsors: https://docs.opencollective.com/help/collectives/github-sponsors" href="https://github.com/sponsors/?utm_source=opencollective&amp;utm_medium=github&amp;utm_campaign=Carbon" target="_blank" rel="sponsored"><img alt="GitHub Sponsors" src="https://opencollective-production.s3.us-west-1.amazonaws.com/87b1d240-f617-11ea-9960-fd7e8ab20fe4.png" width="73" height="64"></a>
@@ -142,7 +142,7 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
 
 Thank you to all our backers! 🙏
 
-<a href="https://opencollective.com/Carbon#backers" target="_blank"><img src="https://opencollective.com/Carbon/backers.svg?width=890&version=2023-06-08-07-12"></a>
+<a href="https://opencollective.com/Carbon#backers" target="_blank"><img src="https://opencollective.com/Carbon/backers.svg?width=890"></a>
 
 [[Become a backer](https://opencollective.com/Carbon#backer)]
 
diff --git a/sponsors.php b/sponsors.php
index 4f6d994e..80128161 100644
--- a/sponsors.php
+++ b/sponsors.php
@@ -27,18 +27,10 @@ function getOpenCollectiveSponsors(): string
 
     $list = array_map(static function (array $member) {
         $createdAt = CarbonImmutable::parse($member['createdAt']);
-        $lastTransactionAt = CarbonImmutable::parse($member['lastTransactionAt']);
-
-        if ($createdAt->format('d H:i:s.u') > $lastTransactionAt->format('d H:i:s.u')) {
-            $createdAt = $createdAt
-                ->setDay($lastTransactionAt->day)
-                ->modify($lastTransactionAt->format('H:i:s.u'));
-        }
-
-        $monthlyContribution = (float) ($member['totalAmountDonated'] / ceil($createdAt->floatDiffInMonths()));
+        $monthlyContribution = (float) ($member['totalAmountDonated'] / (1 + $createdAt->diffInMonths()));
 
         if (
-            $lastTransactionAt->isAfter('last month') &&
+            CarbonImmutable::parse($member['lastTransactionAt'])->isAfter('last month') &&
             $member['lastTransactionAmount'] > $monthlyContribution
         ) {
             $monthlyContribution = (float) $member['lastTransactionAmount'];
diff --git a/src/Carbon/Traits/Difference.php b/src/Carbon/Traits/Difference.php
index a9868ebf..1f8a3f6a 100644
--- a/src/Carbon/Traits/Difference.php
+++ b/src/Carbon/Traits/Difference.php
@@ -298,9 +298,9 @@ trait Difference
      */
     public function diffInWeekdays($date = null, $absolute = true)
     {
-        return $this->diffInDaysFiltered(function (CarbonInterface $date) {
+        return $this->diffInDaysFiltered(static function (CarbonInterface $date) {
             return $date->isWeekday();
-        }, $date, $absolute);
+        }, $this->resolveCarbon($date)->modify($this->format('H:i:s.u')), $absolute);
     }
 
     /**
@@ -313,9 +313,9 @@ trait Difference
      */
     public function diffInWeekendDays($date = null, $absolute = true)
     {
-        return $this->diffInDaysFiltered(function (CarbonInterface $date) {
+        return $this->diffInDaysFiltered(static function (CarbonInterface $date) {
             return $date->isWeekend();
-        }, $date, $absolute);
+        }, $this->resolveCarbon($date)->modify($this->format('H:i:s.u')), $absolute);
     }
 
     /**


Test patch:
diff --git a/tests/Carbon/DiffTest.php b/tests/Carbon/DiffTest.php
index dd39fc13..b778fa72 100644
--- a/tests/Carbon/DiffTest.php
+++ b/tests/Carbon/DiffTest.php
@@ -357,28 +357,66 @@ class DiffTest extends AbstractTestCase
         $this->assertSame(0, $start->diffInWeekdays($end));
     }
 
+    public function testBug2798ConsistencyWithDiffInDays()
+    {
+        // 0 hour diff
+        $s1 = Carbon::create(2023, 6, 6, 15, 0, 0);
+        $e1 = Carbon::create(2023, 6, 6, 15, 0, 0);
+
+        $this->assertSame(0, $s1->diffInWeekdays($e1));
+        $this->assertSame(0, $s1->diffInDays($e1));
+
+        // 1 hour diff
+        $s2 = Carbon::create(2023, 6, 6, 15, 0, 0);
+        $e2 = Carbon::create(2023, 6, 6, 16, 0, 0);
+
+        $this->assertSame(0, $s2->diffInWeekdays($e2));
+        $this->assertSame(0, $s2->diffInDays($e2));
+
+        // 23 hour diff
+        $s3 = Carbon::create(2023, 6, 6, 15, 0, 0);
+        $e3 = Carbon::create(2023, 6, 7, 14, 0, 0);
+
+        $this->assertSame(1, $s3->diffInWeekdays($e3));
+        $this->assertSame(1, $s3->diffInDays($e3));
+
+        // 24 hour diff
+        $s4 = Carbon::create(2023, 6, 6, 15, 0, 0);
+        $e4 = Carbon::create(2023, 6, 7, 15, 0, 0);
+
+        $this->assertSame(1, $s4->diffInWeekdays($e4));
+        $this->assertSame(1, $s4->diffInDays($e4));
+
+        // 25 hour diff
+        $s5 = Carbon::create(2023, 6, 6, 15, 0, 0);
+        $e5 = Carbon::create(2023, 6, 7, 16, 0, 0);
+
+        $this->assertSame(1, $s5->diffInWeekdays($e5));
+        $this->assertSame(1, $s5->diffInDays($e5));
+    }
+
     public function testDiffInWeekdaysPositive()
     {
         $dt = Carbon::createFromDate(2000, 1, 1);
-        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->endOfMonth()));
+        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->addMonth()));
     }
 
     public function testDiffInWeekdaysNegativeNoSign()
     {
         $dt = Carbon::createFromDate(2000, 1, 31);
-        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->startOfMonth()));
+        $this->assertSame(20, $dt->diffInWeekdays($dt->copy()->startOfMonth()));
     }
 
     public function testDiffInWeekdaysNegativeWithSign()
     {
         $dt = Carbon::createFromDate(2000, 1, 31);
-        $this->assertSame(-21, $dt->diffInWeekdays($dt->copy()->startOfMonth(), false));
+        $this->assertSame(-20, $dt->diffInWeekdays($dt->copy()->startOfMonth(), false));
     }
 
     public function testDiffInWeekendDaysPositive()
     {
         $dt = Carbon::createFromDate(2000, 1, 1);
-        $this->assertSame(10, $dt->diffInWeekendDays($dt->copy()->endOfMonth()));
+        $this->assertSame(10, $dt->diffInWeekendDays($dt->copy()->addMonth()));
     }
 
     public function testDiffInWeekendDaysNegativeNoSign()
diff --git a/tests/CarbonImmutable/DiffTest.php b/tests/CarbonImmutable/DiffTest.php
index 517afa37..d3639fe9 100644
--- a/tests/CarbonImmutable/DiffTest.php
+++ b/tests/CarbonImmutable/DiffTest.php
@@ -307,19 +307,19 @@ class DiffTest extends AbstractTestCase
     public function testDiffInWeekdaysPositive()
     {
         $dt = Carbon::createFromDate(2000, 1, 1);
-        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->endOfMonth()));
+        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->addMonth()));
     }
 
     public function testDiffInWeekdaysNegativeNoSign()
     {
         $dt = Carbon::createFromDate(2000, 1, 31);
-        $this->assertSame(21, $dt->diffInWeekdays($dt->copy()->startOfMonth()));
+        $this->assertSame(20, $dt->diffInWeekdays($dt->copy()->startOfMonth()));
     }
 
     public function testDiffInWeekdaysNegativeWithSign()
     {
         $dt = Carbon::createFromDate(2000, 1, 31);
-        $this->assertSame(-21, $dt->diffInWeekdays($dt->copy()->startOfMonth(), false));
+        $this->assertSame(-20, $dt->diffInWeekdays($dt->copy()->startOfMonth(), false));
     }
 
     public function testDiffInWeekendDaysPositive()