New package: time_plus - A Better Way to Work with DateTime and Duration in Dart

:rocket: time+ — A Better Way to Work with DateTime and Duration in Dart

After years of wrestling with Dart’s native DateTime and Duration APIs, I built time_plus — a lightweight, zero-dependency extension package with 128 carefully designed APIs and over 700 tests validating edge cases, leap logic, conversions, and much more.

It consists of dozens of extensions I had written and used privately across various projects — until finally packaging them together into a single, cohesive package.

It’s precise, production-safe, and battle-tested in real-world apps — designed to give you expressive, reliable time math without the pitfalls or boilerplate.

If you’re working with time in Dart and want tools that just work, feel free to explore the full README — it’s packed with detailed docs, real examples, and everything this package has to offer.

Let me know what you think (:

:package: 100% pure Dart · Zero dependencies · 128 extensions · 700+ tests

:link: https://pub.dev/packages/time_plus


:date: All DateTime Extensions in time_plus

time_plus massively expands the power of DateTime with over 60 precise, calendar-aware extensions — all tested, side-effect-free, and timezone-respecting.

:plus: Add Time

Add any unit to a DateTime, safely and fluently — from microseconds to centuries.
Supports clamping for overflow dates (e.g. Feb 30 → Feb 28/29).

.addMillisecond(int) / .addMillisecond
.addSecond(int)      / .addSecond
.addMinute(int)      / .addMinute
.addHour(int)        / .addHour
.addDay(int)         / .addDay
.addWeek(int)        / .addWeek
.addMonth(int)       / .addMonth
.addYear(int)        / .addYear
.addDecade(int)      / .addDecade
.addCentury(int)     / .addCentury

:minus: Subtract Time

Same methods as above, but with .subtractX() / .subtractX equivalents.
All preserve the original time of day and calendar correctness.


:puzzle_piece: Temporal Comparison

Check if two DateTimes fall in the same unit (e.g. minute, day, decade).

.isSameMicrosecond(other)
.isSameMillisecond(other)
.isSameSecond(other)
.isSameMinute(other)
.isSameHour(other)
.isSameDay(other)
.isSameMonth(other)
.isSameYear(other)
.isSameDecade(other)
.isSameCentury(other)

:brick: Boundaries

Get the exact start or end of a unit.

.startOfMillisecond / .endOfMillisecond
.startOfSecond      / .endOfSecond
.startOfMinute      / .endOfMinute
.startOfHour        / .endOfHour
.startOfDay         / .endOfDay
.startOfWeek        / .endOfWeek
.startOfMonth       / .endOfMonth
.startOfYear        / .endOfYear
.startOfDecade      / .endOfDecade
.startOfCentury     / .endOfCentury

:next_track_button: Recurrence Helpers

Schedule the next matching DateTime, forward-only.

.nextWeekdayTime(weekday, [hour, minute, ...])
.nextTimeOfDay(hour, [minute, ...])

:frog: Leap Year Logic

Expanded leap checks for clean calendar logic.

.isLeapYear      // year has 366 days
.isLeapMonth     // true only for February in leap year
.isLeapDay       // true only for Feb 29

:date: Calendar Info

Accurate day counts based on the current month/year.

.daysInMonth   // 28–31
.daysInYear    // 365 or 366

:tear_off_calendar: Relative Days

Simple helpers for working around “now”.

.yesterday
.tomorrow
.previousWeekday
.nextWeekday

All methods return new immutable instances, respect time zones, and are backed by unit tests. Built for readability, correctness, and production use.


:stopwatch: All Duration Extensions in time_plus

time_plus takes Dart’s Duration from basic to brilliant — with over 60 high-precision utilities for creating, converting, chaining, and analyzing time spans.

:plus: Add Duration Units

Add any time unit to a Duration, including months, years, decades, and centuries — using .addX() or .addX for chainable syntax.

.addMicrosecond / .addMicroseconds(int)
.addMillisecond / .addMilliseconds(int)
.addSecond      / .addSeconds(int)
.addMinute      / .addMinutes(int)
.addHour        / .addHours(int)
.addDay         / .addDays(int)
.addWeek        / .addWeeks(int)
.addMonth       / .addMonths(int)
.addYear        / .addYears(int)
.addDecade      / .addDecades(int)
.addCentury     / .addCenturies(int)

All use Gregorian calendar averages, making them ideal for scheduling and estimations.


:abacus: Convert to Whole Units (inX)

Convert durations into whole integer units, based on precise Gregorian averages.

.inWeeks
.inMonths
.inYears
.inDecades
.inCenturies

Great for analytics and consistent formatting (e.g., Duration(days: 750).inYears → 2).


:divide: Convert to Fractional Units (asX)

Get precise decimal representations with fractional support.

.asMilliseconds, .asSeconds, .asMinutes
.asHours, .asDays, .asWeeks
.asMonths, .asYears, .asDecades, .asCenturies

Ideal for charts, sliders, or estimation UIs.


:puzzle_piece: Breakdown (onlyX)

Extract leftover parts after removing larger units.

.onlyMicroseconds
.onlyMilliseconds
.onlySeconds
.onlyMinutes
.onlyHours
.onlyDays

Example: use .onlyHours to display “3h 25m” style timers.


:soap: Strip Units (withoutX)

Remove full units and isolate what’s left.

.withoutDays
.withoutHours
.withoutMinutes
.withoutSeconds
.withoutMilliseconds

Perfect for isolating “time since midnight” or converting full durations into segments.


:1234: intDuration

Turn any int into a readable Duration — naturally.

5.milliseconds, 10.seconds, 2.hours
3.days, 4.weeks, 6.months, 1.years

Readable, chainable, and great for test code or configs.


:microscope: doubleDuration

Decompose fractions with full accuracy — no rounding loss.

1.5.seconds, 2.25.minutes, 0.5.hours
1.5.days, 0.33.months, 2.75.years

Smartly breaks values into actual Duration components (2.5.days → 2 days + 12 hours).


:building_construction: Factories (DurationFrom)

Construct durations programmatically with named units.

DurationFrom.days(10);
DurationFrom.year;
DurationFrom.centuries(2);

Includes constants like DurationFrom.week, DurationFrom.month, etc.


Why Not Just Use time?

The Dart time package is a simple utility—but it’s limited by design.

While it adds int.hours and double.days, it lacks precision, testing, and scalability:

  • Tied to clock — adds runtime side effects to basic time logic
  • Integer math only2.5.days ≠ 2 days + 12 hours
  • Minimal coverage — only ~15 extensions
  • No calendar support — no leap day, no month math, no .startOfWeek()
  • Fails precision tests — rounding errors on fractional durations
  • No decomposition — can’t get .onlyMinutes or .withoutHours

time_plus includes all that — and 10× more

  • :brain: Double-safe logic.5.days → decomposed into hours, minutes, seconds
  • :light_bulb: Smart .int and .double extensions — readable, precise, and composable
  • :package: 128+ extensions — for DateTime, Duration, int, double, and factories
  • :test_tube: 717 tests — covering edge cases, DST transitions, and long-term accuracy
  • :sparkles: Cleaner API — zero side effects, no dependency on clock or global state
  • :tear_off_calendar: Calendar features — leap year detection, .startOfMonth, .isSameDay, etc.

Small Example

final now = DateTime(2024, 2, 29);
final next = now.addYear.addMonth;     // → 2025-03-29
final exact = 2.5.days;                // → 2d 12h
final match = now.isSameMonth(DateTime(2024, 2, 1)); // true

:link: pub.dev/packages/time_plus

8 Likes

I literally just started porting a time library from C last night to Dart precisely because DateTime and Duration weren’t a good fit for my needs.

I’m excited to take a look at this over the weekend and see if it works for me.

Nice work in the meantime

4 Likes