计算两个日期之间月份的差异


128

在C#/。NET中TimeSpanTotalDaysTotalMinutes等等,但是我不知道总月差的公式。每月变化的日子和leap年不断让我失望。我如何获得TotalMonths

编辑对不起,不是更清晰:我知道我不能真正从得到这个TimeSpan,但我想用TotalDaysTotalMinutes将是一个很好的例子来表达我一直在寻找......除了我试图获得总计个月。

例如:2009年12月25日-2009年10月6日= 2 TotalMonths。10月6日至11月5日等于0个月。1月11日,1个月。12月6日,2个月


2
您对2009年12月25日至2009年10月6日有什么期望?
杰夫·摩泽尔

2
如何定义几个月的时间跨度?
Aliostad 2011年

1
@Aliostad-没有日期,您可以将一个月定义为30天,并且非常准确。
ChaosPandion 2011年

出于某种原因,mod将其与该问题合并。
Jamiec 2011年

实际上,您需要在这里阅读我的文章,该文章回答了这个问题并提供了编码解决方案stackoverflow.com/questions/1916358/…忽略巨魔(brianary)并通过与supercat的评论来关注我的谈话。在一个时间间隔的开始和结束的几个月,我们称之为“孤立月份”,问题归结为如何用天数来定义这些孤立月份-一旦您确定了(以及您想如何定义它),剩下的只是代码(包含在内)。我的防御 基于我认为我的用户将会期望的内容
Erx_VB.NExT.Coder 2012年

Answers:


222

您将无法从中获取价格TimeSpan,因为“月”是可变的计量单位。您必须自己计算它,并且必须弄清楚您希望它如何工作。

例如,要日期喜欢July 5, 2009August 4, 2009产生一个月或零月区别?如果你说这应该产生一个,然后怎么样July 31, 2009August 1, 2009?是一个月?仅仅是Month日期值的差异,还是与实际时间跨度更相关?确定所有这些规则的逻辑很重要,因此您必须确定自己的规则并实施适当的算法。

如果您想要的只是月份中的不同(完全不考虑日期值),则可以使用以下方法:

public static int MonthDifference(this DateTime lValue, DateTime rValue)
{
    return (lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year);
}

请注意,这将返回一个相对差,这意味着如果rValue大于lValue,则返回值将为负。如果需要绝对差异,可以使用以下方法:

public static int MonthDifference(this DateTime lValue, DateTime rValue)
{
    return Math.Abs((lValue.Month - rValue.Month) + 12 * (lValue.Year - rValue.Year));
}

@Dinah这只是一个近似值,如果您想知道真实的.Month和.Years-我刚刚发布了您可以阅读的答案。尽管就近似而言,这是一个很好的近似(Adam Robinson的道具),但是您应该记住,如果您使用这些近似中的任何一种,都只会无意中欺骗用户。
Erx_VB.NExT.Coder

@ Erx_VB.NExT.Coder:谢谢你的道具,但是尽管你的回答说没有一个答案考虑到一个月是一个可变的度量单位这一事实,但似乎大多数答案都是这样。他们只是不使用您的特定近似值。举个例子,我回答的第一句话表明它是可变的。包括您在内的任何答案都是近似值,仅因为它不是精确答案。您的“ 2个月”结果对于不同的输入可能意味着不同的事情,所以这是一个近似值。
亚当·罗宾逊

但是,我的数据不是一个近似值,如果今天是3月14日,则根据jan的时间为31天,而feb的时间为29天这一事实来计算两个连续的月份。现在,您是正确的,因为我的方法不是“一般”月份的定义,而您的是!但是,仅当您报告“此评论在x月和y天AGO中发布”之类的情况下,我的才适用。“ AGO”部分有所不同,因为它指的是前x个月,因此需要计算前x个月根据那x个月中有多少天!链接....
Erx_VB.NExT.Coder

那有意义吗?因此,如果您指的是特定的已知月份,那么我的方法是100%准确的,并且您将是一个近似值;但是,如果您通常指的是一个月,则您的近似值将是一个更好的主意,并且我的想法不是一个好主意(不是为此而做的,使用它毫无意义)。这里是链接到我的文章描述的问题,并提供解决方案:stackoverflow.com/questions/1916358/...
Erx_VB.NExT.Coder

2
这似乎与Sql Server DateDiff(month,...)函数使用的逻辑相同。它还具有非常简洁,易于解释和理解的优点。我将解释如下...从一个日期到另一个日期,您必须翻阅日历中的几页?
JoelFan

51

(我意识到这是一个老问题,但是...)

这是比较痛苦的纯.NET做。我建议我自己的Noda Time库,该库是专门为以下目的而设计的:

LocalDate start = new LocalDate(2009, 10, 6);
LocalDate end = new LocalDate(2009, 12, 25);
Period period = Period.Between(start, end);
int months = period.Months;

(还有其他选择,例如,即使您只想数月甚至数年,也可以使用Period period = Period.Between(start, end, PeriodUnits.Months);


我下载了您的库,并复制了您上面编写的代码,但是我收到了编译时错误。错误1运算符'-'不能应用于'NodaTime.LocalDate'和'NodaTime.LocalDate'类型的操作数。我知道这是5年的帖子,从那时开始有什么变化,这使此代码无法正常工作?
哈坎·菲斯蒂克(HakanFıstık),2015年

1
@HakamFostok:对不起-在2.0发行时它可以工作,但是在此之前您需要使用Period.Between。编辑了代码,使其可与NodaTime 1.3.1一起使用。
乔恩·斯基特

非常感谢NodaTime库确实做了我想要做的事情。我不仅要计算两个日期之间的月份,还要计算剩余的日期,这正是NodaTime所做的,再次感谢。
HakanFıstık'15

1
@JonSkeet您的图书馆确实是黑魔法。日期无时无刻不在咬我。该代码段为我节省了大量时间。
onefootswill

28

也许您不想知道月份分数;这段代码呢?


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"



1
我不明白*100。应该是* 12吗?
褶皱

9

您必须先定义TotalMonths的含义。
一个简单的定义将一个月定为30.4天(365.25 / 12)。

除此之外,任何包含分数的定义似乎都没有用,更常见的整数值(日期之间的整月)也取决于非标准的业务规则。


9

我已经编写了一个非常简单的扩展方法DateTimeDateTimeOffset执行此操作。我希望它像TotalMonths属性上的属性一样TimeSpan正常工作:即返回两个日期之间的完整月份数,而忽略任何不完整的月份。因为它基于此,DateTime.AddMonths()所以尊重不同的月份长度,并返回人类几个月内会理解的内容。

(不幸的是,您不能将其作为TimeSpan上的扩展方法实现,因为它不能保留对实际使用日期的了解,而且几个月以来它们都很重要。)

代码和测试都可以在GitHub上获得。代码很简单:

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;
}

它通过了所有这些单元测试用例:

// 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)));

3
质朴,但最好的解决方案。复制并粘贴。谢谢
Daniel Dolz

8

您需要在日期时间外自行解决。结束时如何处理存根天取决于您要使用它的目的。

一种方法是计算月份,然后在最后修正几天。就像是:

   DateTime start = new DateTime(2003, 12, 25);
   DateTime end = new DateTime(2009, 10, 6);
   int compMonth = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
   double daysInEndMonth = (end - end.AddMonths(1)).Days;
   double months = compMonth + (start.Day - end.Day) / daysInEndMonth;

不错的代码,但是,有1个bug:相反:(截至2月28日+ 1个月== 3月28日):-) // decimal daysInEndMonth =(end-end.AddMonths(1))。Days; 我建议:小数daysInEndMonth = DateTime.DaysInMonth(end.Year,end.Month)* -1;
bezieur 2014年

3

我会这样:

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;
}

4
那当然是一种算法,但是可以大大简化为return (dtOther.Month - dtThis.Month) + 12 * (dtOther.Year - dtThis.Year);
亚当·鲁滨逊

1
两个问题:您是从2个日期开始,而不是一个TimeSpan。其次,您在两个月的第一天之间进行计算,这是一个非常可疑的定义。尽管有时候可能是正确的。
Henk Holterman

@Henk:是的,当然这并不总是正确的,这就是为什么我说这是我会这样做的原因,而不是任何人应该这样做的原因。OP没有指定结果的计算方式。@亚当:哇,我觉得方式太复杂了……对我来说这经常发生。感谢您的评论,您显然是对的,您的版本要好得多。从现在开始,我将使用它。
马克西米利安·梅耶

@亚当:为什么不提交这个作为实际答案呢?这是迄今为止最紧凑的。很好吃
迪纳

@Dinah:我不想假设这就是您真正想要的。如果是这样,我已经编辑了以前的答案以包括这种方法。
亚当·罗宾逊

3

关于这一点,没有很多明确的答案,因为您一直在假设事情。

该解决方案假设您要保存一个月中的某天进行比较,从而计算出两个月之间的日期(这意味着在计算中考虑了该月中的某天)

例如,如果您的日期为2012年1月30日,那么2012年2月29日将不是一个月,而是2013年3月1日。

它已经过相当彻底的测试,可能会在我们使用时稍后进行清理,并且使用两个日期而不是Timepan,这可能更好。希望这对其他人有帮助。

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
}

3

如果您需要整整几个月的时间,可接受的答案将非常有效。

我需要几个月的时间。这是我用几个月的时间提出的解决方案:

    /// <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
    }

我还需要一年的差异,而部分年份也同样需要。这是我想出的解决方案:

    /// <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);
    }

YearDifference当您的函数出现逻辑错误时lValue.Month < rValue.Month-我已解决了这一问题,您可能需要复习...
Stobor

2

我知道老问题,但可能会帮助某人。我在上面使用了@Adam接受的答案,但是然后检查了差异是1还是-1,然后检查它是否是一个完整日历月的差异。所以21/07/55和20/08/55不会是一个月,但是21/07/55和21/07/55会是一个月。

/// <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);
}

2
case IntervalType.Month:
    returnValue = start.AddMonths(-end.Month).Month.ToString();
    break;
case IntervalType.Year:
    returnValue = (start.Year - end.Year).ToString();
    break;

2
与代码一起进行描述也将对其他读者有所帮助。
Boeckm,2012年

是的,请添加一些评论。
Amar 2012年

1

几个月的问题在于,这实际上不是一个简单的度量-它们不是恒定大小。您需要为要包含的内容定义规则,然后从那里开始工作。例如1月1日至2月1日-您可以说那里涉及2个月,也可以说是1个月。那么从“ 1 Jan 20:00”到“ 1 Feb 00:00”-那不是一个完整的月呢?那是0吗?1个 反过来(从1月1日00:00到2月1日20:00)... 1呢?2?

恐怕先定义规则,然后您必须自己编写代码。


1

如果要128th Feb和之间获得结果1st March

DateTime date1, date2;
int monthSpan = (date2.Year - date1.Year) * 12 + date2.Month - date1.Month

这似乎与Sql Server DateDiff(month,...)函数使用的逻辑相同。它还具有非常简洁,易于解释和理解的优点。我将解释如下...从一个日期到另一个日期,您必须翻阅日历中的几页?
JoelFan

1

库考虑了DateTime的所有部分,计算了月份的差额:

// ----------------------------------------------------------------------
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

实际上,以下是您执行此操作最准确的方法,因为“ 1个月”的定义会根据它所在的月份而变化,并且其他答案都没有考虑到这一点!如果您想了解框架中未包含的有关该问题的更多信息,可以阅读这篇文章:具有.Years和.Months的Real Timespan对象(但是,阅读和理解下面的函数不是必需的,它可以正常工作100%,而不会出现其他人喜欢使用的近似值固有的不准确性-并可以用您的框架中内置的.Reverse函数随意替换.ReverseIt函数(此处仅出于完整性)。

请注意,您可以获得任意数量的日期/时间精度,秒和分钟或秒,分钟和天,最多可达几年(可能包含6个部分/段)。如果您指定前两个且已超过一年,则它将返回“ 1年零3个月前”,而不会返回其余部分,因为您已请求了两个细分。如果仅使用了几个小时,则只会返回“ 2小时零一分钟”。当然,如果您指定1、2、3、4、5或6个segmet,则同样的规则适用(由于秒,分钟,小时,天,月,年,年仅产生6种类型,因此最大为6)。它还将纠正语法问题,例如“分钟”与“分钟”,具体取决于它是否为1分钟或更长,所有类型均相同,并且生成的“字符串”将始终在语法上正确。

以下是一些使用示例:bAllowSegments标识要显示的段数...即:如果为3,则返回字符串为(作为示例)... "3 years, 2 months and 13 days"(不包括小时,分钟和秒作为前3个时间)类别)),但是,如果日期是较新的日期(例如几天前的日期),则指定相同的细分(3)将返回"4 days, 1 hour and 13 minutes ago",因此将所有内容都考虑在内!

如果bAllowSegments为2,它将返回"3 years and 2 months",如果返回6(最大值)"3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds",则应注意,这将NEVER RETURN是类似的事情,"0 years, 0 months, 0 days, 3 hours, 2 minutes and 13 seconds ago"因为即使您指定了6个段,它也可以理解前3个段中没有日期数据并忽略它们。 ,所以请放心:)。当然,如果其中有一个段为0,则在形成字符串时会考虑到这一点,并显示为,"3 days and 4 seconds ago"而忽略“ 0 hours”部分!享受,如果您愿意,请发表评论。

 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

当然,您将需要一个“ ReplaceLast”函数,该函数需要一个源字符串,一个参数指定需要替换的内容,另一个arg指定要替换的内容,并且仅替换该字符串的最后一次出现...如果您没有它或不想实现它,我已将其包括在内,因此在这里,它可以“按原样”运行而无需修改。我知道不再需要反向功能(存在于.net中),但是ReplaceLast和ReverseIt函数是从.net之前的日期开始继承的,因此请原谅它看起来过旧(仍然可以100%使用, em已有十多年的历史了,可以保证它们没有错误)... :)。干杯。

<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 

0

如果您想要确切的数字,那么您就不能仅从Timespan中获取信息,因为您需要知道交易的月份,以及是否要进行a年,如您所说。

要么寻找一个近似的数字,要么对原始的DateTime进行一些烦恼



0

在idiomatic-c#中,没有内置的方法可以准确地做到这一点。有一些变通办法,例如人们已经编码的此CodeProject示例


0

如果您要处理的是几个月和几年,那么您需要知道每月多少天以及哪些年份是years年的东西。

输入公历(和其他特定于文化的日历实现)。

虽然“日历”没有提供直接计算两个时间点之间的差的方法,但确实提供了诸如

DateTime AddWeeks(DateTime time, int weeks)
DateTime AddMonths(DateTime time, int months)
DateTime AddYears(DateTime time, int years)

0
DateTime start = new DateTime(2003, 12, 25);
DateTime end = new DateTime(2009, 10, 6);
int compMonth = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
double daysInEndMonth = (end - end.AddMonths(1)).Days;
double months = compMonth + (start.Day - end.Day) / daysInEndMonth;

0

该方法返回一个列表,其中包含3个元素,第一个是year,第二个是month,end元素是day:

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;
        }
    }

0

这是我为了使我发现准确的月份有所不同而做出的贡献:

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;
        }
    }
}

用法:

Int32 months = DateTime.Now.DiffMonths( DateTime.Now.AddYears( 5 ) );

您可以创建另一个名为DiffYears的方法,并在while循环中应用与上述和AddYears完全相同的逻辑,而不是AddMonths。


0

比赛还很晚,但我想这可能会对某人有所帮助。多数人倾向于按月逐月进行测量,但事实是月份会有所不同。使用这种思路,我创建了一个班轮,比较我们的日期。使用以下过程。

  1. 比较年份中任何大于1的年份都将乘以12,在任何情况下都不能小于1个整年。
  2. 如果结束年份更长,我们需要评估当前日期是否大于或等于前一天2A。如果结束日期大于或等于我们采用当前月份,然后加上12个月减去开始月份2B的月份。如果结束日期小于开始日期,则我们执行与上述相同的操作,只是在开始月份减去之前减去1
  3. 如果结束年份不长,我们将执行与2A / 2B相同的操作,但是不增加12个月的时间,因为我们不需要在一年左右进行评估。

        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)));

三元死亡?
SpaceBison

0

我对这个答案的看法也使用了扩展方法,但是它可以返回肯定或否定的结果。

public static int MonthsBefore(this DateTime dt1, DateTime dt2)
{
    (DateTime early, DateTime late, bool dt2After) = dt2 > dt1 ? (dt1,dt2,true) : (dt2,dt1,false);
    DateTime tmp; // Save the result so we don't repeat work
    int months = 1;
    while ((tmp = early.AddMonths(1)) <= late)
    {
        early = tmp;
        months++;
    }
    return (months-1)*(dt2After ? 1 : -1);
}

几个测试:

// Just under 1 month's diff
Assert.AreEqual(0, new DateTime(2014, 1, 1).MonthsBefore(new DateTime(2014, 1, 31)));
// Just over 1 month's diff
Assert.AreEqual(1, new DateTime(2014, 1, 1).MonthsBefore(new DateTime(2014, 2, 2)));    
// Past date returns NEGATIVE
Assert.AreEqual(-6, new DateTime(2012, 1, 1).MonthsBefore(new DateTime(2011, 6, 10)));

0

综合以上两个答案,另一种扩展方法是:

public static int ElapsedMonths(this DateTime date1, DateTime date2)
{
    DateTime earlierDate = (date1 > date2) ? date2 : date1;
    DateTime laterDate = (date1 > date2) ? date1 : date2;
    var eMonths = (laterDate.Month - earlierDate.Month) + 12 * (laterDate.Year - earlierDate.Year) - 
                                            ((earlierDate.Day > laterDate.Day) ? 1 : 0);
    return eMonths;
}

感谢@AdamRobinson和@MarkWhittaker


-1

计算两个日期之间的月份数:

$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
这是PHP,而不是C#。
AFract
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.