Welcome to the Dart Language and Library Newsletter.
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.
// 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);
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.
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.
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: 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 :)
--