mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-08-29 09:16:15 +00:00
New possibilities for date range triggers #3403
This commit is contained in:
@@ -35,6 +35,7 @@ class ParseDateString
|
||||
*/
|
||||
public function parseDate(string $date): Carbon
|
||||
{
|
||||
$date = strtolower($date);
|
||||
// parse keywords:
|
||||
if (in_array($date, $this->keywords, true)) {
|
||||
return $this->parseKeyword($date);
|
||||
@@ -48,18 +49,189 @@ class ParseDateString
|
||||
|
||||
// if + or -:
|
||||
if (0 === strpos($date, '+') || 0 === strpos($date, '-')) {
|
||||
|
||||
return $this->parseRelativeDate($date);
|
||||
}
|
||||
if ('xxxx-xx-xx' === strtolower($date)) {
|
||||
throw new FireflyException(sprintf('[a]Not a recognised date format: "%s"', $date));
|
||||
}
|
||||
// can't do a partial year:
|
||||
$substrCount = substr_count(substr($date, 0, 4), 'x', 0);
|
||||
if (10 === strlen($date) && $substrCount > 0 && $substrCount < 4) {
|
||||
throw new FireflyException(sprintf('[b]Not a recognised date format: "%s"', $date));
|
||||
}
|
||||
|
||||
throw new FireflyException('Not recognised.');
|
||||
// maybe a date range
|
||||
if (10 === strlen($date) && (false !== strpos($date, 'xx') || false !== strpos($date, 'xxxx'))) {
|
||||
Log::debug(sprintf('[c]Detected a date range ("%s"), return a fake date.', $date));
|
||||
// very lazy way to parse the date without parsing it, because this specific function
|
||||
// cant handle date ranges.
|
||||
return new Carbon('1984-09-17');
|
||||
}
|
||||
|
||||
throw new FireflyException(sprintf('[d]Not a recognised date format: "%s"', $date));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isDateRange(string $date): bool
|
||||
{
|
||||
$date = strtolower($date);
|
||||
// not 10 chars:
|
||||
if (10 !== strlen($date)) {
|
||||
return false;
|
||||
}
|
||||
// all x'es
|
||||
if ('xxxx-xx-xx' === strtolower($date)) {
|
||||
return false;
|
||||
}
|
||||
// no x'es
|
||||
if (false === strpos($date, 'xx') && false === strpos($date, 'xxxx')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function parseRange(string $date, Carbon $journalDate): array
|
||||
{
|
||||
// several types of range can be submitted
|
||||
switch (true) {
|
||||
default:
|
||||
break;
|
||||
case $this->isDayRange($date):
|
||||
return $this->parseDayRange($date, $journalDate);
|
||||
case $this->isMonthRange($date):
|
||||
return $this->parseMonthRange($date, $journalDate);
|
||||
case $this->isYearRange($date):
|
||||
return $this->parseYearRange($date, $journalDate);
|
||||
case $this->isMonthDayRange($date):
|
||||
return $this->parseMonthDayRange($date, $journalDate);
|
||||
case $this->isDayYearRange($date):
|
||||
return $this->parseDayYearRange($date, $journalDate);
|
||||
case $this->isMonthYearRange($date):
|
||||
return $this->parseMonthYearRange($date, $journalDate);
|
||||
}
|
||||
|
||||
return [
|
||||
'start' => new Carbon('1984-09-17'),
|
||||
'end' => new Carbon('1984-09-17'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isDayRange(string $date): bool
|
||||
{
|
||||
// if regex for xxxx-xx-DD:
|
||||
$pattern = '/^xxxx-xx-(0[1-9]|[12][\d]|3[01])$/';
|
||||
if (preg_match($pattern, $date)) {
|
||||
Log::debug(sprintf('"%s" is a day range.', $date));
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(sprintf('"%s" is not a day range.', $date));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseDayRange(string $date, Carbon $journalDate): array
|
||||
{
|
||||
// format of string is xxxx-xx-DD
|
||||
$validDate = str_replace(['xxxx'], [$journalDate->year], $date);
|
||||
$validDate = str_replace(['xx'], [$journalDate->format('m')], $validDate);
|
||||
Log::debug(sprintf('parseDayRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay();
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isMonthRange(string $date): bool
|
||||
{
|
||||
// if regex for xxxx-MM-xx:
|
||||
$pattern = '/^xxxx-(0[1-9]|1[012])-xx$/';
|
||||
if (preg_match($pattern, $date)) {
|
||||
Log::debug(sprintf('"%s" is a month range.', $date));
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(sprintf('"%s" is not a month range.', $date));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isMonthYearRange(string $date): bool
|
||||
{
|
||||
// if regex for YYYY-MM-xx:
|
||||
$pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-xx$/';
|
||||
if (preg_match($pattern, $date)) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isYearRange(string $date): bool
|
||||
{
|
||||
// if regex for YYYY-xx-xx:
|
||||
$pattern = '/^(19|20)\d\d-xx-xx$/';
|
||||
if (preg_match($pattern, $date)) {
|
||||
Log::debug(sprintf('"%s" is a year range.', $date));
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(sprintf('"%s" is not a year range.', $date));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
*
|
||||
* @return Carbon
|
||||
*/
|
||||
private function parseDefaultDate(string $date): Carbon
|
||||
protected function parseDefaultDate(string $date): Carbon
|
||||
{
|
||||
return Carbon::createFromFormat('Y-m-d', $date);
|
||||
}
|
||||
@@ -69,7 +241,7 @@ class ParseDateString
|
||||
*
|
||||
* @return Carbon
|
||||
*/
|
||||
private function parseKeyword(string $keyword): Carbon
|
||||
protected function parseKeyword(string $keyword): Carbon
|
||||
{
|
||||
$today = Carbon::today()->startOfDay();
|
||||
switch ($keyword) {
|
||||
@@ -99,12 +271,65 @@ class ParseDateString
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseMonthRange(string $date, Carbon $journalDate): array
|
||||
{
|
||||
// because 31 would turn February into March unexpectedly and the exact day is irrelevant here.
|
||||
$day = $journalDate->format('d');
|
||||
if ((int) $day > 28) {
|
||||
$day = '28';
|
||||
}
|
||||
|
||||
// format of string is xxxx-MM-xx
|
||||
$validDate = str_replace(['xxxx'], [$journalDate->year], $date);
|
||||
$validDate = str_replace(['xx'], [$day], $validDate);
|
||||
Log::debug(sprintf('parseMonthRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfMonth();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfMonth();
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseMonthYearRange(string $date, Carbon $journalDate): array
|
||||
{
|
||||
// because 31 would turn February into March unexpectedly and the exact day is irrelevant here.
|
||||
$day = $journalDate->format('d');
|
||||
if ((int) $day > 28) {
|
||||
$day = '28';
|
||||
}
|
||||
|
||||
// format of string is YYYY-MM-xx
|
||||
$validDate = str_replace(['xx'], [$day], $date);
|
||||
Log::debug(sprintf('parseMonthYearRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfMonth();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfMonth();
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
*
|
||||
* @return Carbon
|
||||
*/
|
||||
private function parseRelativeDate(string $date): Carbon
|
||||
protected function parseRelativeDate(string $date): Carbon
|
||||
{
|
||||
Log::debug(sprintf('Now in parseRelativeDate("%s")', $date));
|
||||
$parts = explode(' ', $date);
|
||||
@@ -154,4 +379,106 @@ class ParseDateString
|
||||
return $today;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseYearRange(string $date, Carbon $journalDate): array
|
||||
{
|
||||
// format of string is YYYY-xx-xx
|
||||
// kind of a convulted way of replacing variables but I'm lazy.
|
||||
$validDate = str_replace(['xx-xx'], [sprintf('%s-xx', $journalDate->format('m'))], $date);
|
||||
$validDate = str_replace(['xx'], [$journalDate->format('d')], $validDate);
|
||||
Log::debug(sprintf('parseYearRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfYear();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfYear();
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
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])$/';
|
||||
if (preg_match($pattern, $date)) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
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])$/';
|
||||
if (preg_match($pattern, $date)) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseMonthDayRange(string $date, Carbon $journalDate): array
|
||||
{
|
||||
// Any year.
|
||||
// format of string is xxxx-MM-DD
|
||||
$validDate = str_replace(['xxxx'], [$journalDate->year], $date);
|
||||
Log::debug(sprintf('parseMonthDayRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay();
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $date
|
||||
* @param Carbon $journalDate
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseDayYearRange(string $date, Carbon $journalDate): array
|
||||
{
|
||||
// Any year.
|
||||
// format of string is YYYY-xx-DD
|
||||
$validDate = str_replace(['xx'], [$journalDate->format('m')], $date);
|
||||
Log::debug(sprintf('parseDayYearRange: Parsed "%s" into "%s"', $date, $validDate));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay();
|
||||
$end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay();
|
||||
|
||||
return [
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -82,7 +82,8 @@ final class DateAfter extends AbstractTrigger implements TriggerInterface
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($date->isAfter($ruleDate)) {
|
||||
$isDateRange = $dateParser->isDateRange($this->triggerValue);
|
||||
if (false === $isDateRange && $date->isAfter($ruleDate)) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'%s is after %s, so return true.',
|
||||
@@ -93,6 +94,32 @@ final class DateAfter extends AbstractTrigger implements TriggerInterface
|
||||
|
||||
return true;
|
||||
}
|
||||
// could be a date range.
|
||||
if ($isDateRange) {
|
||||
Log::debug(sprintf('Date value is "%s", representing a range.', $this->triggerValue));
|
||||
$range = $dateParser->parseRange($this->triggerValue, $date);
|
||||
if ($date->isAfter($range['end'])) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'%s is after [%s/%s], so return true.',
|
||||
$date->format('Y-m-d H:i:s'),
|
||||
$range['start']->format('Y-m-d H:i:s'),
|
||||
$range['end']->format('Y-m-d H:i:s'),
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'%s is NOT after [%s/%s], so return false.',
|
||||
$date->format('Y-m-d H:i:s'),
|
||||
$range['start']->format('Y-m-d H:i:s'),
|
||||
$range['end']->format('Y-m-d H:i:s'),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
Log::debug(
|
||||
sprintf(
|
||||
|
@@ -82,7 +82,9 @@ final class DateBefore extends AbstractTrigger implements TriggerInterface
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($date->isBefore($ruleDate)) {
|
||||
$isDateRange = $dateParser->isDateRange($this->triggerValue);
|
||||
|
||||
if (false === $isDateRange && $date->isBefore($ruleDate)) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'%s is before %s, so return true.',
|
||||
@@ -94,6 +96,35 @@ final class DateBefore extends AbstractTrigger implements TriggerInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
// could be a date range.
|
||||
if ($isDateRange) {
|
||||
Log::debug(sprintf('Date value is "%s", representing a range.', $this->triggerValue));
|
||||
$range = $dateParser->parseRange($this->triggerValue, $date);
|
||||
if ($date->isBefore($range['start'])) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'%s is before [%s/%s], so return true.',
|
||||
$date->format('Y-m-d H:i:s'),
|
||||
$range['start']->format('Y-m-d H:i:s'),
|
||||
$range['end']->format('Y-m-d H:i:s'),
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'%s is NOT before [%s/%s], so return false.',
|
||||
$date->format('Y-m-d H:i:s'),
|
||||
$range['start']->format('Y-m-d H:i:s'),
|
||||
$range['end']->format('Y-m-d H:i:s'),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'%s is NOT before %s, so return true.',
|
||||
|
@@ -82,7 +82,9 @@ final class DateIs extends AbstractTrigger implements TriggerInterface
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($ruleDate->isSameDay($date)) {
|
||||
$isDateRange = $dateParser->isDateRange($this->triggerValue);
|
||||
|
||||
if (false === $isDateRange && $ruleDate->isSameDay($date)) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'%s is on the same day as %s, so return true.',
|
||||
@@ -94,6 +96,34 @@ final class DateIs extends AbstractTrigger implements TriggerInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
// could be a date range.
|
||||
if ($isDateRange) {
|
||||
Log::debug(sprintf('Date value is "%s", representing a range.', $this->triggerValue));
|
||||
$range = $dateParser->parseRange($this->triggerValue, $date);
|
||||
if ($date->isAfter($range['start']) && $date->isBefore($range['end'])) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'%s is between [%s/%s], so return true.',
|
||||
$date->format('Y-m-d H:i:s'),
|
||||
$range['start']->format('Y-m-d H:i:s'),
|
||||
$range['end']->format('Y-m-d H:i:s'),
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'%s is NOT between [%s/%s], so return false.',
|
||||
$date->format('Y-m-d H:i:s'),
|
||||
$range['start']->format('Y-m-d H:i:s'),
|
||||
$range['end']->format('Y-m-d H:i:s'),
|
||||
)
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'%s is NOT on the same day as %s, so return true.',
|
||||
|
Reference in New Issue
Block a user