TIME(7) RSpade Framework Manual TIME(7) NAME Rsx_Time, Rsx_Date - Date and datetime handling for RSpade applications SYNOPSIS PHP Datetime: use App\RSpade\Core\Time\Rsx_Time; $now = Rsx_Time::now(); $iso = Rsx_Time::to_iso($datetime); $localized = Rsx_Time::to_user_timezone($datetime); $formatted = Rsx_Time::format_datetime_with_tz($datetime); $relative = Rsx_Time::relative($datetime); PHP Date: use App\RSpade\Core\Time\Rsx_Date; $today = Rsx_Date::today(); $formatted = Rsx_Date::format($date); $is_past = Rsx_Date::is_past($date); JavaScript Datetime: const now = Rsx_Time.now(); const iso = Rsx_Time.to_iso(datetime); const formatted = Rsx_Time.format_datetime_with_tz(datetime); const relative = Rsx_Time.relative(datetime); JavaScript Date: const today = Rsx_Date.today(); const formatted = Rsx_Date.format(date); const is_past = Rsx_Date.is_past(date); DESCRIPTION RSpade provides two separate classes for handling temporal values: Rsx_Time - Datetimes (moments in time) Represents specific moments. Always has time component. Timezone-aware. Stored in UTC, displayed in user's timezone. Format: ISO 8601 "2024-12-24T15:30:45.123Z" Rsx_Date - Dates (calendar dates) Represents calendar dates without time. Timezone-agnostic. "December 24, 2025" is the same day everywhere. Format: Always "YYYY-MM-DD" (e.g., "2024-12-24") STRINGS, NOT OBJECTS All external-facing APIs return STRINGS, not Carbon or Date objects: $model->created_at // "2024-12-24T15:30:45.123Z" (string) $model->due_date // "2024-12-24" (string) Rsx_Time::now_iso() // "2024-12-24T15:30:45.123Z" (string) Rsx_Time::to_iso($x) // "2024-12-24T15:30:45.123Z" (string) Rsx_Time::add($x, 60) // "2024-12-24T15:31:45.123Z" (string) This design keeps PHP and JavaScript in sync - identical string formats on both sides, no serialization surprises. Carbon is used internally for calculations, but never exposed externally. The only method returning Carbon is parse(), for internal calculations: $carbon = Rsx_Time::parse($time); // Carbon for calculations $result = Rsx_Time::to_iso($carbon); // Back to string for output CRITICAL: Type Separation Date functions THROW if passed a datetime. Datetime functions THROW if passed a date-only string. This is intentional. Mixing dates and datetimes causes bugs: - "2024-12-24" as datetime would become midnight UTC, wrong in other timezones - "2024-12-24T00:00:00Z" as date loses the time information Examples of errors: Rsx_Time::parse('2024-12-24') // THROWS: "Use Rsx_Date::parse() for dates without time components" Rsx_Date::parse('2024-12-24T15:30:00Z') // THROWS: "Use Rsx_Time::parse() for datetimes with time components" DATE VS DATETIME Use DATE when: - Due dates (the task is due on this calendar day) - Birth dates (born on this day, no time) - Anniversaries, holidays - Any value where time of day is irrelevant Use DATETIME when: - Event start/end times - Created/updated timestamps - Scheduled appointments - Any value where the exact moment matters Database columns: DATE - For date-only fields DATETIME(3) - For datetime fields (millisecond precision) RSX_DATE CLASS All functions work with "YYYY-MM-DD" format strings. Parsing & Validation parse($input) Returns "YYYY-MM-DD" string or null. THROWS on datetime input. is_date($input) Returns true if input is valid date string. Current Date today() Returns today's date as "YYYY-MM-DD" in user's timezone. PHP: $today = Rsx_Date::today(); JS: const today = Rsx_Date.today(); Formatting format($date) Display format: "Dec 24, 2025" format_iso($date) Ensures "YYYY-MM-DD" format. Comparison is_today($date) True if date is today is_past($date) True if date is before today is_future($date) True if date is after today diff_days($d1, $d2) Days between dates (positive if d2 > d1) Database to_database($date) Returns "YYYY-MM-DD" for database storage (same as ISO format). RSX_TIME CLASS All functions accept ISO 8601 datetime strings or Carbon/Date objects. All functions RETURN strings (except parse, which returns Carbon for internal calculations). Parsing & Validation parse($input) Returns Carbon (PHP) or Date (JS) in UTC. Used for internal calculations, not for output. THROWS on date-only string input. is_datetime($input) Returns true if input is valid datetime (not date-only). Current Time now() Returns current time as Carbon/Date (for calculations) now_iso() Returns current time as ISO 8601 string (preferred) now_ms() Returns current time as Unix milliseconds Timezone Handling get_user_timezone() Returns user's IANA timezone (e.g., "America/Chicago"). to_timezone($time, $tz) Convert datetime to specific timezone. to_user_timezone($time) Convert datetime to user's timezone. get_timezone_abbr($time, $tz) Get timezone abbreviation (e.g., "CST", "CDT"). DST-aware based on the actual date. Serialization to_iso($time) Returns ISO 8601 UTC string: "2024-12-24T15:30:45.123Z" to_ms($time) Returns Unix timestamp in milliseconds. to_database($time) (PHP only) Returns MySQL format: "2024-12-24 15:30:45.123" Formatting format_time($time, $tz) "3:30 PM" format_datetime($time, $tz) "Dec 24, 2024, 3:30 PM" format_datetime_with_tz($time) "Dec 24, 2024, 3:30 PM CST" format($time, $format, $tz) (PHP only) Custom PHP date format Duration & Relative diff_seconds($start, $end) Seconds between two datetimes. seconds_until($time) (JS only) Seconds until future time. seconds_since($time) (JS only) Seconds since past time. duration_to_human($seconds, $short) Long: "2 hours and 30 minutes" Short: "2h 30m" relative($time) "2 hours ago", "in 3 days", "just now" Arithmetic add($time, $seconds) Add seconds to time. Returns ISO 8601 string. subtract($time, $seconds) Subtract seconds from time. Returns ISO 8601 string. Comparison is_past($time) True if datetime is in the past is_future($time) True if datetime is in the future is_today($time) True if datetime is today (in user's timezone) Live Updates (JavaScript only) countdown($element, target_time, options) Live countdown to future time. Updates every second. const ctrl = Rsx_Time.countdown($('#timer'), deadline, { short: true, on_complete: () => alert('Done!') }); ctrl.stop(); // Stop the countdown countup($element, start_time, options) Live elapsed time since past time. Rsx_Time.countup($('.elapsed'), started_at, { short: true }); TIMEZONE INITIALIZATION User timezone is resolved in order: 1. login_users.timezone (user's preference) 2. config('rsx.datetime.default_timezone') 3. 'America/Chicago' (hardcoded fallback) Page Load On page load, window.rsxapp includes: server_time - ISO 8601 UTC timestamp from server user_timezone - IANA timezone identifier Rsx_Time._on_framework_core_init() reads these automatically. AJAX Sync Every AJAX response includes _server_time and _user_timezone. Rsx_Time.sync_from_ajax() is called automatically to: - Update user timezone if changed - Sync server time offset on first request or timezone change This corrects for client clock skew. Rsx_Time.now() returns server-adjusted time. COMPONENT EXPECTATIONS Date Picker Components val() returns "YYYY-MM-DD" or null val(value) accepts "YYYY-MM-DD" or null THROWS if passed datetime format Internal display shows localized format (e.g., "Dec 24, 2025") class Date_Picker extends Form_Input_Abstract { val(value) { if (arguments.length === 0) { return this.state.value; // "YYYY-MM-DD" or null } if (value != null && !Rsx_Date.is_date(value)) { throw new Error('Date_Picker requires YYYY-MM-DD format'); } this.state.value = value; this._update_display(); } } Datetime Picker Components val() returns ISO 8601 string or null val(value) accepts ISO 8601 string or null THROWS if passed date-only format Internal display shows localized time in user's timezone class Datetime_Picker extends Form_Input_Abstract { val(value) { if (arguments.length === 0) { return this.state.value; // ISO 8601 or null } if (value != null && !Rsx_Time.is_datetime(value)) { throw new Error('Datetime_Picker requires ISO 8601 format'); } this.state.value = value; this._update_display(); } } DATA FLOW Date Field (e.g., due_date) Database: DATE column, value "2025-12-24" | PHP Model: $task->due_date = "2025-12-24" (string) | (Rsx_Date_Cast returns YYYY-MM-DD strings, NOT Carbon) | JSON Response: {"due_date": "2025-12-24"} | JS Model: task.due_date = "2025-12-24" (string) | Date Picker: val() = "2025-12-24", display "Dec 24, 2025" | Form Submit: {"due_date": "2025-12-24"} | PHP Controller: Rsx_Date::parse($params['due_date']) | Database: "2025-12-24" Datetime Field (e.g., scheduled_at) Database: DATETIME(3), value "2025-12-24 15:30:45.123" (UTC) | PHP Model: $event->scheduled_at = "2025-12-24T15:30:45.123Z" (string) | (Rsx_DateTime_Cast converts MySQL format to ISO 8601) | JSON Serialize: {"scheduled_at": "2025-12-24T15:30:45.123Z"} | JS Model: event.scheduled_at = "2025-12-24T15:30:45.123Z" (string) | Datetime Picker: val() = ISO string, display "Dec 24, 9:30 AM CST" | User edits to "Dec 24, 10:00 AM CST" Picker converts to UTC: "2025-12-24T16:00:00.000Z" | Form Submit: {"scheduled_at": "2025-12-24T16:00:00.000Z"} | PHP Controller: Rsx_Time::parse($params['scheduled_at']) -> Carbon | (only if calculations needed; can also save string directly) | Database: "2025-12-24 16:00:00.000" EXAMPLES PHP - Date handling: $due_date = Rsx_Date::parse($params['due_date']); // "2025-12-24" if (Rsx_Date::is_past($due_date)) { return response_error(Ajax::ERROR_VALIDATION, [ 'due_date' => 'Due date cannot be in the past' ]); } $task->due_date = Rsx_Date::to_database($due_date); $task->save(); PHP - Datetime handling: $event_time = Rsx_Time::parse($params['event_time']); return [ 'id' => $event->id, 'event_time' => Rsx_Time::to_iso($event_time), 'formatted' => Rsx_Time::format_datetime_with_tz($event_time), 'is_past' => Rsx_Time::is_past($event_time), ]; JavaScript - Date display: const due = this.data.task.due_date; // "2025-12-24" this.$sid('due').text(Rsx_Date.format(due)); // "Dec 24, 2025" if (Rsx_Date.is_past(due)) { this.$sid('due').addClass('text-danger'); } JavaScript - Datetime display with countdown: const event_time = this.data.event.scheduled_at; // ISO string this.$sid('time').text(Rsx_Time.format_datetime(event_time)); this.$sid('relative').text(Rsx_Time.relative(event_time)); if (Rsx_Time.is_future(event_time)) { this._countdown = Rsx_Time.countdown( this.$sid('countdown'), event_time, { short: true, on_complete: () => this.reload() } ); } ERROR HANDLING Wrong type errors are thrown immediately: // PHP try { Rsx_Time::parse('2025-12-24'); } catch (\InvalidArgumentException $e) { // "Rsx_Time::parse() received date-only string..." } // JavaScript try { Rsx_Time.parse('2025-12-24'); } catch (e) { // "Rsx_Time.parse() received date-only string..." } These errors indicate a programming mistake - the wrong function is being used. Fix the code rather than catching the exception. CONFIGURATION system/config/rsx.php: 'datetime' => [ 'default_timezone' => env('RSX_DEFAULT_TIMEZONE', 'America/Chicago'), ], User timezone stored in login_users.timezone column. MODEL CASTING Model date/datetime columns are automatically cast to strings via custom Eloquent casts. No configuration needed - columns are detected from the database schema. Rsx_DateTime_Cast (for DATETIME/TIMESTAMP columns) - Database read: "2024-12-24 15:30:45" -> "2024-12-24T15:30:45.000Z" - Database write: "2024-12-24T15:30:45.000Z" -> "2024-12-24 15:30:45" - Accepts: ISO 8601 strings, MySQL datetime strings, Unix timestamps - Rejects: Carbon objects (use to_iso() to convert first) Rsx_Date_Cast (for DATE columns) - Database read: "2024-12-24" -> "2024-12-24" - Database write: "2024-12-24" -> "2024-12-24" - Accepts: YYYY-MM-DD strings only - Rejects: Carbon objects, datetime strings Why strings instead of Carbon? 1. Prevents timezone bugs - dates have no timezone, Carbon assumes one 2. PHP/JS parity - identical formats on both sides 3. No serialization surprises - what you see is what you get 4. Simpler mental model - just strings everywhere Overriding casts: If a model needs different behavior, override $casts in the model: protected $casts = [ 'special_date' => 'date', // Use Laravel's default ]; SEE ALSO Rsx_Time, Rsx_Date, Rsx_DateTime_Cast, Rsx_Date_Cast AUTHOR RSpade Framework RSpade December 2025 TIME(7)