การคำนวณส่วนต่างเป็นเดือนระหว่างวันที่สองวัน


128

ใน C # /. NET TimeSpanมีTotalDays, TotalMinutesฯลฯ แต่ฉันไม่สามารถคิดออกสูตรสำหรับความแตกต่างรวมเดือน วันแปรผันต่อเดือนและปีอธิกสุรทินยังคงทิ้งฉันไว้ ฉันจะรับTotalMonthsได้อย่างไร

แก้ไขขออภัยที่ไม่ชัดเจนยิ่งขึ้น: ฉันรู้ว่าฉันไม่สามารถรับสิ่งนี้ได้จริง ๆTimeSpanแต่ฉันคิดว่าใช้TotalDaysแล้วและTotalMinutesจะเป็นตัวอย่างที่ดีในการแสดงสิ่งที่ฉันกำลังมองหา ... ยกเว้นฉันกำลังพยายามหาจำนวนเดือนทั้งหมด

ตัวอย่าง: 25 ธันวาคม 2009 - 6 ตุลาคม 2009 = 2 เดือนรวม 6 ตุลาคมถึง 5 พฤศจิกายนเท่ากับ 0 เดือน ในวันที่ 6 พ.ย. 1 เดือน ในวันที่ 6 ธันวาคม 2 เดือน


2
สิ่งที่คุณคาดหวังสำหรับ 25 ธันวาคม 2009 - 6 ตุลาคม 2009
เจฟฟ์ Moser

2
คุณกำหนด TimeSpan เป็นเดือนได้อย่างไร
Aliostad

1
@Aliostad - หากไม่มีวันที่คุณสามารถกำหนดเดือนเป็น 30 วันและค่อนข้างแม่นยำ
ChaosPandion

มันถูกรวมเข้ากับคำถามนี้โดย mod ด้วยเหตุผลบางอย่าง
Jamiec

ที่จริงแล้วคุณต้องอ่านโพสต์ของฉันที่นี่ซึ่งตอบคำถามนี้ & ให้การแก้ปัญหาการเข้ารหัสstackoverflow.com/questions/1916358/…ละเว้นโทรลล์ (ไบรอัน) และใส่ใจกับการสนทนาของฉันผ่านความเห็นกับ supercat เดือนที่เริ่มต้นและสิ้นสุดช่วงเวลาที่เราเรียกว่า "เด็กกำพร้าเดือน" และคำถามก็มาถึงวิธีการกำหนดเดือนกำพร้าเหล่านี้ในแง่ของวัน - เมื่อคุณได้กำหนดว่า (& คุณต้องการกำหนดมันอย่างไร ) ส่วนที่เหลือเป็นเพียงรหัส (ซึ่งรวมอยู่) def ของฉัน ขึ้นอยู่กับสิ่งที่ฉันคิดว่าผู้ใช้ของฉันจะคาดหวัง
Erx_VB.NExT.Coder

คำตอบ:


222

คุณจะไม่สามารถรับสิ่งนั้นได้จาก a TimeSpanเพราะ "เดือน" เป็นหน่วยวัดตัวแปร คุณจะต้องคำนวณด้วยตัวคุณเองและคุณต้องรู้ว่าคุณต้องการให้มันทำงานอย่างไร

ตัวอย่างเช่นวันที่ควรเป็นJuly 5, 2009และAugust 4, 2009ให้ผลต่างเดือนหรือศูนย์หนึ่งเดือนหรือไม่ ถ้าคุณบอกว่ามันควรจะให้ผลผลิตหนึ่งแล้วสิ่งที่เกี่ยวกับJuly 31, 2009และAugust 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 นี่เป็นเพียงการประมาณถ้าคุณต้องการทราบความจริง. เดือนและปี - ฉันเพิ่งโพสต์คำตอบสำหรับสิ่งที่คุณสามารถอ่าน แม้ว่าเท่าที่ประมาณดังนั้นนี่คือการประมาณที่ดี (อุปกรณ์ประกอบฉากกับ Adam Robinson) อย่างไรก็ตามคุณควรจำไว้ว่าหากคุณใช้การประมาณใด ๆ เหล่านี้คุณกำลังโกหกผู้ใช้ของคุณโดยไม่ตั้งใจ
Erx_VB.NExT.Coder

@ Erx_VB.NExT.Coder: ขอบคุณสำหรับอุปกรณ์ประกอบฉาก แต่ในขณะที่คำตอบของคุณระบุว่าไม่มีคำตอบใดที่ใช้ข้อเท็จจริงที่ว่าหนึ่งเดือนเป็นหน่วยของการวัดที่แปรปรวนดูเหมือนว่าส่วนใหญ่ทำ พวกเขาไม่ได้ใช้การประมาณเฉพาะของคุณ ในกรณีที่ประโยคแรกในคำตอบของฉันบ่งชี้ว่ามันเป็นตัวแปร คำตอบของคุณรวมอยู่ด้วยเป็นการประมาณเพียงเพราะมันไม่ใช่คำตอบที่แม่นยำ ผลลัพธ์ "2 เดือน" ของคุณอาจมีความหมายต่างกันสำหรับอินพุตที่แตกต่างกันดังนั้นจึงเป็นการประมาณ
อดัมโรบินสัน

ของฉันไม่ใช่การประมาณว่าถ้าวันนี้เป็นวันที่ 14 มีนาคมจากนั้นทั้งสองเดือนจะคำนวณจากความจริงที่ว่า jan มี 31 วันในนั้นและ feb มี 29 วันในนั้น ตอนนี้คุณถูกต้องแล้วในวิธีการของฉันไม่ใช่คำจำกัดความของเดือน "ทั่วไป" & ของคุณคือ! อย่างไรก็ตามฉันจะใช้งานได้ก็ต่อเมื่อคุณกำลังรายงานสิ่งต่าง ๆ เช่น "ความคิดเห็นนี้ถูกโพสต์ x เดือนและ y วันที่ผ่านมา" ส่วนที่ "AGO" สร้างความแตกต่างเพราะมันหมายถึง x เดือนก่อนหน้านั้นจะต้องคำนวณ x เดือนก่อนหน้า ขึ้นอยู่กับว่ามีกี่วันในเดือน x นั้น! ลิงก์ ....
Erx_VB.NExT.Coder

มันสมเหตุสมผลไหม ดังนั้นหากคุณหมายถึงเฉพาะเดือนที่ทราบแล้ววิธีการของฉันถูกต้อง 100% และคุณจะประมาณอย่างไรก็ตามถ้าคุณหมายถึงเดือนโดยทั่วไปคุณประมาณจะเป็นความคิดที่ดีขึ้นและ ฉันก็จะเป็นความคิดที่ดี (มันไม่ได้ทำเพื่อสิ่งนั้นและจะไม่มีประโยชน์ในการใช้มัน) นี่คือลิงค์ไปยังบทความของฉันที่อธิบายถึงปัญหาและการแก้ปัญหา: stackoverflow.com/questions/1916358/…
Erx_VB.NExT.Coder

2
ดูเหมือนว่าจะเป็นตรรกะเดียวกับที่ใช้โดยฟังก์ชัน DateDiff (เดือน, ... ) ของ Sql Server นอกจากนี้ยังมีข้อดีของการกระชับและอธิบายและเข้าใจได้ง่าย ฉันจะอธิบายดังต่อไปนี้ ... คุณต้องเปลี่ยนกี่หน้าในปฏิทินจากวันหนึ่งไปอีกวันหนึ่ง
JoelFan

51

(ฉันรู้ว่านี่เป็นคำถามเก่า แต่ ... )

สิ่งนี้ค่อนข้างเจ็บปวดที่ต้องทำใน pure .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 ปีสิ่งใดที่เปลี่ยนไปจากเวลานั้นซึ่งทำให้รหัสนี้ไม่ทำงาน?
Hakan Fıstık

1
@HakamFostok: ขออภัย - มันจะทำงานเมื่อ 2.0 ถูกปล่อยออกมา Period.Betweenแต่จนแล้วคุณจะต้องใช้ แก้ไขรหัสเพื่อให้สามารถทำงานกับ NodaTime 1.3.1 ได้
Jon Skeet

ขอบคุณมากห้องสมุด NodaTime ทำในสิ่งที่ฉันต้องการจริงๆ ฉันต้องการคำนวณไม่เพียงแค่เดือนระหว่างวันที่สองวัน แต่ยังเหลืออีกวันและสิ่งที่ NodaTime ทำเช่นนี้ต้องขอบคุณอีกครั้ง
Hakan Fıstık

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 หรือไม่
Ruffles

9

คุณจะต้องกำหนดความหมายโดย TotalMonths เพื่อเริ่มต้นด้วย
คำจำกัดความง่ายๆทำให้เดือนอยู่ที่ 30.4 วัน (365.25 / 12)

นอกเหนือจากนั้นคำจำกัดความใด ๆ รวมถึงเศษส่วนดูเหมือนไร้ประโยชน์และค่าจำนวนเต็มทั่วไป (ตลอดทั้งเดือนระหว่างวันที่) ยังขึ้นอยู่กับกฎเกณฑ์ทางธุรกิจที่ไม่ได้มาตรฐานอีกด้วย


9

ฉันได้เขียนวิธีการขยายที่ง่ายมากDateTimeและDateTimeOffsetทำเช่นนี้ ฉันต้องการให้มันทำงานเหมือน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 ข้อบกพร่อง: แทน: (เมื่อ 28 กุมภาพันธ์ + 1 เดือน == 28 มีนาคม) :-) // วันทศนิยม InInndMonth ทศนิยม (สิ้นสุด - สิ้นสุด EndAdAdMMthth (1)) วัน; ฉันขอแนะนำ: ทศนิยมวัน InEndMonth = DateTime.DaysInMonth (สิ้นสุดปี, สิ้นสุด. เดือน) * -1;
bezieur

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

1
ปัญหาสองประการ: คุณเริ่มต้นจาก 2 วันไม่ใช่ TimeSpan ประการที่สองคุณคำนวณระหว่างวันที่ 1 ของทั้งสองเดือนนั่นคือคำจำกัดความที่น่าสงสัยมาก แม้ว่ามันอาจจะถูกต้องในบางครั้ง
Henk Holterman

@Henk: ใช่แน่นอนมันไม่ถูกเสมอไปนั่นเป็นเหตุผลที่ฉันพูดว่านี่เป็นวิธีที่ฉันจะทำไม่ใช่คนที่ควรทำ OP ไม่ได้ระบุว่าควรคำนวณผลลัพธ์อย่างไร @ อดัม: ว้าวฉันคิดว่ามันซับซ้อนเกินไปอีกแล้ว ... มันเกิดขึ้นกับฉันบ่อยเกินไป ขอบคุณสำหรับความคิดเห็นคุณเห็นได้ชัดว่าเวอร์ชั่นของคุณดีกว่ามาก ฉันจะใช้สิ่งนี้นับจากนี้
Maximilian Mayerl

@Adam: ทำไมคุณไม่ส่งเป็นคำตอบที่แท้จริง?! นี่คือกะทัดรัดที่สุดจนถึง ลื่นมาก
Dinah

@Dinah: ฉันไม่ต้องการที่จะคิดว่านั่นคือสิ่งที่คุณต้องการจริง ๆ ถ้าใช่ฉันได้แก้ไขคำตอบก่อนหน้าของฉันเพื่อรวมวิธีการนี้
Adam Robinson เมื่อ

3

ไม่มีคำตอบที่ชัดเจนเกี่ยวกับเรื่องนี้เพราะคุณมักจะคิดในสิ่งต่าง ๆ เสมอ

โซลูชันนี้จะคำนวณระหว่างวันที่สองเดือนในช่วงเวลาสมมติว่าคุณต้องการบันทึกวันของเดือนเพื่อเปรียบเทียบ (หมายถึงวันที่ของเดือนนั้นถูกพิจารณาในการคำนวณ)

ตัวอย่างถ้าคุณมีวันที่ 30 มกราคม 2012, 29 กุมภาพันธ์ 2012 จะไม่เป็นเดือน แต่จะวันที่ 1 มีนาคม 2013

มันได้รับการทดสอบอย่างทั่วถึงอาจจะล้างมันในภายหลังเมื่อเราใช้และใช้เวลาสองวันแทน Timespan ซึ่งน่าจะดีกว่า หวังว่านี่จะช่วยคนอื่นได้

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

ใช่โปรดเพิ่มความเห็น
Amar

1

ปัญหาเกี่ยวกับเดือนคือมันไม่ได้เป็นตัวชี้วัดง่ายๆ - พวกเขาไม่ได้ขนาดคงที่ คุณจะต้องกำหนดกฎของคุณสำหรับสิ่งที่คุณต้องการรวมและทำงานจากที่นั่น ตัวอย่างเช่น 1 มกราคมถึง 1 กุมภาพันธ์ - คุณสามารถโต้แย้งได้ 2 เดือนที่เกี่ยวข้องหรือคุณอาจพูดว่านั่นคือหนึ่งเดือน ถ้าอย่างนั้นแล้ววันที่ "1 มกราคม 20:00" ถึง "1 ก.พ. 00:00" - นั่นไม่ใช่ทั้งเดือน นั่นคือ 0 หรือไม่ 1? แล้วรอบอื่น ๆ (1 ม.ค. 00:00 ถึง 1 ก.พ. 20:00) ... 1? 2?

ขั้นแรกให้นิยามกฎจากนั้นคุณจะต้องเขียนรหัสด้วยตัวเองฉันกลัว ...


1

หากคุณต้องการได้ผลลัพธ์1ระหว่าง28th Febและ1st March:

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

ดูเหมือนว่าจะเป็นตรรกะเดียวกับที่ใช้โดยฟังก์ชัน DateDiff (เดือน, ... ) ของ Sql Server นอกจากนี้ยังมีข้อดีของการกระชับและอธิบายและเข้าใจได้ง่าย ฉันจะอธิบายดังต่อไปนี้ ... คุณต้องเปลี่ยนกี่หน้าในปฏิทินจากวันหนึ่งไปอีกวันหนึ่ง
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 เดือน" เปลี่ยนแปลงขึ้นอยู่กับว่าเป็นเดือนใดและไม่ใช่คำตอบอื่นที่คำนึงถึงสิ่งนี้! หากคุณต้องการข้อมูลเพิ่มเติมเกี่ยวกับปัญหาที่ไม่ได้อยู่ในกรอบคุณสามารถอ่านโพสต์นี้: วัตถุ Real Timepan ด้วย. Years & .Months (อย่างไรก็ตามการอ่านโพสต์นั้นไม่จำเป็นต้องเข้าใจและใช้ฟังก์ชั่นด้านล่างนี้ มันใช้งานได้ 100% โดยไม่ผิดพลาดจากการประมาณที่คนอื่นชอบที่จะใช้ - และรู้สึกอิสระที่จะแทนที่ฟังก์ชัน. Reverse มันด้วยฟังก์ชัน. Reverse ที่คุณมีอยู่ในกรอบงานของคุณ

โปรดทราบว่าคุณสามารถรับวันที่ / เวลาจำนวนวินาทีและนาทีหรือวินาทีวินาทีและวันใด ๆ ไม่เกินปี (ซึ่งจะมี 6 ส่วน / ส่วน) หากคุณระบุสองอันดับแรกและมีอายุเกินหนึ่งปีรายการดังกล่าวจะกลับมา "1 ปีและ 3 เดือนที่ผ่านมา" และจะไม่ส่งคืนส่วนที่เหลือเนื่องจากคุณขอสองกลุ่ม หากมีอายุเพียงไม่กี่ชั่วโมงก็จะกลับมา "2 ชั่วโมงและ 1 นาทีก่อน" แน่นอนว่าจะใช้กฎเดียวกันนี้หากคุณระบุ segmets 1, 2, 3, 4, 5 หรือ 6 (maxes out ที่ 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"เมื่อมันเข้าใจว่าไม่มีข้อมูลวันที่ในกลุ่ม 3 อันดับแรกและไม่สนใจแม้ว่าคุณจะระบุกลุ่มที่ 6 ดังนั้นไม่ต้องกังวล :) แน่นอนถ้ามีเซ็กเมนต์ที่มี 0 อยู่ในนั้นก็จะนำมาพิจารณาเมื่อสร้างสตริงและจะแสดงเป็น"3 days and 4 seconds ago"และละเว้นส่วน "0 ชั่วโมง"! สนุกและโปรดแสดงความคิดเห็นหากคุณต้องการ

 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" ซึ่งรับสตริงต้นฉบับและอาร์กิวเมนต์ที่ระบุสิ่งที่จำเป็นต้องเปลี่ยนและอาร์กิวเมนต์อื่นระบุสิ่งที่คุณต้องการแทนที่ด้วยและจะแทนที่การเกิดครั้งสุดท้ายของสตริงนั้นเท่านั้น ... ฉันได้รวมหนึ่งไว้ถ้าคุณไม่มีหรือไม่ต้องการนำไปใช้ดังนั้นนี่คือมันจะทำงาน "ตามสภาพ" โดยไม่จำเป็นต้องแก้ไข ฉันรู้ว่าฟังก์ชั่น reverseit ไม่ต้องการอีกต่อไป (มีอยู่ใน. net) แต่แทนที่ ReplaceLast และ ReverseIt func ดำเนินการตั้งแต่วันก่อนหน้า. ดังนั้นโปรดแก้ตัวว่าวันที่มันอาจจะดูยังคงใช้งานได้ 100% ยาวกว่าสิบปีสามารถรับประกันได้ว่าพวกเขาจะปราศจากข้อผิดพลาด) ... :) ไชโย

<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 เนื่องจากคุณจำเป็นต้องรู้ว่าคุณกำลังติดต่อกับเดือนใดและคุณกำลังติดต่อกับปีอธิกสุรทินเหมือนที่คุณพูดหรือไม่

ไม่ว่าจะเป็นหมายเลขโดยประมาณหรือใช้ความคิดสร้างสรรค์กับ DateTimes ดั้งเดิม


0

http://www.astro.uu.nl/~strous/AA/en/reken/juliaansedag.html

หากคุณสามารถแปลงเวลาจากวันที่แบบคริสต์ศักราชเป็นหมายเลขวัน Julianคุณเพียงแค่สร้างโอเปอเรเตอร์เพื่อทำการเปรียบเทียบหมายเลขวัน zulian ซึ่งสามารถพิมพ์สองครั้งเพื่อรับเดือนวันวินาที ฯลฯ ดูด้านบน ลิงก์สำหรับอัลกอริทึมสำหรับการแปลงจากเกรโกเรียนเป็นจูเลียน



0

หากคุณกำลังเผชิญกับเดือนและปีที่คุณต้องการสิ่งที่รู้ว่ากี่วันในแต่ละเดือนมีและปีที่ก้าวกระโดดปี

เข้าสู่ปฏิทินแบบคริสต์ศักราช (และการใช้งานปฏิทินเฉพาะวัฒนธรรมอื่น ๆ)

ในขณะที่ปฏิทินไม่มีวิธีในการคำนวณความแตกต่างโดยตรงระหว่างสองจุดในเวลา แต่ก็มีวิธีเช่น

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 องค์ประกอบแรกคือปีที่สองคือเดือนและองค์ประกอบสิ้นสุดคือวัน:

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 และใช้ตรรกะเดียวกันกับข้างบนและ AddYears แทน AddMonths ในลูป while


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

คำนวณจำนวนเดือนระหว่าง 2 วัน:

$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 #
หยุด
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.