Dart Language and Library Newsletter

2017-10-20 @floitschG

Welcome to the Dart Language and Library Newsletter.

DateTime

This week‘s newsletter is all about DateTime. It is shorter than usual, but that’s because there is an accompanying blog post, which contains lots of additional related information: https://medium.com/@florian_32814/date-time-526a4f86badb

As mentioned in the post, we are in the process of refactoring the DateTime class in Dart. We want to make it less error-prone for our developers to use it for calendar-dates. Concretely, we will provide date-specific constructors and methods:

  /**
   * Constructs a [DateTime] instance with the current date.
   *
   * The resulting [DateTime] is in the UTC timezone and has the time set to 00:00.
   */
  factory DateTime.today();

  /**
   * Constructs a [DateTime] for the given date.
   *
   * The resulting [DateTime] is in the UTC timezone and has the time set to 00:00.
   */
  DateTime.date(int year, int month, int day);

  /**
   * Returns this instance suitable for date computations.
   *
   * The resulting DateTime is in the UTC timezone and has the time set to 00:00.
   */
  DateTime toDate() => new DateTime.date(year, month, day);

  /**
   * Returns which day of the year this DateTime represents.
   *
   * The 1st of January returns 1.
   */
  int get dayInYear;
}

As can be seen, these constructors and members encourage the use of DateTime for calendar-dates in a safe way. They all default to UTC, where daylight saving is not an issue.

We furthermore want to make it easier to adjust a given DateTime instance. One common operation is to add a full month or day to a given date-time and to expect that the clock time stays unchanged. Because of daylight saving this is too cumbersome with the current DateTime API. In Dart 2.0 we plan to refactor the existing add method (in a breaking way) to support such operations:

  /**
   * Returns a new [DateTime] instance with the provided arguments added to
   * to [this].
   *
   * Adding a specific number of months will clamp the day, if the resulting
   * day would not be in the same month anymore:
   *
   * ```
   * new DateTime(2017, 03, 31).add(months: 1); // => 2017-04-30.
   * ```
   *
   * Days are added in such a way that the resulting time is the same (if that's
   * possible). When daylight saving changes occur, adding a single [day] might
   * add as little as 23, and as much as 25 hours.
   *
   * The arguments are added in the following way:
   * * Compute a new clock-time using [microseconds], [milliseconds], [seconds],
   *   [minutes], [hours]. At this time, days are assumed to be 24 hours long
   *   (without any daylight saving changes). If any unit overflows or
   *   underflows, the next higher unit is updated correspondingly.
   * * Any over- or underflow days are added to the [days] value.
   * * A new calendar date is computed by adding 12 * [years] + [months] months 
   *   to the current calendar date. If necessary, the date is then clamped.
   * * Once the date is valid, the updated [days] value is added to the
   *   calendar date.
   * * The new date and time values are used to compute a new [DateTime] as if
   *   the [DateTime] constructor was called. Non-existing or ambiguous times
   *   (because of daylight saving changes) are resolved at this point.
   * * Finally, the [duration] is added to the result of this computation.
   *
   * All arguments may be negative.
   * ```
   * var tomorrowTwoHoursEarlier = date.add(days: 1, hours: -2);
   * var lastDayOfMonth = date.with(day: 1).add(month: 1, days: -1);
   * ```
   */
  // All values default to 0 (or a duration of 0).
  DateTime add({int years, int months, int days, int hours,
    int minutes, int seconds, int milliseconds, int microseconds,
    Duration duration});

As can be seen by the documentation, the operation is not trivial anymore. This is a good thing: otherwise our users would need to think about this themselves.

While the change to DateTime.add is breaking, the work-around is simple: dt.add(someDuration) becomes simply dt.add(duration: someDuration).

The second common operation is to replace just one property of the instance. We will provide the with method for this purpose:

  /**
   * Returns a [DateTime] instance with the provided arguments replaced by the
   * new values.
   *
   * The returned DateTime is constructed as if the [DateTime] constructor was
   * called. This means that over and underflows are allowed.
   */
  DateTime with({int year, int month, int day, int hour, int minute, int second,
      int millisecond, int microsecond, bool isUtc});

This change requires a small modification to the Dart language, because with is currently a keyword. It is, however, only used for mixin applications, which means that we can make with a built-in identifier (which is allowed to be a method-name) without complicating Dart's parser.

Finally, we will improve the DateTime.parse method to support more formats (in particular RFC1123), and add a method to print the given date-time as RFC1123 (the format used for cookies).

Altogether, we hope that these changes will make Dart's DateTime class less error-prone, more convenient, and more versatile.