Skip to content

A drop-in replacement for DateTime with full IANA timezone support, intuitive arithmetic, and flexible formatting.

License

Notifications You must be signed in to change notification settings

MasterHiei/easy_date_time

Repository files navigation

easy_date_time

Timezone-aware DateTime for Dart

A drop-in replacement for DateTime with full IANA timezone support, intuitive arithmetic, and flexible formatting. Immutable, accurate, and developer-friendly.

Build Status pub package codecov

中文 | 日本語


Why easy_date_time?

Dart's built-in DateTime and existing libraries often face limitations when handling complex timezone scenarios:

Solution Behavior This Library
DateTime Implicitly converts parsed offset to UTC Preserves original time values
timezone Requires manual getLocation() calls Provides constants like TimeZones.tokyo
intl Focused on formatting output Compatible for combined use
jiffy Mutable object design Immutable, implements DateTime interface

Comparison:

// ❌ Native DateTime: Implicitly converts to UTC/Local, losing the original offset context.
DateTime.parse('2025-12-07T10:30:00+08:00').hour      // → 2 (Changed to UTC hour)

// ✅ EasyDateTime: Preserves the exact parsed hour and offset.
EasyDateTime.parse('2025-12-07T10:30:00+08:00').hour  // → 10 (Preserved)

Key Features

🌍 Full IANA Timezone Support

Use standard IANA constants or custom strings.

final tokyo = EasyDateTime.now(location: TimeZones.tokyo);

🕒 Lossless Parsing

No implicit UTC conversion. Retains the exact parsed values.

EasyDateTime.parse('2025-12-07T10:00+08:00').hour // -> 10

➕ Intuitive Arithmetic

Natural syntax for date calculations.

final later = now + 2.hours + 30.minutes;

🧱 Safe Date Calculation

Handles month overflow intelligently.

jan31.copyWithClamped(month: 2); // -> Feb 28

📝 Flexible Formatting

Performance-optimized formatting.

dt.format('yyyy-MM-dd'); // -> 2025-12-07

Installation

Add the following to your pubspec.yaml:

dependencies:
  easy_date_time: ^0.4.2

Note: You must initialize the timezone database before using the library.

void main() {
  EasyDateTime.initializeTimeZone();  // Required

  // Optional: Set a global default location
  EasyDateTime.setDefaultLocation(TimeZones.shanghai);

  runApp(MyApp());
}

Quick Start

final now = EasyDateTime.now();  // Uses default or local timezone
final tokyo = EasyDateTime.now(location: TimeZones.tokyo);
final parsed = EasyDateTime.parse('2025-12-07T10:30:00+08:00');

print(parsed.hour);  // 10

Working with Timezones

1. Common Timezones (Recommended)

Use pre-defined constants for common timezones:

final tokyo = EasyDateTime.now(location: TimeZones.tokyo);
final shanghai = EasyDateTime.now(location: TimeZones.shanghai);

2. Custom IANA Timezones

You can also use standard IANA strings:

final nairobi = EasyDateTime.now(location: getLocation('Africa/Nairobi'));

3. Global Default Timezone

Setting a default location allows EasyDateTime.now() to use that timezone globally.

EasyDateTime.setDefaultLocation(TimeZones.shanghai);

final now = EasyDateTime.now();  // Returns time in Asia/Shanghai

Managing the default timezone:

// Get the current default timezone
final current = EasyDateTime.getDefaultLocation();

// Clear the default (reverts to system local timezone)
EasyDateTime.clearDefaultLocation();

// Get the effective default location (user-set or system local)
final effective = EasyDateTime.effectiveDefaultLocation;  // or effectiveDefaultLocation

Preserving Time Semantics

EasyDateTime preserves both the literal time and the timezone location, even when parsing strings with offsets:

final dt = EasyDateTime.parse('2025-12-07T10:30:00+08:00');

print(dt.hour);          // 10
print(dt.locationName);  // Asia/Shanghai

Use explicit methods to convert timezones:

final ny = dt.inLocation(TimeZones.newYork);
final utc = dt.toUtc();

Timezone Conversion

Comparing the same instant across different timezones:

final tokyo = EasyDateTime.now(location: TimeZones.tokyo);
final newYork = tokyo.inLocation(TimeZones.newYork);

print(tokyo.isAtSameMomentAs(newYork));  // true: Represents the same absolute instant

Date Arithmetic

final now = EasyDateTime.now();
final tomorrow = now + 1.days;
final later = now + 2.hours + 30.minutes;

Handling Month Overflow

EasyDateTime provides safe handling for month overflows:

final jan31 = EasyDateTime.utc(2025, 1, 31);

jan31.copyWith(month: 2);        // ⚠️ Mar 3rd (Standard overflow)
jan31.copyWithClamped(month: 2); // ✅ Feb 28 (Clamped to last valid day)

Start and End of Time Units

Truncate or extend a datetime to the boundary of a time unit:

final dt = EasyDateTime(2025, 6, 18, 14, 30, 45); // Wednesday

dt.startOf(DateTimeUnit.day);   // 2025-06-18 00:00:00
dt.startOf(DateTimeUnit.week);  // 2025-06-16 00:00:00 (Monday)
dt.startOf(DateTimeUnit.month); // 2025-06-01 00:00:00

dt.endOf(DateTimeUnit.day);     // 2025-06-18 23:59:59.999999
dt.endOf(DateTimeUnit.week);    // 2025-06-22 23:59:59.999999 (Sunday)
dt.endOf(DateTimeUnit.month);   // 2025-06-30 23:59:59.999999

Week boundaries follow ISO 8601 (Monday = first day of week).


Date Formatting

Use the format() method with pattern tokens for flexible date/time formatting:

final dt = EasyDateTime(2025, 12, 1, 14, 30, 45);

dt.format('yyyy-MM-dd');           // '2025-12-01'
dt.format('yyyy/MM/dd HH:mm:ss');  // '2025/12/01 14:30:45'
dt.format('MM/dd/yyyy');           // '12/01/2025'
dt.format('hh:mm a');              // '02:30 PM'

Tip

Performance Optimization: For hot paths (e.g., loops), use EasyDateTimeFormatter to pre-compile patterns.

// Compiled once, reused multiple times
static final formatter = EasyDateTimeFormatter('yyyy-MM-dd HH:mm');
String result = formatter.format(date);

Predefined Formats

Use DateTimeFormats for common patterns:

dt.format(DateTimeFormats.isoDate);      // '2025-12-01'
dt.format(DateTimeFormats.isoTime);      // '14:30:45'
dt.format(DateTimeFormats.isoDateTime);  // '2025-12-01T14:30:45'
dt.format(DateTimeFormats.time12Hour);   // '02:30 PM'
dt.format(DateTimeFormats.time24Hour);   // '14:30'
dt.format(DateTimeFormats.rfc2822);      // 'Mon, 01 Dec 2025 14:30:45 +0800'

Pattern Tokens

Token Description Example
yyyy 4-digit year 2025
MM/M Month (padded/unpadded) 01, 1
MMM Month abbreviation Jan, Dec
dd/d Day (padded/unpadded) 01, 1
EEE Day-of-week abbreviation Mon, Sun
HH/H 24-hour (padded/unpadded) 09, 9
hh/h 12-hour (padded/unpadded) 02, 2
mm/m Minutes (padded/unpadded) 05, 5
ss/s Seconds (padded/unpadded) 05, 5
SSS Milliseconds 123
a AM/PM marker AM, PM
xxxxx Timezone offset with colon +08:00, -05:00
xxxx Timezone offset +0800, -0500
xx Short timezone offset +08, -05
X ISO timezone (Z or +0800) Z, +0800

Extension Handling

This package adds extensions on int (e.g., 1.days). If this conflicts with other packages, hide the extension via specialized imports:

import 'package:easy_date_time/easy_date_time.dart' hide DurationExtension;

Integration with intl

For locale-aware formatting (e.g., "January" → "一月"), use EasyDateTime with the intl package:

import 'package:intl/intl.dart';
import 'package:easy_date_time/easy_date_time.dart';

final dt = EasyDateTime.now(location: TimeZones.tokyo);

// Locale-aware formatting via intl
DateFormat.yMMMMd('ja').format(dt);  // '2025年12月20日'
DateFormat.yMMMMd('en').format(dt);  // 'December 20, 2025'

Note: EasyDateTime implements DateTime, so it works directly with DateFormat.format().


JSON & Serialization

Compatible with json_serializable and freezed via a custom converter:

class EasyDateTimeConverter implements JsonConverter<EasyDateTime, String> {
  const EasyDateTimeConverter();

  @override
  EasyDateTime fromJson(String json) => EasyDateTime.fromIso8601String(json);

  @override
  String toJson(EasyDateTime object) => object.toIso8601String();
}

Important Notes

Equality Comparison

EasyDateTime follows Dart's DateTime semantics for equality:

final utc = EasyDateTime.utc(2025, 1, 1, 0, 0);
final local = EasyDateTime.parse('2025-01-01T08:00:00+08:00');

// Same moment, different timezone type (UTC vs non-UTC)
utc == local;                  // false
utc.isAtSameMomentAs(local);   // true
Method Compares Use Case
== Moment + timezone type (UTC/non-UTC) Exact equality
isAtSameMomentAs() Absolute instant only Cross-timezone comparison
isBefore() / isAfter() Chronological order Sorting, range checks

Other Notes

  • Only valid IANA timezone offsets are supported; non-standard offsets will throw an error.
  • EasyDateTime.initializeTimeZone() must be called before use.

Parsing User Input

Use tryParse for handling potentially invalid user input safely:

final dt = EasyDateTime.tryParse(userInput);
if (dt == null) {
  print('Invalid date format');
}

Contributing

Issues and Pull Requests are welcome. Please refer to CONTRIBUTING.md for guidelines.


License

BSD 2-Clause

About

A drop-in replacement for DateTime with full IANA timezone support, intuitive arithmetic, and flexible formatting.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Contributors 2

  •  
  •  

Languages