Date, Time & Duration
The DateTime Class
Dart’s DateTime class is the foundation for working with dates and times. It represents a point in time, either in UTC or the local time zone. Understanding DateTime is essential for scheduling, logging, data processing, and any time-sensitive application.
Creating DateTime Objects
void main() {
// Current date and time
final now = DateTime.now();
print(now); // 2024-03-15 14:30:00.000
// Specific date and time
final specific = DateTime(2024, 3, 15, 14, 30, 0);
print(specific); // 2024-03-15 14:30:00.000
// UTC time
final utc = DateTime.utc(2024, 3, 15, 14, 30);
print(utc); // 2024-03-15 14:30:00.000Z
print(utc.isUtc); // true
// Only date (time defaults to 00:00:00)
final dateOnly = DateTime(2024, 3, 15);
print(dateOnly); // 2024-03-15 00:00:00.000
// Accessing components
print('Year: ${now.year}');
print('Month: ${now.month}'); // 1-12
print('Day: ${now.day}'); // 1-31
print('Hour: ${now.hour}'); // 0-23
print('Minute: ${now.minute}'); // 0-59
print('Second: ${now.second}'); // 0-59
print('Millisecond: ${now.millisecond}');
print('Microsecond: ${now.microsecond}');
print('Weekday: ${now.weekday}'); // 1=Monday ... 7=Sunday
}
DateTime months are 1-based (January = 1, December = 12) and weekdays follow ISO 8601 (Monday = 1, Sunday = 7). This differs from some other languages where months are 0-based or Sunday = 0.Parsing Dates with DateTime.parse
The DateTime.parse and DateTime.tryParse methods convert strings into DateTime objects. They accept a subset of ISO 8601 formats.
Parsing Date Strings
void main() {
// Standard ISO 8601 formats
final d1 = DateTime.parse('2024-03-15');
print(d1); // 2024-03-15 00:00:00.000
final d2 = DateTime.parse('2024-03-15 14:30:00');
print(d2); // 2024-03-15 14:30:00.000
final d3 = DateTime.parse('2024-03-15T14:30:00Z'); // UTC
print(d3); // 2024-03-15 14:30:00.000Z
print(d3.isUtc); // true
final d4 = DateTime.parse('2024-03-15T14:30:00+05:00'); // With offset
print(d4.toUtc()); // 2024-03-15 09:30:00.000Z
// Safe parsing with tryParse
final valid = DateTime.tryParse('2024-03-15');
final invalid = DateTime.tryParse('not-a-date');
print(valid); // 2024-03-15 00:00:00.000
print(invalid); // null
// IMPORTANT: DateTime.parse does NOT handle formats like '03/15/2024'
// For custom formats, use the intl package (shown later)
}
UTC vs Local Time
Understanding the difference between UTC and local time is crucial for applications that deal with users in different time zones, server logs, or API communications.
UTC and Local Time Conversion
void main() {
// Local time
final local = DateTime.now();
print('Local: $local');
print('Is UTC: ${local.isUtc}'); // false
print('Timezone: ${local.timeZoneName}'); // e.g., AST, EST, PST
print('Offset: ${local.timeZoneOffset}'); // e.g., 3:00:00.000000
// Convert local to UTC
final utc = local.toUtc();
print('UTC: $utc');
print('Is UTC: ${utc.isUtc}'); // true
// Convert UTC back to local
final backToLocal = utc.toLocal();
print('Back to local: $backToLocal');
// Create UTC directly
final utcDirect = DateTime.utc(2024, 3, 15, 12, 0);
print(utcDirect); // 2024-03-15 12:00:00.000Z
print(utcDirect.toLocal()); // Depends on local timezone
// millisecondsSinceEpoch — universal timestamp
final epoch = local.millisecondsSinceEpoch;
print('Epoch ms: $epoch');
// Reconstruct from epoch
final fromEpoch = DateTime.fromMillisecondsSinceEpoch(epoch);
print('From epoch: $fromEpoch');
final fromEpochUtc = DateTime.fromMillisecondsSinceEpoch(epoch, isUtc: true);
print('From epoch (UTC): $fromEpochUtc');
}
The Duration Class
The Duration class represents a span of time. It is used for date arithmetic, timeouts, delays, and measuring elapsed time.
Working with Duration
void main() {
// Creating Duration objects
final fiveMinutes = Duration(minutes: 5);
final oneHour = Duration(hours: 1);
final complex = Duration(hours: 2, minutes: 30, seconds: 15);
print(fiveMinutes); // 0:05:00.000000
print(oneHour); // 1:00:00.000000
print(complex); // 2:30:15.000000
// Accessing components
print('In minutes: ${complex.inMinutes}'); // 150
print('In seconds: ${complex.inSeconds}'); // 9015
print('In milliseconds: ${complex.inMilliseconds}'); // 9015000
// Duration arithmetic
final total = fiveMinutes + oneHour;
print('Total: $total'); // 1:05:00.000000
final difference = oneHour - fiveMinutes;
print('Difference: $difference'); // 0:55:00.000000
final doubled = fiveMinutes * 2;
print('Doubled: $doubled'); // 0:10:00.000000
// Negative durations
final negative = Duration(hours: -2);
print(negative); // -2:00:00.000000
print(negative.isNegative); // true
print(negative.abs()); // 2:00:00.000000
// Comparison
print(fiveMinutes < oneHour); // true
print(fiveMinutes > oneHour); // false
print(fiveMinutes == Duration(seconds: 300)); // true
}
Date Arithmetic
You can add or subtract Duration from DateTime to calculate future or past dates. For calendar-aware operations (like adding months), you need manual calculations.
Adding and Subtracting Time
void main() {
final now = DateTime(2024, 3, 15, 10, 0);
// Add duration
final inTwoHours = now.add(Duration(hours: 2));
print(inTwoHours); // 2024-03-15 12:00:00.000
final tomorrow = now.add(Duration(days: 1));
print(tomorrow); // 2024-03-16 10:00:00.000
final nextWeek = now.add(Duration(days: 7));
print(nextWeek); // 2024-03-22 10:00:00.000
// Subtract duration
final yesterday = now.subtract(Duration(days: 1));
print(yesterday); // 2024-03-14 10:00:00.000
// Difference between two dates
final start = DateTime(2024, 1, 1);
final end = DateTime(2024, 12, 31);
final diff = end.difference(start);
print('Days between: ${diff.inDays}'); // 365
// Adding months (calendar-aware — must be done manually)
DateTime addMonths(DateTime date, int months) {
var year = date.year;
var month = date.month + months;
while (month > 12) {
month -= 12;
year++;
}
while (month < 1) {
month += 12;
year--;
}
// Clamp day to valid range for the target month
final maxDay = DateTime(year, month + 1, 0).day;
final day = date.day > maxDay ? maxDay : date.day;
return DateTime(year, month, day, date.hour, date.minute, date.second);
}
final jan31 = DateTime(2024, 1, 31);
print(addMonths(jan31, 1)); // 2024-02-29 (leap year, clamped!)
print(addMonths(jan31, 2)); // 2024-03-31
}
Duration(days: 30) does not correctly add “one month” because months have different lengths (28-31 days). Always use calendar-aware logic when working with months or years. Similarly, adding Duration(days: 365) is not always “one year” due to leap years.Comparing Dates
Dart provides several ways to compare DateTime objects. Understanding comparison semantics is important for sorting, filtering, and scheduling.
Date Comparison Methods
void main() {
final date1 = DateTime(2024, 3, 15);
final date2 = DateTime(2024, 6, 20);
final date3 = DateTime(2024, 3, 15);
// Comparison operators
print(date1.isBefore(date2)); // true
print(date1.isAfter(date2)); // false
print(date1.isAtSameMomentAs(date3)); // true
// compareTo for sorting
print(date1.compareTo(date2)); // negative (date1 is before date2)
print(date2.compareTo(date1)); // positive
print(date1.compareTo(date3)); // 0 (equal)
// Sorting a list of dates
final dates = [
DateTime(2024, 12, 25),
DateTime(2024, 1, 1),
DateTime(2024, 7, 4),
DateTime(2024, 3, 15),
];
dates.sort((a, b) => a.compareTo(b));
print(dates.map((d) => '${d.month}/${d.day}').toList());
// [1/1, 3/15, 7/4, 12/25]
// Check if a date is within a range
bool isInRange(DateTime date, DateTime start, DateTime end) {
return !date.isBefore(start) && !date.isAfter(end);
}
final checkDate = DateTime(2024, 5, 10);
print(isInRange(checkDate, date1, date2)); // true
}
Formatting Dates with the intl Package
Dart’s built-in DateTime.toString() produces ISO format, which is not user-friendly. The intl package provides DateFormat for locale-aware, customizable formatting.
Date Formatting with DateFormat
import 'package:intl/intl.dart';
void main() {
final now = DateTime(2024, 3, 15, 14, 30, 0);
// Predefined formats
print(DateFormat.yMMMd().format(now)); // Mar 15, 2024
print(DateFormat.yMMMMEEEEd().format(now)); // Friday, March 15, 2024
print(DateFormat.Hm().format(now)); // 14:30
print(DateFormat.jm().format(now)); // 2:30 PM
// Custom patterns
print(DateFormat('yyyy-MM-dd').format(now)); // 2024-03-15
print(DateFormat('dd/MM/yyyy').format(now)); // 15/03/2024
print(DateFormat('EEEE, MMMM d, y').format(now)); // Friday, March 15, 2024
print(DateFormat('h:mm a').format(now)); // 2:30 PM
print(DateFormat('MMM d, y ''at'' h:mm a').format(now)); // Mar 15, 2024 at 2:30 PM
// Locale-specific formatting
print(DateFormat.yMMMd('ar').format(now)); // ١٥ مارس ٢٠٢٤
print(DateFormat.yMMMd('fr').format(now)); // 15 mars 2024
print(DateFormat.yMMMd('ja').format(now)); // 2024/03/15
// Parsing with DateFormat
final parsed = DateFormat('MM/dd/yyyy').parse('03/15/2024');
print(parsed); // 2024-03-15 00:00:00.000
}
intl package, add it to your pubspec.yaml: dependencies: intl: ^0.19.0. The package also provides number formatting, plural rules, and message translation capabilities beyond just dates.Stopwatch for Measuring Elapsed Time
The Stopwatch class provides precise time measurement for performance profiling and benchmarking.
Using Stopwatch
void main() {
final stopwatch = Stopwatch();
// Start measuring
stopwatch.start();
// Simulate work
int sum = 0;
for (var i = 0; i < 1000000; i++) {
sum += i;
}
// Stop and read
stopwatch.stop();
print('Elapsed: ${stopwatch.elapsed}');
print('Milliseconds: ${stopwatch.elapsedMilliseconds}');
print('Microseconds: ${stopwatch.elapsedMicroseconds}');
// Reset and reuse
stopwatch.reset();
print('After reset: ${stopwatch.elapsedMilliseconds}'); // 0
// Lap timing helper
stopwatch.start();
// ... phase 1 ...
final lap1 = stopwatch.elapsedMilliseconds;
// ... phase 2 ...
final lap2 = stopwatch.elapsedMilliseconds;
stopwatch.stop();
print('Phase 1: ${lap1}ms');
print('Phase 2: ${lap2 - lap1}ms');
print('Total: ${stopwatch.elapsedMilliseconds}ms');
}
Practical Example: Age Calculator
Calculating a person’s age requires careful handling of months and days, not just subtracting years.
Precise Age Calculator
class Age {
final int years;
final int months;
final int days;
Age(this.years, this.months, this.days);
@override
String toString() => '$years years, $months months, $days days';
}
Age calculateAge(DateTime birthDate, [DateTime? referenceDate]) {
final now = referenceDate ?? DateTime.now();
int years = now.year - birthDate.year;
int months = now.month - birthDate.month;
int days = now.day - birthDate.day;
if (days < 0) {
months--;
// Days in the previous month
final prevMonth = DateTime(now.year, now.month, 0);
days += prevMonth.day;
}
if (months < 0) {
years--;
months += 12;
}
return Age(years, months, days);
}
void main() {
final birthday = DateTime(1995, 8, 15);
final today = DateTime(2024, 3, 15);
final age = calculateAge(birthday, today);
print('Age: $age'); // Age: 28 years, 7 months, 0 days
// Days until next birthday
var nextBirthday = DateTime(today.year, birthday.month, birthday.day);
if (nextBirthday.isBefore(today) || nextBirthday.isAtSameMomentAs(today)) {
nextBirthday = DateTime(today.year + 1, birthday.month, birthday.day);
}
final daysUntil = nextBirthday.difference(today).inDays;
print('Days until next birthday: $daysUntil'); // 153
}
Practical Example: Countdown Timer
A countdown timer calculates the remaining time until a target date and displays it in a human-readable format.
Countdown Timer
class Countdown {
final DateTime target;
Countdown(this.target);
Duration get remaining {
final now = DateTime.now();
if (now.isAfter(target)) return Duration.zero;
return target.difference(now);
}
bool get isExpired => DateTime.now().isAfter(target);
String get formatted {
final r = remaining;
if (r == Duration.zero) return 'Expired!';
final days = r.inDays;
final hours = r.inHours % 24;
final minutes = r.inMinutes % 60;
final seconds = r.inSeconds % 60;
final parts = <String>[];
if (days > 0) parts.add('$days day${days == 1 ? '' : 's'}');
if (hours > 0) parts.add('$hours hour${hours == 1 ? '' : 's'}');
if (minutes > 0) parts.add('$minutes minute${minutes == 1 ? '' : 's'}');
if (seconds > 0) parts.add('$seconds second${seconds == 1 ? '' : 's'}');
return parts.join(', ');
}
}
void main() {
// Countdown to New Year 2025
final newYear = DateTime(2025, 1, 1);
final countdown = Countdown(newYear);
print('Time until New Year: ${countdown.formatted}');
print('Is expired: ${countdown.isExpired}');
}
Practical Example: Date Range Utilities
Date range operations are common in scheduling, reporting, and calendar applications.
Date Range Class
class DateRange {
final DateTime start;
final DateTime end;
DateRange(this.start, this.end) : assert(!end.isBefore(start));
Duration get duration => end.difference(start);
int get dayCount => duration.inDays + 1; // Inclusive
bool contains(DateTime date) {
return !date.isBefore(start) && !date.isAfter(end);
}
bool overlaps(DateRange other) {
return !end.isBefore(other.start) && !start.isAfter(other.end);
}
DateRange? intersection(DateRange other) {
if (!overlaps(other)) return null;
final s = start.isAfter(other.start) ? start : other.start;
final e = end.isBefore(other.end) ? end : other.end;
return DateRange(s, e);
}
/// Generates all dates in the range (inclusive).
Iterable<DateTime> get days sync* {
var current = DateTime(start.year, start.month, start.day);
final endDate = DateTime(end.year, end.month, end.day);
while (!current.isAfter(endDate)) {
yield current;
current = current.add(Duration(days: 1));
}
}
/// Returns only weekdays (Monday-Friday).
Iterable<DateTime> get weekdays =>
days.where((d) => d.weekday <= 5);
@override
String toString() => '$start - $end ($dayCount days)';
}
void main() {
final q1 = DateRange(DateTime(2024, 1, 1), DateTime(2024, 3, 31));
final march = DateRange(DateTime(2024, 3, 1), DateTime(2024, 3, 31));
print('Q1: ${q1.dayCount} days'); // Q1: 91 days
print('Q1 contains Mar 15: ${q1.contains(DateTime(2024, 3, 15))}'); // true
print('Q1 overlaps March: ${q1.overlaps(march)}'); // true
final overlap = q1.intersection(march);
print('Overlap: $overlap'); // March 1-31
// Business days in March 2024
final businessDays = march.weekdays.length;
print('Business days in March: $businessDays'); // 21
}
Practical Example: Simple Scheduling System
Combining DateTime, Duration, and date ranges to build a basic scheduling system.
Event Scheduler
class ScheduleEvent {
final String title;
final DateTime start;
final Duration duration;
ScheduleEvent(this.title, this.start, this.duration);
DateTime get end => start.add(duration);
bool conflictsWith(ScheduleEvent other) {
return start.isBefore(other.end) && end.isAfter(other.start);
}
@override
String toString() {
final endTime = end;
return '$title: ${_formatTime(start)} - ${_formatTime(endTime)}';
}
String _formatTime(DateTime dt) =>
'${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
}
class Scheduler {
final List<ScheduleEvent> _events = [];
List<ScheduleEvent> get events => List.unmodifiable(_events);
bool addEvent(ScheduleEvent event) {
// Check for conflicts
for (final existing in _events) {
if (existing.conflictsWith(event)) {
print('Conflict: "${event.title}" overlaps with "${existing.title}"');
return false;
}
}
_events.add(event);
_events.sort((a, b) => a.start.compareTo(b.start));
return true;
}
List<ScheduleEvent> eventsOn(DateTime date) {
return _events.where((e) =>
e.start.year == date.year &&
e.start.month == date.month &&
e.start.day == date.day).toList();
}
}
void main() {
final scheduler = Scheduler();
final today = DateTime(2024, 3, 15);
scheduler.addEvent(ScheduleEvent(
'Team Meeting',
DateTime(2024, 3, 15, 9, 0),
Duration(hours: 1),
));
scheduler.addEvent(ScheduleEvent(
'Lunch',
DateTime(2024, 3, 15, 12, 0),
Duration(minutes: 45),
));
// This will show a conflict
scheduler.addEvent(ScheduleEvent(
'Conflicting Call',
DateTime(2024, 3, 15, 9, 30),
Duration(minutes: 30),
));
print('\nSchedule for today:');
for (final event in scheduler.eventsOn(today)) {
print(' $event');
}
// Team Meeting: 09:00 - 10:00
// Lunch: 12:00 - 12:45
}
timezone package for proper IANA timezone support. Dart’s built-in DateTime only supports UTC and the system’s local timezone, which is insufficient for applications that schedule events across multiple time zones.Summary
Dart provides solid tools for date, time, and duration operations. Key takeaways:
DateTimerepresents a point in time; create with constructors,.now(), or.parse().- Always store/transmit in UTC; convert to local only for display.
Durationrepresents a span of time; useadd()andsubtract()for date arithmetic.- Adding months/years requires calendar-aware logic —
Durationalone is not sufficient. - Use the
intlpackage (DateFormat) for locale-aware formatting and parsing of custom date strings. Stopwatchis ideal for measuring elapsed time in performance-critical code.- Build utilities like
DateRangefor overlap detection, iteration, and business day counting.