Optimize queries for statistics.

This commit is contained in:
James Cole
2025-09-26 06:05:37 +02:00
parent 08879d31ba
commit 4ec2fcdb8a
92 changed files with 6499 additions and 6514 deletions

View File

@@ -29,7 +29,6 @@ use Carbon\CarbonInterface;
use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\Exceptions\FireflyException;
use Illuminate\Support\Facades\Log;
use function Safe\preg_match;
/**
@@ -79,15 +78,15 @@ class ParseDateString
public function parseDate(string $date): Carbon
{
Log::debug(sprintf('parseDate("%s")', $date));
$date = strtolower($date);
$date = strtolower($date);
// parse keywords:
if (in_array($date, $this->keywords, true)) {
return $this->parseKeyword($date);
}
// if regex for YYYY-MM-DD:
$pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])$/';
$result = preg_match($pattern, $date);
$pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])$/';
$result = preg_match($pattern, $date);
if (0 !== $result) {
return $this->parseDefaultDate($date);
}
@@ -114,99 +113,13 @@ class ParseDateString
return new Carbon('1984-09-17');
}
// maybe a year, nothing else?
if (4 === strlen($date) && is_numeric($date) && (int) $date > 1000 && (int) $date <= 3000) {
if (4 === strlen($date) && is_numeric($date) && (int)$date > 1000 && (int)$date <= 3000) {
return new Carbon(sprintf('%d-01-01', $date));
}
throw new FireflyException(sprintf('[d] Not a recognised date format: "%s"', $date));
}
protected function parseKeyword(string $keyword): Carbon
{
$today = today(config('app.timezone'))->startOfDay();
return match ($keyword) {
default => $today,
'yesterday' => $today->subDay(),
'tomorrow' => $today->addDay(),
'start of this week' => $today->startOfWeek(CarbonInterface::MONDAY),
'end of this week' => $today->endOfWeek(CarbonInterface::SUNDAY),
'start of this month' => $today->startOfMonth(),
'end of this month' => $today->endOfMonth(),
'start of this quarter' => $today->startOfQuarter(),
'end of this quarter' => $today->endOfQuarter(),
'start of this year' => $today->startOfYear(),
'end of this year' => $today->endOfYear(),
};
}
protected function parseDefaultDate(string $date): Carbon
{
$result = false;
try {
$result = Carbon::createFromFormat('Y-m-d', $date);
} catch (InvalidFormatException $e) {
Log::error(sprintf('parseDefaultDate("%s") ran into an error, but dont mind: %s', $date, $e->getMessage()));
}
if (false === $result) {
return today(config('app.timezone'))->startOfDay();
}
return $result;
}
protected function parseRelativeDate(string $date): Carbon
{
Log::debug(sprintf('Now in parseRelativeDate("%s")', $date));
$parts = explode(' ', $date);
$today = today(config('app.timezone'))->startOfDay();
$functions = [
[
'd' => 'subDays',
'w' => 'subWeeks',
'm' => 'subMonths',
'q' => 'subQuarters',
'y' => 'subYears',
],
[
'd' => 'addDays',
'w' => 'addWeeks',
'm' => 'addMonths',
'q' => 'addQuarters',
'y' => 'addYears',
],
];
foreach ($parts as $part) {
Log::debug(sprintf('Now parsing part "%s"', $part));
$part = trim($part);
// verify if correct
$pattern = '/[+-]\d+[wqmdy]/';
$result = preg_match($pattern, $part);
if (0 === $result) {
Log::error(sprintf('Part "%s" does not match regular expression. Will be skipped.', $part));
continue;
}
$direction = str_starts_with($part, '+') ? 1 : 0;
$period = $part[strlen($part) - 1];
$number = (int) substr($part, 1, -1);
if (!array_key_exists($period, $functions[$direction])) {
Log::error(sprintf('No method for direction %d and period "%s".', $direction, $period));
continue;
}
$func = $functions[$direction][$period];
Log::debug(sprintf('Will now do %s(%d) on %s', $func, $number, $today->format('Y-m-d')));
$today->{$func}($number); // @phpstan-ignore-line
Log::debug(sprintf('Resulting date is %s', $today->format('Y-m-d')));
}
return $today;
}
public function parseRange(string $date): array
{
// several types of range can be submitted
@@ -269,16 +182,34 @@ class ParseDateString
return false;
}
/**
* format of string is xxxx-xx-DD
*/
protected function parseDayRange(string $date): array
protected function isDayYearRange(string $date): bool
{
$parts = explode('-', $date);
// if regex for YYYY-xx-DD:
$pattern = '/^(19|20)\d\d-xx-(0[1-9]|[12]\d|3[01])$/';
$result = preg_match($pattern, $date);
if (0 !== $result) {
Log::debug(sprintf('"%s" is a day/year range.', $date));
return [
'day' => $parts[2],
];
return true;
}
Log::debug(sprintf('"%s" is not a day/year range.', $date));
return false;
}
protected function isMonthDayRange(string $date): bool
{
// if regex for xxxx-MM-DD:
$pattern = '/^xxxx-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])$/';
$result = preg_match($pattern, $date);
if (0 !== $result) {
Log::debug(sprintf('"%s" is a month/day range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a month/day range.', $date));
return false;
}
protected function isMonthRange(string $date): bool
@@ -296,17 +227,19 @@ class ParseDateString
return false;
}
/**
* format of string is xxxx-MM-xx
*/
protected function parseMonthRange(string $date): array
protected function isMonthYearRange(string $date): bool
{
Log::debug(sprintf('parseMonthRange: Parsed "%s".', $date));
$parts = explode('-', $date);
// if regex for YYYY-MM-xx:
$pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-xx$/';
$result = preg_match($pattern, $date);
if (0 !== $result) {
Log::debug(sprintf('"%s" is a month/year range.', $date));
return [
'month' => $parts[1],
];
return true;
}
Log::debug(sprintf('"%s" is not a month/year range.', $date));
return false;
}
protected function isYearRange(string $date): bool
@@ -324,6 +257,131 @@ class ParseDateString
return false;
}
/**
* format of string is xxxx-xx-DD
*/
protected function parseDayRange(string $date): array
{
$parts = explode('-', $date);
return [
'day' => $parts[2],
];
}
protected function parseDefaultDate(string $date): Carbon
{
$result = false;
try {
$result = Carbon::createFromFormat('Y-m-d', $date);
} catch (InvalidFormatException $e) {
Log::error(sprintf('parseDefaultDate("%s") ran into an error, but dont mind: %s', $date, $e->getMessage()));
}
if (false === $result) {
return today(config('app.timezone'))->startOfDay();
}
return $result;
}
protected function parseKeyword(string $keyword): Carbon
{
$today = today(config('app.timezone'))->startOfDay();
return match ($keyword) {
default => $today,
'yesterday' => $today->subDay(),
'tomorrow' => $today->addDay(),
'start of this week' => $today->startOfWeek(CarbonInterface::MONDAY),
'end of this week' => $today->endOfWeek(CarbonInterface::SUNDAY),
'start of this month' => $today->startOfMonth(),
'end of this month' => $today->endOfMonth(),
'start of this quarter' => $today->startOfQuarter(),
'end of this quarter' => $today->endOfQuarter(),
'start of this year' => $today->startOfYear(),
'end of this year' => $today->endOfYear(),
};
}
/**
* format of string is xxxx-MM-xx
*/
protected function parseMonthRange(string $date): array
{
Log::debug(sprintf('parseMonthRange: Parsed "%s".', $date));
$parts = explode('-', $date);
return [
'month' => $parts[1],
];
}
/**
* format of string is YYYY-MM-xx
*/
protected function parseMonthYearRange(string $date): array
{
Log::debug(sprintf('parseMonthYearRange: Parsed "%s".', $date));
$parts = explode('-', $date);
return [
'year' => $parts[0],
'month' => $parts[1],
];
}
protected function parseRelativeDate(string $date): Carbon
{
Log::debug(sprintf('Now in parseRelativeDate("%s")', $date));
$parts = explode(' ', $date);
$today = today(config('app.timezone'))->startOfDay();
$functions = [
[
'd' => 'subDays',
'w' => 'subWeeks',
'm' => 'subMonths',
'q' => 'subQuarters',
'y' => 'subYears',
],
[
'd' => 'addDays',
'w' => 'addWeeks',
'm' => 'addMonths',
'q' => 'addQuarters',
'y' => 'addYears',
],
];
foreach ($parts as $part) {
Log::debug(sprintf('Now parsing part "%s"', $part));
$part = trim($part);
// verify if correct
$pattern = '/[+-]\d+[wqmdy]/';
$result = preg_match($pattern, $part);
if (0 === $result) {
Log::error(sprintf('Part "%s" does not match regular expression. Will be skipped.', $part));
continue;
}
$direction = str_starts_with($part, '+') ? 1 : 0;
$period = $part[strlen($part) - 1];
$number = (int)substr($part, 1, -1);
if (!array_key_exists($period, $functions[$direction])) {
Log::error(sprintf('No method for direction %d and period "%s".', $direction, $period));
continue;
}
$func = $functions[$direction][$period];
Log::debug(sprintf('Will now do %s(%d) on %s', $func, $number, $today->format('Y-m-d')));
$today->{$func}($number); // @phpstan-ignore-line
Log::debug(sprintf('Resulting date is %s', $today->format('Y-m-d')));
}
return $today;
}
/**
* format of string is YYYY-xx-xx
*/
@@ -337,50 +395,6 @@ class ParseDateString
];
}
protected function isMonthDayRange(string $date): bool
{
// if regex for xxxx-MM-DD:
$pattern = '/^xxxx-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])$/';
$result = preg_match($pattern, $date);
if (0 !== $result) {
Log::debug(sprintf('"%s" is a month/day range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a month/day range.', $date));
return false;
}
/**
* format of string is xxxx-MM-DD
*/
private function parseMonthDayRange(string $date): array
{
Log::debug(sprintf('parseMonthDayRange: Parsed "%s".', $date));
$parts = explode('-', $date);
return [
'month' => $parts[1],
'day' => $parts[2],
];
}
protected function isDayYearRange(string $date): bool
{
// if regex for YYYY-xx-DD:
$pattern = '/^(19|20)\d\d-xx-(0[1-9]|[12]\d|3[01])$/';
$result = preg_match($pattern, $date);
if (0 !== $result) {
Log::debug(sprintf('"%s" is a day/year range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a day/year range.', $date));
return false;
}
/**
* format of string is YYYY-xx-DD
*/
@@ -395,32 +409,17 @@ class ParseDateString
];
}
protected function isMonthYearRange(string $date): bool
{
// if regex for YYYY-MM-xx:
$pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-xx$/';
$result = preg_match($pattern, $date);
if (0 !== $result) {
Log::debug(sprintf('"%s" is a month/year range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a month/year range.', $date));
return false;
}
/**
* format of string is YYYY-MM-xx
* format of string is xxxx-MM-DD
*/
protected function parseMonthYearRange(string $date): array
private function parseMonthDayRange(string $date): array
{
Log::debug(sprintf('parseMonthYearRange: Parsed "%s".', $date));
Log::debug(sprintf('parseMonthDayRange: Parsed "%s".', $date));
$parts = explode('-', $date);
return [
'year' => $parts[0],
'month' => $parts[1],
'day' => $parts[2],
];
}
}