So, the short answer is that Adam is right, and roll
is there specifically for handling date pickers and similar use cases, and add
is there specifically to handle months and years, because Duration
can't (since months and years don't convert to hecto-nanoseconds without knowing a starting point). But I'll take a stab at it a more in depth answer in case that clarifies things.
DateTime
, Date
, and TimeOfDay
were designed for calendar-based operations. They have no relation to the system clock, they have no concept of time zone, and they hold the parts of the date/time as separate units (year, month, day, hour, etc.) internally. So, when you set the hour
field on a DateTime
, you are literally just adjusting one of DateTime
's member variables. And when you add a Duration
to them, that has to be split up to add to each unit individually and thus doing something like adding a Duration
of 7 hours could increment more than just the hour
field. e.g.
auto dt = DateTime(2015, 8, 2, 20, 0, 0);
dt += hours(7);
assert(dt == DateTime(2015, 8, 3, 3, 0, 0));
And since Duration
holds its value internally in hecto-nanoseconds, you're not necessarily adding to a specific unit anyway. i.e. It's not like += hours(7)
is specifically adding to the hour
field. It's adding some number of hecto-nanoseconds to the whole DateTime
and has to figure out how that affects each of the units rather than it affecting a specific unit. e.g.
auto dt = DateTime(2015, 8, 2, 20, 0, 0);
dt += hours(7) + minutes(5) + seconds(22);
assert(dt == DateTime(2015, 8, 3, 3, 5, 22));
roll
on the other hand is specifically for adding to a specific unit without affecting any of the others. As Adam correctly determined, its primary use case was for spin controls and the like in date picker dialogs, where you end up adjusting a particular time unit by some amount, and you don't want to affect the other units, just the one that you're tweaking. So, if you add 7 hours, you don't end up with the day incrementing, just the hours.
auto dt = DateTime(2015, 8, 2, 20, 0, 0);
dt.roll!"hours"(7);
assert(dt == DateTime(2015, 8, 2, 3, 0, 0));
Now, things do get a bit funnier with months and years, because they don't have a fixed length. And that's why Duration
does not support them. You can't say that x
hecto-nanoseconds is equivalent to y
months or z
years. But while Duration
doesn't make sense with regards to months or years, we still want to be able to add months and years to a Date
or DateTime
. So, we have add!"years"
and add!"months"
to handle that, and it allows you to control some of the oddities like whether adding 1 month to January 31st should end up at the end of February or the beginning of March (since there is no February 31st).
None of this takes any kind of time zone into account. It's purely dealing with calendar time. add
and roll
are purely calendar-based operations, and +=
as implemented on DateTime
, Date
, and TimeOfDay
is a calendar-based operation. It is taking the years, months, days, etc. into account when it does its calculations (and can't do otherwise in their case, because the units are split out among their member variables).
And then we have SysTime
. It's intended to represent the system's time (which does mean taking the time zone into account). However, the only way to avoid DST-related issues and the like is to always keep the time in UTC. So, SysTime
always keeps its time internally in UTC (in hecto-nanoseconds) and uses a TimeZone
object (LocalTime
by default) to convert that to a particular time zone only when it needs to. This avoids all kinds of annoying time-related bugs. And anyone operating on the system's time should be using SysTime
rather than DateTime
, Date
, or TimeOfDay
.
So, when you add to a SysTime
using +=
, you're really just adding the hecto-nanoseconds from the Duration
to the hecto-nanoseconds that SysTime
uses internally. No calendar operations occur whatsoever (and if they did, you'd end up with lots of DST-related bugs). SysTime
is not inherently calendar-based at all. In order to do any calendar operations, a SysTime
's hecto-nanoseconds that it holds internally must be converted to whatever that represents in terms of a date on the Gregorian calendar (using the TimeZone
object to take the time zone into account).
However, in order to be user-friendly, SysTime
provides many of the calendar-based properties and functions that DateTime
does (e.g. the year
, month
, day
, hour
, etc. properties and the add
and roll
functions). To support that, it basically ends up converting to Date
or DateTime
internally to do them, which means taking the time zone into account and which potentially means introducing DST bugs if they're not used correctly. And in some cases, it's annoyingly inefficient. For example, if you have a SysTime
variable called st
auto year = st.year;
auto month = st.month;
auto day = st.day;
converts st
to Date
three times, whereas it would have been more efficient to just convert to a Date
explicitly and get the units from there. So, that user-friendliness is a bit of a double-edged sword.
Also, it raises the question that Adam raised about how adding and rolling deal with DST. roll
and add
on SysTime
both deal with a Date
or DateTime
internally, and that takes DST into account, because converting from SysTime
to Date
or DateTime
converts it from UTC to whatever its TimeZone
object represents. So, roll
and add
operate on SysTime
exactly how they'd operate on DateTime
- because they're converting to DateTime
and back again.
But as I already said, +=
on SysTime
just adds the Duration
to its UTC time. So, there is no way on a SysTime
to add units smaller than months to it and take DST into account. You'd have explicitly convert to a DateTime
, use +=
on that, and then convert it back to a SysTime
, which is a bit ugly, but it's not something that you'd normally want. I suppose that we could make SysTime.add
work with units smaller than months, but then you'd just end up with folks using that and ending up with DST-related bugs when they should have been using +=
but didn't understand the difference.
Really, at this point, I question that adding those calendar functions to SysTime
was wise and that it's actually ultimately harming usability rather than helping as was intended. But I doubt that we could change it at this point - or more accurately, while we could change it, I question that deprecating those functions on SysTime
and forcing folks to change their code would be worth the pain that it would cause. If I could redo it though, I'd seriously consider not putting any calendar-based operations on SysTime
.
In any case, hopefully that wall of text was enlightening enough to be worth your time.