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.

Prerequisites

What You’ll Learn

  • How Carbon/CarbonImmutable behaves inside Ormed models
  • How timezone handling affects timestamps and persistence
  • How to avoid mutation bugs in date-time workflows

Step Outcome

By the end of this page, you should be able to:

  • Use immutable date-time flows safely in model logic
  • Avoid accidental in-place date mutations
  • Keep storage/display timezone behavior consistent
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 ✓
}

Default to immutable date handling in app code; convert to mutable only in narrow, controlled utility flows.

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

Verify Date/Time Behavior

  1. Assert timestamp values round-trip correctly through insert/query.
  2. Assert TimestampsTZ paths store/retrieve UTC values.
  3. Add a test that catches accidental mutable Carbon side effects.

Read This Next