Tighten CLAUDE.dist.md for LLM audience - 15% size reduction Add Repeater_Simple_Input component for managing lists of simple values Add Polymorphic_Field_Helper for JSON-encoded polymorphic form fields Fix incorrect data-sid selector in route-debug help example Fix Form_Utils to use component.$sid() instead of data-sid selector Add response helper functions and use _message as reserved metadata key 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
384 lines
13 KiB
Plaintext
Executable File
384 lines
13 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);
|
|
$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")
|
|
|
|
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 work with ISO 8601 datetime strings or Carbon/Date objects.
|
|
|
|
Parsing & Validation
|
|
parse($input)
|
|
Returns Carbon (PHP) or Date (JS) in UTC.
|
|
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 (UTC)
|
|
now_iso() Returns current time as ISO 8601 string
|
|
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.
|
|
|
|
subtract($time, $seconds)
|
|
Subtract seconds from time.
|
|
|
|
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)
|
|
|
|
|
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 = Carbon instance (UTC)
|
|
|
|
|
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
|
|
|
|
|
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.
|
|
|
|
SEE ALSO
|
|
Reference document: /var/www/html/date_vs_datetime_refactor.md
|
|
|
|
AUTHOR
|
|
RSpade Framework
|
|
|
|
RSpade December 2025 TIME(7)
|