如何获得年/月/周/日两个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。
有两个函数可以执行所有的Assert
, CheckResults()
和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有一个小错误,我没有修复。 我很感激任何花时间与其他人分享他们的作品,因为这个项目非常有趣。
我的结论
有三种:
-
日历很难。 我写了九个testing,其中三个可能有两个答案。 我只有一个答案的一些testing可能不会被一致同意。 只是在我们说“1个月后”或“2年前”这个问题时想到的意思,在很多情况下都是非常棘手的。 而且这些代码都不得不处理所有复杂的事情,比如闰年。 所有这些都使用库代码来处理date。 如果你想象在几天,几周,几个月和几年中写出“时间”这个“规范”,那就是各种各样的事情。 因为小学以后我们都很了解,并且每天都用它,所以我们对许多特质都是盲目的。 这个问题不是学术问题 – 在债券和其他金融产品的会计软件中,将时间段分解成几年,几个月和几个月是必不可less的。
-
编写正确的代码很难。 有很多的错误。 在稍微比较模糊的话题或不太受欢迎的问题上,比没有被评论者指出的问题存在的机会多得多,比这个问题要高得多。 你永远不应该永远不要把代码从你的程序中复制到你的程序中,而不能完全理解它的function。 另一方面,您可能不应该在准备好复制和粘贴的答案中编写代码,而是使用智能和富有performance力的伪代码来让人们了解解决scheme并实现自己的版本(使用自己的错误!)
-
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.
- Declare a intermediateDate variable and initialize to the originalDate
- Find difference between years.(yearDiff)
- Add yearDiff to intermediateDate and check whether the value is greater than today's date.
- If newly obtained intermediateDate > today's date adjust the yearDiff and intermediateDate by one.
- 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