Framework updates
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -113,16 +113,26 @@ class Rsx_Date
|
||||
// CURRENT DATE
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Alias for today()
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function now(): string
|
||||
{
|
||||
return static::today();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get today's date as "YYYY-MM-DD"
|
||||
* Uses the user's timezone to determine what "today" is
|
||||
* Uses user → site → default timezone resolution
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function today(): string
|
||||
{
|
||||
$user_tz = Rsx_Time::get_user_timezone();
|
||||
return Carbon::now($user_tz)->format('Y-m-d');
|
||||
$tz = Rsx_Time::get_user_timezone();
|
||||
return Carbon::now($tz)->format('Y-m-d');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
@@ -230,6 +240,299 @@ class Rsx_Date
|
||||
return $carbon1->diffInDays($carbon2, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format as relative date ("Today", "Yesterday", "3 days ago", "in 5 days")
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string
|
||||
*/
|
||||
public static function relative($date): string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$days = static::diff_days(static::today(), $parsed);
|
||||
|
||||
if ($days === 0) {
|
||||
return 'Today';
|
||||
} elseif ($days === 1) {
|
||||
return 'Tomorrow';
|
||||
} elseif ($days === -1) {
|
||||
return 'Yesterday';
|
||||
} elseif ($days > 1) {
|
||||
return "in {$days} days";
|
||||
} else {
|
||||
return abs($days) . ' days ago';
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// ARITHMETIC
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Add days to a date
|
||||
*
|
||||
* @param mixed $date
|
||||
* @param int $days Can be negative to subtract
|
||||
* @return string|null "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
public static function add_days($date, int $days): ?string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed)->addDays($days);
|
||||
return $carbon->format('Y-m-d');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// WEEK/MONTH BOUNDARIES
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get the Monday of the week containing the date
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string|null "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
public static function start_of_week($date): ?string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed)->startOfWeek(Carbon::MONDAY);
|
||||
return $carbon->format('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Sunday of the week containing the date
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string|null "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
public static function end_of_week($date): ?string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed)->endOfWeek(Carbon::SUNDAY);
|
||||
return $carbon->format('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first day of the month containing the date
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string|null "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
public static function start_of_month($date): ?string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed)->startOfMonth();
|
||||
return $carbon->format('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last day of the month containing the date
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string|null "YYYY-MM-DD" or null if invalid input
|
||||
*/
|
||||
public static function end_of_month($date): ?string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed)->endOfMonth();
|
||||
return $carbon->format('Y-m-d');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date falls on a weekend (Saturday or Sunday)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_weekend($date): bool
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->isWeekend();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date falls on a weekday (Monday-Friday)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_weekday($date): bool
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->isWeekday();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// COMPONENT EXTRACTORS
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get day of month (1-31)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return int|null
|
||||
*/
|
||||
public static function day($date): ?int
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) explode('-', $parsed)[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get day of week (0=Sunday, 6=Saturday)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return int|null
|
||||
*/
|
||||
public static function dow($date): ?int
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->dayOfWeek;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full day name ("Monday", "Tuesday", etc.)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string
|
||||
*/
|
||||
public static function dow_human($date): string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->format('l');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short day name ("Mon", "Tue", etc.)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string
|
||||
*/
|
||||
public static function dow_short($date): string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->format('D');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get month (1-12)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return int|null
|
||||
*/
|
||||
public static function month($date): ?int
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) explode('-', $parsed)[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full month name ("January", "February", etc.)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string
|
||||
*/
|
||||
public static function month_human($date): string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->format('F');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short month name ("Jan", "Feb", etc.)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return string
|
||||
*/
|
||||
public static function month_human_short($date): string
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $parsed);
|
||||
return $carbon->format('M');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get year (e.g., 2025)
|
||||
*
|
||||
* @param mixed $date
|
||||
* @return int|null
|
||||
*/
|
||||
public static function year($date): ?int
|
||||
{
|
||||
$parsed = static::parse($date);
|
||||
if (!$parsed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) explode('-', $parsed)[0];
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DATABASE
|
||||
// =========================================================================
|
||||
|
||||
@@ -31,6 +31,40 @@ use App\RSpade\Core\Session\Session;
|
||||
*/
|
||||
class Rsx_Time
|
||||
{
|
||||
// =========================================================================
|
||||
// TIMEZONE CACHING
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Cached user timezone
|
||||
* @var string|null
|
||||
*/
|
||||
private static ?string $_cached_user_timezone = null;
|
||||
|
||||
/**
|
||||
* User ID when timezone was cached (for invalidation)
|
||||
* @var int|null
|
||||
*/
|
||||
private static ?int $_cached_user_id = null;
|
||||
|
||||
/**
|
||||
* Site ID when timezone was cached (for invalidation)
|
||||
* @var int|null
|
||||
*/
|
||||
private static ?int $_cached_site_id = null;
|
||||
|
||||
/**
|
||||
* Clear cached timezone (called when session user/site changes)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear_timezone_cache(): void
|
||||
{
|
||||
static::$_cached_user_timezone = null;
|
||||
static::$_cached_user_id = null;
|
||||
static::$_cached_site_id = null;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// CURRENT TIME
|
||||
// =========================================================================
|
||||
@@ -210,24 +244,65 @@ class Rsx_Time
|
||||
/**
|
||||
* Get the current user's timezone
|
||||
* Resolution: user setting → site default → config default → America/Chicago
|
||||
* Result is cached and invalidated when session user/site changes
|
||||
*
|
||||
* @return string IANA timezone identifier
|
||||
*/
|
||||
public static function get_user_timezone(): string
|
||||
{
|
||||
$current_user_id = Session::get_login_user_id();
|
||||
$current_site_id = Session::get_site_id();
|
||||
|
||||
// Check if cache is valid
|
||||
if (static::$_cached_user_timezone !== null
|
||||
&& static::$_cached_user_id === $current_user_id
|
||||
&& static::$_cached_site_id === $current_site_id) {
|
||||
return static::$_cached_user_timezone;
|
||||
}
|
||||
|
||||
// Cache miss - recalculate
|
||||
$timezone = null;
|
||||
|
||||
// Check logged-in user's preference
|
||||
$login_user = Session::get_login_user();
|
||||
if ($login_user && !empty($login_user->timezone)) {
|
||||
return $login_user->timezone;
|
||||
$timezone = $login_user->timezone;
|
||||
}
|
||||
|
||||
// Check site default (future enhancement)
|
||||
// $site = Session::get_site();
|
||||
// if ($site && !empty($site->timezone)) {
|
||||
// return $site->timezone;
|
||||
// }
|
||||
// Check site default
|
||||
if ($timezone === null) {
|
||||
$site = Session::get_site();
|
||||
if ($site && !empty($site->timezone)) {
|
||||
$timezone = $site->timezone;
|
||||
}
|
||||
}
|
||||
|
||||
// Config default
|
||||
if ($timezone === null) {
|
||||
$timezone = config('rsx.datetime.default_timezone', 'America/Chicago');
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
static::$_cached_user_timezone = $timezone;
|
||||
static::$_cached_user_id = $current_user_id;
|
||||
static::$_cached_site_id = $current_site_id;
|
||||
|
||||
return $timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current site's timezone (ignoring user preference)
|
||||
* Resolution: site default → config default → America/Chicago
|
||||
*
|
||||
* @return string IANA timezone identifier
|
||||
*/
|
||||
public static function get_site_timezone(): string
|
||||
{
|
||||
$site = Session::get_site();
|
||||
if ($site && !empty($site->timezone)) {
|
||||
return $site->timezone;
|
||||
}
|
||||
|
||||
return config('rsx.datetime.default_timezone', 'America/Chicago');
|
||||
}
|
||||
|
||||
@@ -308,6 +383,28 @@ class Rsx_Time
|
||||
return $end_carbon->diffInSeconds($start_carbon, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seconds until a future time (negative if past)
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int
|
||||
*/
|
||||
public static function seconds_until($time): int
|
||||
{
|
||||
return static::diff_seconds(static::now(), $time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seconds since a past time (negative if future)
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int
|
||||
*/
|
||||
public static function seconds_since($time): int
|
||||
{
|
||||
return static::diff_seconds($time, static::now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration as human-readable string
|
||||
*
|
||||
@@ -517,6 +614,170 @@ class Rsx_Time
|
||||
return static::format($time, 'M j, Y g:i A T', $timezone);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// COMPONENT EXTRACTORS
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Get day of month (1-31)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function day($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return (int) static::to_user_timezone($carbon)->format('j');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get day of week (0=Sunday, 6=Saturday)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function dow($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return static::to_user_timezone($carbon)->dayOfWeek;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full day name ("Monday", "Tuesday", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return string
|
||||
*/
|
||||
public static function dow_human($time): string
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return '';
|
||||
}
|
||||
return static::to_user_timezone($carbon)->format('l');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short day name ("Mon", "Tue", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return string
|
||||
*/
|
||||
public static function dow_short($time): string
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return '';
|
||||
}
|
||||
return static::to_user_timezone($carbon)->format('D');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get month (1-12)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function month($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return (int) static::to_user_timezone($carbon)->format('n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full month name ("January", "February", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return string
|
||||
*/
|
||||
public static function month_human($time): string
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return '';
|
||||
}
|
||||
return static::to_user_timezone($carbon)->format('F');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short month name ("Jan", "Feb", etc.)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return string
|
||||
*/
|
||||
public static function month_human_short($time): string
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return '';
|
||||
}
|
||||
return static::to_user_timezone($carbon)->format('M');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get year (e.g., 2025)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function year($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return (int) static::to_user_timezone($carbon)->format('Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hour (0-23)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function hour($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return (int) static::to_user_timezone($carbon)->format('G');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get minute (0-59)
|
||||
* Uses user's timezone
|
||||
*
|
||||
* @param mixed $time
|
||||
* @return int|null
|
||||
*/
|
||||
public static function minute($time): ?int
|
||||
{
|
||||
$carbon = static::parse($time);
|
||||
if (!$carbon) {
|
||||
return null;
|
||||
}
|
||||
return (int) static::to_user_timezone($carbon)->format('i');
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DATABASE HELPERS
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user