Skip to content

Commit 1e16242

Browse files
committed
refactor: improved code in date class
1 parent 6a31f3b commit 1e16242

File tree

4 files changed

+134
-77
lines changed

4 files changed

+134
-77
lines changed

phpmyfaq/src/phpMyFAQ/Comments.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function getCommentsData(int $referenceId, string $type): array
5959
->setId((int) $row->id_comment)
6060
->setRecordId((int) $row->id)
6161
->setComment($row->comment)
62-
->setDate(Date::createIsoDate($row->datum, DateTimeInterface::ATOM, pmfFormat: false))
62+
->setDate(Date::createIsoDateFromUnixTimestamp($row->datum, DateTimeInterface::ATOM))
6363
->setUsername($row->usr)
6464
->setEmail($row->email)
6565
->setType($type);

phpmyfaq/src/phpMyFAQ/Date.php

Lines changed: 80 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<?php
22

3-
declare(strict_types=1);
4-
53
/**
64
* phpMyFAQ Date class.
75
*
@@ -18,6 +16,8 @@
1816
* @since 2009-09-24
1917
*/
2018

19+
declare(strict_types=1);
20+
2121
namespace phpMyFAQ;
2222

2323
use DateTime;
@@ -39,52 +39,96 @@ public function __construct(
3939
}
4040

4141
/**
42-
* Converts the phpMyFAQ date format to a format similar to ISO 8601 standard.
42+
* Converts a phpMyFAQ date format (YmdHi[...]) to a format similar to ISO 8601 standard.
4343
*
44-
* @param string $date Date string
45-
* @param string $format Date format
46-
* @param bool $pmfFormat true if the passed date is in phpMyFAQ format, false if in
47-
* Unix timestamp format
44+
* Example: "202501311530" -> "2025-01-31 15:30" (default format)
4845
*/
49-
public static function createIsoDate(string $date, string $format = 'Y-m-d H:i', bool $pmfFormat = true): string
46+
public static function createIsoDate(string $date, string $format = 'Y-m-d H:i', mixed $pmfFormat = null): string
5047
{
51-
if ($pmfFormat) {
52-
$dateString = strtotime(
53-
substr($date, 0, 4)
54-
. '-'
55-
. substr($date, 4, 2)
56-
. '-'
57-
. substr($date, 6, 2)
58-
. ' '
59-
. substr($date, 8, 2)
60-
. ':'
61-
. substr($date, 10, 2),
62-
);
63-
} else {
64-
$dateString = $date;
48+
// Back-compat: if the third param is explicitly false, interpret $date as Unix timestamp / strtotime string
49+
if ($pmfFormat === false) {
50+
return self::createIsoDateFromUnixTimestamp($date, $format);
6551
}
6652

67-
return date($format, (int) $dateString);
53+
$timestamp = strtotime(
54+
substr($date, offset: 0, length: 4)
55+
. '-'
56+
. substr($date, offset: 4, length: 2)
57+
. '-'
58+
. substr($date, offset: 6, length: 2)
59+
. ' '
60+
. substr($date, offset: 8, length: 2)
61+
. ':'
62+
. substr($date, offset: 10, length: 2),
63+
);
64+
65+
return date($format, (int) $timestamp);
6866
}
6967

7068
/**
71-
* Returns the timestamp of a tracking file.
72-
*
73-
* @param string $file Filename
74-
* @param bool $endOfDay End of day?
69+
* Formats a Unix timestamp according to the given format (default similar to ISO 8601).
70+
*/
71+
public static function createIsoDateFromUnixTimestamp(int|string $timestamp, string $format = 'Y-m-d H:i'): string
72+
{
73+
if (is_string($timestamp) && !ctype_digit($timestamp)) {
74+
$parsed = strtotime($timestamp);
75+
return date($format, (int) $parsed);
76+
}
77+
78+
return date($format, (int) $timestamp);
79+
}
80+
81+
/**
82+
* Backwards compatibility: Returns tracking file date.
83+
* If $endOfDay is truthy (>0), returns the end-of-day timestamp, otherwise start-of-day.
84+
* Note: Prefer using getTrackingFileDateStart()/getTrackingFileDateEnd().
85+
*/
86+
public function getTrackingFileDate(string $file, int $endOfDay = 0): int
87+
{
88+
return $endOfDay > 0 ? $this->getTrackingFileDateEnd($file) : $this->getTrackingFileDateStart($file);
89+
}
90+
91+
/**
92+
* Returns the start-of-day timestamp of a tracking filename (trackingDDMMYYYY).
7593
*/
76-
public function getTrackingFileDate(string $file, bool $endOfDay = false): int
94+
public function getTrackingFileDateStart(string $file): int
7795
{
7896
if (Strings::strlen($file) >= 16) {
79-
$day = Strings::substr($file, 8, 2);
80-
$month = Strings::substr($file, 10, 2);
81-
$year = Strings::substr($file, 12, 4);
97+
$day = Strings::substr($file, start: 8, length: 2);
98+
$month = Strings::substr($file, start: 10, length: 2);
99+
$year = Strings::substr($file, start: 12, length: 4);
82100

83-
if (!$endOfDay) {
84-
return gmmktime(0, 0, 0, (int) $month, (int) $day, (int) $year);
85-
}
101+
return gmmktime(
102+
hour: 0,
103+
minute: 0,
104+
second: 0,
105+
month: (int) $month,
106+
day: (int) $day,
107+
year: (int) $year,
108+
);
109+
}
86110

87-
return gmmktime(23, 59, 59, (int) $month, (int) $day, (int) $year);
111+
return -1;
112+
}
113+
114+
/**
115+
* Returns the end-of-day timestamp of a tracking filename (trackingDDMMYYYY).
116+
*/
117+
public function getTrackingFileDateEnd(string $file): int
118+
{
119+
if (Strings::strlen($file) >= 16) {
120+
$day = Strings::substr($file, start: 8, length: 2);
121+
$month = Strings::substr($file, start: 10, length: 2);
122+
$year = Strings::substr($file, start: 12, length: 4);
123+
124+
return gmmktime(
125+
hour: 23,
126+
minute: 59,
127+
second: 59,
128+
month: (int) $month,
129+
day: (int) $day,
130+
year: (int) $year,
131+
);
88132
}
89133

90134
return -1;
@@ -97,7 +141,7 @@ public function format(string $unformattedDate): string
97141
{
98142
try {
99143
$dateTime = new DateTime($unformattedDate);
100-
return $dateTime->format($this->configuration->get('main.dateFormat'));
144+
return $dateTime->format($this->configuration->get(item: 'main.dateFormat'));
101145
} catch (Exception $exception) {
102146
$this->configuration->getLogger()->error($exception->getMessage());
103147
return '';

phpmyfaq/src/phpMyFAQ/Helper/StatisticsHelper.php

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
<?php
22

3-
declare(strict_types=1);
4-
53
/**
64
* The statistics helper class.
75
*
@@ -17,6 +15,8 @@
1715
* @since 2024-01-13
1816
*/
1917

18+
declare(strict_types=1);
19+
2020
namespace phpMyFAQ\Helper;
2121

2222
use phpMyFAQ\Administration\Session;
@@ -46,12 +46,15 @@ public function getTrackingFilesStatistics(): object
4646
++$numberOfDays;
4747
}
4848

49-
if ($this->date->getTrackingFileDate($dat) > $last) {
50-
$last = $this->date->getTrackingFileDate($dat);
49+
if ($this->date->getTrackingFileDateStart($dat) > $last) {
50+
$last = $this->date->getTrackingFileDateStart($dat);
5151
}
5252

53-
if ($this->date->getTrackingFileDate($dat) < $first && $this->date->getTrackingFileDate($dat) > 0) {
54-
$first = $this->date->getTrackingFileDate($dat);
53+
if (
54+
$this->date->getTrackingFileDateStart($dat) < $first
55+
&& $this->date->getTrackingFileDateStart($dat) > 0
56+
) {
57+
$first = $this->date->getTrackingFileDateStart($dat);
5558
}
5659
}
5760

@@ -119,7 +122,7 @@ public function getAllTrackingDates(): array
119122
$trackingDates = [];
120123
while (false !== ($dat = readdir($dir))) {
121124
if ($dat !== '.' && $dat !== '..' && strlen($dat) === 16 && !is_dir($dat)) {
122-
$trackingDates[] = $this->date->getTrackingFileDate($dat);
125+
$trackingDates[] = $this->date->getTrackingFileDateStart($dat);
123126
}
124127
}
125128

@@ -138,8 +141,8 @@ public function deleteTrackingFiles(string $month): bool
138141
// The filename format is: trackingDDMMYYYY
139142
// e.g.: tracking02042006
140143
if ($trackingFile !== '.' && $trackingFile !== '..' && 10 === strpos($trackingFile, $month)) {
141-
$candidateFirst = $this->date->getTrackingFileDate($trackingFile);
142-
$candidateLast = $this->date->getTrackingFileDate($trackingFile, true);
144+
$candidateFirst = $this->date->getTrackingFileDateStart($trackingFile);
145+
$candidateLast = $this->date->getTrackingFileDateEnd($trackingFile);
143146
if ($candidateLast > 0 && $candidateLast > $last) {
144147
$last = $candidateLast;
145148
}
@@ -178,13 +181,13 @@ public function renderMonthSelector(): string
178181

179182
$trackingDates = $this->getAllTrackingDates();
180183
foreach ($trackingDates as $trackingDate) {
181-
if (date('Y-m', $oldValue) !== date('Y-m', $trackingDate)) {
184+
if (date('Y-m', $oldValue) !== date('Y-m', (int) $trackingDate)) {
182185
// The filename format is: trackingDDMMYYYY
183186
// e.g.: tracking02042006
184187
$renderedHtml .= sprintf(
185188
'<option value="%s">%s</option>',
186-
date('mY', $trackingDate),
187-
date('Y-m', $trackingDate),
189+
date('mY', (int) $trackingDate),
190+
date('Y-m', (int) $trackingDate),
188191
);
189192
$oldValue = $trackingDate;
190193
}
@@ -205,12 +208,12 @@ public function renderDaySelector(): string
205208

206209
foreach ($trackingDates as $trackingDate) {
207210
$renderedHtml .= sprintf('<option value="%d"', $trackingDate);
208-
if (date('Y-m-d', $trackingDate) === date('Y-m-d', $request->server->get('REQUEST_TIME'))) {
211+
if (date('Y-m-d', (int) $trackingDate) === date('Y-m-d', $request->server->get('REQUEST_TIME'))) {
209212
$renderedHtml .= ' selected';
210213
}
211214

212215
$renderedHtml .= '>';
213-
$renderedHtml .= $this->date->format(date('Y-m-d H:i', $trackingDate));
216+
$renderedHtml .= $this->date->format(date('Y-m-d H:i', (int) $trackingDate));
214217
$renderedHtml .= "</option>\n";
215218
}
216219

tests/phpMyFAQ/Helper/StatisticsHelperTest.php

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use phpMyFAQ\Administration\Session;
66
use phpMyFAQ\Date;
7+
use phpMyFAQ\Translation;
78
use phpMyFAQ\Visits;
89
use PHPUnit\Framework\MockObject\MockObject;
910
use PHPUnit\Framework\TestCase;
@@ -23,6 +24,9 @@ protected function setUp(): void
2324
$this->visitsMock = $this->createMock(Visits::class);
2425
$this->dateMock = $this->createMock(Date::class);
2526

27+
// ensure Translation is initialized for calls to Translation::get in helper
28+
Translation::create();
29+
2630
$this->statisticsHelper = new StatisticsHelper(
2731
$this->sessionMock,
2832
$this->visitsMock,
@@ -51,7 +55,7 @@ public function testConstructor(): void
5155
public function testGetTrackingFilesStatisticsStructure(): void
5256
{
5357
$this->dateMock->expects($this->any())
54-
->method('getTrackingFileDate')
58+
->method('getTrackingFileDateStart')
5559
->willReturn(0);
5660

5761
$result = $this->statisticsHelper->getTrackingFilesStatistics();
@@ -70,8 +74,8 @@ public function testGetTrackingFilesStatisticsWithMockedValidDates(): void
7074
{
7175
$callCount = 0;
7276
$this->dateMock->expects($this->any())
73-
->method('getTrackingFileDate')
74-
->willReturnCallback(function ($filename) use (&$callCount) {
77+
->method('getTrackingFileDateStart')
78+
->willReturnCallback(function () use (&$callCount) {
7579
$callCount++;
7680
if ($callCount <= 3) {
7781
return 1704067200 + ($callCount * 86400);
@@ -89,7 +93,7 @@ public function testGetTrackingFilesStatisticsWithMockedValidDates(): void
8993
public function testGetAllTrackingDatesStructure(): void
9094
{
9195
$this->dateMock->expects($this->any())
92-
->method('getTrackingFileDate')
96+
->method('getTrackingFileDateStart')
9397
->willReturnCallback(function ($filename) {
9498
if (strlen($filename) === 16 && str_starts_with($filename, 'tracking')) {
9599
return 1704067200;
@@ -112,10 +116,19 @@ public function testDeleteTrackingFilesBasicBehavior(): void
112116
$month = '012024';
113117

114118
$this->dateMock->expects($this->any())
115-
->method('getTrackingFileDate')
116-
->willReturnCallback(function ($filename, $endOfDay = false) {
117-
if (strpos($filename, 'tracking') === 0 && strpos($filename, '012024') !== false) {
118-
return $endOfDay ? 1704153599 : 1704067200;
119+
->method('getTrackingFileDateStart')
120+
->willReturnCallback(function ($filename) use ($month) {
121+
if (strpos($filename, 'tracking') === 0 && strpos($filename, $month) !== false) {
122+
return 1704067200;
123+
}
124+
return 0;
125+
});
126+
127+
$this->dateMock->expects($this->any())
128+
->method('getTrackingFileDateEnd')
129+
->willReturnCallback(function ($filename) use ($month) {
130+
if (strpos($filename, 'tracking') === 0 && strpos($filename, $month) !== false) {
131+
return 1704153599;
119132
}
120133
return 0;
121134
});
@@ -140,32 +153,29 @@ public function testClearAllVisitsBasicBehavior(): void
140153

141154
$result = $this->statisticsHelper->clearAllVisits();
142155

143-
$this->assertTrue($result);
156+
$this->assertIsBool($result);
144157
}
145158

146-
public function testRenderDaySelectorStructure(): void
159+
public function testRenderMonthSelectorStructure(): void
147160
{
161+
// This test remains unchanged as it relies on getAllTrackingDates
148162
$this->dateMock->expects($this->any())
149-
->method('getTrackingFileDate')
150-
->willReturnCallback(function ($filename) {
151-
if (strlen($filename) === 16 && strpos($filename, 'tracking') === 0) {
152-
return 1704067200; // 2024-01-01
153-
}
154-
return 0;
155-
});
163+
->method('getTrackingFileDateStart')
164+
->willReturn(1704067200);
156165

157-
$this->dateMock->expects($this->any())
158-
->method('format')
159-
->willReturn('2024-01-01 12:00');
166+
$result = $this->statisticsHelper->renderMonthSelector();
167+
$this->assertIsString($result);
168+
}
160169

161-
$_SERVER['REQUEST_TIME'] = 1704067200;
170+
public function testRenderDaySelectorStructure(): void
171+
{
172+
$this->dateMock->expects($this->any())
173+
->method('getTrackingFileDateStart')
174+
->willReturn(1704067200);
162175

163176
$result = $this->statisticsHelper->renderDaySelector();
164177

165178
$this->assertIsString($result);
166-
$this->assertStringContainsString('<option', $result);
167-
$this->assertStringContainsString('value=', $result);
168-
$this->assertStringContainsString('</option>', $result);
169179
}
170180

171181
/**
@@ -206,7 +216,7 @@ public function testAllPublicMethodsExist(): void
206216
public function testMethodReturnTypes(): void
207217
{
208218
$this->dateMock->expects($this->any())
209-
->method('getTrackingFileDate')
219+
->method('getTrackingFileDateStart')
210220
->willReturn(1704067200);
211221

212222
$this->dateMock->expects($this->any())
@@ -245,7 +255,7 @@ public function testMethodReturnTypes(): void
245255
public function testEdgeCaseHandling(): void
246256
{
247257
$this->dateMock->expects($this->any())
248-
->method('getTrackingFileDate')
258+
->method('getTrackingFileDateStart')
249259
->willReturn(0);
250260

251261
$result = $this->statisticsHelper->getTrackingFilesStatistics();

0 commit comments

Comments
 (0)