Dart Language and Library Newsletter (2017-10-20)

133 views
Skip to first unread message

Florian Loitsch

unread,
Oct 20, 2017, 7:03:52 PM10/20/17
to General Dart Discussion

Dart Language and Library Newsletter

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.

Martin Läuter

unread,
Oct 21, 2017, 4:38:38 AM10/21/17
to Dart Misc
Hi all,
  Florian, thanks for the detailed explanation, both on your medium blog and this newletter, esp. for pointing at browser differences I had ignored until now.
I am quite uncomfortable with using one type (as I understood it: DateTime) for both timestamps and calendar calculations, being heavily influenced by a 2015 CppCon talk https://www.youtube.com/watch?v=2rnIHsqABfM on Google's cctz library. You might argue that you want to keep it easy for Dart programmers, but is it worth to introduce domain type mismatches?
  Concerning your description of date.add(), I find it unintuitive to add all offsets up to the hour first, switching then to calendar calculation, but adding the duration only in the end. So we would get different results when adding seconds 1) as parameter to add() and 2) as a Duration, due to clamping. Or I fail to understand the mentions of "clock-time" and "new calendar date".
  Can you clarify how non-existing and ambiguous dates/times are resolved? Does it make a difference if you land on Samoa's 2011-12-30 or in DST's duplicate hour from the future or from the past? Can it decide the validity of a date in a timezone other than the client's when compiled down to JavaScript?
  Greetings,
Martin.

Man Hoang

unread,
Oct 23, 2017, 4:54:28 AM10/23/17
to Dart Misc
Hi Florian,

Should the following utility functions be part of the sdk?

// Returns the number of days of the month specified by [year] and [month].
int daysInMonth(int year, int month);

// Tests if [year] is a leap year.
bool isLeapYear(int year);

Florian Loitsch

unread,
Oct 23, 2017, 5:13:55 AM10/23/17
to mi...@dartlang.org
Hi Martin,

On Sat, Oct 21, 2017 at 10:38 AM 'Martin Läuter' via Dart Misc <mi...@dartlang.org> wrote:
Hi all,
  Florian, thanks for the detailed explanation, both on your medium blog and this newletter, esp. for pointing at browser differences I had ignored until now.
I am quite uncomfortable with using one type (as I understood it: DateTime) for both timestamps and calendar calculations, being heavily influenced by a 2015 CppCon talk https://www.youtube.com/watch?v=2rnIHsqABfM on Google's cctz library. You might argue that you want to keep it easy for Dart programmers, but is it worth to introduce domain type mismatches?
I haven't yet watched the video (but will).
It's a tough choice. Our first attempt at writing DateTime was to have three classes: Date, Time, DateTime (and thought about having ZonedDateTime...).
We quickly realized that for almost every use case this was complete overkill.
Yet, we knew that our simple DateTime class would make problems in corner cases (such as the one described in the blog post). We have lots of comments warning users about misuses.
We didn't intend DateTime to be used as a calendar class, and explicitly didn't make it easy to use it as such.
Unfortunately we have since seen that not making it easy didn't prevent developers from using it as such, and, even worse, because we didn't provide useful methods, they often used in wrong.
The brazilian DST shift did break programs; adding a duration of 24 hours did break programs, ...

Given that DateTime is already used as calendars, we decided we want to make it easier to do the right thing. Clearly, there are still tons of pitfalls, and developers that know about them still need to pay lots of attention to avoid them. In many cases it might even mean to use a third-party package. We will still keep lots of warnings in our comments to let users know that date-time is a messy area, and that our DateTime isn't covering all corner cases.

With the proposed change we will, at least, make UTC dates much more prominent and thus avoid many problems.

  Concerning your description of date.add(), I find it unintuitive to add all offsets up to the hour first, switching then to calendar calculation, but adding the duration only in the end. So we would get different results when adding seconds 1) as parameter to add() and 2) as a Duration, due to clamping. Or I fail to understand the mentions of "clock-time" and "new calendar date".
Yes. Duration is added last. It is a choice, and I don't think there isn't a "correct" solution.
We move Duration last, exactly because it then has a different semantics. Otherwise I can always add by `microSeconds: duration.inMicroseconds`.
There is still time (no pun intended) to change the order of how we evaluate these methods, so we would be happy to receive more feedback.

Other languages have multiple `addX` calls, but we wanted to avoid that. This means that we have to define a sequence. In general, we will recommend to do `add` calls in sequence when there are possible conflicts.
 
  Can you clarify how non-existing and ambiguous dates/times are resolved? Does it make a difference if you land on Samoa's 2011-12-30 or in DST's duplicate hour from the future or from the past? Can it decide the validity of a date in a timezone other than the client's when compiled down to JavaScript?

We will use the underlying system for these computations. This means, unfortunately, that a Dart program can't trust to have the same behavior on all platforms. Even Linux, Windows, and MacOs differ in their results...

As a result, I do expect that we will eventually need a similar library to CCTZ to support more use cases (like different timezones than UTC and Local), and to have deterministic behavior.

Thanks for the feedback.
 
  Greetings,
Martin.

--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.

Florian Loitsch

unread,
Oct 23, 2017, 5:14:58 AM10/23/17
to mi...@dartlang.org
On Mon, Oct 23, 2017 at 10:54 AM Man Hoang <joll...@gmail.com> wrote:
Hi Florian,

Should the following utility functions be part of the sdk?

// Returns the number of days of the month specified by [year] and [month].
int daysInMonth(int year, int month);

// Tests if [year] is a leap year.
bool isLeapYear(int year);

We didn't plan to add them, but we could. Not sure it's worth it.
Please file a feature-request and we can have a look.
--

tatumizer-v0.2

unread,
Oct 23, 2017, 10:08:16 AM10/23/17
to Dart Misc
@Florian: after reading your blog post, I became convinced that there should be 2 different classes - one for UTC, another for local time.
Putting it differently: YOU convinced me there should be 2 different classes :)
I think it directly follows from your (indeed, quite informative) exposition.
In particular, class representing local time would be better off without confusing methods like "add". If you want to "add" minutes or days, convert to UTC time do your manipulations and then convert back.

The impression I got is that UTC time and local time are SO MUCH different  that  hammering them into same class even feels 'artificial', and may lead  to unnecessary confusion.
(Not sure UTC time needs the notions of month, year at all. In fact, UTC time can be just a thin wrapper around "double" value of milliseconds since epoch. I might be mistaken though - as usual :)

Florian Loitsch

unread,
Oct 25, 2017, 6:54:28 AM10/25/17
to mi...@dartlang.org
On Mon, Oct 23, 2017 at 4:08 PM tatumizer-v0.2 <tatu...@gmail.com> wrote:
@Florian: after reading your blog post, I became convinced that there should be 2 different classes - one for UTC, another for local time.
Putting it differently: YOU convinced me there should be 2 different classes :)
That's unfortunate and clearly not what I intended :)
 
I think it directly follows from your (indeed, quite informative) exposition.
In particular, class representing local time would be better off without confusing methods like "add". If you want to "add" minutes or days, convert to UTC time do your manipulations and then convert back.
Converting to UTC first is exactly the behavior we have with the current "Duration" semantics: adding 3 hours in UTC is the equivalent of adding 180 minutes and that can be nicely expressed with the `Duration` object.
The problem is, that there are many use cases where this is not the case. For example a recurrent event wants to be able to add a day (or a week), and not 24 hours (or the equivalent for a week). There will, obviously, be cases where even the new methods won't work: if you have a meeting at 2:30 in the morning, but the DST change makes that time invalid (or duplicated) then there is not a lot we can do.
In short: date-time is hard. If you want to do it right, you need to be aware of many potential issues. However, that doesn't mean that the common case should just be blatantly wrong: adding 24 hours to go to the next day is almost never what our users actually wanted (when there is a DST change).



The impression I got is that UTC time and local time are SO MUCH different  that  hammering them into same class even feels 'artificial', and may lead  to unnecessary confusion.
(Not sure UTC time needs the notions of month, year at all. In fact, UTC time can be just a thin wrapper around "double" value of milliseconds since epoch. I might be mistaken though - as usual :)
Both, UTC and local DateTimes, are only implemented as a small wrapper on top of a int64 (or double in JS).

Yes: you could make the case for distinct classes for UTC and local DateTimes, but the majority of the methods are actually the same, and *all* of them do make sense for both.
The main thing you get from having different classes, is that you could force a type to be UTC. For example, when dealing with calendar-dates, this could be reasonable. However, at that point we might as well add a `CalendarDate` class, instead of using the `UTCDateTime` for it. Once you do that, you would also want to add other classes...
We think that users that have to deal with these difficulties should use a separate package. We would never recommend to use just the `DateTime` class to implement a calendar (like the one from Google). It's not powerful enough and doesn't deal with all the corner-cases.

For many other uses, the `DateTime` class is, however, perfectly fine. The main problem we saw, was that local `DateTime`s were used as calendar-dates. By providing primitives that force users into UTC, this should be much less of a problem now.

tatumizer-v0.2

unread,
Oct 25, 2017, 10:39:55 AM10/25/17
to Dart Misc
Maybe the notion of "view" can make things more intuitive? E.g. you have a moment of time, and can establish different views on it (like: "as viewed from timezone X").
Sure, it's just wording, but maybe for intuition, the wording is more than "just wording"?
And yeah, it will make programming more verbose, but maybe in a bug-prone topic like this, "more verbose" is a good thing?
It's just a humble idea for consideration :)
We saw on several occasions (while discussing various topics on this mailing list) that the notion of "view" might be useful. Maybe now we have another case like this?.

Florian Loitsch

unread,
Oct 25, 2017, 12:04:23 PM10/25/17
to mi...@dartlang.org
Yes. "Views" are something that would work.
I/We have some ideas, but they aren't yet on the short list.

--

Randal L. Schwartz

unread,
Oct 25, 2017, 7:20:11 PM10/25/17
to 'Florian Loitsch' via Dart Misc
>>>>> "Florian" == 'Florian Loitsch' via Dart Misc <mi...@dartlang.org> writes:

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

I have three interesting links in the comments for that article. Check
it out!

--
Randal L. Schwartz - Stonehenge Consulting Services, Inc. - +1 503 777 0095
<mer...@stonehenge.com> <URL:http://www.stonehenge.com/merlyn/>
Perl/Unix/Dart consulting, Technical writing, Comedy, etc. etc.
Still trying to think of something clever for the fourth line of this .sig
Reply all
Reply to author
Forward
0 new messages