Files
rspade_system/app/RSpade/man/time.txt
root f70ca09f78 Framework updates
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-12 17:25:07 +00:00

499 lines
18 KiB
Plaintext
Executable File

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);
$relative = Rsx_Date::relative($date);
$is_past = Rsx_Date::is_past($date);
$next_week = Rsx_Date::add_days($date, 7);
$monday = Rsx_Date::start_of_week($date);
$month_name = Rsx_Date::month_human($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 relative = Rsx_Date.relative(date);
const is_past = Rsx_Date.is_past(date);
const next_week = Rsx_Date.add_days(date, 7);
const monday = Rsx_Date.start_of_week(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();
now()
Alias for today(). Provided for consistency with Rsx_Time::now().
Formatting
format($date)
Display format: "Dec 24, 2025"
format_iso($date)
Ensures "YYYY-MM-DD" format.
relative($date)
Human-readable relative date.
"Today", "Yesterday", "Tomorrow", "3 days ago", "in 5 days"
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)
Arithmetic
add_days($date, $days)
Add (or subtract with negative) days from date.
Returns "YYYY-MM-DD" or null if invalid input.
Week/Month Boundaries
start_of_week($date) Monday of the week containing date
end_of_week($date) Sunday of the week containing date
start_of_month($date) First day of month (YYYY-MM-01)
end_of_month($date) Last day of month
is_weekend($date) True if Saturday or Sunday
is_weekday($date) True if Monday-Friday
Component Extractors
All extractors return null for invalid input.
day($date) Day of month (1-31)
dow($date) Day of week integer (0=Sunday, 6=Saturday)
dow_human($date) Full day name ("Monday", "Tuesday", etc.)
dow_short($date) Short day name ("Mon", "Tue", etc.)
month($date) Month number (1-12)
month_human($date) Full month name ("January", "February")
month_human_short($date) Short month name ("Jan", "Feb")
year($date) Four-digit year (e.g., 2025)
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)
Seconds until future time (negative if in past).
seconds_since($time)
Seconds since past time (negative if in future).
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)
Component Extractors
Extract date/time components from a datetime. All require timezone
parameter ($tz) or use user's timezone if omitted.
Return null for invalid input.
day($time, $tz) Day of month (1-31)
dow($time, $tz) Day of week integer (0=Sunday, 6=Saturday)
dow_human($time, $tz) Full day name ("Monday", "Tuesday", etc.)
dow_short($time, $tz) Short day name ("Mon", "Tue", etc.)
month($time, $tz) Month number (1-12)
month_human($time, $tz) Full month name ("January", "February")
month_human_short($time, $tz) Short month name ("Jan", "Feb")
year($time, $tz) Four-digit year (e.g., 2025)
hour($time, $tz) Hour (0-23)
minute($time, $tz) Minute (0-59)
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. sites.timezone (site's default timezone)
3. config('rsx.datetime.default_timezone')
4. 'America/Chicago' (hardcoded fallback)
Timezone is cached per-request and auto-invalidates when user or site
changes (e.g., after login/logout or site switch).
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.
Site timezone stored in sites.timezone column (default: 'America/Chicago').
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 January 2026 TIME(7)