Date and Time Handling
Ormed provides robust support for date and time handling, integrating the standard Dart DateTime with the Carbonized library for a fluent, Laravel-like experience.
The carbonized package is automatically exported by package:ormed/ormed.dart, so you don't need to add it to your pubspec.yaml or import it separately.
DateTime vs Carbon
While you can use standard DateTime objects for all your model fields, Ormed's built-in features like Timestamps and Soft Deletes return CarbonInterface instances.
Why Carbon?
Carbon extends DateTime functionality with:
- Fluent manipulation:
date.addDays(5).subHours(2) - Human-readable differences:
date.diffForHumans() - Easy formatting:
date.format('Y-m-d') - Timezone management:
date.toUtc(),date.setTimezone('America/New_York')
Mutable vs Immutable Carbon
Carbon provides two variants with very different behavior. Understanding this distinction is critical to avoid subtle bugs.
Carbon (Mutable)
The Carbon class is mutable - methods like subDay(), addDays(), etc. modify the instance in-place and return this:
// ⚠️ WARNING: Carbon (mutable) mutates in-place!
void mutableCarbonPitfall() {
final date = Carbon.parse('2024-12-21');
print(date.toDateString()); // 2024-12-21
final yesterday = date.subDay(); // Mutates `date` in-place!
print(date.toDateString()); // 2024-12-20 (unexpected!)
print(identical(date, yesterday)); // true - same object!
}
CarbonImmutable (Safe)
The CarbonImmutable class is immutable - methods return new instances, leaving the original unchanged:
// ✅ CarbonImmutable is safe - methods return new instances
void immutableCarbonSafe() {
final date = CarbonImmutable.parse('2024-12-21');
print(date.toDateString()); // 2024-12-21
final yesterday = date.subDay(); // Returns NEW instance
print(date.toDateString()); // 2024-12-21 (unchanged!)
print(yesterday.toDateString()); // 2024-12-20
}
Ormed Returns Immutable Timestamps
All Ormed timestamp getters (createdAt, updatedAt, deletedAt) return immutable Carbon instances. This design choice prevents accidental mutation of model state:
// Ormed timestamp getters return immutable Carbon instances
void ormedTimestampsAreImmutable() {
// When you access createdAt/updatedAt/deletedAt from a model,
// Ormed returns immutable instances so you can safely chain methods:
// final user = await repo.find(1);
// final createdAt = user.createdAt; // CarbonImmutable
//
// // Safe to chain - original is never mutated
// final yesterday = createdAt.subDay();
// final isRecent = createdAt.isAfter(Carbon.now().subDays(7));
//
// print(createdAt); // Still the original value ✓
}
Converting Between Mutable and Immutable
// Converting between mutable and immutable
void convertingCarbon() {
// Mutable → Immutable
final mutable = Carbon.now();
final immutable = mutable.toImmutable();
// Immutable → Mutable (creates a copy)
final backToMutable = immutable.toMutable();
// Create a copy of mutable Carbon
final copy = mutable.copy();
copy.subDay(); // Only affects the copy
}
Configuration
You can configure global Carbon settings using CarbonConfig. This is typically done during application bootstrap.
import 'package:ormed/ormed.dart';
void main() async {
// Basic configuration
CarbonConfig.configure(
defaultTimezone: 'UTC',
defaultLocale: 'en_US',
);
// For named timezone support (e.g., 'Europe/London'),
// you must initialize TimeMachine data:
await CarbonConfig.configureWithTimeMachine(
defaultTimezone: 'America/New_York',
);
}
Timestamps and Soft Deletes
When using the Timestamps or SoftDeletes mixins, the generated fields (createdAt, updatedAt, deletedAt) provide a flexible API.
Getters
The getters return CarbonInterface?, allowing you to immediately use fluent methods:
final user = await repo.find(1);
print(user?.createdAt?.diffForHumans());
Setters
The setters are typed as Object? and accept:
DateTimeCarbonInterface(includingCarboninstances)null
user.updatedAt = DateTime.now();
user.updatedAt = Carbon.now().addHours(1);
Timezones
TimestampsTZ and SoftDeletesTZ
If you use the TZ variants of the mixins (TimestampsTZ, SoftDeletesTZ), Ormed ensures that:
- Values are converted to UTC before being sent to the database.
- Values retrieved from the database are treated as UTC.
Driver Support
Different database drivers handle timezones differently:
- PostgreSQL: Supports
TIMESTAMP WITH TIME ZONE. - MySQL:
DATETIMEcolumns are typically timezone-naive. Ormed formats these as strings. - SQLite: Stored as ISO-8601 strings or integers.
It is highly recommended to store all timestamps in UTC and convert to the user's local timezone only for display.
Custom Date Fields
If you define your own DateTime fields in a model, they will use the DateTimeCodec by default. This codec automatically handles Carbon instances during encoding and decoding.
()
class Event extends Model<Event> {
final DateTime scheduledAt;
Event({required this.scheduledAt});
}
// Usage
final event = $Event(scheduledAt: Carbon.tomorrow());
await repo.insert(event);
Best Practices
// Best practices for Carbon in Ormed
void carbonBestPractices() {
// 1. Prefer CarbonImmutable for local variables
final date = CarbonImmutable.now();
// 2. Use toImmutable() when you need to store a reference
final stored = Carbon.now().toImmutable();
// 3. Model timestamps are already immutable - just use them
// final yesterday = user.createdAt?.subDay(); // Safe!
// 4. Use copy() if you need to mutate a mutable Carbon
final mutable = Carbon.now();
final safeCopy = mutable.copy();
safeCopy.addDays(5); // Only copy is affected
}
Summary
| Situation | Recommendation |
|---|---|
Model timestamps (createdAt, etc.) | Already immutable - just use them |
| Local date variables | Use CarbonImmutable.now() |
| Need to mutate a date | Use copy() first, or toMutable() |
| Storing a date reference | Call toImmutable() |
| Passing dates to functions | Prefer CarbonInterface parameter type |