Skip to main content

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.

info

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

Important

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:

  • DateTime
  • CarbonInterface (including Carbon instances)
  • 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:

  1. Values are converted to UTC before being sent to the database.
  2. Values retrieved from the database are treated as UTC.

Driver Support

Different database drivers handle timezones differently:

  • PostgreSQL: Supports TIMESTAMP WITH TIME ZONE.
  • MySQL: DATETIME columns 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

SituationRecommendation
Model timestamps (createdAt, etc.)Already immutable - just use them
Local date variablesUse CarbonImmutable.now()
Need to mutate a dateUse copy() first, or toMutable()
Storing a date referenceCall toImmutable()
Passing dates to functionsPrefer CarbonInterface parameter type