calculating the difference in months between two dates
在c/.net中,
编辑:对不起,我不太清楚:我知道我实际上不能从
示例:2009年12月25日-2009年10月6日=总计2个月。10月6日至11月5日等于0个月。11月6日,1个月。12月6日,2个月
你不能从
例如,像
如果您只需要几个月的差异——完全忽略日期值——那么您可以使用这个:
1 2 3 4 | public static int MonthDifference(this DateTime lValue, DateTime rValue) { return (lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year); } |
请注意,这会返回一个相对差异,这意味着如果
1 2 3 4 | public static int MonthDifference(this DateTime lValue, DateTime rValue) { return Math.Abs((lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year)); } |
(我知道这是个老问题,但是…)
在纯.NET中这样做比较痛苦。我推荐我自己的Noda时间库,它是专门为以下内容设计的:
1 2 3 4 |
(还有其他选择,例如,如果你只想数月甚至是数年,你可以使用
也许你不想知道月份分数,这段代码呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <wyn> public static class DateTimeExtensions { public static int TotalMonths(this DateTime start, DateTime end) { return (start.Year * 12 + start.Month) - (end.Year * 12 + end.Month); } } // Console.WriteLine( // DateTime.Now.TotalMonths( // DateTime.Now.AddMonths(-1))); // prints"1" </wyn> |
首先,你必须定义你所说的月份总数。一个简单的定义将一个月定为30.4天(365.25/12)。
除此之外,任何定义(包括分数)似乎都是无用的,更常见的整数值(日期之间的整月)也依赖于非标准的业务规则。
你需要自己把日期弄错。你如何处理最后的剩余天数将取决于你想用它来做什么。
一种方法是计算月份,然后在结束时更正天数。类似:
1 2 3 4 5 |
我已经在
(不幸的是,您不能在TimeSpan上将其实现为扩展方法,因为这样无法保留所使用的实际日期的知识,而且对于几个月来说,它们很重要。)
代码和测试在GitHub上都可用。代码非常简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public static int GetTotalMonthsFrom(this DateTime dt1, DateTime dt2) { DateTime earlyDate = (dt1 > dt2) ? dt2.Date : dt1.Date; DateTime lateDate = (dt1 > dt2) ? dt1.Date : dt2.Date; // Start with 1 month's difference and keep incrementing // until we overshoot the late date int monthsDiff = 1; while (earlyDate.AddMonths(monthsDiff) <= lateDate) { monthsDiff++; } return monthsDiff - 1; } |
它通过了所有这些单元测试用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // Simple comparison Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 1))); // Just under 1 month's diff Assert.AreEqual(0, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 1, 31))); // Just over 1 month's diff Assert.AreEqual(1, new DateTime(2014, 1, 1).GetTotalMonthsFrom(new DateTime(2014, 2, 2))); // 31 Jan to 28 Feb Assert.AreEqual(1, new DateTime(2014, 1, 31).GetTotalMonthsFrom(new DateTime(2014, 2, 28))); // Leap year 29 Feb to 29 Mar Assert.AreEqual(1, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2012, 3, 29))); // Whole year minus a day Assert.AreEqual(11, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2012, 12, 31))); // Whole year Assert.AreEqual(12, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2013, 1, 1))); // 29 Feb (leap) to 28 Feb (non-leap) Assert.AreEqual(12, new DateTime(2012, 2, 29).GetTotalMonthsFrom(new DateTime(2013, 2, 28))); // 100 years Assert.AreEqual(1200, new DateTime(2000, 1, 1).GetTotalMonthsFrom(new DateTime(2100, 1, 1))); // Same date Assert.AreEqual(0, new DateTime(2014, 8, 5).GetTotalMonthsFrom(new DateTime(2014, 8, 5))); // Past date Assert.AreEqual(6, new DateTime(2012, 1, 1).GetTotalMonthsFrom(new DateTime(2011, 6, 10))); |
我会这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | static int TotelMonthDifference(this DateTime dtThis, DateTime dtOther) { int intReturn = 0; dtThis = dtThis.Date.AddDays(-(dtThis.Day-1)); dtOther = dtOther.Date.AddDays(-(dtOther.Day-1)); while (dtOther.Date > dtThis.Date) { intReturn++; dtThis = dtThis.AddMonths(1); } return intReturn; } |
在这个问题上没有太多明确的答案,因为你总是在假设事情。
此解决方案计算两个日期之间的月数,假设要保存月的某一天进行比较(即计算中考虑月的某一天)。
例如,如果您的日期是2012年1月30日,那么2012年2月29日不是一个月,而是2013年3月1日。
它经过了相当彻底的测试,可能会在以后使用时清理干净,并且需要两个日期而不是一个时间跨度,这可能更好。希望这能帮助其他人。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | private static int TotalMonthDifference(DateTime dtThis, DateTime dtOther) { int intReturn = 0; bool sameMonth = false; if (dtOther.Date < dtThis.Date) //used for an error catch in program, returns -1 intReturn--; int dayOfMonth = dtThis.Day; //captures the month of day for when it adds a month and doesn't have that many days int daysinMonth = 0; //used to caputre how many days are in the month while (dtOther.Date > dtThis.Date) //while Other date is still under the other { dtThis = dtThis.AddMonths(1); //as we loop, we just keep adding a month for testing daysinMonth = DateTime.DaysInMonth(dtThis.Year, dtThis.Month); //grabs the days in the current tested month if (dtThis.Day != dayOfMonth) //Example 30 Jan 2013 will go to 28 Feb when a month is added, so when it goes to march it will be 28th and not 30th { if (daysinMonth < dayOfMonth) // uses day in month max if can't set back to day of month dtThis.AddDays(daysinMonth - dtThis.Day); else dtThis.AddDays(dayOfMonth - dtThis.Day); } if (((dtOther.Year == dtThis.Year) && (dtOther.Month == dtThis.Month))) //If the loop puts it in the same month and year { if (dtOther.Day >= dayOfMonth) //check to see if it is the same day or later to add one to month intReturn++; sameMonth = true; //sets this to cancel out of the normal counting of month } if ((!sameMonth)&&(dtOther.Date > dtThis.Date))//so as long as it didn't reach the same month (or if i started in the same month, one month ahead, add a month) intReturn++; } return intReturn; //return month } |
我知道这个老问题,但可能会帮助别人。我已经使用了@adam接受了上面的答案,但是检查了差异是1还是-1,然后检查是否是整个日历月的差异。所以,55年7月21日和55年8月20日不是一个完整的月,但55年7月21日和55年7月21日是。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | /// <summary> /// Amended date of birth cannot be greater than or equal to one month either side of original date of birth. /// </summary> /// <param name="dateOfBirth">Date of birth user could have amended.</param> /// <param name="originalDateOfBirth">Original date of birth to compare against.</param> /// <returns></returns> public JsonResult ValidateDateOfBirth(string dateOfBirth, string originalDateOfBirth) { DateTime dob, originalDob; bool isValid = false; if (DateTime.TryParse(dateOfBirth, out dob) && DateTime.TryParse(originalDateOfBirth, out originalDob)) { int diff = ((dob.Month - originalDob.Month) + 12 * (dob.Year - originalDob.Year)); switch (diff) { case 0: // We're on the same month, so ok. isValid = true; break; case -1: // The month is the previous month, so check if the date makes it a calendar month out. isValid = (dob.Day > originalDob.Day); break; case 1: // The month is the next month, so check if the date makes it a calendar month out. isValid = (dob.Day < originalDob.Day); break; default: // Either zero or greater than 1 month difference, so not ok. isValid = false; break; } if (!isValid) return Json("Date of Birth cannot be greater than one month either side of the date we hold.", JsonRequestBehavior.AllowGet); } else { return Json("Date of Birth is invalid.", JsonRequestBehavior.AllowGet); } return Json(true, JsonRequestBehavior.AllowGet); } |
当你想要一个月的时间时,这个被接受的答案会非常有效。
我需要几个月。这是我提出的部分几个月的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /// <summary> /// Calculate the difference in months. /// This will round up to count partial months. /// </summary> /// <param name="lValue"></param> /// <param name="rValue"></param> /// <returns></returns> public static int MonthDifference(DateTime lValue, DateTime rValue) { var yearDifferenceInMonths = (lValue.Year - rValue.Year) * 12; var monthDifference = lValue.Month - rValue.Month; return yearDifferenceInMonths + monthDifference + (lValue.Day > rValue.Day ? 1 : 0); // If end day is greater than start day, add 1 to round up the partial month } |
我还需要一年的差额,部分年份的差额也是如此。以下是我提出的解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /// <summary> /// Calculate the differences in years. /// This will round up to catch partial months. /// </summary> /// <param name="lValue"></param> /// <param name="rValue"></param> /// <returns></returns> public static int YearDifference(DateTime lValue, DateTime rValue) { return lValue.Year - rValue.Year + (lValue.Month > rValue.Month // Partial month, same year ? 1 : ((lValue.Month = rValue.Month) && (lValue.Day > rValue.Day)) // Partial month, same year and month ? 1 : 0); } |
1 2 3 4 5 6 | case TipoIntervalo.Mes: retorno = inicio.AddMonths(-fim.Month).Month.ToString(); break; case TipoIntervalo.Ano: retorno = (inicio.Year - fim.Year).ToString(); break; |
月份的问题在于,它不是一个简单的衡量标准——它们的大小不是固定的。您需要为要包含的内容定义规则,然后从中开始工作。例如1月1日到2月1日-你可以争辩说那里有2个月,或者你可以说那是一个月。那么,"1月1日20:00"到"2月1日00:00"怎么办?这还不算一个完整的月。那是0吗?1?换个方向怎么样(1月1日00:00到2月1日20:00)1?2?
首先定义规则,然后你必须自己编写代码,恐怕…
如果你想在
1 2 | DateTime date1, date2; int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month |
考虑到日期时间的所有部分,此库计算月份差异:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | // ---------------------------------------------------------------------- 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 } // DateDiffSample |
下面实际上是最准确的方法,因为"1个月"的定义随月份的不同而变化,而其他答案中没有一个考虑到这一点!如果你想了解更多关于框架中没有内置的问题的信息,你可以阅读这篇文章:一个带有.years&;个月的实时时间跨度对象(然而,阅读这篇文章并不需要理解和使用下面的函数,它可以100%地工作,而没有其他人喜欢使用的近似值固有的不精确性-并且感觉自由用内置的.reverse it函数替换.reverseit函数(这只是为了完整性)。
请注意,您可以在任何地方获得任意数量的日期/时间准确性、秒和分钟,或秒、分钟和天,最多可达年(包含6个部分/段)。如果您指定了前两个,并且它已经超过一年了,它将返回"1年3个月前",并且不会返回其余部分,因为您已经请求了两个段。如果它只有几个小时的历史,那么它将只返回"2小时1分钟前"。当然,如果指定1、2、3、4、5或6个分段(因为秒、分钟、小时、天、月、年仅构成6种类型,所以最大值为6),则应用相同的规则。它还可以纠正语法问题,比如"分钟"和"分钟",这取决于它是1分钟还是1分钟以上,对于所有类型来说都是一样的,并且生成的"字符串"在语法上总是正确的。
以下是一些使用示例:Ballowsegments标识要显示多少段…例如:如果是3,那么返回字符串将是…
如果ballowsegments为2,则返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | Public Function RealTimeUntilNow(ByVal dt As DateTime, Optional ByVal bAllowSegments As Byte = 2) As String ' bAllowSegments identifies how many segments to show... ie: if 3, then return string would be (as an example)... '"3 years, 2 months and 13 days" the top 3 time categories are returned, if bAllowSegments is 2 it would return '"3 years and 2 months" and if 6 (maximum value) would return"3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds" Dim rYears, rMonths, rDays, rHours, rMinutes, rSeconds As Int16 Dim dtNow = DateTime.Now Dim daysInBaseMonth = Date.DaysInMonth(dt.Year, dt.Month) rYears = dtNow.Year - dt.Year rMonths = dtNow.Month - dt.Month If rMonths < 0 Then rMonths += 12 : rYears -= 1 ' add 1 year to months, and remove 1 year from years. rDays = dtNow.Day - dt.Day If rDays < 0 Then rDays += daysInBaseMonth : rMonths -= 1 rHours = dtNow.Hour - dt.Hour If rHours < 0 Then rHours += 24 : rDays -= 1 rMinutes = dtNow.Minute - dt.Minute If rMinutes < 0 Then rMinutes += 60 : rHours -= 1 rSeconds = dtNow.Second - dt.Second If rSeconds < 0 Then rSeconds += 60 : rMinutes -= 1 ' this is the display functionality Dim sb As StringBuilder = New StringBuilder() Dim iSegmentsAdded As Int16 = 0 If rYears > 0 Then sb.Append(rYears) : sb.Append(" year" & If(rYears <> 1,"s","") &",") : iSegmentsAdded += 1 If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn If rMonths > 0 Then sb.AppendFormat(rMonths) : sb.Append(" month" & If(rMonths <> 1,"s","") &",") : iSegmentsAdded += 1 If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn If rDays > 0 Then sb.Append(rDays) : sb.Append(" day" & If(rDays <> 1,"s","") &",") : iSegmentsAdded += 1 If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn If rHours > 0 Then sb.Append(rHours) : sb.Append(" hour" & If(rHours <> 1,"s","") &",") : iSegmentsAdded += 1 If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn If rMinutes > 0 Then sb.Append(rMinutes) : sb.Append(" minute" & If(rMinutes <> 1,"s","") &",") : iSegmentsAdded += 1 If bAllowSegments = iSegmentsAdded Then GoTo parseAndReturn If rSeconds > 0 Then sb.Append(rSeconds) : sb.Append(" second" & If(rSeconds <> 1,"s","") &"") : iSegmentsAdded += 1 parseAndReturn: ' if the string is entirely empty, that means it was just posted so its less than a second ago, and an empty string getting passed will cause an error ' so we construct our own meaningful string which will still fit into the"Posted * ago" syntax... If sb.ToString ="" Then sb.Append("less than 1 second") Return ReplaceLast(sb.ToString.TrimEnd("",",").ToString,","," and") End Function |
当然,您需要一个"replace last"函数,它接受一个源字符串,一个参数指定需要替换的内容,另一个参数指定要替换的内容,它只替换该字符串的最后一次出现…如果您没有或不想实现它,我已经包括了我的一个,所以在这里,它将"按原样"工作,不需要修改。我知道Reverseit函数不再需要了(存在于.NET中),但replacelast和Reverseit函数是从.NET之前的几天开始执行的,所以请原谅它看起来有多过时(仍然100%有效,使用了10年以上,可以保证它们没有错误)。:)干杯。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <Extension()> _ Public Function ReplaceLast(ByVal sReplacable As String, ByVal sReplaceWhat As String, ByVal sReplaceWith As String) As String ' let empty string arguments run, incase we dont know if we are sending and empty string or not. sReplacable = sReplacable.ReverseIt sReplacable = Replace(sReplacable, sReplaceWhat.ReverseIt, sReplaceWith.ReverseIt, , 1) ' only does first item on reversed version! Return sReplacable.ReverseIt.ToString End Function <Extension()> _ Public Function ReverseIt(ByVal strS As String, Optional ByVal n As Integer = -1) As String Dim strTempX As String ="", intI As Integer If n > strS.Length Or n = -1 Then n = strS.Length For intI = n To 1 Step -1 strTempX = strTempX + Mid(strS, intI, 1) Next intI ReverseIt = strTempX + Right(strS, Len(strS) - n) End Function |
在惯用C语言中,没有内置的方法可以精确地做到这一点。有一些解决方法,比如人们已经编码的这个代码项目示例。
计算两个日期之间的月数:
1 2 3 4 5 6 7 8 9 10 11 12 13 | $date1 = '2017-01-20'; $date2 = '2019-01-20'; $ts1 = strtotime($date1); $ts2 = strtotime($date2); $year1 = date('Y', $ts1); $year2 = date('Y', $ts2); $month1 = date('m', $ts1); $month2 = date('m', $ts2); echo $joining_months = (($year2 - $year1) * 12) + ($month2 - $month1); |
1 2 3 4 5 |
网址:http://www.astro.uu.nl/~strous/aa/en/reken/juliaansedag.html
如果您可以将从公历日期转换为儒略日数,则只需创建一个运算符来比较祖连日数,可以键入double来获取月、日、秒等。请查看上述链接,获取从公历转换为儒略日的算法。
该方法返回一个包含3个元素的列表,第一个元素是年,第二个元素是月,结束元素是日:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | public static List<int> GetDurationInEnglish(DateTime from, DateTime to) { try { if (from > to) return null; var fY = from.Year; var fM = from.Month; var fD = DateTime.DaysInMonth(fY, fM); var tY = to.Year; var tM = to.Month; var tD = DateTime.DaysInMonth(tY, tM); int dY = 0; int dM = 0; int dD = 0; if (fD > tD) { tM--; if (tM <= 0) { tY--; tM = 12; tD += DateTime.DaysInMonth(tY, tM); } else { tD += DateTime.DaysInMonth(tY, tM); } } dD = tD - fD; if (fM > tM) { tY--; tM += 12; } dM = tM - fM; dY = tY - fY; return new List<int>() { dY, dM, dD }; } catch (Exception exception) { //todo: log exception with parameters in db return null; } } |
以下是我对在几个月内获得准确差异的贡献:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | namespace System { public static class DateTimeExtensions { public static Int32 DiffMonths( this DateTime start, DateTime end ) { Int32 months = 0; DateTime tmp = start; while ( tmp < end ) { months++; tmp = tmp.AddMonths( 1 ); } return months; } } } |
用途:
1 | Int32 months = DateTime.Now.DiffMonths( DateTime.Now.AddYears( 5 ) ); |
您可以创建另一个名为diffyears的方法,并在while循环中应用与上面和addyears完全相同的逻辑,而不是addmonths。
如果你想要确切的数字,你不能只从时间跨度开始,因为你需要知道你要处理的是哪个月,以及你是否要处理闰年,就像你说的那样。
要么输入一个大概的数字,要么修改原始日期时间。
比赛进行得太晚了,但我想这可能对某些人有所帮助。大多数人倾向于逐月测量,不包括月份有不同的变化。利用这一思想框架,我创建了一个比较日期的一行程序。使用以下过程。
如果年底不是更大,我们执行2A/2B,但不增加12个月,因为我们不需要在一年左右进行评估。
1 2 3 | DateTime date = new DateTime(2003, 11, 25); DateTime today = new DateTime(2004, 12, 26); var time = (today.Year - date.Year > 1 ? (today.Year - date.Year - 1) * 12 : 0) + (today.Year > date.Year ? (today.Day >= date.Day ? today.Month + 12 - date.Month : today.Month + 12 - (date.Month + 1)) : (today.Day >= date.Day ? today.Month - date.Month : today.Month - (date.Month + 1))); |
如果你处理的是月份和年份,你需要知道每个月有多少天,哪些年份是闰年。
输入公历(和其他特定于文化的日历实现)。
虽然日历不提供直接计算两个时间点之间差异的方法,但它确实有一些方法,例如
1 2 3 | DateTime AddWeeks(DateTime time, int weeks) DateTime AddMonths(DateTime time, int months) DateTime AddYears(DateTime time, int years) |