$item) { if ($item['period']['from'] === $range['from'] && $item['period']['to'] === $range['to']) { $period = $key; break; } } // If it's custom range, store the range if (empty($period)) { $period = $range; } User::saveMeta(self::USER_DATE_RANGE_META_KEY, $period); } /** * Retrieves the period stored in the user's meta data, or from request object. * * @return string|array Could be a period name like '30days' or an array containing 'from' and 'to' date strings. */ public static function retrieve() { $result = []; $period = User::getMeta(self::USER_DATE_RANGE_META_KEY, true); if (!self::validate($period)) { $period = self::$defaultPeriod; } // Predefined date periods like '30days', 'this_month', etc... if (is_string($period)) { $periods = self::getPeriods(); $result = [ 'type' => 'period', 'value' => $period, 'range' => $periods[$period]['period'] ]; } // Custom date range store in usermeta like ['from' => '2024-01-01', 'to' => '2024-01-31'] if (is_array($period)) { $result = [ 'type' => 'custom', 'value' => $period, 'range' => $period ]; } // Manual date range from request object if (Request::has('from') && Request::has('to')) { $result = [ 'type' => 'manual', 'value' => Request::getParams(['from', 'to']), 'range' => Request::getParams(['from', 'to']) ]; } return $result; } /** * Gets a string and returns the specified date range. By default returns the stored period in usermeta. * * @param string|bool $name The name of the date range. By default false. * @param bool $excludeToday Whether to exclude today from the date range. Defaults to false. * @return array The date range. */ public static function get($period = false, $excludeToday = false) { $range = []; // If period is not provided, retrieve it if (!$period) { $storedRange = self::retrieve(); $range = $storedRange['range']; } else { $periods = self::getPeriods(); if (isset($periods[$period])) { $range = $periods[$period]['period']; } } // If period is 'total', set the from date to the initial post date if ($period === 'total') { $range['from'] = Helper::getInitialPostDate(); } if (!empty($range) && $excludeToday) { // Only applicable for periods that their last day is today, like 7days, 30days, etc... if ($period !== 'today' && self::compare($range['to'], 'is', 'today')) { $range['from'] = DateTime::subtract($range['from'], 1); $range['to'] = DateTime::subtract($range['to'], 1); } } return $range; } /** * Get the previous period based on a period name or custom date range. * By default it returns result based on the stored period in usermeta. * * @param mixed $period The name of the period (e.g., '30days', 'this_month') or custom date range. * @param bool $excludeToday Whether to exclude today from date calculations. Defaults to false. * @return array The previous period's date range. */ public static function getPrevPeriod($period = false, $excludeToday = false) { // If period is not provided, retrieve it if (!$period) { $period = self::retrieve(); $period = $period['value']; } // Check if the period name exists in the predefined periods $periods = self::getPeriods(); if (is_string($period) && isset($periods[$period])) { $currentRange = $periods[$period]['period']; $prevRange = $periods[$period]['prev_period']; // Only applicable for periods that their last day is today, like 7days, 30days, etc... if ($excludeToday && self::compare($currentRange['to'], '=', 'today')) { $prevRange['from'] = DateTime::subtract($prevRange['from'], 1); $prevRange['to'] = DateTime::subtract($prevRange['to'], 1); } return $prevRange; } // If it's a custom date range, calculate the previous period dynamically if (is_array($period)) { $range = self::resolveDate($period); $from = strtotime($range['from']); $to = strtotime($range['to']); $periodName = self::getPeriodFromRange($range); if ($periodName) { $currentRange = $periods[$periodName]['period']; $prevRange = $periods[$periodName]['prev_period']; // Only applicable for periods that their last day is today, like 7days, 30days, etc... if ($excludeToday && self::compare($currentRange['to'], 'is', 'today')) { $prevRange['from'] = DateTime::subtract($prevRange['from'], 1); $prevRange['to'] = DateTime::subtract($prevRange['to'], 1); } return $prevRange; } // Calculate the number of days in the current period $daysInPeriod = ($to - $from) / (60 * 60 * 24); // Calculate the previous period dates $prevTo = $from - 1; $prevFrom = $prevTo - $daysInPeriod * 60 * 60 * 24; return [ 'from' => date(DateTime::$defaultDateFormat, $prevFrom), 'to' => date(DateTime::$defaultDateFormat, $prevTo) ]; } // Fallback to default period if period is invalid return self::getPrevPeriod(self::$defaultPeriod); } /** * Retrieves the period name from a predefined date range. * * @param array $date An array containing 'from' and 'to' date strings. * @return string The period name. */ public static function getPeriodFromRange($date) { $periods = self::getPeriods(); foreach ($periods as $key => $period) { if ($date['from'] === $period['period']['from'] && $date['to'] === $period['period']['to']) { return $key; } } return false; } /** * Returns an array of predefined date periods. * * Each date period is represented as an array with two keys: 'period' and 'prev_period'. * The 'period' key represents the actual date period of the given string * The 'prev_period' key represents the date range before to the current date period. * * @return array An array of predefined date periods. */ public static function getPeriods() { return [ 'today' => [ 'period' => [ 'from' => DateTime::get(), 'to' => DateTime::get() ], 'prev_period' => [ 'from' => DateTime::get('-1 day'), 'to' => DateTime::get('-1 day') ], ], 'yesterday' => [ 'period' => [ 'from' => DateTime::get('-1 day'), 'to' => DateTime::get('-1 day'), ], 'prev_period' => [ 'from' => DateTime::get('-2 day'), 'to' => DateTime::get('-2 day') ], ], 'this_week' => [ 'period' => self::calculateWeekRange(0), 'prev_period' => self::calculateWeekRange(1) ], 'last_week' => [ 'period' => self::calculateWeekRange(1), 'prev_period' => self::calculateWeekRange(2) ], 'this_month' => [ 'period' => [ 'from' => DateTime::get('first day of this month'), 'to' => DateTime::get('last day of this month') ], 'prev_period' => [ 'from' => DateTime::get('first day of last month'), 'to' => DateTime::get('last day of last month') ] ], 'last_month' => [ 'period' => [ 'from' => DateTime::get('first day of -1 month'), 'to' => DateTime::get('last day of -1 month'), ], 'prev_period' => [ 'from' => DateTime::get('first day of -2 months'), 'to' => DateTime::get('last day of -2 months') ] ], '7days' => [ 'period' => [ 'from' => DateTime::get('-6 days'), 'to' => DateTime::get() ], 'prev_period' => [ 'from' => DateTime::get('-13 days'), 'to' => DateTime::get('-7 days') ] ], '14days' => [ 'period' => [ 'from' => DateTime::get('-13 days'), 'to' => DateTime::get() ], 'prev_period' => [ 'from' => DateTime::get('-27 days'), 'to' => DateTime::get('-14 days') ] ], '15days' => [ 'period' => [ 'from' => DateTime::get('-14 days'), 'to' => DateTime::get() ], 'prev_period' => [ 'from' => DateTime::get('-29 days'), 'to' => DateTime::get('-15 days') ] ], '28days' => [ 'period' => [ 'from' => DateTime::get('-27 days'), 'to' => DateTime::get() ], 'prev_period' => [ 'from' => DateTime::get('-55 days'), 'to' => DateTime::get('-28 days') ] ], '30days' => [ 'period' => [ 'from' => DateTime::get('-29 days'), 'to' => DateTime::get() ], 'prev_period' => [ 'from' => DateTime::get('-59 days'), 'to' => DateTime::get('-30 days') ] ], '90days' => [ 'period' => [ 'from' => DateTime::get('-89 days'), 'to' => DateTime::get() ], 'prev_period' => [ 'from' => DateTime::get('-179 days'), 'to' => DateTime::get('-90 days') ] ], '6months' => [ 'period' => [ 'from' => DateTime::get('-6 months'), 'to' => DateTime::get() ], 'prev_period' => [ 'from' => DateTime::get('-12 months'), 'to' => DateTime::get('-6 months') ] ], '12months' => [ 'period' => [ 'from' => DateTime::get('-12 months'), 'to' => DateTime::get() ], 'prev_period' => [ 'from' => DateTime::get('-24 months'), 'to' => DateTime::get('-12 months') ] ], 'this_year' => [ 'period' => [ 'from' => DateTime::get('now', 'Y-01-01'), 'to' => DateTime::get('now', 'Y-12-31') ], 'prev_period' => [ 'from' => DateTime::get('-1 year', 'Y-01-01'), 'to' => DateTime::get('-1 year', 'Y-12-31') ] ], 'last_year' => [ 'period' => [ 'from' => DateTime::get('-1 year', 'Y-01-01'), 'to' => DateTime::get('-1 year', 'Y-12-31') ], 'prev_period' => [ 'from' => DateTime::get('-2 year', 'Y-01-01'), 'to' => DateTime::get('-2 year', 'Y-12-31') ] ], 'total' => [ 'period' => [ 'from' => DateTime::get(0), 'to' => DateTime::get() ], 'prev_period' => [ 'from' => DateTime::get(0), 'to' => DateTime::get() ] ], ]; } /** * Compare two dates. * * @param mixed $date1 A date string, array, or period name. * @param string $operator The operator to use for comparison. * @param mixed $date2 A date string, array, or period name. * * @return bool Whether the date ranges match the comparison operator. * @example * DateRange::compare($date, '=', 'today') * DateRange::compare($date, 'in', 'this_month') * DateRange::compare($date1, '!=', $date2) * DateRange::compare($date, 'in', ['from' => '2024-01-01', 'to' => '2024-01-31']) */ public static function compare($date1, $operator, $date2) { $range1 = self::resolveDate($date1); $range2 = self::resolveDate($date2); if (empty($range1) || empty($range2)) return false; $from1 = strtotime($range1['from']); $to1 = strtotime($range1['to']); $from2 = strtotime($range2['from']); $to2 = strtotime($range2['to']); switch ($operator) { case 'in': case 'between': return $from1 >= $from2 && $to1 <= $to2; case '<': return $to1 < $from2; case '<=': return $to1 <= $from2; case '>': return $from1 > $to2; case '>=': return $from1 >= $to2; case '!=': case 'not': return $from1 != $from2 || $to1 != $to2; case '=': case 'is': default: return $from1 == $from2 && $to1 == $to2; } } /** * Resolves the given date input to a 'from' and 'to' date array. * * @param mixed $date A date string, array, or period name. * @return array|bool An array containing 'from' and 'to' date strings. False if the date is invalid. */ private static function resolveDate($date) { // If date is an array if (is_array($date)) { if (isset($date['from'], $date['to'])) { return $date; } if (count($date) == 2) { return ['from' => $date[0], 'to' => $date[1]]; } } // If date is a simple date string if (TimeZone::isValidDate($date)) { return ['from' => $date, 'to' => $date]; } // If date is a period name (string), get the range from the periods if (is_string($date)) { return self::get($date); } return false; } /** * Calculates the date range for a given week, where 0 is the current week, 1 is last week, 2 is two weeks ago, etc. * * @param int $weeksAgo The number of weeks ago to calculate the range for. * @return array An array containing 'from' and 'to' date strings representing the start and end of the week. */ private static function calculateWeekRange($weeksAgo = 0) { $startOfWeek = DateTime::getStartOfWeek('number'); $dateFormat = DateTime::$defaultDateFormat; $today = new \DateTime('now', DateTime::getTimezone()); $today->modify("-{$weeksAgo} weeks"); // Calculate the start of the week $weekStart = clone $today; $weekStart->modify(($startOfWeek - $weekStart->format('w') - 7) % 7 . ' days'); // Calculate the end of the week $weekEnd = clone $weekStart; $weekEnd->modify('+6 days'); return [ 'from' => $weekStart->format($dateFormat), 'to' => $weekEnd->format($dateFormat) ]; } }