如何计算C#中两个日期之间的月差?
是否有相当于VB的DateDiff()
C#中的方法。我需要找到相隔数年的两个日期之间的月份差异。文档说我可以使用TimeSpan
喜欢:
TimeSpan ts = date1 - date2;
但这给了我几天的数据。我不想将此数字除以 30因为不是每个月都是 30 天,而且由于两个操作数的值彼此相差很大,所以恐怕除以 30 可能会给我一个错误的值。
有什么建议么?
这是一个全面的解决方案DateTimeSpan
, 类似于TimeSpan
,除了它包括除时间组件之外的所有日期组件。
用法:
void Main()
{
DateTime compareTo = DateTime.Parse("8/13/2010 8:33:21 AM");
DateTime now = DateTime.Parse("2/9/2012 10:10:11 AM");
var dateSpan = DateTimeSpan.CompareDates(compareTo, now);
Console.WriteLine("Years: " + dateSpan.Years);
Console.WriteLine("Months: " + dateSpan.Months);
Console.WriteLine("Days: " + dateSpan.Days);
Console.WriteLine("Hours: " + dateSpan.Hours);
Console.WriteLine("Minutes: " + dateSpan.Minutes);
Console.WriteLine("Seconds: " + dateSpan.Seconds);
Console.WriteLine("Milliseconds: " + dateSpan.Milliseconds);
}
输出:
年数:1
月数:5
天数:27
小时:1
分钟:36
秒数:50
毫秒:0
为方便起见,我将逻辑归为DateTimeSpan
struct,但你可以移动方法CompareDates
任何你认为合适的地方。另请注意,哪个日期在另一个之前并不重要。
public struct DateTimeSpan
{
public int Years { get; }
public int Months { get; }
public int Days { get; }
public int Hours { get; }
public int Minutes { get; }
public int Seconds { get; }
public int Milliseconds { get; }
public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
{
Years = years;
Months = months;
Days = days;
Hours = hours;
Minutes = minutes;
Seconds = seconds;
Milliseconds = milliseconds;
}
enum Phase { Years, Months, Days, Done }
public static DateTimeSpan CompareDates(DateTime date1, DateTime date2)
{
if (date2 < date1)
{
var sub = date1;
date1 = date2;
date2 = sub;
}
DateTime current = date1;
int years = 0;
int months = 0;
int days = 0;
Phase phase = Phase.Years;
DateTimeSpan span = new DateTimeSpan();
int officialDay = current.Day;
while (phase != Phase.Done)
{
switch (phase)
{
case Phase.Years:
if (current.AddYears(years + 1) > date2)
{
phase = Phase.Months;
current = current.AddYears(years);
}
else
{
years++;
}
break;
case Phase.Months:
if (current.AddMonths(months + 1) > date2)
{
phase = Phase.Days;
current = current.AddMonths(months);
if (current.Day < officialDay && officialDay <= DateTime.DaysInMonth(current.Year, current.Month))
current = current.AddDays(officialDay - current.Day);
}
else
{
months++;
}
break;
case Phase.Days:
if (current.AddDays(days + 1) > date2)
{
current = current.AddDays(days);
var timespan = date2 - current;
span = new DateTimeSpan(years, months, days, timespan.Hours, timespan.Minutes, timespan.Seconds, timespan.Milliseconds);
phase = Phase.Done;
}
else
{
days++;
}
break;
}
}
return span;
}
}
@KirkWoll 谢谢。但是为什么 DateTimeSpan 返回34
这个日期时差的天数实际上是35
timeanddate.com/date/…
@Deeptechtons,不错的收获。您提请我注意了几个问题,都与开始日期有关31
并且日期“经过”几个月的天数更少。我已经颠倒了逻辑(因此它从早到晚,反之亦然),现在在不修改当前日期的情况下累积月份(因此在几个月之间以更少的天数通过)仍然不完全确定理想的结果是什么应该是比较的时候10/31/2012
至11/30/2012
.现在的结果是1
月。
@KirkWoll 感谢您的更新,也许我还有一些问题让我在一些测试后确认它干得好:)
我写了一个答案stackoverflow.com/a/17537472/1737957到一个类似的问题,该问题测试了建议的答案(并发现它们中的大多数都不起作用)。这个答案是少数有效的答案之一(根据我的测试套件)。在我的回答中链接到 github。
@KirkWoll – 此答案似乎不适用于起始日期的日期值高于截止日期的月份或源日期是闰日的极端情况。尝试2020-02-29
至2021-06-29
– 它返回“1y 4m 1d”,但值应该是“1y 4m 0d”,对吧?
@Enigmativity,感谢您的评论。我已经通过在Phase.Months
部分。似乎对我有用。
非常酷,但请注意这个算法有一些边缘情况问题。如果你碰巧使用DateTime.MinValue
或者DateTime.MaxValue
在日期值中,这将失败。例如System.ArgumentOutOfRangeException': "The added or subtracted value results in an un-representable DateTime."
你可以做
if ( date1.AddMonths(x) > date2 )
这非常简单,对我来说非常完美。在计算从 1 个月末到下个月末天数较少的日期时,我很惊喜地看到它按预期工作。例如.. 2018 年 1 月 31 日 + 1 个月 = 218 年 2 月 28 日
这是更好的解决方案之一。
真正简单高效的解决方案!提出的最佳答案。
如果 date1 = 2018-10-28 和 date2 = 2018-12-21 怎么办?答案是 2。而正确答案应该是 3。因为日期范围是 3 个月。如果我们只计算月数而忽略天数。所以这个答案是不正确的。
我是否遗漏了什么……这是对日期是否至少相差给定月数的真/假检查,而不是计算该月数,这是我认为 o/p 要求的。
如果你想要完整的月数,总是正数(2000-01-15, 2000-02-14 返回 0),考虑到一个完整的月份是你下个月的同一天(类似于年龄计算)
public static int GetMonthsBetween(DateTime from, DateTime to)
{
if (from > to) return GetMonthsBetween(to, from);
var monthDiff = Math.Abs((to.Year * 12 + (to.Month - 1)) - (from.Year * 12 + (from.Month - 1)));
if (from.AddMonths(monthDiff) > to || to.Day < from.Day)
{
return monthDiff - 1;
}
else
{
return monthDiff;
}
}
编辑原因:旧代码在某些情况下不正确,例如:
new { From = new DateTime(1900, 8, 31), To = new DateTime(1901, 8, 30), Result = 11 },
Test cases I used to test the function:
var tests = new[]
{
new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 1, 1), Result = 0 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 1, 2), Result = 0 },
new { From = new DateTime(1900, 1, 2), To = new DateTime(1900, 1, 1), Result = 0 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1900, 2, 1), Result = 1 },
new { From = new DateTime(1900, 2, 1), To = new DateTime(1900, 1, 1), Result = 1 },
new { From = new DateTime(1900, 1, 31), To = new DateTime(1900, 2, 1), Result = 0 },
new { From = new DateTime(1900, 8, 31), To = new DateTime(1900, 9, 30), Result = 0 },
new { From = new DateTime(1900, 8, 31), To = new DateTime(1900, 10, 1), Result = 1 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1901, 1, 1), Result = 12 },
new { From = new DateTime(1900, 1, 1), To = new DateTime(1911, 1, 1), Result = 132 },
new { From = new DateTime(1900, 8, 31), To = new DateTime(1901, 8, 30), Result = 11 },
};
为了避免对其他人造成混淆,我认为这种解决方案是不正确的。使用测试用例:new { From = new DateTime(2015, 12, 31), To = new DateTime(2015, 6, 30), Result = 6 }
测试将失败,因为结果为 5。
添加了我建议的修复的快速要点这里
我不确定我明白了,我的函数应该返回 6:dotnetfiddle.net/MRZNnC
我在这里手动复制了测试用例,它有一个错误。失败的规范应该是:new { From = new DateTime(2015, 12, 31), To = new DateTime(2016, 06, 30), Result = 6 }
. “错误”在于to.Day < from.Day
没有考虑到月份可以在不同的“月份中的某天”结束的代码。在这种情况下,从 2015 年 12 月 31 日到 2016 年 6 月 30 日,整个 6 个月将过去(因为 6 月有 30 天),但您的代码将返回 5。
在我看来,这是预期的行为,或者至少是我所期望的行为。我精确地说,当您到达同一天(或在这种情况下为下个月)时,整整一个月。
我理解,这就是为什么我说“我认为解决方案不正确”。我仍然尝试提供一种替代方案,以减少混乱。在我看来,一个月是一个具有可变天数的日历子单元,当到达最后一天时认为一个月完成是有意义的。作为另一个示例(考虑到 2 月可能有 28/29 天),使用您的代码,2016 年 2 月 29 日和 2017 年 2 月 28 日之间的差异将是 11 个月,这对我来说是“令人惊讶的”。
好吧,我想这只是一个偏好问题。我还打算提到闰日案例来证明我的选择是正确的,但它也不是普遍的。有些国家以一种方式和另一种方式制定了闰日生日的规则,在这种情况下,我站在英国一边:en.wikipedia.org/wiki/February_29#Legal_status.
如果您想考虑本月的最后一个(31 到 30)。不知道这是否适用于所有情况,但是如果您忽略这一天,请检查“迄今为止”是否是该月的最后一天,因为那时您拥有该月可以拥有的所有日子。您应该在 2015,12,31 和 2016,06,30 之间获得 6 个月
我通过 MSDN 检查了该方法在 VB.NET 中的用法,似乎它有很多用法。 C# 中没有这样的内置方法。 (即使这不是一个好主意)您可以在 C# 中调用 VB。
- 添加
Microsoft.VisualBasic.dll
以您的项目作为参考 - 利用
Microsoft.VisualBasic.DateAndTime.DateDiff
在你的代码中
为什么你认为这不是一个好主意?直观地说,我猜这个库对于运行时来说“只是另一个 .NET 库”。请注意,我在这里扮演魔鬼的拥护者,我也不愿意这样做,因为它只是“感觉不对”(有点作弊),但我想知道是否有任何令人信服的技术理由不这样做。
@AdamRalph:完全没有理由不这样做。这些库是在 100% 托管代码中实现的,因此与其他所有内容相同。唯一可以想象的区别是Microsoft.VisualBasic.dll
必须加载模块,但是这样做所花费的时间可以忽略不计。没有理由仅仅因为您选择用 C# 编写程序而在经过全面测试和有用的功能方面欺骗自己。 (这适用于像My.Application.SplashScreen
也一样。)
如果您知道它是用 C# 编写的,您会改变主意吗?它是。同样的逻辑,使用 System.Data 和 PresentationFramework 也是作弊,它的大部分是用 C++/CLI 编写的。
@Cody Gray 和 Hans Passant – 你的论点都令人信服,我同意他们的观点。没有技术理由不使用这个库。我想我之所以不愿意,是因为这些库是专门为 VB 设计的,而且它们带有很多奇怪的包袱,这些包袱是专门为让 VB6 程序员对 .NET 编程感到宾至如归而设计的。一个风险可能是它可能会鼓励其他团队成员使用其中的一些,这会不必要地扭曲他们的 C# 最自然的模式。
@Hans Passant:我没有提及编写库所用的语言,因为我意识到这无关紧要。我也猜到它是用 C# 编写的。此外,我不在乎我使用的任何库是否是用 C++/CLI、C#、VB.NET、F# 或任何其他奇怪而美妙的 .NET 语言编写的(尽管我可能会出于原则在 J# 上划清界限;-) )
@AdamRalph:有什么特别的例子能让我想起那个“奇怪的包袱”吗?还是你说这纯粹是假设性的?是的,它可能会扰乱你的一些 C# 伙伴的思想,他们一直在编写大量的代码来做一些你可以用正确的一行来做的事情using
声明,但我怀疑会有任何严重的损害。
对不起,我的评论是针对丹尼的,我意识到我把它弄混了。
@Cody Gray – 我绝对同意 C# 中的 VB 库可能有许多合法的用例。 “奇怪的行李”的一个例子是DateAndTime.Year(DateTime)
,这似乎只是为了降低 VB6 程序员的入门门槛。如果一个相对新手的任务是对使用的代码进行更改Microsoft.VisualBasic
,他们可能会偶然发现并使用DateAndTime.Year(myDate)
代替myDate.Year
.我意识到这是一个微不足道的例子,而且很可能收益往往会超过这个小风险。我不是想在这里发表声明,只是表达一个担忧。
@AdamRalph:似乎更有可能DateAndTime.Year(myDate)
只是打电话myDate.Year
(或者如果你真的很幸运,可以编译成相同的 IL)。但我理解你的意思,我只是想知道你是否有什么特别的想法。你的感觉并不少见;事实上,这可能是常态。我只是认为这在很大程度上是没有根据的。对 VB 6 存在不必要的轻视,其中太多的轻视已经转移到 VB.NET。
@Cody Gray:同意,正如您所说明的那样,这个例子很简单。这是通过调用这种不寻常的(来自 C# POV)方法而引入的额外代码“噪音”,我很想避免这种方法。在一个组织良好的团队中,这些事情无论如何都会在代码审查中被发现并且可以很容易地避免。顺便说一句 – 我不是要攻击 VB6/VB.NET。我将这些方法描述为“奇怪”只是因为,从 .NET POV 来看,没有理由DateAndTime.Year()
存在,鉴于DateTime
有个Year
财产。它的存在只是为了让 VB.NET 看起来更像 VB6。作为一名前 VB6 程序员,我可以理解这一点 😉
这个答案对我来说的问题是 DateDiff 只比较月份部分,而不考虑月份中的哪一天:“较大的间隔。如果 Interval 设置为 DateInterval.Year,则返回值仅根据 Date1 和 Date2 的年份部分计算。类似地,DateInterval.Month 的返回值纯粹是根据参数的年份和月份部分计算得出的,而 DateInterval.Quarter 的返回值则来自包含两个日期的季度。 – 从:msdn.microsoft.com/en-us/library/b5xbyt6f(v=vs.90).aspx
利用野田时间:
LocalDate start = new LocalDate(2013, 1, 5);
LocalDate end = new LocalDate(2014, 6, 1);
Period period = Period.Between(start, end, PeriodUnits.Months);
Console.WriteLine(period.Months); // 16
定义“月差”,“2010 年 5 月 1 日”和“2010 年 6 月 16 日”的月差是多少? 1.5、1 还是其他?
或者,为了进一步强调这一点,2010 年 12 月 31 日和 2011 年 1 月 1 日之间的月份相差多少?根据白天的不同,这可能只有 1 秒的差异;你会把这算作一个月的差异吗?
这是简单而简短的代码以防万一,您仍然无法得到答案,请参阅此POST stackoverflow.com/questions/8820603/…
丹尼:1个月零15天。 stakx:0个月零1天。关键是要得到月零件。这对我来说似乎很明显,是一个很好的问题。
DateDiff
植入:参考来源.microsoft.com/#Microsoft.VisualBasic/….我立即想到的是日期范围所涵盖的日历月数。月的常见业务含义是指日历时间。
我会为此使用 NodaTime:nodatime.org/2.4.x/userguide/arithmetic–> 搜索“查找两个值之间的句点”。
我认为既然 OP 提到了 vb 的 DateDiff,所有这些问题都得到了回答。答案恰好与 SQL Server 的 datediff 相同。只需回答这个问题……要清楚,它是两个日期之间跨越的月数,(包括)