time_plus는 DateTime과 Duration에 직관적인 확장을 제공하여 간편한 시간 조작을 가능하게 하는 Dart 패키지입니다. 시간 단위의 더하기, 빼기, 변환 작업을 간소화하여 날짜 및 시간 연산을 더욱 가독성 있고 간결하게 만듭니다. 이 패키지는 Android, iOS, Web, macOS, Windows, Linux 등 모든 주요 플랫폼을 지원하며 정밀한 시간 처리가 필요한 애플리케이션에 적합합니다. 깔끔한 문법과 최소한의 반복 코드를 통해 지속 시간 계산, 포맷팅, 날짜 조정 등의 일반적인 작업을 간편하게 처리할 수 있습니다.
사용 사례
사용자 시간대 변환 처리
이벤트 지속 시간 계산
반복 작업 예약
캘린더 또는 타이머 앱 만들기
만료일 관리
주요 기능
시간 단위의 간편한 추가/감소
내장된 기간 변환 유틸리티
직관적인 DateTime 확장
크로스 플랫폼 호환성
최소한의 설정 필요
적합한 대상
시간 민감한 데이터를 다루는 개발자
날짜 산술이 필요한 앱
깨끗하고 읽기 쉬운 타임코드를 원하는 팀
자주 시간 조작이 이루어지는 프로젝트
고려 사항
Dart 2.12 이상이 필요합니다
기본적인 시간 연산에 국한됨
내장된 로컬라이제이션 지원 없음
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
Make every microsecond count.
Stop wrestling with DateTime and Duration. time_plus gives you the tools you wish Dart had built in—add time units, break durations down, convert across scales, and do it all with a clear, predictable API. No dependencies. Just useful extensions that make working with time easier.
Table of Contents
📅 DateTime
add – Add time units to DateTime from milliseconds to centuries with .addX() or .addX getters.
subtract – Subtract time units from DateTime using intuitive, calendar-aware methods.
isSame – Compare two dates by year, month, day, hour, to microseconds using isSameX().
startOf / endOf – Get start or end of time units: minute, hour, day, week, month, year.
next – Find the next matching DateTime by weekday or time of day.
leap – Check if DateTime is in a leap year, leap month, or on leap day (Feb 29).
Factories – Create durations from any unit or use built-in constants like DurationFrom.year.
Why Use time_plus?
✅ Zero dependencies – Pure Dart. No setup or third-party overhead.
✅ Lightweight & fast – Minimal footprint with efficient, production-safe code.
✅ Comprehensive time tools – Add, compare, normalize, and decompose dates and durations with ease.
✅ Reliable and robust – Built for real-world use, with 128 extensions and 717 tests ensuring stability.
✅ A better alternative to the time package – Passes tests the time package fails, performs faster, and avoids questionable design choices like hard dependencies on clock and more, does not have "clock" features (keeps separation of concerns).
Ideal for:
🗓 Calendar logic – Scheduling, planning, date math, and recurring events
🧮 Data handling – Grouping, filtering, and comparing DateTime objects with precision
⏱️ UI & analytics – Formatting durations, tracking activity, and displaying time intuitively
time_plus closes the gaps in Dart's native DateTime and Duration APIs — making your code easier to write, easier to read, and easier to trust.
Extend DateTime just like you wish it worked. These extensions let you safely add milliseconds, hours, days, months, and even centuries — with proper handling for leap years and end-of-month overflows.
addMilliseconds(int) / addMillisecond
addSeconds(int) / addSecond
addMinutes(int) / addMinute
addHours(int) / addHour
addDays(int) / addDay — Preserves exact time of day (all following methods do)
addWeeks(int) / addWeek
addMonths(int) / addMonth
addYears(int) / addYear
addDecades(int) / addDecade (10-year spans)
addCenturies(int) / addCentury (100-year spans)
All methods return a new DateTime instance. Month and year-based methods clamp invalid dates (e.g. Feb 30 → Feb 28/29), ensuring always-valid results.
🧪 Example
final now = DateTime(2024, 2, 29);
final nextYear = now.addYear; // → 2025-02-28final chained = now.addYear.addYear.addMonth; // → 2026-03-29final future = now.addDecades(2); // → 2044-02-29final long = now.addCenturies(1); // → 2124-02-29
Important: The addDays and addWeeks and the bigger time units methods preserve the exact time of day and are calendar-aware, making them safe for daylight saving time transitions. They are not equivalent to adding hours.
Safely subtract time from any DateTime using intuitive, calendar-aware methods. These extensions go beyond raw durations to handle months, years, decades, and centuries — correctly accounting for leap years, daylight saving, and invalid dates like Feb 30.
subtractMilliseconds(int) / subtractMillisecond
subtractSeconds(int) / subtractSecond
subtractMinutes(int) / subtractMinute
subtractHours(int) / subtractHour
subtractDays(int) / subtractDay — Preserves exact time of day (all following methods do)
All methods return a new DateTime instance. Month and year-based methods clamp invalid dates (e.g. March 31 → February 28/29), ensuring always-valid results.
🧪 Example
final now = DateTime(2024, 2, 29);
final lastYear = now.subtractYear; // → 2023-02-28final chained = now.subtractYear.subtractMonth; // → 2022-01-28final past = now.subtractDecades(2); // → 2004-02-29final wayBack = now.subtractCenturies(1); // → 1924-02-29
Important: The subtractDays and subtractWeeks and the bigger time units methods preserve the exact time of day and are calendar-aware, making them safe for daylight saving time transitions. They are not equivalent to subtracting hours.
Quickly compare two DateTime instances with precision — from year-level down to microseconds. Each method checks only the relevant granularity, making it ideal for scheduling, display logic, or grouping events.
isSameMicrosecond(DateTime)
isSameMillisecond(DateTime)
isSameSecond(DateTime)
isSameMinute(DateTime)
isSameHour(DateTime)
isSameDay(DateTime)
isSameMonth(DateTime)
isSameYear(DateTime)
isSameDecade(DateTime) (10-year spans)
isSameCentury(DateTime) (100-year spans)
Each method returns true if the current DateTime matches the given one at that specific resolution.
🧪 Example
final a = DateTime(2024, 4, 20, 10, 15);
final b = DateTime(2024, 4, 20, 10, 45);
a.isSameDay(b); // → true
a.isSameHour(b); // → true
a.isSameMinute(b); // → false
Get the exact start or end of any time unit — minute, hour, day, week, month, or year. These helpers return a new DateTime aligned to the boundary, ideal for filtering, grouping, or time range logic.
startOfMillisecond / endOfMillisecond
startOfSecond / endOfSecond
startOfMinute / endOfMinute
startOfHour / endOfHour
startOfDay / endOfDay
startOfWeek / endOfWeek (Monday → Sunday)
startOfMonth / endOfMonth
startOfYear / endOfYear
startOfDecade / endOfDecade (10-year spans)
startOfCentury / endOfCentury (100-year spans)
Each result is a fresh DateTime at the zero point of the boundary (or start of the next one, for endOf...).
Easily find the next time that matches a specific weekday or time of day — skipping the current moment even if it matches. Useful for scheduling recurring events, alarms, or reminders.
nextWeekdayTime(weekday, ...)
→ Returns the next occurrence of a specific weekday (1–7, Monday–Sunday), with optional time.
nextTimeOfDay(hour, ...)
→ Returns the next time that matches the given time of day (tomorrow if already passed today).
These respect the original timezone (UTC/local) and guarantee forward-only results.
📅 daysInMonth / daysInYear — Calendar-Aware Date Info
Get the correct number of days in the current month or year, with full leap year handling built in. Ideal for calendar UIs, range generators, or custom date calculations.
daysInMonth — Returns 28–31 based on month and leap year
daysInYear — Returns 365 or 366 based on the current year
Easily extend a Duration by any time unit — from microseconds to centuries — using intuitive, chainable methods. Supports both numeric arguments and single-unit getters for clear, readable code.
addMicroseconds(int) / addMicrosecond
addMilliseconds(int) / addMillisecond
addSeconds(int) / addSecond
addMinutes(int) / addMinute
addHours(int) / addHour
addDays(int) / addDay
addWeeks(int) / addWeek (7-day spans)
addMonths(int) / addMonth (≈30.436875 days)
addYears(int) / addYear (≈365.2425 days)
addDecades(int) / addDecade (≈3652.425 days)
addCenturies(int) / addCentury (≈36,524.25 days)
All methods return a new Duration without modifying the original instance.
⚠️ Note on accuracy:
The addMonths, addYears, addDecades, and addCenturies methods on Duration now use long-term Gregorian averages
(e.g., 30.436875 days/month, 365.2425 days/year) based on a 400-year cycle.
These are more precise than previous fixed approximations, but still not suitable for exact date calculations.
✅ For calendar-accurate operations (e.g., leap years, end-of-month logic), use the equivalent methods on DateTime instead.
Convert a Duration into whole, integer-based time units — perfect for calculations, comparisons, or formatting where only full units matter. These getters are safe, clear, and based on long-term Gregorian averages.
inWeeks — Full 7-day weeks
inMonths — Full months (≈30.436875 days)
inYears — Full years (≈365.2425 days)
inDecades — Full decades (≈3652.425 days)
inCenturies — Full centuries (≈36,524.25 days)
All values are calculated using integer division and return only whole units.
final d = Duration(days: 750);
d.inYears; // → 2
d.inMonths; // → 24
⚠️ Note on precision:
These unit conversions are based on mean durations over 400 years in the Gregorian calendar.
While significantly more accurate than fixed approximations (like "30 days per month"), they still do not match exact calendar units.
✅ Use DateTime methods for exact date math, including leap years and end-of-month boundaries.
Convert a Duration to floating-point values — including fractions — in any time unit, from milliseconds to centuries. Unlike inX, which gives only whole units, asX provides precise decimal results.
⚠️ Note on precision:
The months/years/decades/centuries conversions are based on mean durations over 400 years in the Gregorian calendar.
While significantly more accurate than fixed approximations (like "30 days per month"), they still do not match exact calendar units.
✅ Use DateTime methods for exact date math, including leap years and end-of-month boundaries.
Strip away full units from a Duration, leaving only the remainder. Useful when you want the leftover time within a day, hour, minute, or second — like isolating time of day or breaking durations into parts.
withoutDays — Removes full days, keeps hours/minutes/etc.
withoutHours — Removes full hours, keeps minutes/seconds/etc.
withoutMinutes — Removes full minutes, keeps seconds/milliseconds/etc.
withoutSeconds — Removes full seconds, keeps milliseconds/microseconds
withoutMilliseconds — Removes full milliseconds, keeps microseconds
Each method returns a new Duration with the higher units removed.
🧪 Example
final d = Duration(hours: 2, minutes: 45, seconds: 30);
final remainder = d.withoutHours; // → 0:45:30.000000
Make your time math more expressive. These extensions let you turn any int into a Duration — from milliseconds to centuries — using natural, readable syntax.
This makes time-based calculations cleaner and easier to read — no more Duration(seconds: 5) boilerplate.
🧪 Example
final delay = 3.seconds + 500.milliseconds;
final timeout = 2.hours + 30.minutes;
final historical = 2.centuries - 1.decade;
final oneYear = 1.days + 2.hours + 30.minutes + 15.seconds;
⚠️ Note on accuracy:
The months/years/decades/centuries are based on long-term Gregorian averages derived from a 400-year calendar cycle.
They are excellent for simulations, scheduling, and time estimation —
but do not reflect the exact number of days in individual months or years.
✅ For exact date handling (leap years, month lengths, DST shifts), use DateTime instead.
🔬 double → Duration — Fractional Time Made Precise
Convert decimal values into smart, decomposed Duration objects. Supports precise handling of fractions — like 1.5 hours or 2.75 months — with full breakdown into microseconds.
1.5.seconds, 2.25.minutes, 0.5.hours
1.5.days, 2.75.weeks, 0.33.months
0.1.years, 1.25.decades, 2.5.centuries
Internally decomposed into full Duration fields: days, hours, minutes, etc., for consistent and readable output — even across large or fractional units.
🧪 Example
final nap = 1.5.hours; // → 1h 30m 0sfinal quarter = 0.25.years; // → ~91 daysfinal longView = 2.5.centuries; // → 91250 days
⚠️ Note on accuracy:
The months/years/decades/centuries are based on long-term Gregorian averages derived from a 400-year calendar cycle.
They are excellent for simulations, scheduling, and time estimation —
but do not reflect the exact number of days in individual months or years.
✅ For exact date handling (leap years, month lengths, DST shifts), use DateTime instead.
This is the core of all Duration creation in the package — powering everything from int and double extensions to add methods and smart conversions.
A unified way to construct Duration instances from any time unit — including long spans like months, years, decades, and centuries. Provides both numeric constructors and ready-to-use constants.
These follow long-term Gregorian calendar averages:
1 week = 7 days
1 month ≈ 30.436875 days
1 year ≈ 365.2425 days
1 decade ≈ 3,652.425 days
1 century ≈ 36,524.25 days
🧪 Example
final workout = DurationFrom.minutes(45);
final breakTime = DurationFrom.week; // constant: 7 daysfinal future = workout + DurationFrom.years(2); // ≈ 730.485 days
⚠️ Note on accuracy:
All factory methods use averaged Gregorian values derived from a 400-year calendar cycle.
These are excellent for simulations, scheduling, and time estimation —
but do not reflect the exact number of days in individual months or years.
✅ For exact date handling (leap years, month lengths, DST shifts), use DateTime instead.
The Dart time package provides a handful of DateTime and Duration extensions, but it's tightly coupled to the clock package and lacks precision, modularity, and extensibility.
By contrast, time_plus was built from real-world project needs. It keeps clock logic out of core date/time operations, performs better, passes more edge-case tests, and offers 8x more functionality with no extra dependencies.
In short:time_plus is a modern, accurate, and robust alternative — not a fork, but a clean rethinking of what Dart’s time handling should be.
The time package provides a minimal set of extensions on Duration and DateTime, but it falls short of the flexibility, performance, and precision needed for real-world applications. Rather than attempting to patch a narrowly scoped and structurally limited library, we built time_plus from the ground up with clear priorities: accuracy, extensibility, modularity, and production safety.
A Different Goal
time_plus didn’t begin as a replacement for time. It started with a completely separate purpose — to add modular, testable, and precise extensions to Duration and DateTime, with a clear boundary between calendar logic and runtime clocks.
Later, once the package matured and handled large-scale duration and date logic cleanly, it expanded to support int/double extensions as well — but with strict attention to precision, performance, and decomposition logic.
In contrast, time was always focused on quick syntax enhancements, like:
final future = 10.minutes.fromNow;
final delay = 2.5.hours.delay;
Which are helpful — but shallow in scope and tied to global clock behavior.
Limited Scope, By Design
time_plus introduces over 120 extensions, covering:
support for int and double → Duration, factories, and more.
Smarter Memory Usage
In time, common getters like daysInMonth create a new list every time they are called — even though the values are static and immutable. This results in unnecessary allocations that can impact both performance and garbage collection in high-frequency code paths.
// time (creates a new list on every access)final days = [
31, // Januaryif (isLeapYear) 29else28,
...
];
In contrast, time_plus uses compile-time constant lists:
This approach is not only more efficient — especially in tight loops or calendar-heavy logic — but also avoids subtle bugs from mutable runtime-created data. It’s a small optimization with real-world impact at scale.
Additionally, time_plus applies the same zero-allocation mindset across Duration math, leap year checks, and unit decompositions — ensuring every operation remains performant, consistent, and GC-friendly.
Clock Coupling in time
A core architectural limitation of the time package is its implicit dependency on the clock package, which introduces hidden state into APIs that should remain deterministic and side-effect-free.
Implicit State in Common Operations
The time package injects runtime logic directly into extension getters such as:
final future = 5.minutes.fromNow;
final past = 2.hours.ago;
final isToday = someDate.isToday;
Although these appear to be lightweight utilities, they internally call clock.now(), meaning:
The result is time-sensitive and non-deterministic
Behavior depends on a global singleton
Testing becomes brittle without additional mocking infrastructure
This design choice tightly couples business logic with environmental state, reducing testability, predictability, and modularity — especially in larger codebases.
Precision Matters
One of the most critical shortcomings in the time package is its reliance on simple multiplication when converting numbers into Duration objects:
Durationget days => milliseconds * Duration.millisecondsPerDay;
This approach treats 2.5.days the same as 2.5 * 86_400_000, which leads to accumulated rounding errors and loss of microsecond-level precision. Worse, this same calculation is applied identically to both int and double types — even though they should be handled differently.
In contrast, time_plus:
Uses the native Duration constructor for all int-based units (e.g. 3.days → Duration(days: 3)), avoiding unnecessary conversions.
For fractional values (double), it performs smart decomposition:
Breaks 2.5.days into Duration(days: 2, hours: 12)
Maintains sub-millisecond precision by working directly with microseconds
Applies this decomposition for all scales — from microseconds to centuries — preserving accuracy and performance across the board.
The result is predictable, consistent, and verifiable time math — even in complex or high-frequency environments. With time_plus, you're not just getting shorthand syntax — you're getting a precision-engineered time system built for production.
Leap Year Logic
In production code, we favor mathematical consistency unless business logic explicitly dictates otherwise. The time package's implementation of isLeapYear includes a hardcoded domain check — restricting results to year >= 1582.
// From `time`boolget isLeapYear =>
year >= 1582 && year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
This artificial constraint introduces ambiguity and silently fails for valid dates before the Gregorian reform — which may be entirely acceptable in many systems (e.g. calendar tools, academic or historical models).
By contrast, time_plus implements pure Gregorian leap year logic, applicable to any DateTime instance, and extends functionality with precise checks for isLeapMonth and isLeapDay.
// From `time_plus`boolget isLeapYear =>
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
This makes the behavior deterministic, testable, and consistent — critical traits in time logic. No hidden assumptions. No unnecessary domain rules. Just predictable, correct results.
Additionally, time_plus introduces:
isLeapMonth – true only for February in leap years
isLeapDay – true only for February 29th in leap years
This level of expressiveness is missing entirely in time, making time_plus a more complete and accurate solution for calendar-based logic.
Certainly. Here's a concise, professional explanation you can include in your README or documentation:
In Dart, all DateTime values follow the proleptic Gregorian calendar, meaning the Gregorian leap year rules are applied consistently across all years, even those before 1582.
This ensures predictable and mathematically correct behavior, regardless of historical transitions. Most applications don’t model historical calendars — they just need consistent and reliable time math.
By avoiding arbitrary cutoffs like year >= 1582, time_plus aligns with Dart’s native behavior and supports a broader range of valid use cases without introducing unnecessary constraints.
Test Failures and Fragile Behavior
Many of time’s numeric and duration operations fail precision-based tests, particularly for fractional durations and boundary cases. Its logic isn’t designed for edge-case correctness or production-scale stability.
time_plus is built with:
717 tests covering edge cases, conversions, rounding, and regressions.
Extensive real-world validation in multiple production apps.
Clean architecture that ensures correctness by design.