Files
rspade_system/app/RSpade/upstream_changes/datetime_strings_12_24.txt
2025-12-24 23:18:11 +00:00

313 lines
12 KiB
Plaintext
Executable File

DATE/DATETIME STRING FORMAT - MIGRATION GUIDE
Date: 2024-12-24
SUMMARY
RSpade now stores and transmits dates and datetimes as strings throughout
the application, not as Carbon objects. This eliminates timezone bugs with
date columns and ensures PHP/JavaScript parity with identical formats on
both sides.
- Dates: "YYYY-MM-DD" strings (e.g., "2024-12-24")
- Datetimes: ISO 8601 UTC strings (e.g., "2024-12-24T15:30:45.123Z")
Model properties like $model->created_at now return strings, not Carbon
instances. Calling Carbon methods like ->format() or ->diffForHumans() on
these properties will cause errors. Use Rsx_Time and Rsx_Date helper classes
instead.
A code quality rule (MODEL-FETCH-DATE-01) now detects server-side date
formatting in model fetch() methods at manifest-time. Dates should be
formatted client-side using the JavaScript Rsx_Time and Rsx_Date classes.
WHY THIS CHANGE
1. Timezone Safety
Carbon objects assume a timezone, but DATE columns have no timezone.
"2024-12-24" is just a calendar day - not midnight in any timezone.
Treating it as Carbon caused subtle bugs when serializing to JSON.
2. PHP/JavaScript Parity
With string-based dates, both PHP and JavaScript see identical values.
No serialization surprises, no format mismatches.
3. Client-Side Formatting
Formatting should happen in the browser where the user's locale and
timezone are known. Server-side formatting creates implicit API
contracts that confuse frontend developers.
AFFECTED CODE PATTERNS
1. Model Property Access
-------------------------------------------------------------------------
Model datetime/date properties now return strings, not Carbon.
Before (BROKEN):
$user->created_at->format('M d, Y');
$user->created_at->diffForHumans();
$project->due_date->format('Y-m-d');
After (CORRECT):
use App\RSpade\Core\Time\Rsx_Time;
use App\RSpade\Core\Time\Rsx_Date;
Rsx_Time::format_date($user->created_at); // "Dec 24, 2024"
Rsx_Time::relative($user->created_at); // "2 hours ago"
Rsx_Date::format($project->due_date); // "Dec 24, 2024"
2. Formatted Fields in fetch() Methods
-------------------------------------------------------------------------
Model fetch() methods should NOT return pre-formatted date fields.
The MODEL-FETCH-DATE-01 rule now blocks this at manifest-time.
Before (BLOCKED BY CODE QUALITY RULE):
public static function fetch($id) {
$data = $model->toArray();
$data['created_at_formatted'] = $model->created_at->format('M d, Y');
$data['created_at_human'] = $model->created_at->diffForHumans();
return $data;
}
After (CORRECT):
public static function fetch($id) {
$data = $model->toArray();
// created_at is already included as ISO string from toArray()
// DO NOT add formatted versions - format client-side
return $data;
}
3. Controller Ajax Responses
-------------------------------------------------------------------------
Controllers returning date data should pass raw values, not formatted.
Before:
return [
'created_at_formatted' => $user->created_at->format('F j, Y'),
'updated_at_human' => $user->updated_at->diffForHumans(),
];
After:
return [
'created_at' => $user->created_at, // ISO string automatically
'updated_at' => $user->updated_at,
];
4. Carbon::parse() in PHP
-------------------------------------------------------------------------
If you need Carbon for calculations, use Rsx_Time::parse() instead.
It returns Carbon internally but validates the input format.
Before:
$carbon = \Carbon\Carbon::parse($project->start_date);
$days_until = $carbon->diffInDays(now());
After:
$carbon = Rsx_Time::parse($user->created_at); // For datetimes
$days_until = $carbon->diffInDays(Rsx_Time::now());
// For date comparisons:
$days = Rsx_Date::diff_days($project->start_date, Rsx_Date::today());
CLIENT-SIDE FORMATTING
JavaScript templates should format dates using Rsx_Time and Rsx_Date:
1. Datetime Formatting (Rsx_Time)
-------------------------------------------------------------------------
For datetime columns (created_at, updated_at, timestamps):
Template (jqhtml):
<%-- Display formatted date --%>
<div><%= Rsx_Time.format_date(this.data.user.created_at) %></div>
<%-- Display with time --%>
<div><%= Rsx_Time.format_datetime(this.data.event.start_time) %></div>
<%-- Display with timezone --%>
<div><%= Rsx_Time.format_datetime_with_tz(this.data.event.start_time) %></div>
<%-- Relative time (e.g., "2 hours ago") --%>
<small><%= Rsx_Time.relative(this.data.user.created_at) %></small>
2. Date Formatting (Rsx_Date)
-------------------------------------------------------------------------
For date-only columns (due_date, birth_date, start_date):
Template (jqhtml):
<%-- Format date --%>
<div><%= Rsx_Date.format(this.data.project.due_date) %></div>
<%-- Check if past due --%>
<% if (Rsx_Date.is_past(this.data.project.due_date)) { %>
<span class="text-danger">Overdue</span>
<% } %>
3. Handling Null Values
-------------------------------------------------------------------------
Check for null before formatting:
<div><%= this.data.user.last_login_at
? Rsx_Time.format_datetime(this.data.user.last_login_at)
: 'Never' %></div>
MIGRATION STEPS
Step 1: Find Carbon method calls on model properties
-------------------------------------------------------------------------
Search for ->format( and ->diffForHumans( on model properties:
grep -rn "->format\s*(" rsx/ --include="*.php" | grep -v "Rsx_Time"
grep -rn "->diffForHumans" rsx/ --include="*.php"
Step 2: Find formatted date fields in fetch() methods
-------------------------------------------------------------------------
Search for _formatted and _human field assignments:
grep -rn "_formatted\s*=" rsx/models/ --include="*.php"
grep -rn "_human\s*=" rsx/models/ --include="*.php"
Step 3: Find Carbon::parse() calls
-------------------------------------------------------------------------
These may need conversion to Rsx_Time::parse() or Rsx_Date::parse():
grep -rn "Carbon::parse" rsx/ --include="*.php"
Step 4: Find template usages of _formatted fields
-------------------------------------------------------------------------
Templates referencing removed fields need updating:
grep -rn "_formatted\|_human" rsx/app/ --include="*.jqhtml"
grep -rn "_formatted\|_human" rsx/app/ --include="*.js"
Step 5: Update JavaScript defaults
-------------------------------------------------------------------------
JS files with this.data defaults for _formatted fields:
grep -rn "created_at_formatted\|updated_at_formatted" rsx/ --include="*.js"
EXAMPLE MIGRATION
Complete example: Converting a view action
PHP Controller (before):
return [
'id' => $user->id,
'name' => $user->name,
'created_at_formatted' => $user->created_at->format('F j, Y'),
];
PHP Controller (after):
return [
'id' => $user->id,
'name' => $user->name,
'created_at' => $user->created_at, // Already ISO string
];
JavaScript (before):
on_create() {
this.data.user = {
id: null,
name: '',
created_at_formatted: '',
};
}
JavaScript (after):
on_create() {
this.data.user = {
id: null,
name: '',
created_at: null,
};
}
Template (before):
<div><%= this.data.user.created_at_formatted %></div>
Template (after):
<div><%= Rsx_Time.format_date(this.data.user.created_at) %></div>
CUSTOM ELOQUENT CASTS
The framework uses custom Eloquent casts to ensure string-based values:
- Rsx_DateTime_Cast: DATETIME/TIMESTAMP columns return ISO 8601 strings
- Rsx_Date_Cast: DATE columns return YYYY-MM-DD strings
These casts are automatically applied via Rsx_Model_Abstract::getCasts().
You do not need to configure anything - model properties automatically
return strings.
If a model needs Laravel's default Carbon behavior for a specific column,
override $casts in the model:
protected $casts = [
'special_timestamp' => 'datetime', // Laravel default (Carbon)
];
AVAILABLE HELPER METHODS
Rsx_Time (for datetimes)
-------------------------------------------------------------------------
PHP:
Rsx_Time::now() // Current time as Carbon
Rsx_Time::now_iso() // Current time as ISO string
Rsx_Time::parse($input) // Parse to Carbon (for calculations)
Rsx_Time::to_iso($time) // Convert to ISO string
Rsx_Time::format_date($time) // "Dec 24, 2024"
Rsx_Time::format_datetime($time) // "Dec 24, 2024 3:30 PM"
Rsx_Time::relative($time) // "2 hours ago"
Rsx_Time::add($time, $seconds) // Add seconds, return ISO string
Rsx_Time::is_past($time) // Boolean
Rsx_Time::is_future($time) // Boolean
JavaScript:
Rsx_Time.now() // Current time as Date
Rsx_Time.now_iso() // Current time as ISO string
Rsx_Time.format_date(iso) // "Dec 24, 2024"
Rsx_Time.format_datetime(iso) // "Dec 24, 2024 3:30 PM"
Rsx_Time.format_datetime_with_tz(iso) // "Dec 24, 2024 3:30 PM CST"
Rsx_Time.relative(iso) // "2 hours ago"
Rsx_Time.countdown($el, target) // Live countdown
Rsx_Time.countup($el, start) // Live elapsed time
Rsx_Date (for dates)
-------------------------------------------------------------------------
PHP:
Rsx_Date::today() // Today as "YYYY-MM-DD"
Rsx_Date::parse($input) // Validate and normalize
Rsx_Date::format($date) // "Dec 24, 2024"
Rsx_Date::is_past($date) // Boolean
Rsx_Date::is_future($date) // Boolean
Rsx_Date::diff_days($d1, $d2) // Days between dates
JavaScript:
Rsx_Date.today() // Today as "YYYY-MM-DD"
Rsx_Date.format(date) // "Dec 24, 2024"
Rsx_Date.is_past(date) // Boolean
Rsx_Date.is_future(date) // Boolean
Rsx_Date.diff_days(d1, d2) // Days between dates
VERIFICATION
1. Run code quality check:
php artisan rsx:check
The MODEL-FETCH-DATE-01 rule will detect any remaining server-side
date formatting in fetch() methods.
2. Search for remaining Carbon method calls:
grep -rn "->format\s*(" rsx/ --include="*.php" | grep -v vendor
3. Test pages that display dates:
- Verify dates render correctly
- Check relative times update properly
- Confirm timezone handling is correct
REFERENCE
php artisan rsx:man time - Complete time/date API documentation
Rsx_Time.php, Rsx_Date.php - PHP implementation
Rsx_Time.js, Rsx_Date.js - JavaScript implementation
Rsx_DateTime_Cast.php - Custom Eloquent cast for datetimes
Rsx_Date_Cast.php - Custom Eloquent cast for dates