Chrono

⚠️ Work in Progress ⚠️

Chrono is a date and time package for Dart supporting all platforms. It offers strongly-typed data classes for timestamps, timezone-independent (plain/local) date and time, as well as different durations based on the proleptic Gregorian calendar, including:

  • arithmetic operations and conversions
  • parsing and formatting for a subset of ISO 8601, RFC 3339, and CC 18011:2018
    • this is implemented for Instant and the date/time classes, but still missing for durations

The following features are not implemented, but could be added in the future:

  • timezone support (work in progress)
  • customizable parsing and formatting
  • internationalization
  • leap second support

Usage

Since Dart Core also provides classes called DateTime and Duration, you might have to add import 'package:chrono/chrono.dart'; manually. If you want to use classes from both sources, you can use an import prefix:

import 'dart:core' as core;
import 'package:chrono/chrono.dart';

void main() {
  final dartCoreDateTime = core.DateTime.now();
  final chronoDateTime = DateTime.nowInLocalZone();
}

Timestamps

A timestamp is a unique point on the UTC timeline, stored as the duration since the Unix epoch. Chrono provides these classes:

If you want to store the exact point in time when something happened, e.g., when a message in a chat app was sent, one of these classes is usually the best choice. The specific one is up to you, based on how precise you want to be.

Past dates (e.g., birthdays) should rather be represented as a Date. Future timestamps, e.g., for scheduling a meeting, should rather be represented as a DateTime and the corresponding timezone.

Date and Time

DateTime combines Date and Time without timezone information. This is also called plain or local time in other languages.

For example, April 23, 2023 at 18:24:20 happened at different moments in different time zones. In Chrono's classes, it would be represented as:

DateTime classes visualization

Class Encoding
DateTime 2023-04-23T18:24:20
Date 2023-04-23
Year 2023
Month 4
YearMonth 2023-04
MonthDay --04-23
OrdinalDate 2023-113
WeekDate 2023-W16-7
YearWeek 2023-W16
Weekday 7
Time 18:24:20

πŸ“… Date

An ISO 8601 calendar date without timezone information, e.g., April 23, 2023.

Dates can be represented by three different classes:

Class Components Conversion Encoding
Date Year + Month + day of month .asDate 2023-04-23
WeekDate YearWeek + Weekday .asWeekDate 2023-W16-7
OrdinalDate Year + day of year .asOrdinalDate 2023-113
  • Year: year in the ISO 8601 calendar (see the class documentation for representation of BCE years)
  • Month: enum of months
  • Weekday: enum of weekdays
  • YearMonth: Year + Month, e.g., for getting the length of a month honoring leap years
  • MonthDay: Month + `Day`, e.g., for representing birthdays without a year (February 29 is allowed)
  • YearWeek: Year + ISO week from Monday to Sunday

⌚ Time

An ISO 8601 time without timezone information, e.g., 18:24:20.123456.

Since fractional seconds are represented using the fixed-point number Fixed class of the package fixed, there's basically no limit to the precision.

Chrono does not support leap seconds: It assumes that all minutes are 60 seconds long.

Durations

Durations fall into three categories:

  • time-based durations, e.g., 1 hour
  • day-based durations, e.g., 2 days
  • month-based durations, e.g., 3 months

One day is not always 24 hours long (due to daylight savings time changes), and one month is not always 30/31 days long (due to leap years and different month lengths). Chrono offers different classes for each category (listed by inheritance):

The Duration class from Dart Core corresponds to TimeDuration/FractionalSeconds, but limited to microsecond precision.

Some duration classes also have a corresponding …Duration class, e.g., Minutes and MinutesDuration. MinutesDuration is an abstract base class for all time-based durations consisting of a whole number of minutes. Minutes is a concrete class extending MinutesDuration. When constructing or returning values, you should use Minutes directly. However, in parameters, you should accept any MinutesDuration. This way, callers can pass not only Minutes, but also Hours. To convert this to Minutes, call asMinutes

Compound Durations

CompoundDuration and CompoundDaysDuration can represent values with mixed signs, e.g., -1 month and 1 day.

When performing additions and subtractions with compound durations, first the Months are evaluated, then the Days, and finally the FractionalSeconds. For example, adding 1 month and -1 day to 2023-08-31 results in 2023-09-29:

  1. First, 1 month is added, resulting in 2023-09-30. (September only has 30 days, so the day is clamped.)
  2. Then, 1 day is subtracted, resulting in 2023-09-29.

(If the order of operations was reversed, the result would be 2023-09-30.)

Serialization

All timestamp, date, and time classes support JSON serialization. (Support for durations will be added in the future.)

Because there are often multiple possibilities of how to encode a value, Chono lets you choose the format: There are subclasses of JsonConverter for each class called <Chrono type>As<JSON type>JsonConverter, e.g., DateTimeAsIsoStringJsonConverter. These are all converters and how they encode February 3, 2001 at 4:05:06.007008009 UTC:

Converter class Encoding example
InstantAsIsoStringJsonConverter "2001-02-03T04:05:06.007008009Z"
UnixEpochNanosecondsAsIsoStringJsonConverter "2001-02-03T04:05:06.007008009Z"
UnixEpochNanosecondsAsIntJsonConverter 981173106007008009
UnixEpochMicrosecondsAsIsoStringJsonConverter "2001-02-03T04:05:06.007008Z"
UnixEpochMicrosecondsAsIntJsonConverter 981173106007008
UnixEpochMillisecondsAsIsoStringJsonConverter "2001-02-03T04:05:06.007Z"
UnixEpochMillisecondsAsIntJsonConverter 981173106007
UnixEpochSecondsAsIsoStringJsonConverter "2001-02-03T04:05:06Z"
UnixEpochSecondsAsIntJsonConverter 981173106
DateTimeAsIsoStringJsonConverter "2001-02-03T04:05:06.007"
DateAsIsoStringJsonConverter "2001-02-03"
YearAsIsoStringJsonConverter "2001"
YearAsIntJsonConverter 2001
MonthAsIntJsonConverter 2
YearMonthAsIsoStringJsonConverter "2001-02"
MonthDayAsIsoStringJsonConverter "--02-03"
OrdinalDateAsIsoStringJsonConverter "2001-034"
WeekDateAsIsoStringJsonConverter "2001-W05-6"
YearWeekAsIsoStringJsonConverter "2001-W05"
WeekdayAsIntJsonConverter 6
TimeAsIsoStringJsonConverter "04:05:06.007"

json_serializable

If you use json_serializable, you can choose between the following approaches:

  1. Annotate your field with the converter you want to use:

    @JsonSerializable()
    class MyClass {
      const MyClass(this.value);
    
      factory MyClass.fromJson(Map<String, dynamic> json) =>
          _$MyClassFromJson(json);
    
      @DateTimeAsIsoStringJsonConverter()
      final DateTime value;
    
      Map<String, dynamic> toJson() => _$MyClassToJson(this);
    }
    
  2. Specify the converter in the JsonSerializable annotation so it applies to all fields in that class:

    @JsonSerializable(converters: [DateTimeAsIsoStringJsonConverter()])
    class MyClass {
      // ...
    }
    
  3. Create a customized instance of JsonSerializable that you can reuse for all your classes:

    const jsonSerializable = JsonSerializable(converters: [DateTimeAsIsoStringJsonConverter()]);
    
    @jsonSerializable
    class MyClass {
      // ...
    }
    

You can also combine these approaches: For example, create a customized JsonSerializable instance with your defaults (approach 3) and overwrite the converter individual fields (approach 1).

Testing

All functions that are based on the current time accept an optional Clock parameter. Please have a look at the clock package for how this can be used to overwrite the time during tests.

Chrono uses Glados for property-based testing.

Comparison to other languages

Dart: chrono Java/Kotlin Rust
UnixEpochTimestamp<D> β€” β€”
Instant β€” β€”
UnixEpochNanoseconds Instant std::time::{Instant, SystemTime}
UnixEpochMicroseconds β€” β€”
UnixEpochMilliseconds β€” β€”
UnixEpochSeconds β€” β€”
DateTime LocalDateTime chrono::NaiveDateTime
Date LocalDate chrono::NaiveDate
Year Year β€”
YearMonth YearMonth β€”
Month Month chrono::Month
MonthDay MonthDay β€”
YearWeek β€” chrono::IsoWeek
Weekday DayOfWeek chrono::Weekday
Time LocalTime chrono::NaiveTime
DateDuration Period β€”
MonthsDuration, Months, Years β€” chrono::Months
FixedDaysDuration, Days, Weeks β€” chrono::Days
TimeDuration Duration std::time::Duration
Clock (from clock) Clock β€”

Libraries

chrono