Skip to content

Commit 793a374

Browse files
authored
feat: Add weekly contribution mode (#362)
1 parent 99b6c81 commit 793a374

File tree

12 files changed

+287
-36
lines changed

12 files changed

+287
-36
lines changed

README.md

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,25 +43,26 @@ The `user` field is the only required option. All other fields are optional.
4343

4444
If the `theme` parameter is specified, any color customizations specified will be applied on top of the theme, overriding the theme's values.
4545

46-
| Parameter | Details | Example |
47-
| :---------------: | :---------------------------------------------: | :---------------------------------------------------------------: |
48-
| `user` | GitHub username to show stats for | `DenverCoder1` |
49-
| `theme` | The theme to apply (Default: `default`) | `dark`, `radical`, etc. [🎨➜](./docs/themes.md) |
50-
| `hide_border` | Make the border transparent (Default: `false`) | `true` or `false` |
51-
| `border_radius` | Set the roundness of the edges (Default: `4.5`) | Number `0` (sharp corners) to `248` (ellipse) |
52-
| `background` | Background color | **hex code** without `#` or **css color** |
53-
| `border` | Border color | **hex code** without `#` or **css color** |
54-
| `stroke` | Stroke line color between sections | **hex code** without `#` or **css color** |
55-
| `ring` | Color of the ring around the current streak | **hex code** without `#` or **css color** |
56-
| `fire` | Color of the fire in the ring | **hex code** without `#` or **css color** |
57-
| `currStreakNum` | Current streak number | **hex code** without `#` or **css color** |
58-
| `sideNums` | Total and longest streak numbers | **hex code** without `#` or **css color** |
59-
| `currStreakLabel` | Current streak label | **hex code** without `#` or **css color** |
60-
| `sideLabels` | Total and longest streak labels | **hex code** without `#` or **css color** |
61-
| `dates` | Date range text color | **hex code** without `#` or **css color** |
62-
| `date_format` | Date format (Default: `M j[, Y]`) | See note below on [Date Formats](#date-formats) |
63-
| `locale` | Locale to use for labels (Default: `en`) | ISO 639-1 code (See [`translations.php`](./src/translations.php)) |
64-
| `type` | Output format (Default: `svg`) | Current options: `svg`, `png` or `json` |
46+
| Parameter | Details | Example |
47+
| :---------------: | :---------------------------------------------: | :-----------------------------------------------------------------------: |
48+
| `user` | GitHub username to show stats for | `DenverCoder1` |
49+
| `theme` | The theme to apply (Default: `default`) | `dark`, `radical`, etc. [🎨➜](./docs/themes.md) |
50+
| `hide_border` | Make the border transparent (Default: `false`) | `true` or `false` |
51+
| `border_radius` | Set the roundness of the edges (Default: `4.5`) | Number `0` (sharp corners) to `248` (ellipse) |
52+
| `background` | Background color | **hex code** without `#` or **css color** |
53+
| `border` | Border color | **hex code** without `#` or **css color** |
54+
| `stroke` | Stroke line color between sections | **hex code** without `#` or **css color** |
55+
| `ring` | Color of the ring around the current streak | **hex code** without `#` or **css color** |
56+
| `fire` | Color of the fire in the ring | **hex code** without `#` or **css color** |
57+
| `currStreakNum` | Current streak number | **hex code** without `#` or **css color** |
58+
| `sideNums` | Total and longest streak numbers | **hex code** without `#` or **css color** |
59+
| `currStreakLabel` | Current streak label | **hex code** without `#` or **css color** |
60+
| `sideLabels` | Total and longest streak labels | **hex code** without `#` or **css color** |
61+
| `dates` | Date range text color | **hex code** without `#` or **css color** |
62+
| `date_format` | Date format (Default: `M j[, Y]`) | See note below on [Date Formats](#date-formats) |
63+
| `locale` | Locale to use for labels (Default: `en`) | ISO 639-1 code (See [`translations.php`](./src/translations.php)) |
64+
| `type` | Output format (Default: `svg`) | Current options: `svg`, `png` or `json` |
65+
| `mode` | Streak mode (Default: `daily`) | `daily` (contribute daily) or `weekly` (contribute once per Sun-Sat week) |
6566

6667
## 🖌 Themes
6768

src/card.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,8 +243,13 @@ function generateCard(array $stats, array $params = null): string
243243

244244
// if the translations contain a newline, split the text into two tspan elements
245245
$totalContributionsText = splitLines($localeTranslations["Total Contributions"], 24, -9);
246-
$currentStreakText = splitLines($localeTranslations["Current Streak"], 22, -9);
247-
$longestStreakText = splitLines($localeTranslations["Longest Streak"], 24, -9);
246+
if ($stats["mode"] === "weekly") {
247+
$currentStreakText = splitLines($localeTranslations["Week Streak"], 22, -9);
248+
$longestStreakText = splitLines($localeTranslations["Longest Week Streak"], 24, -9);
249+
} else {
250+
$currentStreakText = splitLines($localeTranslations["Current Streak"], 22, -9);
251+
$longestStreakText = splitLines($localeTranslations["Longest Streak"], 24, -9);
252+
}
248253

249254
// if the ranges contain over 28 characters, split the text into two tspan elements
250255
$totalContributionsRange = splitLines($totalContributionsRange, 28, 0);

src/demo/index.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ function gtag() {
128128
<option value="[Y.]n.j">2016.8.10</option>
129129
</select>
130130

131+
<label for="mode">Streak Mode</label>
132+
<select class="param" id="mode" name="mode">
133+
<option value="daily">Daily</option>
134+
<option value="weekly">Weekly</option>
135+
</select>
136+
131137
<details class="advanced">
132138
<summary>⚙ Advanced Options</summary>
133139
<div class="content parameters">

src/demo/js/script.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const preview = {
1111
date_format: "",
1212
locale: "en",
1313
border_radius: "4.5",
14+
mode: "daily",
1415
},
1516

1617
/**

src/demo/preview.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
declare(strict_types=1);
44

55
require_once "../card.php";
6+
require_once "../stats.php";
7+
8+
$mode = $_GET["mode"] ?? "daily";
69

710
// generate demo stats
811
$demoStats = [
12+
"mode" => "daily",
913
"totalContributions" => 2048,
1014
"firstContribution" => "2016-08-10",
1115
"longestStreak" => [
@@ -20,6 +24,20 @@
2024
],
2125
];
2226

27+
if ($mode == "weekly") {
28+
$demoStats["mode"] = "weekly";
29+
$demoStats["longestStreak"] = [
30+
"start" => "2021-12-19",
31+
"end" => "2022-03-13",
32+
"length" => 13,
33+
];
34+
$demoStats["currentStreak"] = [
35+
"start" => getPreviousSunday(date("Y-m-d", strtotime("-15 days"))),
36+
"end" => getPreviousSunday(date("Y-m-d")),
37+
"length" => 3,
38+
];
39+
}
40+
2341
// set content type to SVG image
2442
header("Content-Type: image/svg+xml");
2543

src/index.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@
3434
// get streak stats for user given in query string
3535
$contributionGraphs = getContributionGraphs($_REQUEST["user"]);
3636
$contributions = getContributionDates($contributionGraphs);
37-
$stats = getContributionStats($contributions);
37+
if (isset($_GET["mode"]) && $_GET["mode"] === "weekly") {
38+
$stats = getWeeklyContributionStats($contributions);
39+
} else {
40+
$stats = getContributionStats($contributions);
41+
}
3842
renderOutput($stats);
3943
} catch (InvalidArgumentException | AssertionError $error) {
4044
renderOutput($error->getMessage(), $error->getCode());

src/stats.php

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ function getContributionDates(array $contributionGraphs): array
216216
}
217217

218218
/**
219-
* Get a stats array with the contribution count, streak, and dates
219+
* Get a stats array with the contribution count, daily streak, and dates
220220
*
221221
* @param array<string, int> $contributions Y-M-D contribution dates with contribution counts
222222
* @return array<string, mixed> Streak stats
@@ -230,6 +230,7 @@ function getContributionStats(array $contributions): array
230230
$today = array_key_last($contributions);
231231
$first = array_key_first($contributions);
232232
$stats = [
233+
"mode" => "daily",
233234
"totalContributions" => 0,
234235
"firstContribution" => "",
235236
"longestStreak" => [
@@ -279,3 +280,94 @@ function getContributionStats(array $contributions): array
279280
}
280281
return $stats;
281282
}
283+
284+
/**
285+
* Get the previous Sunday of a given date
286+
*
287+
* @param string $date Date to get previous Sunday of (Y-m-d)
288+
* @return string Previous Sunday
289+
*/
290+
function getPreviousSunday(string $date): string
291+
{
292+
$dayOfWeek = date("w", strtotime($date));
293+
return date("Y-m-d", strtotime("-$dayOfWeek days", strtotime($date)));
294+
}
295+
296+
/**
297+
* Get a stats array with the contribution count, weekly streak, and dates
298+
*
299+
* @param array<string, int> $contributions Y-M-D contribution dates with contribution counts
300+
* @return array<string, mixed> Streak stats
301+
*/
302+
function getWeeklyContributionStats(array $contributions): array
303+
{
304+
// if no contributions, display error
305+
if (empty($contributions)) {
306+
throw new AssertionError("No contributions found.", 204);
307+
}
308+
$thisWeek = getPreviousSunday(array_key_last($contributions));
309+
$first = array_key_first($contributions);
310+
$firstWeek = getPreviousSunday($first);
311+
$stats = [
312+
"mode" => "weekly",
313+
"totalContributions" => 0,
314+
"firstContribution" => "",
315+
"longestStreak" => [
316+
"start" => $firstWeek,
317+
"end" => $firstWeek,
318+
"length" => 0,
319+
],
320+
"currentStreak" => [
321+
"start" => $firstWeek,
322+
"end" => $firstWeek,
323+
"length" => 0,
324+
],
325+
];
326+
327+
// calculate contributions per week
328+
$weeks = [];
329+
foreach ($contributions as $date => $count) {
330+
$week = getPreviousSunday($date);
331+
if (!isset($weeks[$week])) {
332+
$weeks[$week] = 0;
333+
}
334+
if ($count > 0) {
335+
$weeks[$week] += $count;
336+
// set first contribution date the first time
337+
if (!$stats["firstContribution"]) {
338+
$stats["firstContribution"] = $date;
339+
}
340+
}
341+
}
342+
343+
// calculate the stats from the contributions array
344+
foreach ($weeks as $week => $count) {
345+
// add contribution count to total
346+
$stats["totalContributions"] += $count;
347+
// check if still in streak
348+
if ($count > 0) {
349+
// increment streak
350+
++$stats["currentStreak"]["length"];
351+
$stats["currentStreak"]["end"] = $week;
352+
// set start on first week of streak
353+
if ($stats["currentStreak"]["length"] == 1) {
354+
$stats["currentStreak"]["start"] = $week;
355+
}
356+
// update longestStreak
357+
if ($stats["currentStreak"]["length"] > $stats["longestStreak"]["length"]) {
358+
// copy current streak start, end, and length into longest streak
359+
$stats["longestStreak"]["start"] = $stats["currentStreak"]["start"];
360+
$stats["longestStreak"]["end"] = $stats["currentStreak"]["end"];
361+
$stats["longestStreak"]["length"] = $stats["currentStreak"]["length"];
362+
}
363+
}
364+
// reset streak but give exception for this week
365+
elseif ($week != $thisWeek) {
366+
// reset streak
367+
$stats["currentStreak"]["length"] = 0;
368+
$stats["currentStreak"]["start"] = $thisWeek;
369+
$stats["currentStreak"]["end"] = $thisWeek;
370+
}
371+
}
372+
return $stats;
373+
}

src/translations.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
"Total Contributions" => "Total Contributions",
3030
"Current Streak" => "Current Streak",
3131
"Longest Streak" => "Longest Streak",
32+
"Week Streak" => "Week Streak",
33+
"Longest Week Streak" => "Longest Week Streak",
3234
"Present" => "Present",
3335
],
3436
// Locales below are sorted alphabetically
@@ -67,6 +69,8 @@
6769
"Total Contributions" => "סכום התרומות",
6870
"Current Streak" => "רצף נוכחי",
6971
"Longest Streak" => "רצף הכי ארוך",
72+
"Week Streak" => "רצף שבועי",
73+
"Longest Week Streak" => "רצף שבועי הכי ארוך",
7074
"Present" => "היום",
7175
],
7276
"hi" => [
@@ -92,6 +96,8 @@
9296
"Total Contributions" => "総コントリビューション数",
9397
"Current Streak" => "現在のストリーク",
9498
"Longest Streak" => "最長のストリーク",
99+
"Week Streak" => "週間ストリーク",
100+
"Longest Week Streak" => "最長の週間ストリーク",
95101
"Present" => "",
96102
],
97103
"kn" => [

tests/RenderTest.php

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ final class RenderTest extends TestCase
2323
];
2424

2525
private $testStats = [
26+
"mode" => "daily",
2627
"totalContributions" => 2048,
2728
"firstContribution" => "2016-08-10",
2829
"longestStreak" => [
@@ -108,17 +109,6 @@ public function testBorderRadius(): void
108109
);
109110
}
110111

111-
/**
112-
* Test JSON render
113-
*/
114-
public function testJsonRender(): void
115-
{
116-
// Check json that is returned
117-
$render = json_encode($this->testStats);
118-
$expected = file_get_contents("tests/expected/test_stats.json");
119-
$this->assertEquals($expected, $render);
120-
}
121-
122112
/**
123113
* Test split lines function
124114
*/

0 commit comments

Comments
 (0)