如何获得年/月/周/日两个date之间的差异?

如何有效地获得年/月/周/日两个date的差异?

例如。 两个date之间的差异是1年,2个月,3周,4天。

差值表示两个date之间的年份,月份,星期和date的计数。

这实际上是相当棘手的。 不同的总天数可能会导致相同的结果。 例如:

  • 2008年6月19日至2010年6月19日= 2年,也是365 * 2天

  • 2006年6月19日至2008年6月19日= 2年,闰年也为365 + 366天

你可能要减去几年,直到你有两个不到一年的时间。 然后减去几个月,直到你有两个不到一个月的date。

进一步的混淆:减去(或增加)月份是非常棘手的,因为你可能会从“3月30日”的date开始 – 比这更早一个月?

更进一步的混淆( 可能不相关):即使一天也不总是24小时。 夏令时任何人?

甚至进一步的混乱(几乎肯定相关):即使一分钟也不总是60秒。 闰秒高度混淆…

我现在没有时间做出正确的方式来做这件事 – 这个答案主要是为了提出一个事实,那就是听起来不那么简单。

编辑:不幸的是,我不会有足够的时间来充分回答这个问题。 我build议你首先定义一个代表Period的结构体:

 public struct Period { private readonly int days; public int Days { get { return days; } } private readonly int months; public int Months { get { return months; } } private readonly int years; public int Years { get { return years; } } public Period(int years, int months, int days) { this.years = years; this.months = months; this.days = days; } public Period WithDays(int newDays) { return new Period(years, months, newDays); } public Period WithMonths(int newMonths) { return new Period(years, newMonths, days); } public Period WithYears(int newYears) { return new Period(newYears, months, days); } public static DateTime operator +(DateTime date, Period period) { // TODO: Implement this! } public static Period Difference(DateTime first, DateTime second) { // TODO: Implement this! } } 

我build议你首先实现+运算符,它应该通知Difference方法 – 你应该确保first + (Period.Difference(first, second)) == second为所有的first / second值。

首先写出一整套unit testing – 最初是“简单”的情况,然后转向棘手的闰年。 我知道一般的做法是一次写一个testing,但在开始任何实现工作之前,我会亲自对其中的一部分进行头脑风暴。

让自己有一天正确地实施这一点。 这是棘手的东西。

请注意,我在这里省略了几周 – 至less这个价值很容易,因为它总是7天。 所以给定(积极)的时期,你会有:

 int years = period.Years; int months = period.Months; int weeks = period.Days / 7; int daysWithinWeek = period.Days % 7; 

(我build议你避免考虑消极的时期 – 确保所有的事情都是积极的。)

部分原因是为了试图正确地回答这个问题(也许甚至是明确地…),部分是为了检查可以信任的代码粘贴到SO上,还有一部分是作为查找错误的练习,我创build了一堆对这个问题进行unit testing,并将它们应用到这个页面的许多build议的解决scheme和一些重复的部分。

结果是确凿的:没有一个代码贡献准确地回答了这个问题。 更新:我现在有四个正确的解决这个问题,包括我自己的,见下面的更新。

代码testing

从这个问题,我testing了以下用户的代码:Mohammed Ijas Nasirudeen,ruffin,Malu MN,Dave,pk。,Jani,lc。

这些都是在他们的代码中提供了所有三年,几个月和几天的答案。 请注意,其中有两个,戴夫和贾尼,给出了总天数和月份,而不是总数在计算几个月后的剩余月份,以及在计算月份后的总天数。 我认为就OP而言,答案是错误的,但在这些情况下,unit testing显然不会告诉你什么。 (请注意,在Jani的情况下,这是我的错误,他的代码实际上是正确的 – 请参阅下面的更新4)

Jon Skeet,Aghasoleimani,Mukesh Kumar,Richard,Colin,sheir,只是我看到Chalkey和Andy的回答是不完整的。 这并不意味着答案不是很好,实际上其中有几个对于解决scheme是有益的贡献。 这只是意味着没有代码需要两个DateTime并返回3个int我可以正确testing。 其中有四个是关于使用TimeSpan 。 正如许多人所提到的, TimeSpan不会返回任何大于天数的计数。

我testing的其他答案是从

  • 问题3054715 – LukeH,ho1和这个。 ___curious_geek
  • 问题6260372 – Chuck Rostance和Jani(与这个问题相同的答案)
  • 问题9(!) – Dylan Hayes,Jon和Rajeshwaran SP

这个.___ curious_geek的答案是他链接到的一个页面上的代码,我不认为他是这样写的。 Jani的答案是唯一一个使用.Net的外部库时间库。

所有这些问题的所有其他答案似乎是不完整的。 问题9是关于年龄的年龄,三个答案是超过了简短和计算的年,月和日的答案。 如果有人发现这个问题的重复,请让我知道。

我如何testing

很简单:我做了一个界面

 public interface IDateDifference { void SetDates(DateTime start, DateTime end); int GetYears(); int GetMonths(); int GetDays(); } 

对于每一个答案,我写了一个类实现这个接口,使用复制和粘贴的代码作为基础。 当然,我不得不用不同的签名等来调整函数,但我试图做最less的编辑,保留所有的逻辑代码。

我在一个抽象的generics类中写了一堆NUnittesting

 [TestFixture] public abstract class DateDifferenceTests<DDC> where DDC : IDateDifference, new() 

并添加了一个空的派生类

 public class Rajeshwaran_S_P_Test : DateDifferenceTests<Rajeshwaran_S_P> { } 

到每个IDateDifference类的源文件。

NUnit足够聪明,做剩下的事情。

testing

其中一些是事先写好的,其余的则是为了尝试和打破看似有效的实现。

 [TestFixture] public abstract class DateDifferenceTests<DDC> where DDC : IDateDifference, new() { protected IDateDifference ddClass; [SetUp] public void Init() { ddClass = new DDC(); } [Test] public void BasicTest() { ddClass.SetDates(new DateTime(2012, 12, 1), new DateTime(2012, 12, 25)); CheckResults(0, 0, 24); } [Test] public void AlmostTwoYearsTest() { ddClass.SetDates(new DateTime(2010, 8, 29), new DateTime(2012, 8, 14)); CheckResults(1, 11, 16); } [Test] public void AlmostThreeYearsTest() { ddClass.SetDates(new DateTime(2009, 7, 29), new DateTime(2012, 7, 14)); CheckResults(2, 11, 15); } [Test] public void BornOnALeapYearTest() { ddClass.SetDates(new DateTime(2008, 2, 29), new DateTime(2009, 2, 28)); CheckControversialResults(0, 11, 30, 1, 0, 0); } [Test] public void BornOnALeapYearTest2() { ddClass.SetDates(new DateTime(2008, 2, 29), new DateTime(2009, 3, 1)); CheckControversialResults(1, 0, 0, 1, 0, 1); } [Test] public void LongMonthToLongMonth() { ddClass.SetDates(new DateTime(2010, 1, 31), new DateTime(2010, 3, 31)); CheckResults(0, 2, 0); } [Test] public void LongMonthToLongMonthPenultimateDay() { ddClass.SetDates(new DateTime(2009, 1, 31), new DateTime(2009, 3, 30)); CheckResults(0, 1, 30); } [Test] public void LongMonthToShortMonth() { ddClass.SetDates(new DateTime(2009, 8, 31), new DateTime(2009, 9, 30)); CheckControversialResults(0, 1, 0, 0, 0, 30); } [Test] public void LongMonthToPartWayThruShortMonth() { ddClass.SetDates(new DateTime(2009, 8, 31), new DateTime(2009, 9, 10)); CheckResults(0, 0, 10); } private void CheckResults(int years, int months, int days) { Assert.AreEqual(years, ddClass.GetYears()); Assert.AreEqual(months, ddClass.GetMonths()); Assert.AreEqual(days, ddClass.GetDays()); } private void CheckControversialResults(int years, int months, int days, int yearsAlt, int monthsAlt, int daysAlt) { // gives the right output but unhelpful messages bool success = ((ddClass.GetYears() == years && ddClass.GetMonths() == months && ddClass.GetDays() == days) || (ddClass.GetYears() == yearsAlt && ddClass.GetMonths() == monthsAlt && ddClass.GetDays() == daysAlt)); Assert.IsTrue(success); } } 

大多数的名字都有点傻,并没有真正解释为什么代码可能会通过testing,但是看两个date和答案应该足以理解testing。

有两个函数可以执行所有的AssertCheckResults()CheckControversialResults() 。 这些工作很好地保存input并给出正确的结果,但不幸的是,它们使得难以确切地发现错误(因为CheckControversialResults()Assert将以“Expected true”失败,而不是告诉你哪个值是不正确的。任何人都有一个更好的方法来做到这一点(避免每次写同样的检查,但有更多有用的错误信息),请让我知道。

CheckControversialResults()用于几种情况,对于什么是正确的,似乎有两种不同的观点。 我有我自己的意见,但我认为我应该在我接受这里的自由。 其中的要点是决定2月29日之后的一年是2月28日还是3月1日。

这些testing是问题的关键,而且很可能有错误,所以如果你发现错误的话,请做出评论。 听取其他testing的一些build议来检查未来答案的答案也是很好的。

没有testing涉及一天的时间 – 所有DateTime都是在午夜。 包括时间在内,只要明确了上下几天工作(我认为是如此),可能会出现更多的缺陷。

结果

完整的记分牌结果如下:

 ChuckRostance_Test 3 failures SSSFSSFSF Dave_Test 6 failures FFSFFFFSS Dylan_Hayes_Test 9 failures FFFFFFFFF ho1_Test 3 failures FFSSSSFSS Jani_Test 6 failures FFSFFFFSS Jon_Test 1 failure SSSSSSFSS lc_Test 2 failures SSSSSFFSS LukeH_Test 1 failure SSSSSSFSS Malu_MN_Test 1 failure SSSSSSSFS Mohammed_Ijas_Nasirudeen_Test 2 failures FSSFSSSSS pk_Test 6 failures FFFSSFFFS Rajeshwaran_S_P_Test 7 failures FFSFFSFFF ruffin_Test 3 failures FSSFSSFSS this_curious_geek_Test 2 failures FSSFSSSSS 

但请注意,Jani的解决scheme实际上是正确的,并通过了所有testing – 请参阅下面的更新4。

这些列按照testing名称的字母顺序排列:

  • AlmostThreeYearsTest
  • AlmostTwoYearsTest
  • BasicTest
  • BornOnALeapYearTest
  • BornOnALeapYearTest2
  • LongMonthToLongMonth
  • LongMonthToLongMonthPenultimateDay
  • LongMonthToPartWayThruShortMonth
  • LongMonthToShortMonth

三个答案都只失败了一次,Jon,LukeH和Manu MN。 记住这些testing可能是专门为解决这些答案中的缺陷而编写的。

每个testing都至less通过一段代码,稍微让人确信没有一个testing是错误的。

一些答案失败了很多testing。 我希望没有人觉得这是对这个海报的努力的谴责。 首先,成功的数量是相当随意的,因为testing并不能均匀覆盖问题空间的问题领域。 其次,这不是生产代码 – 答案是张贴的,所以人们可以从中学习,而不是完全复制到他们的程序。 代码失败了很多testing仍然可以有很好的想法。 至less有一个失败了很多testing有一个小错误,我没有修复。 我很感激任何花时间与其他人分享他们的作品,因为这个项目非常有趣。

我的结论

有三种:

  1. 日历很难。 我写了九个testing,其中三个可能有两个答案。 我只有一个答案的一些testing可能不会被一致同意。 只是在我们说“1个月后”或“2年前”这个问题时想到的意思,在很多情况下都是非常棘手的。 而且这些代码都不得不处理所有复杂的事情,比如闰年。 所有这些都使用库代码来处理date。 如果你想象在几天,几周,几个月和几年中写出“时间”这个“规范”,那就是各种各样的事情。 因为小学以后我们都很了解,并且每天都用它,所以我们对许多特质都是盲目的。 这个问题不是学术问题 – 在债券和其他金融产品的会计软件中,将时间段分解成几年,几个月和几个月是必不可less的。

  2. 编写正确的代码很难。 有很多的错误。 在稍微比较模糊的话题或不太受欢迎的问题上,比没有被评论者指出的问题存在的机会多得多,比这个问题要高得多。 你永远不应该永远不要把代码从你的程序中复制到你的程序中,而不能完全理解它的function。 另一方面,您可能不应该在准备好复制和粘贴的答案中编写代码,而是使用智能和富有performance力的伪代码来让人们了解解决scheme并实现自己的版本(使用自己的错误!)

  3. unit testing是有帮助的。 我仍然有意向我发布自己的解决scheme(当其他人find隐藏的,不正确的假设时)!这样做是通过将它们转换为unit testing来“拯救错误”的一个很好的例子,修复下一个版本的代码。

更新

整个项目现在在https://github.com/jwg4/date-difference这包括我自己的尝试;jwg.cs ,它通过我目前所有的testing,包括一些新的检查适当的时间处理。 随意添加更多的testing来打破这个和其他的实现或更好的代码来回答这个问题。

更新2

@MattJohnson增加了一个使用Jon Skeet的NodaTime的实现。 它通过了所有当前的testing。

更新3

@ KirkWoll的答案差异在两个date之间的月份已添加到github上的项目。 它通过了所有当前的testing。

更新4

@贾尼在评论中指出我错误地使用了他的代码。 他确实提出了正确计算年,月和日的方法(除了一些计算总的天数和月数,而不是余数),但我错误地使用了我的testing代码中的错误。 我已经纠正了我的包装,他的代码,现在通过了所有的testing。 现在有四个正确的解决scheme,其中贾尼是第一个。 两个使用库(Intenso.TimePeriod和NodaTime)和两个从头开始编写。

闰年和不平衡的月份实际上使这是一个不平凡的问题。 我敢肯定,有人可以提出一个更有效的方法,但这里有一个选项 – 首先近似于小方面,并调整(未经testing):

 public static void GetDifference(DateTime date1, DateTime date2, out int Years, out int Months, out int Weeks, out int Days) { //assumes date2 is the bigger date for simplicity //years TimeSpan diff = date2 - date1; Years = diff.Days / 366; DateTime workingDate = date1.AddYears(Years); while(workingDate.AddYears(1) <= date2) { workingDate = workingDate.AddYears(1); Years++; } //months diff = date2 - workingDate; Months = diff.Days / 31; workingDate = workingDate.AddMonths(Months); while(workingDate.AddMonths(1) <= date2) { workingDate = workingDate.AddMonths(1); Months++; } //weeks and days diff = date2 - workingDate; Weeks = diff.Days / 7; //weeks always have 7 days Days = diff.Days % 7; } 

对于年/月/周的正确差分计算,必须考虑CultureInfo日历

  • 飞跃与非飞跃的年份
  • 几个月不同的天数
  • 不同的周数(根据星期的第一天和日历周的规则而不同)

.NET时间库的DateDiff类尊重所有这些因素:

 // ---------------------------------------------------------------------- public void DateDiffSample() { DateTime date1 = new DateTime( 2009, 11, 8, 7, 13, 59 ); Console.WriteLine( "Date1: {0}", date1 ); // > Date1: 08.11.2009 07:13:59 DateTime date2 = new DateTime( 2011, 3, 20, 19, 55, 28 ); Console.WriteLine( "Date2: {0}", date2 ); // > Date2: 20.03.2011 19:55:28 DateDiff dateDiff = new DateDiff( date1, date2 ); // differences Console.WriteLine( "DateDiff.Years: {0}", dateDiff.Years ); // > DateDiff.Years: 1 Console.WriteLine( "DateDiff.Quarters: {0}", dateDiff.Quarters ); // > DateDiff.Quarters: 5 Console.WriteLine( "DateDiff.Months: {0}", dateDiff.Months ); // > DateDiff.Months: 16 Console.WriteLine( "DateDiff.Weeks: {0}", dateDiff.Weeks ); // > DateDiff.Weeks: 70 Console.WriteLine( "DateDiff.Days: {0}", dateDiff.Days ); // > DateDiff.Days: 497 Console.WriteLine( "DateDiff.Weekdays: {0}", dateDiff.Weekdays ); // > DateDiff.Weekdays: 71 Console.WriteLine( "DateDiff.Hours: {0}", dateDiff.Hours ); // > DateDiff.Hours: 11940 Console.WriteLine( "DateDiff.Minutes: {0}", dateDiff.Minutes ); // > DateDiff.Minutes: 716441 Console.WriteLine( "DateDiff.Seconds: {0}", dateDiff.Seconds ); // > DateDiff.Seconds: 42986489 // elapsed Console.WriteLine( "DateDiff.ElapsedYears: {0}", dateDiff.ElapsedYears ); // > DateDiff.ElapsedYears: 1 Console.WriteLine( "DateDiff.ElapsedMonths: {0}", dateDiff.ElapsedMonths ); // > DateDiff.ElapsedMonths: 4 Console.WriteLine( "DateDiff.ElapsedDays: {0}", dateDiff.ElapsedDays ); // > DateDiff.ElapsedDays: 12 Console.WriteLine( "DateDiff.ElapsedHours: {0}", dateDiff.ElapsedHours ); // > DateDiff.ElapsedHours: 12 Console.WriteLine( "DateDiff.ElapsedMinutes: {0}", dateDiff.ElapsedMinutes ); // > DateDiff.ElapsedMinutes: 41 Console.WriteLine( "DateDiff.ElapsedSeconds: {0}", dateDiff.ElapsedSeconds ); // > DateDiff.ElapsedSeconds: 29 // description Console.WriteLine( "DateDiff.GetDescription(1): {0}", dateDiff.GetDescription( 1 ) ); // > DateDiff.GetDescription(1): 1 Year Console.WriteLine( "DateDiff.GetDescription(2): {0}", dateDiff.GetDescription( 2 ) ); // > DateDiff.GetDescription(2): 1 Year 4 Months Console.WriteLine( "DateDiff.GetDescription(3): {0}", dateDiff.GetDescription( 3 ) ); // > DateDiff.GetDescription(3): 1 Year 4 Months 12 Days Console.WriteLine( "DateDiff.GetDescription(4): {0}", dateDiff.GetDescription( 4 ) ); // > DateDiff.GetDescription(4): 1 Year 4 Months 12 Days 12 Hours Console.WriteLine( "DateDiff.GetDescription(5): {0}", dateDiff.GetDescription( 5 ) ); // > DateDiff.GetDescription(5): 1 Year 4 Months 12 Days 12 Hours 41 Mins Console.WriteLine( "DateDiff.GetDescription(6): {0}", dateDiff.GetDescription( 6 ) ); // > DateDiff.GetDescription(6): 1 Year 4 Months 12 Days 12 Hours 41 Mins 29 Secs } // DateDiffSample 

DateDiff也计算Quarters的差异。

怎么样使用System.Data.Linq命名空间和它的SqlMethods.DateDiffMonth方法?

例如,说:

 DateTime starDT = {01-Jul-2009 12:00:00 AM} DateTime endDT = {01-Nov-2009 12:00:00 AM} 

然后:

 int monthDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffMonth(startDT, endDT); 

==> 4

SqlMethods类中还有其他DateDiff静态方法。

减去两个DateTime实例,为您提供具有Days属性的TimeSpan 。 (例如在PowerShell中):

 PS>([datetime] :: today  -  [datetime]“2009-04-07”)


天:89
小时:0
分钟:0
秒:0
毫秒:0
蜱:76896000000000
总计:89
总时数:2136
 TotalMinutes:128160
 TotalSeconds:7689600
 TotalMilliseconds:7689600000

将天转换为几年或几周是相对容易的(一年中的天数可能是365,365.25,…取决于上下文)。 几个月更难,因为没有基准date,你不知道哪个月的长度适用。

假设你想从你的基准date开始,你可以在计算第一年(检查闰年),然后月份长度(从startDate.Month索引),然后几周(剩余天数除以7),然后天数(余数)。

有很多边缘案例要考虑,例如2005-03-01是从2004-03-01开始的一年,从2004年2月29日开始这取决于你是什么意思的“年份”。

如果您减去两个DateTime,实例,则将返回一个TimeSpan实例,它将表示这两个date之间的差异。

那么,@Jon Skeet,如果我们不担心得到更多的粒度比天(而且仍然将日子变成更大的单位,而不是一天的总数),按照OP,它在C#中确实不是那么难。 datemath如此困难的原因是每个组合单位的单位数量经常发生变化。 想象一下,如果每加仑3加仑的气体只有3夸脱,但每12日是7,除了星期五,当…

幸运的是,date只是通过最大整数函数的漫长过程。 这些疯狂的例外是令人发狂的,除非你已经一路走过那个诡异的单位,什么时候又不是什么大事了。 如果你出生在12/25/1900,那么无论闰年或秒钟还是夏令时,你在12/25/2000都还是100。 一旦你淹没了构成最后一个复合单位的百分比,你就恢复了团结。 你已经添加了一个,并重新开始。

这只是说,如果你要做几个月到几天,唯一奇怪的单位就是月份(几天)。 如果您需要从月份值中借款来处理您减去的天数,那么您只需要知道上个月的天数。 没有其他exception值。

和C#给你在System.DateTime.DaysInMonth(intYear,intMonth)。

(如果你的月份比你的月份小,那就没有问题了,每年有12个月。)

如果我们更加细化,相同的交易……你只需要知道最后(复合单位)有多less(小单位)。 一旦你过去了,你会得到更多的整数值(复合单位)。 然后减去你从那里开始你错过了多less个小单位。然后加回你有多less人通过复合单位中断了你的Now。

所以这是我从第一次减去两个date后得到的结果。 它可能工作。 希望有用。

(编辑:更改NewMonth> OldMonth检查NewMonth> = OldMonth,因为我们不需要借一个,如果几个月是相同的(同上天)。也就是说,2011年11月11日减去2010年11月9日是给-1年,12个月,2天(即2天,但皇室我们借,当皇室不需要。)

(编辑:必须检查月=月,当我们需要借用天减去dteThen.Day从dteNow.Day&dteNow.Day <dteThen.Day,因为我们不得不减去一年得到11个月和额外的天好吧,所以有几个exception点; ^我觉得我现在很近。)

 private void Form1_Load(object sender, EventArgs e) { DateTime dteThen = DateTime.Parse("3/31/2010"); DateTime dteNow = DateTime.Now; int intDiffInYears = 0; int intDiffInMonths = 0; int intDiffInDays = 0; if (dteNow.Month >= dteThen.Month) { if (dteNow.Day >= dteThen.Day) { // this is a best case, easy subtraction situation intDiffInYears = dteNow.Year - dteThen.Year; intDiffInMonths = dteNow.Month - dteThen.Month; intDiffInDays = dteNow.Day - dteThen.Day; } else { // else we need to substract one from the month diff (borrow the one) // and days get wacky. // Watch for the outlier of Month = Month with DayNow < DayThen, as then we've // got to subtract one from the year diff to borrow a month and have enough // days to subtract Then from Now. if (dteNow.Month == dteThen.Month) { intDiffInYears = dteNow.Year - dteThen.Year - 1; intDiffInMonths = 11; // we borrowed a year and broke ONLY // the LAST month into subtractable days // Stay with me -- because we borrowed days from the year, not the month, // this is much different than what appears to be a similar calculation below. // We know we're a full intDiffInYears years apart PLUS eleven months. // Now we need to know how many days occurred before dteThen was done with // dteThen.Month. Then we add the number of days we've "earned" in the current // month. // // So 12/25/2009 to 12/1/2011 gives us // 11-9 = 2 years, minus one to borrow days = 1 year difference. // 1 year 11 months - 12 months = 11 months difference // (days from 12/25 to the End Of Month) + (Begin of Month to 12/1) = // (31-25) + (0+1) = // 6 + 1 = // 7 days diff // // 12/25/2009 to 12/1/2011 is 1 year, 11 months, 7 days apart. QED. int intDaysInSharedMonth = System.DateTime.DaysInMonth(dteThen.Year, dteThen.Month); intDiffInDays = intDaysInSharedMonth - dteThen.Day + dteNow.Day; } else { intDiffInYears = dteNow.Year - dteThen.Year; intDiffInMonths = dteNow.Month - dteThen.Month - 1; // So now figure out how many more days we'd need to get from dteThen's // intDiffInMonth-th month to get to the current month/day in dteNow. // That is, if we're comparing 2/8/2011 to 11/7/2011, we've got (10/8-2/8) = 8 // full months between the two dates. But then we've got to go from 10/8 to // 11/07. So that's the previous month's (October) number of days (31) minus // the number of days into the month dteThen went (8), giving the number of days // needed to get us to the end of the month previous to dteNow (23). Now we // add back the number of days that we've gone into dteNow's current month (7) // to get the total number of days we've gone since we ran the greatest integer // function on the month difference (23 to the end of the month + 7 into the // next month == 30 total days. You gotta make it through October before you // get another month, G, and it's got 31 days). int intDaysInPrevMonth = System.DateTime.DaysInMonth(dteNow.Year, (dteNow.Month - 1)); intDiffInDays = intDaysInPrevMonth - dteThen.Day + dteNow.Day; } } } else { // else dteThen.Month > dteNow.Month, and we've got to amend our year subtraction // because we haven't earned our entire year yet, and don't want an obo error. intDiffInYears = dteNow.Year - dteThen.Year - 1; // So if the dates were THEN: 6/15/1999 and NOW: 2/20/2010... // Diff in years is 2010-1999 = 11, but since we're not to 6/15 yet, it's only 10. // Diff in months is (Months in year == 12) - (Months lost between 1/1/1999 and 6/15/1999 // when dteThen's clock wasn't yet rolling == 6) = 6 months, then you add the months we // have made it into this year already. The clock's been rolling through 2/20, so two months. // Note that if the 20 in 2/20 hadn't been bigger than the 15 in 6/15, we're back to the // intDaysInPrevMonth trick from earlier. We'll do that below, too. intDiffInMonths = 12 - dteThen.Month + dteNow.Month; if (dteNow.Day >= dteThen.Day) { intDiffInDays = dteNow.Day - dteThen.Day; } else { intDiffInMonths--; // subtract the month from which we're borrowing days. // Maybe we shoulda factored this out previous to the if (dteNow.Month > dteThen.Month) // call, but I think this is more readable code. int intDaysInPrevMonth = System.DateTime.DaysInMonth(dteNow.Year, (dteNow.Month - 1)); intDiffInDays = intDaysInPrevMonth - dteThen.Day + dteNow.Day; } } this.addToBox("Years: " + intDiffInYears + " Months: " + intDiffInMonths + " Days: " + intDiffInDays); // adds results to a rich text box. } 
 DateTime dt1 = new DateTime(2009, 3, 14); DateTime dt2 = new DateTime(2008, 3, 15); int diffMonth = Math.Abs((dt2.Year - dt1.Year)*12 + dt1.Month - dt2.Month) 

我遇到这个职位,同时寻求解决类似的问题。 我试图以年,月,周,日为单位find动物的年龄。 Those values are then displayed in SpinEdits where the user can manually change the values to find/estimate a birth date. When my form was passed a birth date from a month with less than 31 days, the value calculated was 1 day off. I based my solution off of Ic's answer above.

Main calculation method that is called after my form loads.

  birthDateDisplay.Text = birthDate.ToString("MM/dd/yyyy"); DateTime currentDate = DateTime.Now; Int32 numOfDays = 0; Int32 numOfWeeks = 0; Int32 numOfMonths = 0; Int32 numOfYears = 0; // changed code to follow this model http://stackoverflow.com/posts/1083990/revisions //years TimeSpan diff = currentDate - birthDate; numOfYears = diff.Days / 366; DateTime workingDate = birthDate.AddYears(numOfYears); while (workingDate.AddYears(1) <= currentDate) { workingDate = workingDate.AddYears(1); numOfYears++; } //months diff = currentDate - workingDate; numOfMonths = diff.Days / 31; workingDate = workingDate.AddMonths(numOfMonths); while (workingDate.AddMonths(1) <= currentDate) { workingDate = workingDate.AddMonths(1); numOfMonths++; } //weeks and days diff = currentDate - workingDate; numOfWeeks = diff.Days / 7; //weeks always have 7 days // if bday month is same as current month and bday day is after current day, the date is off by 1 day if(DateTime.Now.Month == birthDate.Month && DateTime.Now.Day < birthDate.Day) numOfDays = diff.Days % 7 + 1; else numOfDays = diff.Days % 7; // If the there are fewer than 31 days in the birth month, the date calculated is 1 off // Dont need to add a day for the first day of the month int daysInMonth = 0; if ((daysInMonth = DateTime.DaysInMonth(birthDate.Year, birthDate.Month)) != 31 && birthDate.Day != 1) { startDateforCalc = DateTime.Now.Date.AddDays(31 - daysInMonth); // Need to add 1 more day if it is a leap year and Feb 29th is the date if (DateTime.IsLeapYear(birthDate.Year) && birthDate.Day == 29) startDateforCalc = startDateforCalc.AddDays(1); } yearsSpinEdit.Value = numOfYears; monthsSpinEdit.Value = numOfMonths; weeksSpinEdit.Value = numOfWeeks; daysSpinEdit.Value = numOfDays; 

And then, in my spinEdit_EditValueChanged event handler, I calculate the new birth date starting from my startDateforCalc based on the values in the spin edits. (SpinEdits are constrained to only allow >=0)

 birthDate = startDateforCalc.Date.AddYears(-((Int32)yearsSpinEdit.Value)).AddMonths(-((Int32)monthsSpinEdit.Value)).AddDays(-(7 * ((Int32)weeksSpinEdit.Value) + ((Int32)daysSpinEdit.Value))); birthDateDisplay.Text = birthDate.ToString("MM/dd/yyyy"); 

I know its not the prettiest solution, but it seems to be working for me for all month lengths and years.

Days: (endDate – startDate).Days
Weeks: (endDate – startDate).Days / 7
Years: Months / 12
Months: A TimeSpan only provides Days, so use the following code to get the number of whole months between a specified start and end date. For example, the number of whole months between 01/10/2000 and 02/10/2000 is 1. The the number of whole months between 01/10/2000 and 02/09/2000 is 0.

  public int getMonths(DateTime startDate, DateTime endDate) { int months = 0; if (endDate.Month <= startDate.Month) { if (endDate.Day < startDate.Day) { months = (12 * (endDate.Year - startDate.Year - 1)) + (12 - startDate.Month + endDate.Month - 1); } else if (endDate.Month < startDate.Month) { months = (12 * (endDate.Year - startDate.Year - 1)) + (12 - startDate.Month + endDate.Month); } else // (endDate.Month == startDate.Month) && (endDate.Day >= startDate.Day) { months = (12 * (endDate.Year - startDate.Year)); } } else if (endDate.Day < startDate.Day) { months = (12 * (endDate.Year - startDate.Year)) + (endDate.Month - startDate.Month) - 1; } else // (endDate.Month > startDate.Month) && (endDate.Day >= startDate.Day) { months = (12 * (endDate.Year - startDate.Year)) + (endDate.Month - startDate.Month); } return months; } 

If you have to find the difference between originalDate and today's date, Here is a reliable algorithm without so many condition checks.

  1. Declare a intermediateDate variable and initialize to the originalDate
  2. Find difference between years.(yearDiff)
  3. Add yearDiff to intermediateDate and check whether the value is greater than today's date.
  4. If newly obtained intermediateDate > today's date adjust the yearDiff and intermediateDate by one.
  5. Continue above steps for month and Days.

I have used System.Data.Linq functions to do find the year, month and day differences. Please find c# code below

  DateTime todaysDate = DateTime.Now; DateTime interimDate = originalDate; ///Find Year diff int yearDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffYear(interimDate, todaysDate); interimDate = interimDate.AddYears(yearDiff); if (interimDate > todaysDate) { yearDiff -= 1; interimDate = interimDate.AddYears(-1); } ///Find Month diff int monthDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffMonth(interimDate, todaysDate); interimDate = interimDate.AddMonths(monthDiff); if (interimDate > todaysDate) { monthDiff -= 1; interimDate = interimDate.AddMonths(-1); } ///Find Day diff int daysDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffDay(interimDate, todaysDate); 
 private void dateTimePicker1_ValueChanged(object sender, EventArgs e) { int gyear = dateTimePicker1.Value.Year; int gmonth = dateTimePicker1.Value.Month; int gday = dateTimePicker1.Value.Day; int syear = DateTime.Now.Year; int smonth = DateTime.Now.Month; int sday = DateTime.Now.Day; int difday = DateTime.DaysInMonth(syear, gmonth); agedisplay = (syear - gyear); lmonth = (smonth - gmonth); lday = (sday - gday); if (smonth < gmonth) { agedisplay = agedisplay - 1; } if (smonth == gmonth) { if (sday < (gday)) { agedisplay = agedisplay - 1; } } if (smonth < gmonth) { lmonth = (-(-smonth)+(-gmonth)+12); } if (lday < 0) { lday = difday - (-lday); lmonth = lmonth - 1; } if (smonth == gmonth && sday < gday&&gyear!=syear) { lmonth = 11; } ageDisplay.Text = Convert.ToString(agedisplay) + " Years, " + lmonth + " Months, " + lday + " Days."; } 

Use Noda Time :

 var ld1 = new LocalDate(2012, 1, 1); var ld2 = new LocalDate(2013, 12, 25); var period = Period.Between(ld1, ld2); Debug.WriteLine(period); // "P1Y11M24D" (ISO8601 format) Debug.WriteLine(period.Years); // 1 Debug.WriteLine(period.Months); // 11 Debug.WriteLine(period.Days); // 24 
 TimeSpan period = endDate.AddDays(1) - startDate; DateTime date = new DateTime(period.Ticks); int totalYears = date.Year - 1; int totalMonths = ((date.Year - 1) * 12) + date.Month - 1; int totalWeeks = (int)period.TotalDays / 7; 

date.Year – 1 because the year 0 doesn't exist. date.Month – 1, the month 0 doesn't exist

I have below solution which works correctly for me(After doing some Test cases). Extra Test cases are acceptable.

 public class DateDiffernce { private int m_nDays = -1; private int m_nWeek; private int m_nMonth = -1; private int m_nYear; public int Days { get { return m_nDays; } } public int Weeks { get { return m_nWeek; } } public int Months { get { return m_nMonth; } } public int Years { get { return m_nYear; } } public void GetDifferenceBetwwenTwoDate(DateTime objDateTimeFromDate, DateTime objDateTimeToDate) { if (objDateTimeFromDate.Date > objDateTimeToDate.Date) { DateTime objDateTimeTemp = objDateTimeFromDate; objDateTimeFromDate = objDateTimeToDate; objDateTimeToDate = objDateTimeTemp; } if (objDateTimeFromDate == objDateTimeToDate) { //textBoxDifferenceDays.Text = " Same dates"; //textBoxAllDifference.Text = " Same dates"; return; } // If From Date's Day is bigger than borrow days from previous month // & then subtract. if (objDateTimeFromDate.Day > objDateTimeToDate.Day) { objDateTimeToDate = objDateTimeToDate.AddMonths(-1); int nMonthDays = DateTime.DaysInMonth(objDateTimeToDate.Year, objDateTimeToDate.Month); m_nDays = objDateTimeToDate.Day + nMonthDays - objDateTimeFromDate.Day; } // If From Date's Month is bigger than borrow 12 Month // & then subtract. if (objDateTimeFromDate.Month > objDateTimeToDate.Month) { objDateTimeToDate = objDateTimeToDate.AddYears(-1); m_nMonth = objDateTimeToDate.Month + 12 - objDateTimeFromDate.Month; } //Below are best cases - simple subtraction if (m_nDays == -1) { m_nDays = objDateTimeToDate.Day - objDateTimeFromDate.Day; } if (m_nMonth == -1) { m_nMonth = objDateTimeToDate.Month - objDateTimeFromDate.Month; } m_nYear = objDateTimeToDate.Year - objDateTimeFromDate.Year; m_nWeek = m_nDays / 7; m_nDays = m_nDays % 7; } } 

Use the Subtract method of the DateTime object which returns a TimeSpan

 DateTime dt1 = new DateTime(2009, 3, 14); DateTime dt2 = new DateTime(2008, 3, 15); TimeSpan ts = dt1.Subtract(dt2); Double days = ts.TotalDays; Double hours = ts.TotalHours; Double years = ts.TotalDays / 365; 

Based on Joaquim's answer, but fixing the calculation when end date month is less than start date month, and adding code to handle end date before start date:

  public static class GeneralHelper { public static int GetYears(DateTime startDate, DateTime endDate) { if (endDate < startDate) return -GetYears(endDate, startDate); int years = (endDate.Year - startDate.Year); if (endDate.Year == startDate.Year) return years; if (endDate.Month < startDate.Month) return years - 1; if (endDate.Month == startDate.Month && endDate.Day < startDate.Day) return years - 1; return years; } public static int GetMonths(DateTime startDate, DateTime endDate) { if (startDate > endDate) return -GetMonths(endDate, startDate); int months = 12 * GetYears(startDate, endDate); if (endDate.Month > startDate.Month) months = months + endDate.Month - startDate.Month; else months = 12 - startDate.Month + endDate.Month; if (endDate.Day < startDate.Day) months = months - 1; return months; } } [TestClass()] public class GeneralHelperTest { [TestMethod] public void GetYearsTest() { Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2000, 12, 31))); Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 4, 4))); Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 5, 4))); Assert.AreEqual(1, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 5, 5))); Assert.AreEqual(1, GeneralHelper.GetYears(new DateTime(2000, 5, 5), new DateTime(2001, 12, 31))); Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2000, 12, 31), new DateTime(2000, 5, 5))); Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2001, 4, 4), new DateTime(2000, 5, 5))); Assert.AreEqual(0, GeneralHelper.GetYears(new DateTime(2001, 5, 4), new DateTime(2000, 5, 5))); Assert.AreEqual(-1, GeneralHelper.GetYears(new DateTime(2001, 5, 5), new DateTime(2000, 5, 5))); Assert.AreEqual(-1, GeneralHelper.GetYears(new DateTime(2001, 12, 31), new DateTime(2000, 5, 5))); } [TestMethod] public void GetMonthsTest() { Assert.AreEqual(0, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 4))); Assert.AreEqual(1, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 5))); Assert.AreEqual(1, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2000, 6, 6))); Assert.AreEqual(11, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 5, 4))); Assert.AreEqual(12, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 5, 5))); Assert.AreEqual(13, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2001, 6, 6))); Assert.AreEqual(0, GeneralHelper.GetMonths(new DateTime(2000, 6, 4), new DateTime(2000, 5, 5))); Assert.AreEqual(-1, GeneralHelper.GetMonths(new DateTime(2000, 6, 5), new DateTime(2000, 5, 5))); Assert.AreEqual(-1, GeneralHelper.GetMonths(new DateTime(2000, 6, 6), new DateTime(2000, 5, 5))); Assert.AreEqual(-11, GeneralHelper.GetMonths(new DateTime(2001, 5, 4), new DateTime(2000, 5, 5))); Assert.AreEqual(-12, GeneralHelper.GetMonths(new DateTime(2001, 5, 5), new DateTime(2000, 5, 5))); Assert.AreEqual(-13, GeneralHelper.GetMonths(new DateTime(2001, 6, 6), new DateTime(2000, 5, 5))); } } 

EDIT No that still doesn't work. It fails this test:

 Assert.AreEqual(24, GeneralHelper.GetMonths(new DateTime(2000, 5, 5), new DateTime(2003, 5, 5))); 

Expected:<24>. Actual:<12>

I was trying to find a clear answer for Years, Months and Days, and I didn't find anything clear, If you are still looking check this method:

  public static string GetDifference(DateTime d1, DateTime d2) { int[] monthDay = new int[12] { 31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; DateTime fromDate; DateTime toDate; int year; int month; int day; int increment = 0; if (d1 > d2) { fromDate = d2; toDate = d1; } else { fromDate = d1; toDate = d2; } // Calculating Days if (fromDate.Day > toDate.Day) { increment = monthDay[fromDate.Month - 1]; } if (increment == -1) { if (DateTime.IsLeapYear(fromDate.Year)) { increment = 29; } else { increment = 28; } } if (increment != 0) { day = (toDate.Day + increment) - fromDate.Day; increment = 1; } else { day = toDate.Day - fromDate.Day; } // Month Calculation if ((fromDate.Month + increment) > toDate.Month) { month = (toDate.Month + 12) - (fromDate.Month + increment); increment = 1; } else { month = (toDate.Month) - (fromDate.Month + increment); increment = 0; } // Year Calculation year = toDate.Year - (fromDate.Year + increment); return year + " years " + month + " months " + day + " days"; } 
  Console.WriteLine("Enter your Date of Birth to Know your Current age in DD/MM/YY Format"); string str = Console.ReadLine(); DateTime dt1 = DateTime.Parse(str); DateTime dt2 = DateTime.Parse("10/06/2012"); int result = (dt2 - dt1).Days; result = result / 365; Console.WriteLine("Your Current age is {0} years.",result); 
 DateTime startTime = DateTime.Now; DateTime endTime = DateTime.Now.AddSeconds( 75 ); TimeSpan span = endTime.Subtract ( startTime ); Console.WriteLine( "Time Difference (seconds): " + span.Seconds ); Console.WriteLine( "Time Difference (minutes): " + span.Minutes ); Console.WriteLine( "Time Difference (hours): " + span.Hours ); Console.WriteLine( "Time Difference (days): " + span.Days ); 

输出:

 Time Difference (seconds): 15 Time Difference (minutes): 1 Time Difference (hours): 0 Time Difference (days): 0