【问题标题】:Adding two DateTime objects together将两个 DateTime 对象加在一起
【发布时间】:2015-01-21 12:27:05
【问题描述】:

有没有比这更好的方法将一个 DateTime 对象添加到另一个对象:

DateTime first = new DateTime(2000, 1, 1);
DateTime second = new DateTime(11, 2, 5, 10, 10, 11);

DateTime result = first.AddYears(second.Year);
DateTime result = first.AddMonths(second.Month);
...

等等……

在这个例子中我想得到DateTime(2011, 3, 6, 10, 10, 11)

编辑

经过密集的头脑风暴后,似乎没有什么不同的方法,但为了方便起见,可以将其封装在额外的类和 operator+ 中,就像 JonSkeet 的回答中一样

【问题讨论】:

  • @TimSchmelter:为 DateTime 创建 Add/+ 函数/运算符在您建议的方式中没有意义,因为 DateTime 类型没有中性值(“零”) .您不能构造将提供Month=0second 实例,因此对于这种“添加”方式,“1 年”将添加 1 年 1 个月,或者您需要手动将 second 转换为“十二月”(12 个月)以获得真正的 1 年抵消。在您注意到 Days 存在同样的问题后,它变得更加复杂,它并不总是可以转换为简单的“31”。这就是为什么会有TimeSpan 类型

标签: c# datetime add


【解决方案1】:

将两个DateTime 值相加是没有意义的。如果您想表示“11 年、2 个月、5 天、10 小时、10 分钟和 11 秒”,那么您应该表示它。这与 0011-02-05T10:10:11 相同。特别是,例如,您永远无法添加“2 个月零 30 天”。同样,您永远无法仅添加一年,因为日期中的月份和日期值不能为 0。

现在没有 BCL 类型来代表“11 年 [...]”的概念,但您可以相当容易地创建自己的类型。作为替代方案,您可以使用我的具有PeriodNoda Time 项目来实现此目的:

var localDateTime = new LocalDate(2000, 1, 10).AtMidnight();
var period = new PeriodBuilder {
    Years = 11, Months = 2, Days = 5,
    Hours = 10, Minutes = 10, Seconds = 11
}.Build();
var result = localDateTime + period;

与此处提供的其他一些答案相反,您不能为此目的使用TimeSpanTimeSpan 没有任何月份和年份的概念,因为它们的长度不同,而 TimeSpan 表示固定数量的滴答声。 (如果您的最大单位是天,那么您可以使用 TimeSpan,但鉴于您的示例,我假设您需要数月和数年。)

如果您不想使用 Noda Time,我建议您自己伪造一个类似 Period 的课程。这很容易做到——例如:

// Untested and quickly hacked up. Lots more API you'd probably
// want, string conversions, properties etc.
public sealed class Period
{
    private readonly int years, months, days, hours, minutes, seconds;

    public Period(int years, int months, int days,
                  int hours, int minutes, int seconds)
    {
        this.years = years;
        this.months = months;
        this.days = days;
        this.hours = hours;
        this.minutes = minutes;
        this.seconds = seconds;
    }

    public static DateTime operator+(DateTime lhs, Period rhs)
    {
        // Note: order of operations is important here.
        // Consider January 1st + (1 month and 30 days)...
        // what do you want the result to be?
        return lhs.AddYears(rhs.years)
                  .AddMonths(rhs.months)
                  .AddDays(rhs.days)
                  .AddHours(rhs.hours)
                  .AddMinutes(rhs.minutes)
                  .AddSeconds(rhs.seconds);
    }
}

用法:

DateTime first = new DateTime(2000, 1, 1);
Period second = new Period(11, 2, 5, 10, 10, 11);
DateTime result = first + second;

您需要了解DateTime.Add 将如何处理不可能的情况 - 例如,将一个月添加到 1 月 31 日将得到 2 月 28 日/29 日,具体取决于它是否是闰年。

我在此处列出的简单方法(通过中间值)有其缺点,因为这种截断可以在不需要时发生两次(添加年,然后添加月) - 例如,“2 月 29 日 + 1 年+ 1 个月”在逻辑上可能是“3 月 29 日”,但它实际上会以“3 月 28 日”结束,因为在添加月份之前会截断到 2 月 28 日。

尝试找出一种“正确”的日历算术方法是fiendishly difficult,尤其是在某些情况下,人们可能不同意“正确”的答案是什么。在上面的代码中,我选择了简单性和可预测性 - 根据您的实际需求,您可能需要更复杂的东西。

【讨论】:

  • 是的,所以second 变量应该是TimeSpan 类型 - 然后很容易将它添加到DateTime
  • @pwas:不,因为您不能将“11 年零 2 个月”表示为 TimeSpan。
  • @code4life:不,这不可能的。代表“1 个月”的TimeSpan 会是什么样子?里面有多少滴答声?
  • @code4life:是的,但是 OP 显然希望能够表示“11 年零 2 个月”,而 不能 独立地用几天表示一些基准日期。如果您的最大单位是天,那么当然,TimeSpan 没问题。
  • @quetzalcoatl:它会做任何DateTime.Add* 所做的事情,即在必要时截断到月底。这在日期/时间 API 中是很常见的做法。我会添加一个注释,但我不会称其为“邪恶” - 在日期中添加句点是我们在现实世界中每天处理的非常常见要求。跨度>
【解决方案2】:
DateTime first = new DateTime(2000, 1, 1);
DateTime second = new DateTime(11, 2, 5, 10, 10, 11);

DateTime result = new DateTime(first.Ticks + second.Ticks);

【讨论】:

  • 阅读其他解决方案后,您的...最简单的解决方案,最好的
【解决方案3】:

您有一个DateTime,它代表一个时间点。并且您想在其中添加若干年/月/日/小时/分钟/秒。

DataTime 的变化不是一个点,而是一个向量(点之间的差异)。很容易将一个误认为另一个,因为它们通常具有相似的结构。但是,这种类型错误会带来很多痛苦。

避免它并不能解决你的痛苦,但它可以让它变得可控。

将两个DateTime 加在一起就是将两个点加在一起。有点像将洛杉矶的位置添加到纽约。

现在,将 LA 到 NY 到 London 的“向量”相加是有意义的——因为旅行向量是向量,而不是点。而point+vector就是一个点。

所以这意味着你需要创建一个时间向量类型。一个简单的时间跨度是一种选择,但可能不合适:因为您关心的是月、年和日,而不是纳秒或绝对持续时间。

我将向量的名称命名为CalendarVector,因为它代表日历上的运动,而不是时间本身。

简单的第一步是为每个子类型的时间创建一个元组——年、月、日等——然后以任意顺序将它们添加到您的原始DateTime,并带有重载的operator+

你应该支持:

DateTime = DateTime + CalendarVector
CalendarVector = CalendarVector + CalendarVector
CalendarVector = CalendarVector - CalendarVector
CalendarVector = int * CalendarVector
CalendarVector = - CalendarVector
DateTime = DateTime - CalendarVector
CalendarVector = DateTime - DateTime

理想情况下。 CalendarVector + DateTime 重载是可选的,但可能不需要。

但是,这只会让你成功。

剩下的大问题是 CalendarVector 添加不通勤。将 1 个月添加到 DateTime,然后添加 1 天,与添加 1 天然后添加 1 个月不同。

这是基本的。

有“1月31日后1个月是什么意思”的问题,可以回答,但任何合理的回答都不能解决通勤问题。

因此,您计划的构造函数——在其中输入年数、月数、天数、小时数、分钟数秒数——其含义是模棱两可的。

所以一个健壮的解决方案不应该有那个构造函数。

一种解决方案是创建您明确添加在一起的 YearsMonthsDaysHoursMinutesSeconds 类型。它们加在一起的顺序是它们应用于您添加它的DateTime 的顺序。在DateTime 上的最终应用程序之前避免了通勤和“简化”——所以+1 year, +2 days, -1 month, -1 year, -2 days, +1 month 不是零转换。

DateTime-DateTime 有一个相关的问题——它应该返回一个CalendarVector v 这样lhs = rhs + v,但是有多个这样的向量。球坐标也会出现同样的问题——你是指绕地球的短途还是长途?在某些情况下这并不重要——但是你将结果减半以找到中间点。此外,当您接近“世界的另一端”时,您会遇到不连续性。

所以我的建议是维护DateTime 对象的转换列表。 1 year 是一种转换,包括将年份字段添加 1,然后修复其他字段以使其保持一致。这些转换支持否定。加法是从左到右一次应用一个。否定也可以颠倒应用顺序,并且“同类”的相邻变换可以组合(因此+1个月-1个月成为恒等变换,而不是基于下个月的月底的钳位操作),或者不(所以x = x+1 month,那么下一行的x = x-1 monthx = x + 1 month - 1 month一样)。

另一种方法是坚持要求用户提供在这些特殊情况下应该做什么的策略(这种情况……一直都在发生),因为这个问题非常棘手,一个“解决”问题的库可以充其量只是突出问题并迫使客户端程序员思考并做出决定。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-12
    • 2013-10-09
    • 1970-01-01
    • 2013-01-24
    • 2018-11-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多