日期卷在哪里有用?

Where is Date roll is useful?

我正在阅读 Phobos 文档。有些方法的逻辑有时看不懂

Date roll

Adds the given number of years or months to this Date. A negative number will subtract. The difference between rolling and adding is that rolling does not affect larger units.

也许是 Phobos 没有考虑好,也许我不明白它有什么用。

如果我将 2013-07-01 添加到 200 天,我希望得到 2014 年,而不是 2013 年。

谁能解释一下其中的逻辑?

roll 也出现在 Java 中。我记得唯一一次用这两种语言使用它,尽管是在实现一个小的日期选择器小部件。每件事都有单独的字段:月、日、年,并且您希望用户能够在不增加月份的情况下旋转几天,因为它们是单独的字段。 (假设它设置为 30 号,而他们想要 select 1 号。最快的方法可能是按下向上箭头并让它滚动。)

可能还有其他用途,我现在不记得了。即使在我不久前实现 ical 支持时(遗憾的是,专有的),我也从未使用过 roll 方法,但那里可能有它的潜力。 ical 是日历上重复事件的标准,其中有一些棘手的事情,您可以通过几种不同的方式实现它。

评论里你也问为什么不能add天。原因是添加天数非常简单,并且包含 opBinary:

datetime += 5.days; // works

虽然这在几个月内不起作用,因为几个月的持续时间是可变的。这就是 add 方法派上用场的地方:假设现在是 1 月 30 日,您添加一个月。您可以只添加 31 天,因为 1 月有 31 天。那会让你在 3 月 2 日登陆(我想,在我的脑海里这样做)......但是 1 月 30 日 + 1 个月可能也需要是 2 月 28 日。或者可能是 2 月 29 日。假设你要支付月底到期的月度账单每个月的截止日期之间的天数将发生变化。

add 方法可以解决这个问题,让您可以选择是在最后一天跌倒还是延续到下个月。

不过,我有点认为日子也没有固定长度....考虑 DST 过渡。 (或闰秒,但处理它们会很疯狂)。或许我们可以问问JMD

所以,简短的回答是 roll 专门用于处理日期选择器和类似用例,add 专门用于处理月份和年份,因为 Duration 不能(因为月和年在不知道起点的情况下不会转换为百纳秒)。但我会尝试更深入的回答,以防澄清问题。

DateTimeDateTimeOfDay 专为基于日历的操作而设计。它们与系统时钟无关,它们没有时区的概念,它们在内部将 date/time 的部分作为单独的单位(年、月、日、小时等)保存。因此,当您在 DateTime 上设置 hour 字段时,您实际上只是在调整 DateTime 的成员变量之一。当您向它们添加 Duration 时,必须将其拆分以单独添加到每个单元,因此做一些像添加 7 小时的 Duration 可能增加的不仅仅是 hour 场地。例如

auto dt = DateTime(2015, 8, 2, 20, 0, 0);
dt += hours(7);
assert(dt == DateTime(2015, 8, 3, 3, 0, 0));

并且由于 Duration 在内部以百纳秒为单位保存其值,因此您不一定要添加到特定单位。也就是说,+= hours(7) 并不是专门添加到 hour 字段的。它向整个 DateTime 添加了一些百纳秒,并且必须弄清楚它如何影响每个单位而不是它影响特定单位。例如

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 专门用于添加到特定单元而不影响任何其他单元。正如 Adam 正确确定的那样,它的主要用例是用于日期选择器对话框中的旋转控件等,您最终会在其中将特定时间单位调整一定量,并且您不想影响其他单位,而只是影响那个你在调整。因此,如果您增加 7 小时,您最终不会增加一天,只会增加小时数。

auto dt = DateTime(2015, 8, 2, 20, 0, 0);
dt.roll!"hours"(7);
assert(dt == DateTime(2015, 8, 2, 3, 0, 0));

现在,随着月份和年份的增加,事情确实变得更有趣了,因为它们没有固定的长度。这就是 Duration 不支持它们的原因。您不能说 x 百纳秒等于 y 个月或 z 年。但是,虽然 Duration 对于月份或年份没有意义,但我们仍然希望能够将月份和年份添加到 DateDateTime。所以,我们有 add!"years"add!"months" 来处理这个问题,它允许你控制一些奇怪的事情,比如在 1 月 31 日加上 1 个月应该在 2 月底还是 3 月初结束(因为没有 2 月 31 日)。

None 其中考虑了任何类型的时区。它纯粹是在处理日历时间。 addroll 是纯粹基于日历的操作,而 DateTimeDateTimeOfDay 上实现的 += 是基于日历的手术。它在计算时考虑了年、月、日等(并且在他们的情况下不能这样做,因为单位在他们的成员变量之间分开)。

然后我们有 SysTime。它旨在表示系统的时间(这确实意味着要考虑时区)。但是,避免 DST 相关问题等的唯一方法是始终保持 UTC 时间。因此,SysTime 始终在内部保持 UTC 时间(以百纳秒为单位),并仅在需要时使用 TimeZone 对象(默认情况下为 LocalTime )将其转换为特定时区到。这避免了各种烦人的与时间相关的错误。任何操作系统时间的人都应该使用 SysTime 而不是 DateTimeDateTimeOfDay.

因此,当您使用 += 添加到 SysTime 时,您实际上只是将 Duration 的百纳秒添加到 [=46] 的百纳秒=] 在内部使用。没有任何日历操作发生(如果发生了,你最终会遇到很多与 DST 相关的错误)。 SysTime 根本不是基于日历的。为了进行任何日历操作,SysTime 内部保存的百纳秒必须转换为公历日期表示的任何值(使用 TimeZone 对象来获取考虑时区)。

但是,为了用户友好,SysTime 提供了许多 DateTime 提供的基于日历的属性和功能(例如 yearmonthdayhour 等属性以及 addroll 函数)。为了支持这一点,它基本上最终会在内部转换为 DateDateTime 来执行它们,这意味着要考虑时区,这可能意味着如果使用不当会引入 DST 错误。在某些情况下,它的效率低得令人恼火。例如,如果您有一个名为 st

SysTime 变量
auto year = st.year;
auto month = st.month;
auto day = st.day;

st 转换为 Date 三次,而直接转换为 Date 并从那里获取单位会更有效。因此,用户友好性有点像一把双刃剑。

此外,它提出了 Adam 提出的关于如何添加和滚动处理 DST 的问题。 rollSysTime 上的 add 都在内部处理 DateDateTime,并且考虑到了 DST,因为从 SysTime 转换为DateDateTime 将它从 UTC 转换为它的 TimeZone 对象代表的任何东西。因此,rolladdSysTime 的操作与它们对 DateTime 的操作完全相同 - 因为它们正在转换为 DateTime 并再次转换回来。

但是正如我已经说过的,SysTime 上的 += 只是将 Duration 添加到它的 UTC 时间。因此,无法在 SysTime 上添加小于月的单位并考虑夏令时。您已经明确转换为 DateTime,在其上使用 +=,然后将其转换回 SysTime,这有点难看,但这不是您想要的通常想要。我想我们可以让 SysTime.add 使用小于月的单位,但是你最终只会让人们使用它,并在他们应该使用 += 时以 DST 相关的错误告终,但是不明白其中的区别。

实际上,在这一点上,我怀疑将这些日历功能添加到 SysTime 是否明智,它实际上最终损害了可用性,而不是像预期的那样提供帮助。但我怀疑我们现在是否可以更改它 - 或者更准确地说,虽然我们 可以 更改它,但我质疑在 SysTime 上弃用这些功能并强迫人们更改他们的功能代码带来的痛苦是值得的。不过,如果我可以重做,我会认真考虑不对 SysTime.

进行任何基于日历的操作

无论如何,希望那面文字墙具有足够的启发性,值得您花时间。