Framework updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-01-12 17:25:07 +00:00
parent ee709ae86d
commit f70ca09f78
12 changed files with 1234 additions and 156 deletions

View File

@@ -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
// =========================================================================

View File

@@ -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
// =========================================================================