Carbon (Date/Time)
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 powerful date/time utility inspired by PHP's Carbon library. Carbon provides an expressive, fluent API for creating, formatting, and manipulating dates and times.
// 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#
// 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());
// Specific dates
final today = Carbon.today(); // Today at 00:00:00
final tomorrow = Carbon.tomorrow();
final yesterday = Carbon.yesterday();
Formatting#
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#
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#
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#
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#
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#
final start = Carbon.parse('2024-01-15');
final end = Carbon.parse('2024-01-20');
start.diff(end).inDays; // 5
start.diff(end).inHours; // 120
start.diff(end).inMinutes; // 7200
Human Readable#
The diffForHumans() method returns a human-readable string:
Carbon.now().subMinutes(5).diffForHumans(); // "5 minutes ago"
Carbon.now().subHours(2).diffForHumans(); // "2 hours ago"
Carbon.now().subDays(1).diffForHumans(); // "1 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 1 hour"
Relative To Another Date#
final date1 = Carbon.parse('2024-01-15');
final date2 = Carbon.parse('2024-01-20');
date1.diffForHumans(date2); // "5 days before"
date2.diffForHumans(date1); // "5 days after"
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:
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:
{
"time": {
"units_short": { "minute": ":count dk" },
"ago": ":time önce"
}
}
// 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:
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.
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 seamlessly with Eloquent models through attribute casting:
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#
// 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:
class User extends Model with HasTimestamps {
// Automatically available:
// Carbon? get createdAt
// Carbon? get updatedAt
}
// Usage
WText('Member since ${user.createdAt?.format('MMM yyyy')}');