Repository: DenverCoder1/github-readme-streak-stats (php)

Issue:
Feature request: Exclude weekends?
**Is your feature request related to a problem? Please describe.**
My max streak is 5 - I either don't code on the weekend, or use another account 😭 . I would like to be incentivised to push something every weekday.

**Describe the solution you'd like**
It would be awesome to have an 'exclude weekends' option in the config demo page https://streak-stats.demolab.com/demo

**Describe alternatives you've considered**
I could use the weekly version of the streak, but this doesn't incentivise me to code every day within the work week, so doesn't satisfy the requirement on this feature request.

**Additional context**
N/A.


Pull request description:
feat: Add support for excluding days of the week from the streak
## Description

Resolves #408

https://github-readm-feat-exclu-fo41zs.herokuapp.com/demo/

Usage: `&exclude_days=Sun,Sat`

### Type of change

<!-- Please delete options that are not relevant. -->

- [ ] Bug fix (added a non-breaking change which fixes an issue)
- [x] New feature (added a non-breaking change which adds functionality)
- [ ] Updated documentation (updated the readme, templates, or other repo files)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

## How Has This Been Tested?

<!--
If you have changed a feature of the stats cards, please describe the tests you made to verify your changes.
Changes strictly related to documentation can skip this section.
-->

- [x] Tested locally with a valid username
- [x] Tested locally with an invalid username
- [x] Ran tests with `composer test`
- [x] Added or updated test cases to test new features

## Checklist:

- [x] I have checked to make sure no other [pull requests](https://github.com/DenverCoder1/github-readme-streak-stats/pulls?q=is%3Apr+sort%3Aupdated-desc+) are open for this issue
- [x] The code is properly formatted and is consistent with the existing code style
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [X] My changes generate no new warnings

## Screenshots

![image](https://user-images.githubusercontent.com/20955511/232533059-b553f119-b447-4424-8c4d-5e36155ed614.png)

![image](https://user-images.githubusercontent.com/20955511/232532808-416d692b-cf75-4f64-a338-3ad5881ccaf8.png)

> **Note** Translations will be required for the word "Excluding"
>     
> ![image](https://user-images.githubusercontent.com/20955511/232533265-e573c1bb-ce77-498e-a7a3-ed30037b8808.png)


Patch:
diff --git a/README.md b/README.md
index 65ff7d8..5a6e44b 100644
--- a/README.md
+++ b/README.md
@@ -43,27 +43,28 @@ The `user` field is the only required option. All other fields are optional.
 
 If the `theme` parameter is specified, any color customizations specified will be applied on top of the theme, overriding the theme's values.
 
-|      Parameter       |                     Details                     |                                              Example                                               |
-| :------------------: | :---------------------------------------------: | :------------------------------------------------------------------------------------------------: |
-|        `user`        |        GitHub username to show stats for        |                                           `DenverCoder1`                                           |
-|       `theme`        |     The theme to apply (Default: `default`)     |                          `dark`, `radical`, etc. [🎨➜](./docs/themes.md)                           |
-|    `hide_border`     | Make the border transparent (Default: `false`)  |                                         `true` or `false`                                          |
-|   `border_radius`    | Set the roundness of the edges (Default: `4.5`) |                           Number `0` (sharp corners) to `248` (ellipse)                            |
-|     `background`     |  Background color (eg. `f2f2f2`, `35,d22,00f`)  | **hex code** without `#`, **css color**, or gradient in the form `angle,start_color,...,end_color` |
-|       `border`       |                  Border color                   |                             **hex code** without `#` or **css color**                              |
-|       `stroke`       |       Stroke line color between sections        |                             **hex code** without `#` or **css color**                              |
-|        `ring`        |   Color of the ring around the current streak   |                             **hex code** without `#` or **css color**                              |
-|        `fire`        |          Color of the fire in the ring          |                             **hex code** without `#` or **css color**                              |
-|   `currStreakNum`    |              Current streak number              |                             **hex code** without `#` or **css color**                              |
-|      `sideNums`      |        Total and longest streak numbers         |                             **hex code** without `#` or **css color**                              |
-|  `currStreakLabel`   |              Current streak label               |                             **hex code** without `#` or **css color**                              |
-|     `sideLabels`     |         Total and longest streak labels         |                             **hex code** without `#` or **css color**                              |
-|       `dates`        |              Date range text color              |                             **hex code** without `#` or **css color**                              |
-|    `date_format`     | Date format pattern or empty for locale format  |                        See note below on [📅 Date Formats](#-date-formats)                         |
-|       `locale`       |  Locale for labels and numbers (Default: `en`)  |                            ISO 639-1 code - See [🗪 Locales](#-locales)                             |
-|        `type`        |         Output format (Default: `svg`)          |                              Current options: `svg`, `png` or `json`                               |
-|        `mode`        |         Streak mode (Default: `daily`)          |             `daily` (contribute daily) or `weekly` (contribute once per Sun-Sat week)              |
-| `disable_animations` |    Disable SVG animations (Default: `false`)    |                                         `true` or `false`                                          |
+|      Parameter       |                     Details                      |                                              Example                                               |
+| :------------------: | :----------------------------------------------: | :------------------------------------------------------------------------------------------------: |
+|        `user`        |        GitHub username to show stats for         |                                           `DenverCoder1`                                           |
+|       `theme`        |     The theme to apply (Default: `default`)      |                          `dark`, `radical`, etc. [🎨➜](./docs/themes.md)                           |
+|    `hide_border`     |  Make the border transparent (Default: `false`)  |                                         `true` or `false`                                          |
+|   `border_radius`    | Set the roundness of the edges (Default: `4.5`)  |                           Number `0` (sharp corners) to `248` (ellipse)                            |
+|     `background`     |  Background color (eg. `f2f2f2`, `35,d22,00f`)   | **hex code** without `#`, **css color**, or gradient in the form `angle,start_color,...,end_color` |
+|       `border`       |                   Border color                   |                             **hex code** without `#` or **css color**                              |
+|       `stroke`       |        Stroke line color between sections        |                             **hex code** without `#` or **css color**                              |
+|        `ring`        |   Color of the ring around the current streak    |                             **hex code** without `#` or **css color**                              |
+|        `fire`        |          Color of the fire in the ring           |                             **hex code** without `#` or **css color**                              |
+|   `currStreakNum`    |              Current streak number               |                             **hex code** without `#` or **css color**                              |
+|      `sideNums`      |         Total and longest streak numbers         |                             **hex code** without `#` or **css color**                              |
+|  `currStreakLabel`   |               Current streak label               |                             **hex code** without `#` or **css color**                              |
+|     `sideLabels`     |         Total and longest streak labels          |                             **hex code** without `#` or **css color**                              |
+|       `dates`        |              Date range text color               |                             **hex code** without `#` or **css color**                              |
+|    `date_format`     |  Date format pattern or empty for locale format  |                        See note below on [📅 Date Formats](#-date-formats)                         |
+|       `locale`       |  Locale for labels and numbers (Default: `en`)   |                            ISO 639-1 code - See [🗪 Locales](#-locales)                             |
+|        `type`        |          Output format (Default: `svg`)          |                              Current options: `svg`, `png` or `json`                               |
+|        `mode`        |          Streak mode (Default: `daily`)          |             `daily` (contribute daily) or `weekly` (contribute once per Sun-Sat week)              |
+|    `exclude_days`    | List of days of the week to exclude from streaks |       Comma-separated list of day abbreviations (Sun,Mon,Tue,Wed,Thu,Fri,Sat) e.g. `Sun,Sat`       |
+| `disable_animations` |    Disable SVG animations (Default: `false`)     |                                         `true` or `false`                                          |
 
 ### 🖌 Themes
 
diff --git a/scripts/translation-progress.php b/scripts/translation-progress.php
index 1849f68..030fff9 100644
--- a/scripts/translation-progress.php
+++ b/scripts/translation-progress.php
@@ -17,6 +17,7 @@ function getProgress(array $translations): array
         "Week Streak",
         "Longest Week Streak",
         "Present",
+        "Excluding",
     ];
 
     $translations_file = file(__DIR__ . "/../src/translations.php");
diff --git a/src/card.php b/src/card.php
index 66b5b2a..df469cb 100644
--- a/src/card.php
+++ b/src/card.php
@@ -53,6 +53,37 @@ function formatDate(string $dateString, string|null $format, string $locale): st
     return htmlspecialchars($formatted);
 }
 
+/**
+ * Translate days of the week
+ *
+ * Takes a list of days (eg. ["Sun", "Mon", "Sat"]) and returns the short abbreviation of the days of the week in another locale
+ * e.g. ["Sun", "Mon", "Sat"] -> ["dim", "lun", "sam"]
+ *
+ * @param array<string> $days List of days to translate
+ * @param string $locale Locale code
+ *
+ * @return array<string> Translated days
+ */
+function translateDays(array $days, string $locale): array
+{
+    if ($locale === "en") {
+        return $days;
+    }
+    $patternGenerator = new IntlDatePatternGenerator($locale);
+    $pattern = $patternGenerator->getBestPattern("EEE");
+    $dateFormatter = new IntlDateFormatter(
+        $locale,
+        IntlDateFormatter::NONE,
+        IntlDateFormatter::NONE,
+        pattern: $pattern
+    );
+    $translatedDays = [];
+    foreach ($days as $day) {
+        $translatedDays[] = $dateFormatter->format(new DateTime($day));
+    }
+    return $translatedDays;
+}
+
 /**
  * Normalize a theme name
  *
@@ -335,6 +366,21 @@ function generateCard(array $stats, array $params = null): string
     $currentStreakRange = splitLines($currentStreakRange, 28, 0);
     $longestStreakRange = splitLines($longestStreakRange, 28, 0);
 
+    // if days are excluded, add a note to the corner
+    $excludedDays = "";
+    if (!empty($stats["excludedDays"])) {
+        $daysCommaSeparated = implode(", ", translateDays($stats["excludedDays"], $localeCode));
+        $offset = $direction === "rtl" ? 495 - 5 : 5;
+        $excludedDays = "<g style='isolation: isolate'>
+                <!-- Excluded Days -->
+                <g transform='translate({$offset},187)'>
+                    <text stroke-width='0' text-anchor='right' fill='{$theme["dates"]}' stroke='none' font-family='\"Segoe UI\", Ubuntu, sans-serif' font-weight='400' font-size='10px' font-style='normal' style='opacity: 0; animation: fadein 0.5s linear forwards 0.9s'>
+                        * {$localeTranslations["Excluding"]} {$daysCommaSeparated}
+                    </text>
+                </g>
+            </g>";
+    }
+
     return "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'
                 style='isolation: isolate' viewBox='0 0 495 195' width='495px' height='195px' direction='{$direction}'>
         <style>
@@ -443,6 +489,7 @@ function generateCard(array $stats, array $params = null): string
                     </text>
                 </g>
             </g>
+            {$excludedDays}
         </g>
     </svg>
 ";
diff --git a/src/demo/css/style.css b/src/demo/css/style.css
index 3b3a544..c7215da 100644
--- a/src/demo/css/style.css
+++ b/src/demo/css/style.css
@@ -185,6 +185,35 @@ h2 {
   text-transform: capitalize;
 }
 
+.weekdays input {
+  display: none !important;
+}
+
+.weekdays input[type="checkbox"] + label {
+  font-size: 90%;
+  display: inline-block;
+  border-radius: 6px;
+  height: 30px;
+  width: 30px;
+  margin-right: 3px;
+  line-height: 28px;
+  text-align: center;
+  cursor: pointer;
+  background: var(--card-background);
+  color: var(--text);
+  border: 1px solid var(--border);
+}
+
+.weekdays input[type="checkbox"]:checked + label {
+  background: var(--text);
+  color: var(--background);
+}
+
+.weekdays input[type="checkbox"]:disabled + label {
+  background: var(--card-background);
+  color: var(--stroke);
+}
+
 span[title="required"] {
   color: var(--red);
 }
diff --git a/src/demo/index.php b/src/demo/index.php
index bd3580c..573de37 100644
--- a/src/demo/index.php
+++ b/src/demo/index.php
@@ -137,6 +137,25 @@ function camelToSkewer(string $str): string
                     <option value="weekly">Weekly</option>
                 </select>
 
+                <label for="exclude_days">Exclude Days</label>
+                <div class="weekdays">
+                    <input type="checkbox" value="Sun" id="weekday-sun" />
+                    <label for="weekday-sun" data-tooltip="Exclude Sunday" title="Exclude Sunday">S</label>
+                    <input type="checkbox" value="Mon" id="weekday-mon" />
+                    <label for="weekday-mon" data-tooltip="Exclude Monday" title="Exclude Monday">M</label>
+                    <input type="checkbox" value="Tue" id="weekday-tue" />
+                    <label for="weekday-tue" data-tooltip="Exclude Tuesday" title="Exclude Tuesday">T</label>
+                    <input type="checkbox" value="Wed" id="weekday-wed" />
+                    <label for="weekday-wed" data-tooltip="Exclude Wednesday" title="Exclude Wednesday">W</label>
+                    <input type="checkbox" value="Thu" id="weekday-thu" />
+                    <label for="weekday-thu" data-tooltip="Exclude Thursday" title="Exclude Thursday">T</label>
+                    <input type="checkbox" value="Fri" id="weekday-fri" />
+                    <label for="weekday-fri" data-tooltip="Exclude Friday" title="Exclude Friday">F</label>
+                    <input type="checkbox" value="Sat" id="weekday-sat" />
+                    <label for="weekday-sat" data-tooltip="Exclude Saturday" title="Exclude Saturday">S</label>
+                    <input type="text" id="exclude-days" name="exclude_days" class="param" />
+                </div>
+
                 <label for="type">Output Type</label>
                 <select class="param" id="type" name="type">
                     <option value="svg">SVG</option>
diff --git a/src/demo/js/script.js b/src/demo/js/script.js
index 1141c0c..d27e591 100644
--- a/src/demo/js/script.js
+++ b/src/demo/js/script.js
@@ -13,6 +13,7 @@ const preview = {
     border_radius: "4.5",
     mode: "daily",
     type: "svg",
+    exclude_days: "",
   },
 
   /**
@@ -374,6 +375,38 @@ window.addEventListener(
       preview.checkColor(backgroundParams[1], "background-color1");
       preview.checkColor(backgroundParams[2], "background-color2");
     }
+    // set weekday checkboxes
+    const excludeDays = searchParams.get("exclude_days");
+    if (excludeDays) {
+      excludeDays.split(",").forEach((day) => {
+        const checkbox = document.querySelector(`.weekdays input[type="checkbox"][value="${day}"]`);
+        if (checkbox) {
+          checkbox.checked = true;
+        }
+      });
+    }
+    // when weekdays are toggled, update the input field
+    document.querySelectorAll('.weekdays input[type="checkbox"]').forEach((el) => {
+      el.addEventListener("click", () => {
+        const checked = document.querySelectorAll('.weekdays input[type="checkbox"]:checked');
+        document.querySelector("#exclude-days").value = [...checked].map((node) => node.value).join(",");
+        preview.update();
+      });
+    });
+    // when mode is set to "weekly", disable checkboxes, otherwise enable them
+    document.querySelector("#mode").addEventListener("change", () => {
+      const mode = document.querySelector("#mode").value;
+      document.querySelectorAll(".weekdays input[type='checkbox']").forEach((el) => {
+        const labelEl = el.nextElementSibling;
+        if (mode === "weekly") {
+          el.disabled = true;
+          labelEl.title = "Disabled in weekly mode";
+        } else {
+          el.disabled = false;
+          labelEl.title = labelEl.dataset.tooltip;
+        }
+      });
+    });
     // update previews
     preview.update();
   },
diff --git a/src/demo/preview.php b/src/demo/preview.php
index 80ef736..2a43bc6 100644
--- a/src/demo/preview.php
+++ b/src/demo/preview.php
@@ -22,6 +22,7 @@ $demoStats = [
         "end" => date("Y-m-d"),
         "length" => 16,
     ],
+    "excludedDays" => normalizeDays(explode(",", $_GET["exclude_days"] ?? "")),
 ];
 
 if ($mode == "weekly") {
@@ -36,6 +37,7 @@ if ($mode == "weekly") {
         "end" => getPreviousSunday(date("Y-m-d")),
         "length" => 3,
     ];
+    unset($demoStats["excludedDays"]);
 }
 
 // set content type to SVG image
diff --git a/src/index.php b/src/index.php
index cfa4d4a..2de2c31 100644
--- a/src/index.php
+++ b/src/index.php
@@ -38,7 +38,9 @@ try {
     if (isset($_GET["mode"]) && $_GET["mode"] === "weekly") {
         $stats = getWeeklyContributionStats($contributions);
     } else {
-        $stats = getContributionStats($contributions);
+        // split and normalize excluded days
+        $excludeDays = normalizeDays(explode(",", $_GET["exclude_days"] ?? ""));
+        $stats = getContributionStats($contributions, $excludeDays);
     }
     renderOutput($stats);
 } catch (InvalidArgumentException | AssertionError $error) {
diff --git a/src/stats.php b/src/stats.php
index 0ad21e4..ba77b4e 100644
--- a/src/stats.php
+++ b/src/stats.php
@@ -267,13 +267,48 @@ function getContributionDates(array $contributionGraphs): array
     return $contributions;
 }
 
+/**
+ * Normalize names of days of the week (eg. ["Sunday", " mon", "TUE"] -> ["Sun", "Mon", "Tue"])
+ *
+ * @param array<string> $days List of days of the week
+ * @return array<string> List of normalized days of the week
+ */
+function normalizeDays(array $days): array
+{
+    return array_filter(
+        array_map(function ($dayOfWeek) {
+            // trim whitespace, capitalize first letter only, return first 3 characters
+            $dayOfWeek = substr(ucfirst(strtolower(trim($dayOfWeek))), 0, 3);
+            // return day if valid, otherwise return null
+            return in_array($dayOfWeek, ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]) ? $dayOfWeek : null;
+        }, $days)
+    );
+}
+
+/**
+ * Check if a day is an excluded day of the week
+ *
+ * @param string $date Date to check (Y-m-d)
+ * @param array<string> $excludedDays List of days of the week to exclude
+ * @return bool True if the day is excluded, false otherwise
+ */
+function isExcludedDay(string $date, array $excludedDays): bool
+{
+    if (empty($excludedDays)) {
+        return false;
+    }
+    $day = date("D", strtotime($date)); // "D" = Mon, Tue, Wed, etc.
+    return in_array($day, $excludedDays);
+}
+
 /**
  * Get a stats array with the contribution count, daily streak, and dates
  *
  * @param array<string,int> $contributions Y-M-D contribution dates with contribution counts
+ * @param array<string> $excludedDays List of days of the week to exclude
  * @return array<string,mixed> Streak stats
  */
-function getContributionStats(array $contributions): array
+function getContributionStats(array $contributions, array $excludedDays = []): array
 {
     // if no contributions, display error
     if (empty($contributions)) {
@@ -295,6 +330,7 @@ function getContributionStats(array $contributions): array
             "end" => $first,
             "length" => 0,
         ],
+        "excludedDays" => $excludedDays,
     ];
 
     // calculate the stats from the contributions array
@@ -302,7 +338,7 @@ function getContributionStats(array $contributions): array
         // add contribution count to total
         $stats["totalContributions"] += $count;
         // check if still in streak
-        if ($count > 0) {
+        if ($count > 0 || ($stats["currentStreak"]["length"] > 0 && isExcludedDay($date, $excludedDays))) {
             // increment streak
             ++$stats["currentStreak"]["length"];
             $stats["currentStreak"]["end"] = $date;
diff --git a/src/translations.php b/src/translations.php
index 995313c..b420e9e 100644
--- a/src/translations.php
+++ b/src/translations.php
@@ -37,6 +37,7 @@ return [
         "Week Streak" => "Week Streak",
         "Longest Week Streak" => "Longest Week Streak",
         "Present" => "Present",
+        "Excluding" => "Excluding",
     ],
     // Locales below are sorted alphabetically
     "ar" => [
@@ -121,6 +122,7 @@ return [
         "Week Streak" => "רצף שבועי",
         "Longest Week Streak" => "רצף שבועי הכי ארוך",
         "Present" => "היום",
+        "Excluding" => "לא כולל",
     ],
     "hi" => [
         "Total Contributions" => "कुल योगदान",


Test patch:
diff --git a/tests/RenderTest.php b/tests/RenderTest.php
index ea338ea..b6ce463 100644
--- a/tests/RenderTest.php
+++ b/tests/RenderTest.php
@@ -36,6 +36,7 @@ final class RenderTest extends TestCase
             "end" => "2019-04-12",
             "length" => 16,
         ],
+        "excludedDays" => [],
     ];
 
     /**
@@ -204,4 +205,14 @@ final class RenderTest extends TestCase
             $render
         );
     }
+
+    /**
+     * Test excluding days
+     */
+    public function testExcludeDays(): void
+    {
+        $this->testStats["excludedDays"] = ["Sun", "Sat"];
+        $render = generateOutput($this->testStats, $this->testParams)["body"];
+        $this->assertStringContainsString("* Excluding Sun, Sat", $render);
+    }
 }
diff --git a/tests/StatsTest.php b/tests/StatsTest.php
index c353464..37c2f77 100644
--- a/tests/StatsTest.php
+++ b/tests/StatsTest.php
@@ -117,6 +117,7 @@ final class StatsTest extends TestCase
                 "end" => "2021-04-18",
                 "length" => 4,
             ],
+            "excludedDays" => [],
         ];
         $this->assertEquals($expected, $stats);
     }
@@ -147,6 +148,7 @@ final class StatsTest extends TestCase
                 "end" => "2021-04-17",
                 "length" => 3,
             ],
+            "excludedDays" => [],
         ];
         $this->assertEquals($expected, $stats);
     }
@@ -177,6 +179,7 @@ final class StatsTest extends TestCase
                 "end" => "2021-04-18",
                 "length" => 0,
             ],
+            "excludedDays" => [],
         ];
         $this->assertEquals($expected, $stats);
     }
@@ -205,6 +208,7 @@ final class StatsTest extends TestCase
                 "end" => date("Y-m-d"),
                 "length" => 370,
             ],
+            "excludedDays" => [],
         ];
         $this->assertEquals($expected, $stats);
     }
@@ -269,6 +273,7 @@ final class StatsTest extends TestCase
                 "end" => date("Y-m-d", strtotime("tomorrow")),
                 "length" => 3,
             ],
+            "excludedDays" => [],
         ];
         $this->assertEquals($expected, $stats);
     }
@@ -387,4 +392,72 @@ final class StatsTest extends TestCase
         ];
         $this->assertEquals($expected, $stats);
     }
+
+    /**
+     * Test stats with excluded days of the week
+     */
+    public function testExcludeDays(): void
+    {
+        $contributions = [
+            "2023-04-12" => 1,
+            "2023-04-13" => 0,
+            "2023-04-14" => 2,
+            "2023-04-15" => 0,
+            "2023-04-16" => 0,
+            "2023-04-17" => 3,
+        ];
+        $excludeDays = ["Sun", "Sat"];
+        $stats = getContributionStats($contributions, $excludeDays);
+        $expected = [
+            "mode" => "daily",
+            "totalContributions" => 6,
+            "firstContribution" => "2023-04-12",
+            "longestStreak" => [
+                "start" => "2023-04-14",
+                "end" => "2023-04-17",
+                "length" => 4,
+            ],
+            "currentStreak" => [
+                "start" => "2023-04-14",
+                "end" => "2023-04-17",
+                "length" => 4,
+            ],
+            "excludedDays" => $excludeDays,
+        ];
+        $this->assertEquals($expected, $stats);
+    }
+
+    /**
+     * Test stats with excluded days of the week and no contribution before weekend
+     */
+    public function testExcludeDaysNoContributionBeforeWeekend(): void
+    {
+        $contributions = [
+            "2023-04-12" => 1,
+            "2023-04-13" => 2,
+            "2023-04-14" => 0,
+            "2023-04-15" => 0,
+            "2023-04-16" => 0,
+            "2023-04-17" => 3,
+        ];
+        $excludeDays = ["Sun", "Sat"];
+        $stats = getContributionStats($contributions, $excludeDays);
+        $expected = [
+            "mode" => "daily",
+            "totalContributions" => 6,
+            "firstContribution" => "2023-04-12",
+            "longestStreak" => [
+                "start" => "2023-04-12",
+                "end" => "2023-04-13",
+                "length" => 2,
+            ],
+            "currentStreak" => [
+                "start" => "2023-04-17",
+                "end" => "2023-04-17",
+                "length" => 1,
+            ],
+            "excludedDays" => $excludeDays,
+        ];
+        $this->assertEquals($expected, $stats);
+    }
 }
diff --git a/tests/TranslationsTest.php b/tests/TranslationsTest.php
index 0fde147..f662c2c 100644
--- a/tests/TranslationsTest.php
+++ b/tests/TranslationsTest.php
@@ -25,6 +25,7 @@ final class TranslationsTest extends TestCase
             "Week Streak",
             "Longest Week Streak",
             "Present",
+            "Excluding",
         ];
         foreach ($locales as $locale) {
             // if it is a string, assert that the alias exists in the translations file
diff --git a/tests/expected/test_card.svg b/tests/expected/test_card.svg
index 2c4e492..1e0904d 100644
--- a/tests/expected/test_card.svg
+++ b/tests/expected/test_card.svg
@@ -106,5 +106,6 @@
                     </text>
                 </g>
             </g>
+            
         </g>
     </svg>