Get trimester (quarter) code in c# - c#

i needed to get the correspondent trimester (periods of 3 months, being the first trimester Jan, Feb and Mar) of a given date. Using the c# System.DateTime struct I didn't manage to find a method for what I was looking for. So I solved like this:
DateTime sDeathDate = DateTime.Parse("18/09/1970");
int iDeathTrimester = Convert.ToInt32(Math.Round(Convert.ToDouble(sDeathDate.Month) / 3 + 0.25));
If someone knows a easier way to do this, please answer.
André

Assuming Jan - Mar is trimester 1, Apr - Jun is trimester 2, Jul - Sep is Trimester 3 and Oct - Dec is trimester 4, then you can use
int trimester = (sDeathDate.Month - 1) / 3 + 1
This is the same as a quarter, did you mean something different?

Math.Ceiling Returns the smallest integer greater than or equal to the specified number.
DateTime sDeathDate = DateTime.Parse("18/11/1970");
int trimester = (int)Math.Ceiling(sDeathDate.Month/3.0);
Note that the code use 3.0 to perform a floating point division and not an integer division

The Time Period Library for .NET includes the class Quarter (period of 3 months):
// ----------------------------------------------------------------------
public void QuarterOfDateSample()
{
DateTime moment = new DateTime( 1970, 9, 15 );
Console.WriteLine( "Quarter : {0}", new Quarter( moment ) );
// > Quarter : Q3 1970; 01.07.1970- 30.09.1970| 91.23:59
Console.WriteLine( "Quarter : {0}", new Quarter( 2006, YearQuarter.First ) );
// > Quarter : Q1 2006; 01.01.2006 - 31.03.2006 | 89.23:59
} // QuarterOfDateSample

If you use Math.Round (Double, MidpointRounding) you can do the round better without adding 0,25
See you

Related

How can I pack a Date and Time into 32-bits?

I have a date represantation with the following order in order to fit an int. The date represantation should be
year : 6 bits
month : 5 bits
day : 5 bits
hours : 4 bits
minutes: 6 bits
seconds: 6 bits
All the dates should be fit into an Unit32 date
[year(5bits),month(5bits),day(6bits),hours(4bits),minutes(6bits),seconds(6bit)]
UInt32 date = 0;
date= date| (UInt32) (DateTime.Now.Year%2000)<< 26;
date= date| ((uint)DateTime.Now.Month << 22);
date= date| (uint)DateTime.Now.Day << 17;
date= date| ((uint)DateTime.Now.Hour << 15);
date= date| ((uint)DateTime.Now.Minute << 6);
date= date| ((uint)DateTime.Now.Second);
string binary = Convert.ToString(date, 2);
Above i have tried some bitwise operation but the result doestn satisfy. what i am missing or what should i do?
For Example current date is: 2021.55.10 13:55:06
Binary conversion above code 1010100110101101000110111000011 hex mean 54D6 8DC3 that means Thursday, 1 January 1970 06:01:58 but it should be today's date
thanks.
There's a few problems with your scheme:
year : 6 bits - 0 to 63
month : 5 bits - 0 to 31
day : 5 bits - 0 to 31
hours : 4 bits - 0 to 15
minutes: 6 bits - 0 to 63
seconds: 6 bits - 0 to 63
The main problem is that your scheme only uses 4 bits for hours - so it cannot represent times between 4pm and midnight (i.e. hours 16 through 23).
Secondarily, your scheme is inefficient because it wastes bits (there's 0-59 seconds in a minute, not 0-63).
...which means your scheme can represent invalid values, such as the 63rd second of the 63rd minute of the 15th hour of the 31st of February - which isn't a real date+time.
Rather than reinventing the wheel, why not use something like Unix time? (i.e. simply the number of seconds since an epoch).
Yes there is a reason memory reason which i cant use unix epox time
...so you need to fit the value into 32-bits. That's fine because you can use Unix time with 32-bit values. Of course this means you'll run into the Year 2038 problem but there's a few possible different solutions:
You can use a lower resolution.
e.g. 2-second or 1-minute resolution.
You can use a different epoch (base offset).
signed 32-bit Unix time uses 1970-01-01 00:00:00 with a max date+time value of 2038-01-19 03:14:07.
Unsigned 32-bit Unix time has a maximum of 2106-02-07 06:28:15.
If you use an epoch of 2000-01-01 00:00:00 with unsigned 32-bit integers then you have a max value of 2136-02-07 14:28:15.
This range of 135 full years is significantly better than your scheme because your scheme uses 6 bits for the year, which is 0-63, and last time I checked, 135 is greater than 63.
So here's a ready-made copy'n'pastable implementation of 32-bit Unix-time with an epoch of 2000-01-01 00:00:00:
private const Int64 YEAR_2000 = 946713600; // The year where there are no elephants. There is also no unethical treatment of elephants.
private static readonly DateTimeOffset _maxValueForUnsigned32Bits = DateTimeOffset( YEAR_2000 + UInt32.MaxValue ); // 2136-02-07 14:28:15
static UInt32 GetTimeAsUInt32( DateTimeOffset dt )
{
if (dt.Year < 2000 || dt > _maxValueForUnsigned32Bits ) throw new ArgumentOutOfRangeException( paramName: nameof(dt), actualValue: dt, message: "Must be between 2000 and 2136-02-07 14:28:15 inclusive." );
Int64 unixFrom2000_64 = dt.ToUnixTimeSeconds() - YEAR_2000;
// `unixFrom2000_64` *will always fit in 32-bits* due to the year range check above, and subtracting the new epoch.
UInt32 unixFrom2000_32 = (UInt32)unixFrom2000_64;
return unixFrom2000_32;
}
static DateTimeOffset GetTimeFromUInt32( UInt32 value )
{
Int64 asUnixTime = value + YEAR_2000;
return DateTimeOffset.FromUnixTimeSeconds( asUnixTime );
}
Usage:
DateTimeOffset now = DateTimeOffset.UtcNow; // WARNING: Using `Now` instead of `UtcNow` will include an offset value that will not be persisted.
Console.WriteLine( "Now: {0}", now );
UInt32 myTime = GetTimeAsUInt32( now );
Console.WriteLine( "As UInt32: {0:D}", myTime );
DateTimeOffset decoded = GetTimeFromUInt32( myTime );
Console.WriteLine( "Decoded from UInt32: {0}", decoded );
Gives me this output:
Now: 10/03/2021 11:26:43 +00:00
As UInt32: 668662003
Decoded from UInt32: 10/03/2021 11:26:43 +00:00

How to manually configure NodaTime IWeekYearRule correctly?

I am wrapping NodaTime for use in our software, and to be future-proof I thought I'd set up IWeekYearRule according to Bcl DateTimeFormat, like this:
var culture = CultureInfo.CurrentCulture;
var weekRule = WeekYearRules.FromCalendarWeekRule(
culture.DateTimeFormat.CalendarWeekRule,
culture.DateTimeFormat.FirstDayOfWeek);
Here in Norway we're following the following settings:
CalendarWeekRule.FirstFourDayWeek
DayOfWeek.Monday
However, much to my surprise, when I checked the week number of the following two dates, kalender.no disagrees:
2018-12-31: week 53 of 2018 (kalender.no says week 1 [implied: of 2019])
2019-01-01: week 1 of 2019 (kalender.no agrees here)
However, if I use WeekYearRules.Iso, they both return "week 1 of 2019", which is the correct one.
I have a bunch of questions but let's focus on the most immediate one:
How can I write the code to "future-proof" this part of the library, so that when we start rolling out the software into more parts of the world, it will calculate week numbers correctly out of the box, according to the local culture?
Or is this just not possible, because the culture information doesn't contain this?
Here's a LINQPad example that demonstrates NodaTime behavior (sorry for not including a [mcve] to begin with):
void Main()
{
"-------------- ISO --------------".Dump();
Test(WeekYearRules.Iso);
"-------------- BCL --------------".Dump();
Test(WeekYearRules.FromCalendarWeekRule(CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday));
}
public static void Test(IWeekYearRule rule)
{
var date = new LocalDate(2018, 12, 31);
$"{date}, {rule.GetWeekOfWeekYear(date)} in {rule.GetWeekYear(date)}".Dump();
date = new LocalDate(2019, 1, 1);
$"{date}, {rule.GetWeekOfWeekYear(date)} in {rule.GetWeekYear(date)}".Dump();
}
Output:
-------------- ISO --------------
mandag 31. desember 2018, 1 in 2019
tirsdag 1. januar 2019, 1 in 2019
-------------- BCL --------------
mandag 31. desember 2018, 53 in 2018
tirsdag 1. januar 2019, 1 in 2019
(and just to be clear, the ISO output is the correct one for Norway)
This is NodaTime 2.4.4, Windows 10, .NET 4.7.2.
And here's a LINQPad example without using NodaTime:
void Main()
{
var culture = CultureInfo.GetCultureInfo("nb-NO");
var calendarWeekRule = culture.DateTimeFormat.CalendarWeekRule.Dump();
var firstDayOfWeek = culture.DateTimeFormat.FirstDayOfWeek.Dump();
var date = new DateTime(2018, 12, 31);
$"{date.DayOfWeek} {date}, week {culture.Calendar.GetWeekOfYear(date, calendarWeekRule, firstDayOfWeek)}".Dump();
date = new DateTime(2019, 1, 1);
$"{date.DayOfWeek} {date}, week {culture.Calendar.GetWeekOfYear(date, calendarWeekRule, firstDayOfWeek)}".Dump();
}
Output:
FirstFourDayWeek
Monday
Monday 31.12.2018 00.00.00, week 53
Tuesday 01.01.2019 00.00.00, week 1
The Monday there is in the same week as week 1 of 2019, and is counted as week 1 in 2019 here in Norway, so the first of the two dates should really have been:
Monday 31.12.2018 00.00.00, week 1

Quartz.Net - Every 3 months

I'm trying to call something every 3 months (quarterly) in Quartz.NET (using both stable and latest version 2 which is beta with same results).
I create cron trigger with 0 30 8 3 */3 ? * to be called every 3 months at 8.30am on third of the month it occurs.
So technically since its 2 of September today I would expect it to trigger tomorrow. However it next run time shows as being next month. Why is that so?
Updated: As per answers I got I created following method - could be useful for someone:
public static string CalculateMonthsWithInterval(int startMonth, int interval)
{
var months = new List<string>();
var monthNames = new [] {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
var monthSelector = startMonth % interval;
for (var i = 0; i < 12; i++)
{
if (i % interval == monthSelector)
{
months.Add(monthNames[i]);
}
}
return string.Join(",", months.ToArray());
}
Ps: I didn't use indexes for months because for some reason it wasn't working well with my Quartz (v2 BETA). Also its easier to read in DB level.
Example call - Every 3 months based on startDate:
var cronMonths = CronUtils.CalculateMonthsWithInterval((startDate.Month - 1), 3);
Well I think that's because the scheduler will verify which month can be divided by 3, since all month in Quartz are based 0 (according to: http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/tutorial-lesson-06), the month that will be scheduled will be january, april, july and october.
0 mod 3 = 0 -> JAN
1 mod 3 = 1 -> FEB
...
8 mod 3 = 2 -> SEP
9 mod 3 = 0 -> OCT
The Quartz scheduler will analyse your cron expression and keep only those where their modulus 3 equals to 0.
If you want it to be 1 month before that (march, june, september and october) you will have to set it to:
0 30 8 3 MAR,JUN,SEP,DEC ? *
A good page to create cron expressions: http://www.cronmaker.com/
Cron format:
0 0 12 1 1/3 ? *
Executes every:
1. Saturday, April 1, 2017 12:00 PM
2. Saturday, July 1, 2017 12:00 PM
3. Sunday, October 1, 2017 12:00 PM
4. Monday, January 1, 2018 12:00 PM
5. Sunday, April 1, 2018 12:00 PM

c# date comparison returning a DateTime object

Given a DateTime object (last purchase) can I compare against DateTime.Now to return a new DateTime object that contains, years, months, days, hours and seconds?
I can compare dates and return the number of years however, I need all details down to the second.
Any help or examples would be appreciated
You don't want to have a new DateTime object. What you want to have is a TimeSpan. And that's what you get if you subtract DateTime instances:
TimeSpan difference = DateTime.Now - yourDate
No, you can't, because a DateTime represents an absolute point in time, not a span of time. The correct object to use for the difference is TimeSpan. It has properties for all the relevant time units. You can get one by just subtracting two DateTimes:
TimeSpan ts = DateTime.Now - purchaseDate;
You can use the TimeSpan to compare two dates. Have a look at
DateTime date1 = new DateTime(2010, 1, 1, 8, 0, 15);
DateTime date2 = new DateTime(2010, 8, 18, 13, 30, 30);
// Calculate the interval between the two dates.
TimeSpan interval = date2 - date1;
Years and months have to be calculated through custom coding as month is a variable unit of measure but since TimeSpan tell you the days you can calculate months and years yourself.
Have a look at this post.
Have a look at this forum.
Hope this help.
TimeSpan is the correct way to handle this, however, if you don't want to do the conversion of months/years yourself, you can always use some recursive method to subtract years until you get to the correct year, months until you get to the correct month, days until you get to the correct day, etc. It would suck and be ugly, though.
Also, you'd need a custom object that would hold the data for you.
A rough cut would be something like
DateTime original = [Whatever]
DateTime compare = [Whatever]
DateTimeDiff difference = GetDifference(original, compare)
GetDifference(DateTime original, DateTime compare)
{
DateTimeDiff difference = new DateTimeDifference
while(!original.Equals(compare) && original.Year >= compare.Year)
{
original.AddYears(-1);
difference.Years++;
}
<snip... //more code to do the same thing for months, days, hours, etc.)
}
Like I said, it would suck and be ugly, but you could do it.
This library includes the class DateDiff with support of Year and Months:
// ----------------------------------------------------------------------
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
} // DateDiffSample
If you want years and months you have to write your own code. It's actually tricky to write this code with an output which is 100% correct due to the different leapyear issues.
Here are a couple of tricky special cases (Note 2012 is a leap year, 2011 isn't):
Between February 28th 2010 and February 28th 2011 - it is 1y, 0m, 0d
Between February 28th 2011 and February 28th 2012 - it is 1y, 11m, 28d
Between February 28th 2011 and February 29th 2012 - it is 1y, 0m, 0d
Between February 29th 2012 and February 28th 2013 - it is 1y, 0m, 0d
Between February 10th 2011 and March 9th 2011 - it is 0y, 0m, 27d
Between February 10th 2012 and March 9th 2012 - it is 0y, 0m, 28d
I think you need to implement something like AllenG's algorithm with a few modifications
You need to start with the earliest datetime (datetime1). Make sure that it is earliest.
Loop until datetime1.AddYears(1) compares as greater than or equal to your latest datetime (datetime2). Make sure to compare the entire datetimes and not just the year property of starttime and endttime.
Increment a year counter on each iteration.
actually add a year to your datetime1 copy.
Start with the datetime1 copy (to which you have added years in step 2)
Loop until datetime1.AddMonths(1) compares as greater than or equal to your latest datetime (datetime2). Again: Make sure you compare the entire datetimes and not just the month component.
Increment a month counter on each iteration.
actually add a month to your datetime1 copy.
etc. etc
This algorithm ensures that you are calculating your years, months, days, hours, minutes and seconds differences using the right excerpt of the underlying calendar.
I think you need to combine the TimeSpan and some custom calculations. This, because you would like the years and months, which have a variable length. In addition you also want the number of hours, minutes and seconds.
I think this approach would serve your needs:
var date1 = new DateTime(2010, 1, 1, 8, 0, 15);
var date2 = new DateTime(2007, 8, 18, 13, 30, 30);
var differenceYears = date1.Year - date2.Year;
var tempDate = date2.AddYears(differenceYears);
var differenceMonths = date1.Month - tempDate.Month;
if (differenceMonths < 0)
{
differenceMonths += 12;
differenceYears--;
}
tempDate = date2.AddYears(differenceYears).AddMonths(differenceMonths);
var additionalTimeSpan = date1 - tempDate;
var differenceDays = additionalTimeSpan.Days;
var differenceHours = additionalTimeSpan.Hours;
var differenceMinutes = additionalTimeSpan.Minutes;
var differenceSeconds = additionalTimeSpan.Seconds;

How to get difference between two dates in Year/Month/Week/Day?

How to get difference between two dates in Year/Month/Week/Day in an efficient way?
eg. difference between two dates is 1 Year, 2 Months, 3 Weeks, 4 Days.
Difference represents count of year(s), month(s), week(s) and day(s) between two dates.
This is actually quite tricky. A different total number of days can result in the same result. For example:
19th June 2008 to 19th June 2010 = 2 years, but also 365 * 2 days
19th June 2006 to 19th June 2008 = 2 years, but also 365 + 366 days due to leap years
You may well want to subtract years until you get to the point where you've got two dates which are less than a year apart. Then subtract months until you get to the point where you've got two dates which are less than a month apart.
Further confusion: subtracting (or adding) months is tricky when you might start with a date of "30th March" - what's a month earlier than that?
Even further confusion (may not be relevant): even a day isn't always 24 hours. Daylight saving anyone?
Even further confusion (almost certainly not relevant): even a minute isn't always 60 seconds. Leap seconds are highly confusing...
I don't have the time to work out the exact right way of doing this right now - this answer is mostly to raise the fact that it's not nearly as simple as it might sound.
EDIT: Unfortunately I'm not going to have enough time to answer this fully. I would suggest you start off by defining a struct representing a 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!
}
}
I suggest you implement the + operator first, which should inform the Difference method - you should make sure that first + (Period.Difference(first, second)) == second for all first/second values.
Start with writing a whole slew of unit tests - initially "easy" cases, then move on to tricky ones involving leap years. I know the normal approach is to write one test at a time, but I'd personally brainstorm a bunch of them before you start any implementation work.
Allow yourself a day to implement this properly. It's tricky stuff.
Note that I've omitted weeks here - that value at least is easy, because it's always 7 days. So given a (positive) period, you'd have:
int years = period.Years;
int months = period.Months;
int weeks = period.Days / 7;
int daysWithinWeek = period.Days % 7;
(I suggest you avoid even thinking about negative periods - make sure everything is positive, all the time.)
Partly as a preparation for trying to answer this question correctly (and maybe even definitively...), partly to examine how much one can trust code that is pasted on SO, and partly as an exercise in finding bugs, I created a bunch of unit tests for this question, and applied them to many proposed solutions from this page and a couple of duplicates.
The results are conclusive: not a single one of the code contributions accurately answers the question. Update: I now have four correct solutions to this question, including my own, see updates below.
Code tested
From this question, I tested code by the following users:
Mohammed Ijas Nasirudeen, ruffin, Malu MN, Dave, pk., Jani, lc.
These were all the answers which provided all three of years, months, and days in their code. Note that two of these, Dave and Jani, gave the total number of days and months, rather than the total number of months left after counting the years, and the total number of days left after counting the months. I think the answers are wrong in terms of what the OP seemed to want, but the unit tests obviously don't tell you much in these cases. (Note that in Jani's case this was my error and his code was actually correct - see Update 4 below)
The answers by Jon Skeet, Aghasoleimani, Mukesh Kumar, Richard, Colin, sheir, just i saw, Chalkey and Andy, were incomplete. This doesn't mean that the answers weren't any good, in fact several of them are useful contributions towards a solution. It just means that there wasn't code taking two DateTimes and returning 3 ints that I could properly test. Four of these do however talk about using TimeSpan. As many people have mentioned, TimeSpan doesn't return counts of anything larger than days.
The other answers I tested were from
question 3054715 - LukeH, ho1 and this. ___curious_geek
question 6260372 - Chuck Rostance and Jani (same answer as this question)
question 9 (!) - Dylan Hayes, Jon and Rajeshwaran S P
this.___curious_geek's answer is code on a page he linked to, which I don't think he wrote. Jani's answer is the only one which uses an external library, Time Period Library for .Net.
All other answers on all these questions seemed to be incomplete. Question 9 is about age in years, and the three answers are ones which exceeded the brief and calculated years, months and days. If anyone finds further duplicates of this question please let me know.
How I tested
Quite simply: I made an interface
public interface IDateDifference
{
void SetDates(DateTime start, DateTime end);
int GetYears();
int GetMonths();
int GetDays();
}
For each answer I wrote a class implementing this interface, using the copied and pasted code as a basis. Of course I had to adapt functions with different signatures etc, but I tried to make the minimal edits to do so, preserving all the logic code.
I wrote a bunch of NUnit tests in an abstract generic class
[TestFixture]
public abstract class DateDifferenceTests<DDC> where DDC : IDateDifference, new()
and added an empty derived class
public class Rajeshwaran_S_P_Test : DateDifferenceTests<Rajeshwaran_S_P>
{
}
to the source file for each IDateDifference class.
NUnit is clever enough to do the rest.
The tests
A couple of these were written in advance and the rest were written to try and break seemingly working implementations.
[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);
}
}
Most of the names are slightly silly and don't really explain why code might fail the test, however looking at the two dates and the answer(s) should be enough to understand the test.
There are two functions that do all the Asserts, CheckResults() and CheckControversialResults(). These work well to save typing and give the right results, but unfortunately they make it harder to see exactly what went wrong (because the Assert in CheckControversialResults() will fail with "Expected true", rather than telling you which value was incorrect. If anyone has a better way to do this (avoid writing the same checks each time, but have more useful error messages) please let me know.
CheckControversialResults() is used for a couple of cases where there seem to be two different opinions on what is right. I have an opinion of my own, but I thought I should be liberal in what I accepted here. The gist of this is deciding whether one year after Feb 29 is Feb 28 or Mar 1.
These tests are the crux of the matter, and there could very well be errors in them, so please do comment if you find one which is wrong. It would be also good to hear some suggestions for other tests to check any future iterations of answers.
No test involves time of day - all DateTimes are at midnight. Including times, as long as it's clear how rounding up and down to days works (I think it is), might show up even more flaws.
The results
The complete scoreboard of results is as follows:
ChuckRostance_Test 3 failures S S S F S S F S F
Dave_Test 6 failures F F S F F F F S S
Dylan_Hayes_Test 9 failures F F F F F F F F F
ho1_Test 3 failures F F S S S S F S S
Jani_Test 6 failures F F S F F F F S S
Jon_Test 1 failure S S S S S S F S S
lc_Test 2 failures S S S S S F F S S
LukeH_Test 1 failure S S S S S S F S S
Malu_MN_Test 1 failure S S S S S S S F S
Mohammed_Ijas_Nasirudeen_Test 2 failures F S S F S S S S S
pk_Test 6 failures F F F S S F F F S
Rajeshwaran_S_P_Test 7 failures F F S F F S F F F
ruffin_Test 3 failures F S S F S S F S S
this_curious_geek_Test 2 failures F S S F S S S S S
But note that Jani's solution was actually correct and passed all tests - see update 4 below.
The columns are in alphabetical order of test name:
AlmostThreeYearsTest
AlmostTwoYearsTest
BasicTest
BornOnALeapYearTest
BornOnALeapYearTest2
LongMonthToLongMonth
LongMonthToLongMonthPenultimateDay
LongMonthToPartWayThruShortMonth
LongMonthToShortMonth
Three answers failed only 1 test each, Jon's, LukeH's and Manu MN's. Bear in mind these tests were probably written specifically to address flaws in those answers.
Every test was passed by at least one piece of code, which is slightly reassuring that none of the tests are erroneous.
Some answers failed a lot of tests. I hope no-one feels this is a condemnation of that poster's efforts. Firstly the number of successes is fairly arbitrary as the tests don't evenly cover the problem areas of the question space. Secondly this is not production code - answers are posted so people can learn from them, not copy them exactly into their programs. Code which fails a lot of tests can still have great ideas in it. At least one piece which failed a lot of tests had a small bug in it which I didn't fix. I'm grateful to anyone who took the time to share their work with everyone else, for making this project so interesting.
My conclusions
There are three:
Calendars are hard.
I wrote nine tests, including three where two answers are possible. Some of the tests where I only had one answer might not be unanimously agreed with. Just thinking about exactly what we mean when we say '1 month later' or '2 years earlier' is tricky in a lot of situations. And none of this code had to deal with all the complexities of things like working out when leap years are. All of it uses library code to handle dates. If you imagine the 'spec' for telling time in days, weeks, months and years written out, there's all sorts of cruft. Because we know it pretty well since primary school, and use it everyday, we are blind to many of the idiosyncracies. The question is not an academic one - various types of decomposition of time periods into years, quarters and months are essential in accounting software for bonds and other financial products.
Writing correct code is hard.
There were a lot of bugs. In slightly more obscure topics or less popular questions than the chances of a bug existing without having been pointed out by a commenter are much, much higher than for this question. You should really never, never copy code from SO into your program without understanding exactly what it does. The flipside of this is that you probably shouldn't write code in your answer that is ready to be copied and pasted, but rather intelligent and expressive pseudo-code that allows someone to understand the solution and implement their own version (with their own bugs!)
Unit tests are helpful.
I am still meaning to post my own solution to this when I get round to it (for someone else to find the hidden, incorrect assumptions in!) Doing this was a great example of 'saving the bugs' by turning them into unit tests to fix the next version of the code with.
Update
The whole project is now at https://github.com/jwg4/date-difference
This includes my own attempt jwg.cs, which passes all the tests I currently have, including a few new ones which check for proper time of day handling. Feel free to add either more tests to break this and other implementations or better code for answering the question.
Update 2
#MattJohnson has added an implementation which uses Jon Skeet's NodaTime. It passes all the current tests.
Update 3
#KirkWoll's answer to Difference in months between two dates has been added to the project on github. It passes all the current tests.
Update 4
#Jani pointed out in a comment that I had used his code wrongly. He did suggest methods that counted the years, months and days correctly, (alongside some which count the total number of days and months, not the remainders) however I mistakenly used the wrong ones in my test code. I have corrected my wrapper around his code and it now passes all tests. There are now four correct solutions, of which Jani's was the first. Two use libraries (Intenso.TimePeriod and NodaTime) and two are written from scratch.
For the correct difference calculation of Years/Months/Weeks, the Calendar of the CultureInfo must be considered:
leap vs. non-leap years
months with different count of days
years with different count of weeks (varying by the first day of week and the calendar week rule)
The DateDiff class of the Time Period Library for .NET respects all these factors:
// ----------------------------------------------------------------------
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 also calculates the difference of Quarters.
Leap years and uneven months actually make this a non-trivial problem. I'm sure someone can come up with a more efficient way, but here's one option - approximate on the small side first and adjust up (untested):
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;
}
What about using the System.Data.Linq namespace and its SqlMethods.DateDiffMonth method?
For example, say:
DateTime starDT = {01-Jul-2009 12:00:00 AM}
DateTime endDT = {01-Nov-2009 12:00:00 AM}
Then:
int monthDiff = System.Data.Linq.SqlClient.SqlMethods.DateDiffMonth(startDT, endDT);
==> 4
There are other DateDiff static methods in the SqlMethods class.
Subtract two DateTime instances to give you a TimeSpan which has a Days property. (E.g. in PowerShell):
PS > ([datetime]::today - [datetime]"2009-04-07")
Days : 89
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 0
Ticks : 76896000000000
TotalDays : 89
TotalHours : 2136
TotalMinutes : 128160
TotalSeconds : 7689600
TotalMilliseconds : 7689600000
Converting days into years or weeks is relatively easy (days in a year could be 365, 365.25, ... depending on context). Months is much harder, because without a base date you don't know which month lengths apply.
Assuming you want to start with your base date, you can incrementally substract while counting first years (checking for leap years), then month lengths (indexing from startDate.Month), then weeks (remaining days divided by 7) and then days (remainder).
There are a lot of edge cases to consider, e.g. 2005-03-01 is one year from 2004-03-01, and from 2004-02-29 depending on what you mean by "Year".
Well, #Jon Skeet, if we're not worried about getting any more granular than days (and still rolling days into larger units rather than having a total day count), as per the OP, it's really not that difficult in C#. What makes date math so difficult is that the number of units in each composite unit often changes. Imagine if every 3rd gallon of gas was only 3 quarts, but each 12th was 7, except on Fridays, when...
Luckily, dates are just a long ride through the greatest integer function. These crazy exceptions are maddening, unless you've gone all the way through the wackily-comprised unit, when it's not a big deal any more. If you're born on 12/25/1900, you're still EXACTLY 100 on 12/25/2000, regardless of the leap years or seconds or daylight savings periods you've been through. As soon as you've slogged through the percentages that make up the last composite unit, you're back to unity. You've added one, and get to start over.
Which is just to say that if you're doing years to months to days, the only strangely comprised unit is the month (of days). If you need to borrow from the month value to handle a place where you're subtracting more days than you've got, you just need to know the number of days in the previous month. No other outliers matter.
And C# gives that to you in System.DateTime.DaysInMonth(intYear, intMonth).
(If your Now month is smaller than your Then month, there's no issue. Every year has 12 months.)
And the same deal if we go more granular... you just need to know how many (small units) are in the last (composite unit). Once you're past, you get another integer value more of (composite unit). Then subtract how many small units you missed starting where you did Then and add back how many of those you went past the composite unit break-off with your Now.
So here's what I've got from my first cut at subtracting two dates. It might work. Hopefully useful.
(EDIT: Changed NewMonth > OldMonth check to NewMonth >= OldMonth, as we don't need to borrow one if the Months are the same (ditto for days). That is, Nov 11 2011 minus Nov 9 2010 was giving -1 year, 12 months, 2 days (ie, 2 days, but the royal we borrowed when royalty didn't need to.)
(EDIT: Had to check for Month = Month when we needed to borrow days to subtract a dteThen.Day from dteNow.Day & dteNow.Day < dteThen.Day, as we had to subtract a year to get 11 months and the extra days. Okay, so there are a few outliers. ;^D I think I'm close now.)
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.
}
If you subtract two instances of DateTime, that will return an instance of TimeSpan, which will represent the difference between the two dates.
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)
I came across this post while looking to solve a similar problem. I was trying to find the age of an animal in units of Years, Months, Weeks, and Days. 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.
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
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
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;
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";
}
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;
}
}
int day=0,month=0,year=0;
DateTime smallDate = Convert.ToDateTime(string.Format("{0}", "01.01.1900"));
DateTime bigDate = Convert.ToDateTime(string.Format("{0}", "05.06.2019"));
TimeSpan timeSpan = new TimeSpan();
//timeSpan is diff between bigDate and smallDate as days
timeSpan = bigDate - smallDate;
//year is totalDays / 365 as int
year = timeSpan.Days / 365;
//smallDate.AddYears(year) is closing the difference for year because we found the year variable
smallDate = smallDate.AddYears(year);
//again subtraction because we don't need the year now
timeSpan = bigDate - smallDate;
//month is totalDays / 30 as int
month = timeSpan.Days / 30;
//smallDate.AddMonths(month) is closing the difference for month because we found the month variable
smallDate = smallDate.AddMonths(month);
if (bigDate > smallDate)
{
timeSpan = bigDate - smallDate;
day = timeSpan.Days;
}
//else it is mean already day is 0
No perfect solution exists for this problem :)!
I'll show that there is in fact no perfectly consistent answer. To simplify, let's disregard years and weeks, just look at months and days in non-leap year.
What is the expected output for:
28/1 to 28/2?
-> Alice: "1 month" (1 month takes you to the same day of next month)
-> Brad: "1 month and 3 days" (3 days until end of Jan and the entire month of Feb)
Both legitimate answers, and this is already problem number one. But it gets worse. Let's reduce the date difference by 1 day and ask again:
28/1 to 27/2?
-> Alice: "30 days"
-> Brad: "Umm... 1 month and 2 days does not seem right!"
Brad has given up on his concept. But Alice is still happy about hers. Next, let's increase the date difference by 1 day from the initial question:
28/1 to 1/3?
-> Alice: "1 month and 1 day" (1 month to 28/2, plus 1 day)
Alice is really confident now.
29/1 to 1/3?
-> Alice: "Umm... still 1 month 1 day?"
30/1 to 1/3?
-> Alice: "Ok, I see... "
Conclusion
Neither Alice's nor Brad's concept stays consistent. You will have to accept one of the following two caveats for a timespan counter:
It jumps from e.g. 1 month to 1 month and 3 days from one day to the next
It stays put at e.g. 1 month and 1 day for up to 4 days in a row
It doesn't always give you exactly the result you would expect
Personally I'd prefer accepting the last one, if it means your counter keeps counting, but I guess it depends on the use case.
Reasonable approximation
Once you accept caveat #3, adding the calculated timespan to DateTime.MinValue and taking the Year/Month/Day from where you land seems a reasonable approximation to me. Below code is adapted from this answer. You could roll in accounting for leap years as well, to have a few more days where the output meets the expectations.
public static string GetYearMonthDayDiff(DateTime start, DateTime end)
{
var span = (end - start).Duration(); // absolute value (allows switched start/end)
var diff = DateTime.MinValue + span;
int years = diff.Year - 1;
int months = diff.Month - 1;
int days = diff.Day - 1; // matter of definition if you start counting at 0 or 1
return $"{years}Y {months}M {days}D";
}
The code above then gives the following answers
28/1 to 27/2? -> 30 days
28/1 to 28/2? -> 1 month
28/1 to 1/3? -> 1 month and 1 day
29/1 to 1/3 -> 1 month
30/1 to 1/3 -> 30 days
Don't you love it, when it all doesn't quite add up :)?
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>
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 );
Output:
Time Difference (seconds): 15
Time Difference (minutes): 1
Time Difference (hours): 0
Time Difference (days): 0

Categories

Resources