easy_date_time
A drop-in replacement for Dart's DateTime with IANA timezone support. It preserves the original time values when parsing and simplifies timezone handling.
// DateTime converts to UTC across timezones, often changing the hour
DateTime.parse('2026-01-18T10:30:00+08:00').hour // 2
// EasyDateTime keeps the original hour and timezone context
EasyDateTime.parse('2026-01-18T10:30:00+08:00').hour // 10
Installation
Dependency
Add the package to your pubspec.yaml:
dependencies:
easy_date_time: ^0.11.0
Initialization
You must initialize the timezone database before using the library:
void main() {
// Call this once before any usage
EasyDateTime.initializeTimeZone();
runApp(MyApp());
}
Usage
Construct and parse with timezone awareness:
// Use a predefined location
final now = EasyDateTime.now(location: TimeZones.tokyo);
// Parse a string while preserving its offset and location
final parsed = EasyDateTime.parse('2026-01-18T10:30:00+08:00');
print(parsed.hour); // 10
print(parsed.locationName); // Asia/Shanghai
Arithmetic with Duration extensions:
final tomorrow = now + 1.days;
final later = now + 2.hours + 30.minutes;
Format with pattern strings:
// 2026-01-18 10:30
print(dt.format('yyyy-MM-dd HH:mm'));
Features
Timezones
There are three ways to specify a timezone (Location):
// 1. Using TimeZones constants (Recommended)
final tokyo = EasyDateTime.now(location: TimeZones.tokyo);
// 2. Using IANA timezone naming
final nairobi = EasyDateTime.now(location: getLocation('Africa/Nairobi'));
// 3. Setting a global default
EasyDateTime.setDefaultLocation(TimeZones.shanghai);
final now = EasyDateTime.now(); // Uses Asia/Shanghai by default
Convert between timezones:
final newYork = tokyo.inLocation(TimeZones.newYork);
tokyo.isAtSameMomentAs(newYork); // true — represents the same instant
Arithmetic
Syntax using extensions:
now + 1.days
now - 2.hours
now + 30.minutes + 15.seconds
DST-Safe Calendar Arithmetic
When adding time across Daylight Saving Time boundaries, addCalendarDays preserves the "wall time" (the time on the clock), whereas standard addition respects physical time.
// DST transition day in New York (March 9, 2025: clocks jump forward)
final dt = EasyDateTime(2025, 3, 9, 0, 0, location: newYork);
dt.addCalendarDays(1); // 2025-03-10 00:00 — Same clock time
dt.add(Duration(days: 1)); // 2025-03-10 01:00 — 24 physical hours later
Convenience getters:
dt.tomorrow // The next calendar day
dt.yesterday // The previous calendar day
dt.dateOnly // Resets time components to 00:00:00
Month Overflow Handling
Handle cases where a month change results in an invalid date (e.g., changing Jan 31st to February):
final jan31 = EasyDateTime.utc(2025, 1, 31);
jan31.copyWith(month: 2); // Mar 3 (Standard overflow behavior)
jan31.copyWithClamped(month: 2); // Feb 28 (Clamped to the last valid day)
Time Unit Boundaries
Calculate start and end points:
dt.startOf(DateTimeUnit.day); // 00:00:00.000
dt.startOf(DateTimeUnit.week); // Monday 00:00:00 (ISO 8601)
dt.startOf(DateTimeUnit.month); // 1st of month 00:00:00
dt.endOf(DateTimeUnit.month); // Last day of month 23:59:59.999
Properties
Date components:
dayOfYear— Day number (1-366)weekOfYear— ISO 8601 week number (1-53)quarter— Quarter of the year (1-4)daysInMonth— Total days in the current month (28-31)isLeapYear— true if leap year
Status checkers:
isToday,isTomorrow,isYesterdayisThisWeek,isThisMonth,isThisYearisWeekend,isWeekdayisPast,isFuture
Timezone info:
isDst— true if currently in Daylight Saving TimelocationName— IANA timezone identifier
Formatting
Format dates using patterns:
dt.format('yyyy-MM-dd'); // 2026-01-18
dt.format('HH:mm:ss'); // 14:30:45
dt.format('hh:mm a'); // 02:30 PM
Or use predefined constants:
dt.format(DateTimeFormats.isoDate); // 2026-01-18
dt.format(DateTimeFormats.isoDateTime); // 2026-01-18T14:30:45
dt.format(DateTimeFormats.rfc2822); // Sun, 18 Jan 2026 14:30:45 +0800
Performance: In loops or high-throughput scenarios, it is recommended to reuse
EasyDateTimeFormatterinstances.
static final formatter = EasyDateTimeFormatter('yyyy-MM-dd HH:mm');
formatter.format(dt);
Pattern Tokens:
| Token | Description | Example |
|---|---|---|
yyyy |
4-digit year | 2025 |
MM / M |
Month | 01 / 1 |
dd / d |
Day | 07 / 7 |
HH / H |
Hour (24h) | 09 / 9 |
hh / h |
Hour (12h) | 02 / 2 |
mm / m |
Minute | 05 / 5 |
ss / s |
Second | 05 / 5 |
SSS |
Millisecond | 123 |
a |
AM / PM | AM / PM |
EEE |
Day of week | Mon |
MMM |
Month name | Dec |
xxxxx |
Offset | +08:00 |
X |
ISO Offset | Z / +0800 |
Integration with intl
EasyDateTime implements the DateTime interface, making it compatible with package:intl without modification:
import 'package:package:intl/intl.dart';
DateFormat.yMMMMd('ja').format(dt); // 2025年12月7日
DateFormat.yMMMMd('en').format(dt); // December 7, 2025
JSON Serialization
Custom converters are provided for json_serializable and freezed:
class EasyDateTimeConverter implements JsonConverter<EasyDateTime, String> {
const EasyDateTimeConverter();
@override
EasyDateTime fromJson(String json) => EasyDateTime.fromIso8601String(json);
@override
String toJson(EasyDateTime object) => object.toIso8601String();
}
Usage with freezed:
@freezed
class Event with _$Event {
const factory Event({
@EasyDateTimeConverter() required EasyDateTime startTime,
}) = _Event;
factory Event.fromJson(Map<String, dynamic> json) => _$EventFromJson(json);
}
Important Notes
Equality (==)
Equality intentionally follows Dart's DateTime semantics.
==compares absolute time value AND timezone/location.isAtSameMomentAscompares only the absolute time value (instant).
final utc = EasyDateTime.utc(2025, 1, 1, 0, 0);
final local = EasyDateTime.parse('2026-01-18T08:00:00+08:00');
utc == local; // false — different Location
utc.isAtSameMomentAs(local); // true — same instant in time
Warning: Do not mix
EasyDateTimeandDateTimein the sameSetorMapkey, as theirhashCodeimplementations differ.
Auto-Correction vs. Strict Mode By default, invalid calendar dates are automatically rolled over (overflow):
EasyDateTime(2025, 2, 30); // Becomes 2025-03-02
To enforce strict validation and reject invalid dates, use the strict parameter:
// Throws FormatException for invalid dates
EasyDateTime.parse('2025-02-30', strict: true);
// Returns null for invalid dates
EasyDateTime.tryParse('2025-02-30', strict: true);
Safe Parsing
Always use tryParse for handling potentially malformed user input:
final dt = EasyDateTime.tryParse(userInput);
if (dt == null) {
// Handle invalid format
}
Extension Conflicts
If 1.days conflicts with another package (like time), hide the extension:
import 'package:easy_date_time/easy_date_time.dart' hide DurationExtension;
Contributing
We welcome contributions! Please see CONTRIBUTING.md for details.
License
BSD 2-Clause
Libraries
- easy_date_time
- A timezone-aware DateTime library for Dart.