Skip to content

Carbon (Date/Time)

View raw .md

Carbon is Magic's date and time utility, inspired by PHP's Carbon library, offering a fluent API for creating, formatting, manipulating, and comparing date values with human-readable output.

On this page

Introduction#

Magic includes Carbon, a date/time utility inspired by PHP's Carbon library. Carbon provides a fluent API for creating, formatting, and manipulating dates and times.

dart
// Create, manipulate, and format dates fluently
Carbon.now().addDays(5).format('MMMM d, yyyy');  // "January 20, 2024"

// Human-readable differences
carbon.diffForHumans();  // "2 days ago"

Creating Carbon Instances#

dart
// Current date/time
final now = Carbon.now();

// Parse from string
final date = Carbon.parse('2024-01-15');
final datetime = Carbon.parse('2024-01-15 14:30:00');
final iso = Carbon.parse('2024-01-15T14:30:00Z');

// From DateTime
final fromDart = Carbon.fromDateTime(DateTime.now());

// From explicit parts (unset parts default to 1970-01-01 00:00:00)
final created = Carbon.create(year: 2024, month: 1, day: 15, hour: 14, minute: 30);

// Relative dates
final today = Carbon.now().startOfDay();   // Today at 00:00:00
final tomorrow = Carbon.now().addDay();
final yesterday = Carbon.now().subDay();

// From a DateTime, through the extension
final converted = DateTime.now().toCarbon();

Formatting#

dart
final date = Carbon.now();

// Standard formats
date.format('yyyy-MM-dd');           // "2024-01-15"
date.format('dd/MM/yyyy');           // "15/01/2024"
date.format('MMMM d, yyyy');         // "January 15, 2024"
date.format('EEEE');                 // "Monday"
date.format('h:mm a');               // "2:30 PM"

// ISO 8601
date.toIso8601String();              // "2024-01-15T14:30:00.000Z"

// Convenience methods
date.toDateString();                 // "2024-01-15"
date.toTimeString();                 // "14:30:00"
date.toDateTimeString();             // "2024-01-15 14:30:00"

Format Tokens#

Token Output Description
yyyy 2024 4-digit year
yy 24 2-digit year
MMMM January Full month name
MMM Jan Abbreviated month
MM 01 2-digit month
dd 15 2-digit day
d 15 Day of month
EEEE Monday Full weekday name
EEE Mon Abbreviated weekday
HH 14 24-hour hour
hh 02 12-hour hour
mm 30 Minutes
ss 00 Seconds
a PM AM/PM

Manipulation#

Adding Time#

dart
final date = Carbon.now();

date.addSeconds(30);
date.addMinutes(15);
date.addHours(2);
date.addDays(5);
date.addWeeks(2);
date.addMonths(3);
date.addYears(1);

Subtracting Time#

dart
date.subSeconds(30);
date.subMinutes(15);
date.subHours(2);
date.subDays(5);
date.subWeeks(2);
date.subMonths(3);
date.subYears(1);

Start/End of Period#

dart
date.startOfDay();     // 00:00:00
date.endOfDay();       // 23:59:59
date.startOfWeek();    // Monday 00:00:00
date.endOfWeek();      // Sunday 23:59:59
date.startOfMonth();   // First day 00:00:00
date.endOfMonth();     // Last day 23:59:59

Comparison#

dart
final date1 = Carbon.parse('2024-01-15');
final date2 = Carbon.parse('2024-01-20');

// Boolean comparisons
date1.isBefore(date2);    // true
date1.isAfter(date2);     // false
date1.isSame(date2);      // false

// Special checks
date.isToday();
date.isTomorrow();
date.isYesterday();
date.isWeekend();
date.isWeekday();
date.isFuture();
date.isPast();

// Between check
date.isBetween(startDate, endDate);

Difference#

dart
final start = Carbon.parse('2024-01-15');
final end = Carbon.parse('2024-01-20');

start.diffInDays(end);      // -5
end.diffInDays(start);      // 5
start.diffInHours(end);     // -120
start.diffInMinutes(end);   // -7200

diffInSeconds, diffInMonths and diffInYears follow the same shape. Each answers this minus other, truncated to whole units, so the result is negative when this is the earlier date.

Human Readable#

The diffForHumans() method returns a human-readable string:

dart
Carbon.now().subMinutes(5).diffForHumans();   // "5 minutes ago"
Carbon.now().subHours(2).diffForHumans();     // "2 hours ago"
Carbon.now().subDays(1).diffForHumans();      // "a day ago"
Carbon.now().subMonths(3).diffForHumans();    // "3 months ago"

// Future dates
Carbon.now().addDays(3).diffForHumans();      // "in 3 days"
Carbon.now().addHours(1).diffForHumans();     // "in an hour"

The wording and the singular forms ("a day", "an hour") come from the jiffy package's English locale.

Relative To Another Date#

dart
final date1 = Carbon.parse('2024-01-15');
final date2 = Carbon.parse('2024-01-20');

date1.diffForHumans(date2);  // "5 days ago"
date2.diffForHumans(date1);  // "in 5 days"

Short Human Readable#

shortDiffForHumans([other]) is a compact ladder (seconds, minutes, hours, days, weeks, months, years, each threshold exclusive of the next) for dense tables and list rows where diffForHumans() reads too wide. It measures against Carbon's clock (frozen while Carbon.setTestNow is set), or against other when given:

dart
createdAt.shortDiffForHumans();  // "14m ago"
dueAt.shortDiffForHumans();      // "5m from now"

A gap under one second reads "Just now"; a future instant reads ":time from now" instead of ":time ago". Months are truncated 30-day buckets, so a 45-day gap reads "1mo ago", not "1 month, 15 days ago".

Every unit and wrapper resolves through the Lang catalogue when present (time.units_short.<unit>, time.ago, time.from_now, time.just_now), falling back to the English literal (:counts, :countm, :counth, :countd, :countw, :countmo, :county, :time ago, :time from now, Just now) when no catalogue is loaded or the key is missing:

json
{
  "time": {
    "units_short": { "minute": ":count dk" },
    "ago": ":time önce"
  }
}
dart
// With the catalogue above loaded for 'tr':
event.shortDiffForHumans();  // "14 dk önce"

Timezone Support#

When localization.auto_detect_timezone is enabled (it defaults to false), Magic resolves the device's real IANA timezone identifier on boot through the platform's native APIs, via the flutter_timezone package. The resolved identifier is what DateManager.instance.timezoneName returns, and the LocalizationInterceptor sends it as the X-Timezone header on every outgoing request. When detection cannot resolve a valid zone, the configured localization.timezone value stays in place; Magic does not guess a zone from the UTC offset, because an offset does not identify a zone (Istanbul and Kyiv share a winter offset but differ in DST rules).

Carbon instances do NOT pick this zone up implicitly. A Carbon built without an explicit zone uses the system local time, so the detected identifier is there for the backend to interpret timestamps with, not a global conversion applied to every instance. To work in a specific zone, pass it explicitly:

dart
Carbon.now('Europe/Istanbul');           // build in a zone
Carbon.now().setTimezone('America/New_York'); // convert an existing instance

Testing#

Carbon.setTestNow([testNow]) freezes the clock Carbon reads, mirroring Laravel's Carbon::setTestNow(). Call it with no argument (or null) to clear the freeze; Carbon.hasTestNow() reports whether one is currently set. The freeze is static state, so clear it in tearDown or it leaks into the next test.

dart
setUp(() {
  Carbon.setTestNow(Carbon.create(year: 2024, month: 1, day: 15, hour: 10));
});

tearDown(() {
  Carbon.setTestNow(); // never leak a frozen clock into the next test
});

test('a rule that reads "now"', () {
  expect(Carbon.now().toDateString(), '2024-01-15');
});

Every read of "now" honours the freeze: Carbon.now(), isToday(), isYesterday(), isTomorrow(), isFuture(), isPast(), and diffForHumans() without an argument. Carbon.now(timezone) still converts the frozen instant into the requested zone. Constructors that take an explicit moment (Carbon.parse(), Carbon.fromDateTime(), Carbon.create()) are unaffected.

Model Integration#

Carbon integrates with Eloquent models through attribute casting:

dart
class Post extends Model with HasTimestamps {
  @override
  Map<String, String> get casts => const {
    'published_at': 'datetime',
    'expires_at': 'datetime',
  };

  // Typed getter
  Carbon? get publishedAt => getAttribute('published_at') as Carbon?;
  Carbon? get expiresAt => getAttribute('expires_at') as Carbon?;
}

Using in Views#

dart
// Display formatted date
WText(post.publishedAt?.format('MMMM d, yyyy') ?? 'Draft')

// Human-readable
WText(post.publishedAt?.diffForHumans() ?? '')  // "2 days ago"

// Conditional display
if (post.expiresAt?.isFuture() == true) {
  WText('Expires ${post.expiresAt!.diffForHumans()}');
}

HasTimestamps Mixin#

Models using HasTimestamps automatically get createdAt and updatedAt as Carbon instances:

dart
class User extends Model with HasTimestamps {
  // Automatically available:
  // Carbon? get createdAt
  // Carbon? get updatedAt
}

// Usage
WText('Member since ${user.createdAt?.format('MMM yyyy')}');
esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.