I work for myself, I am a self-employed coder and as a result I don't have the luxury of code reviews or the ability to improve based upon peer programming. I am going to use this as an exercise to see if the StackOverflow community might help to review a simple method which i've written;
internal static DateTime CONVERT_To_DateTime(int binDate)
{
// 3/10/2008 = 1822556159
// 2/10/2008 = 1822523391
// 1/10/2008 = 1822490623
// 30/09/2008 = 1822392319
// 29/09/2008 = 1822359551
// September 30th 2008
// 1822392319 = 0x6c9f7fff
// 0x6c = 108 = 2008 (based on 1900 start date)
// 0x9 = 9 = September
// 0xf7fff - take top 5 bits = 0x1e = 30
// October 1st 2008
// 1822490623 = 0x6ca0ffff
// 0 x6c = 108 = 2008
// 0 xa = 10 = October
// 0x0ffff - take top 5 bits = 0x01 = 1
// OR using Binary (used by this function)
// a = 1822556159 (3/10/2008)
// 1101100 1010 00011 111111111111111
// b = 1822523391 (2/10/2008)
// 1101100 1010 00010 111111111111111
// c = 1822490623 (1/10/2008)
// 1101100 1010 00001 111111111111111
// D = 1822392319 (30/09/2008)
// 1101100 1001 11110 111111111111111
// Excess 111111 are probably used for time/seconds which
// we do not care for at the current time
var BaseYear = 1900;
// Dump the long date to binary
var strBinary = Convert.ToString(binDate);
// Calculate the year
var strBYear = strBinary.Substring(0, 7);
var iYear = Convert.ToInt32(strBYear, 2) + BaseYear;
// Calculate the month
var strBMonth = strBinary.Substring(7, 4);
var iMonth = Convert.ToInt32(strBMonth, 2);
// Calculate the day
var strBDay = strBinary.Substring(11, 5);
var iDay = Convert.ToInt32(strBDay, 2);
// ensure that month and day have two digits
var strDay = iDay < 10 ? "0" + iDay : iDay.ToString();
var strMonth = iMonth < 10 ? "0" + iMonth : iMonth.ToString();
// Build the final date
var convertedDate = iYear + strMonth + strDay;
return DateTime.ParseExact(convertedDate, "yyyyMMdd", null);
}
This is a method that takes a numeric representation of a date and converts it to a DateTime DataType. I would like the method to be reviewed to acheive the fastest possible execution time because it's being executed within a loop.
Any comments on the method is appreciated as this will be an exercise for me. i look forward to some responses.
Instead of converting to a string, then to integers, then to string, then to date, just get the integers by shifting and masking, and create the DateTime value directly from the integer values:
binDate >>= 15;
int day = binDate & 31;
binDate >>= 5;
int month = binDate & 15;
binDate >>= 8;
int year = binDate + 1900;
return new DateTime(year, month, day);
You're doing string manipulations. This is true performance killer when used in tight loops.
static DateTime ToDateTime(int value)
{
var year = (int)((value & 0xff000000) >> 24);
var month = (value & 0xf00000) >> 20;
var day = (value & (0xf8000)) >> 15;
return new DateTime(1900 + year, month, day);
}
Here's how you do that. First, take 1822490623 and convert it to binary:
0110 1100 1010 0000 1111 1111 1111 1111
This is a mask for year:
f f 0 0 0 0 0 0
This is for month:
0 0 f 0 0 0 0 0
And this is for day:
0 0 0 f 8 0 0 0
"Year" value has to be shifted right by 6 * 4 bits, "month" - by 5 * 4, and "day" - by 3 * 4 + 3 bits.
Welcome to the community, Phillis. :)
Anton is correct, your string manipulations are going to be slow. Because it looks like you're using the parameter as a bitfield, I'd suggest looking into the various (much faster) bit operators: <<, >>, &, |, and ~. It looks like you're trying to do binary manipulation, so use the operators built for it.
E.g. (untested, just off the cuff):
You start with a value of 0x6c9f7fff. The high order byte makes up the year. To mask out everything that isn't the year, do something like:
int year = ((binDate & 0xFF000000) >> 24) + BaseYear;
Likewise, the next 4 bits are the month, so:
int month = (binDate & 0x00F00000) >> 20;
int date = (binDate & 0x000F8000) >> 15;
return new DateTime(year, month, date);
I will suggest you to find the C/C++ code which does similar job; then port it into C#
Related
mydt1 = Convert.ToDateTime(dt.Rows[0].ItemArray[7].ToString());
DateTime dt2 = DateTime.Now;
var hours = (dt2 - mydt1).TotalHours;
if (hours >= 0) {
txtElapse.Text = hours.ToString("0");
var totals = hours * 2;
txtFine.Text = totals.ToString("0");
} else {
txtFine.Text = "0";
txtElapse.Text = "0";
}
I'm creating a library system, and I'm working on the fine. However, I want to exclude every Sunday that passes on the timespan. I don't know how could I do it or what logic I can use.
So you want to get difference (TimeSpan) between 2 DateTime, but ignoring Sunday.
Here is my minimum reproducible example:
using System;
public class Program
{
public static void Main(string[] args)
{
DateTime dt1 = Convert.ToDateTime(args[0]);
DateTime dt2 = Convert.ToDateTime(args[1]);
TimeSpan x = dt2-dt1;
double hours = x.TotalHours;
int numOfWeek = (int)Math.Floor(x.TotalDays/7);
hours = hours - (numOfWeek * 24);
// if difference is more than 1 week, then subtract hours with week difference
DayOfWeek dt1_dow = dt1.DayOfWeek; // System.DayOfWeek is enum, 0=Sunday, 1=Monday, etc
DayOfWeek dt2_dow = dt2.DayOfWeek; // System.DayOfWeek is enum, 0=Sunday, 1=Monday, etc
if( (int)dt1_dow > (int)dt2_dow )
{
hours = hours - 24;
}
// if dt1's day of week is higher than dt2's day of week, then we can be sure that it go over one Sunday
Console.WriteLine(numOfWeek);
Console.WriteLine(hours);
}
}
You can use https://dotnetfiddle.net/ or compile it yourself as console application (in visual studio) or save it as file.cs text file then compile with mono in linux with rm file.exe ; csc file.cs ; mono file.exe '2021-11-05 03:00:00' '2021-11-10 03:00:00'
Test:
$ cal
November 2021
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Test cases:
$ mono a.exe '2021-11-05 03:00:00' '2021-11-10 03:00:00'
0
96
date 5 to 10: pass sunday, not more than 1 week, resulting in 96 hours (correct, since it's 4 days not counting sunday)
$ mono a.exe '2021-11-03 03:00:00' '2021-11-05 03:00:00'
0
48
date 3 to 5: does not pass sunday, not more than 1 week, resulting in 48 hours (correct, since it's 2 days)
$ mono a.exe '2021-11-03 03:00:00' '2021-11-15 03:00:00'
1
240
date 3 to 15: does not pass sunday, more than 1 week, resulting in 240 hours (correct, since it's 10 days not counting sunday)
So in your code:
mydt1 = Convert.ToDateTime(dt.Rows[0].ItemArray[7].ToString());
DateTime dt2 = DateTime.Now;
var hours = (dt2 - mydt1).TotalHours;
// start adding code
int numOfWeek = (int)Math.Floor(x.TotalDays/7);
hours = hours - (numOfWeek * 24);
DayOfWeek dt1_dow = dt1.DayOfWeek;
DayOfWeek dt2_dow = dt2.DayOfWeek;
if( (int)dt1_dow > (int)dt2_dow )
{
hours = hours - 24;
}
// end adding code
if (hours >= 0) {
txtElapse.Text = hours.ToString("0");
var totals = hours * 2;
txtFine.Text = totals.ToString("0");
} else {
txtFine.Text = "0";
txtElapse.Text = "0";
}
I have a class like this:
public class Datumsobjekt
{
public string dateiname { get; set; }
public DateTime zeit { get; set; }
}
When creating a List<Datumsobjekt> and filling it with values, I sometimes do not set zeit. zeit is always ascending, no sorting necessary.
Which leads to a(Example)
filename1.ext - 03.01.15
filename2.ext - 04.01.15
filename3.ext -
filename4.ext - 08.01.15
How do I interpolate between dates in a linear fashion, so every dateiname has a zeit and reapply it to the list?
The goal should be something along the lines of
filename1.ext - 03.01.15
filename2.ext - 04.01.15
filename3.ext - 06.01.15
filename4.ext - 08.01.15
for the first example.
PS: It could also be
filename1.ext - 03.01.15
filename2.ext - 04.01.15
filename3.ext -
filename4.ext -
filename5.ext - 08.01.15
and
filename1.ext - 03.01.15
filename2.ext - 04.01.15
filename3.ext -
filename4.ext -
filename5.ext - 08.01.15
filename6.ext -
filename7.ext -
...
filenamen.ext -
filenamen+1.ext - 09.01.15
ie arbitrary numbers of dateiname without a zeit and interrupted by given zeit.
DateTime has an operator Subtract which gives a TimeSpan object as a result.
This can be used to subtract 2 DateTime objects;
System.DateTime date1 = new System.DateTime(1996, 6, 3, 22, 15, 0);
System.DateTime date2 = new System.DateTime(1996, 12, 6, 13, 2, 0);
System.DateTime date3 = new System.DateTime(1996, 10, 12, 8, 42, 0);
// diff1 gets 185 days, 14 hours, and 47 minutes.
System.TimeSpan diff1 = date2.Subtract(date1);
// diff2 gets 55 days 4 hours and 20 minutes.
System.TimeSpan diff2 = date2 - date3;
TimeSpan has a useful operator Division which takes a double as a divisor and gives a TimeSpan result.
A TimeSpan can be added to a DateTime to get a DateTime result.
If You need n values in-between two selected DateTime's then the TimeSpan has to be divided by n+1.
Example 1:
dateTime1 = d1
datetime2 = ? > 1 empty value
datetime3 = d3
So the interval = (datetime1 - datetime3) / (1 + 1)
And datetime2 = datetime1 + interval.
Example 2:
dateTime1 = d1
datetime2 = ? \
datetime3 = ? |
datetime4 = ? |
datetime5 = ? | 8 empty values
datetime6 = ? |
datetime7 = ? |
datetime8 = ? |
datetime9 = ? /
datetime10 = d10
So the interval = (datetime1 - dateime10) / (8 + 1)
And datetime2 = datetime1 + interval.
And datetime3 = datetime2 + interval.
And datetime4 = datetime3 + interval.
...
The funny thing with indexes of an array is that if 2 indexes are subtracted from each other then they give the required value:
For example 1:
index's: 3 - 1 = 2
For example 2:
index's: 10 - 1 = 9
class Program
{
static void Main()
{
DateTime?[] dates = new DateTime?[]
{
new DateTime(2019,1,1),
null,
new DateTime(2019,1,3),
null,null,
new DateTime(2019,1,6),
null,null,null,
new DateTime(2019,1,10),
null,null,null, null,null,null, null,null,null,
new DateTime(2019,1,20),
};
Console.WriteLine("Before:");
foreach (var zeit in dates)
Console.WriteLine(zeit.HasValue ? zeit.ToString() : "<empty>");
Interpolate_dates(dates);
Console.WriteLine("\nAfter:");
foreach (var zeit in dates)
Console.WriteLine(zeit.HasValue ? zeit.ToString() : "!!ERROR!! - all dates should be interpolated.");
}
public static void Interpolate_dates(Span<DateTime?> dates)
{
if (dates.Length == 0)
return;
if (!dates[0].HasValue)
throw new ArgumentException("First date cannot be null.");
if (!dates[dates.Length - 1].HasValue)
throw new ArgumentException("Last date value cannot be null");
int last_filled_date_index = 0;
for (int checking_index = 1; checking_index < dates.Length; checking_index++)
{
if (dates[checking_index].HasValue)
{
if (checking_index != last_filled_date_index + 1)
{
Interpolate(dates, last_filled_date_index, checking_index);
}
last_filled_date_index = checking_index;
}
}
}
private static void Interpolate(Span<DateTime?> dates, int earlier_date_idx, int later_date_idx)
{
TimeSpan interval = (dates[later_date_idx].Value - dates[earlier_date_idx].Value) / (later_date_idx - earlier_date_idx);
for (int index = earlier_date_idx + 1; index < later_date_idx; index++)
{
dates[index] = dates[index - 1] + interval;
}
}
}
What is this function, I mean for the part where is 0x30? Hex value that points to ASCII table?
This is probably basic stuff, and I'm without knowledge currently.
getCompleteCode("11111");
private string getCompleteCode(string code)
{
var b1 = 10;
var b2 = 20;
var sum = 0;
for (var i = code.Length - 1; i >= 0; i--)
{
var z = code[i] - 0x30;
sum = sum + (z + b1) * b2;
}
Console.WriteLine(sum);
return code;
}
A wonderful example of how one should not implement a logic:
// why not static?
private string getCompleteCode(string code)
{
// what does "b1" as well as "b2" stand for?
var b1 = 10;
var b2 = 20;
var sum = 0;
// the reason for the loop being in reversed order?
for (var i = code.Length - 1; i >= 0; i--)
{
// the question put - what does 0x30 stand for - it's ascii code of '0' char
// so, why magic code 0x30 instead of evident '0'?
var z = code[i] - 0x30;
sum = sum + (z + b1) * b2;
}
// what if I want to compute it in, say, WinForms??
// Never mix business logic (computing some code) and UI (output)
Console.WriteLine(sum);
// we've done a lot of stuff just to return the initial input??
return code;
}
My suggestion for the implementation (providing that code is guaranteed to be a correct input):
//TODO: think on a better name for the method
private static int getCompleteCode(string code) {
return code.Sum(c => (c - '0' + 10) * 20);
}
...
Console.WriteLine(getCompleteCode("11111"));
As others have explained, var z = code[i] - 0x30 is standard code to get the value of a digit character. As Glorin Oakenfoot mentioned, this code doesn't do any bounds checking. So it works great for '0' - 0x30 (effectively, that's '0' - '0', by the way), and for '9' - 0x30. But it will also produce weirdness like 'A' - 0x30 = 32.
Glorin's comment is also right - the rest of the numbers seem very context specific. It seems likely to be a hash function - it calculates a mostly-unique number for a given string. I'll walk you through the loop, but what it's doing doesn't make any real sense. Rather than inputting "11111", let's look at "12345":
1st time through loop: i = 4, code[i] = '5', z = 5, sum = 0 + 15 * 20 = 300
2nd time through loop: i = 3, code[i] = '4', z = 4, sum = 300 + 14 * 20 = 580
3rd time through loop: i = 2, code[i] = '3', z = 3, sum = 580 + 13 * 20 = 840
4th time through loop: i = 1, code[i] = '2', z = 2, sum = 840 + 12 * 20 = 1080
5th time through loop: i = 0, code[i] = '1', z = 1, sum = 1080 + 11 * 20 = 1300
As I mentioned, it's likely this is used for hashing. Every string of digits you enter is likely to have a unique integer code. Hashing of strings can improve efficiency. If nothing else, it gives you a fairly robust test for comparison. See https://en.wikipedia.org/wiki/Hash_function
Of course, most string hash functions don't do the conversion from digit to int. And if the function requires that the input be only digits, as seems to be implied by the conversion to int, the simplest hash for this string is to just parse it to an integer.
The other thing this reminds me of is an attempt to parse an integer string to a digit. That would be very similar, but note the differences below:
var sum = 0
for (var i = 0; i < code.Length; i++)
{
var z = code[i] - '0' // this is the same as - 0x30, but more clear to read
sum = sum * 10 + z
}
Considering the name of the function, though, it seems much more likely that it's intended as a hash function.
0x30 is the ascii value of '0' ... it is normally used to subtract from a character, e.g. '8' - 0x30 = 8 after casting to an integer (either implicitly or explicitly like here).
0x30 y the hex ASCII code for number 0, so z will actually give you the digit in number type.
It removes decimals. It = whatever number rounded.
I am getting the current time and trying to split it into two separate variables.
I want the time to be in 12 hours not 24
When i do this the first and second variable are the same. How can i fix this?
int hour = DateTime.Now.Hour % 12;
if (hour == 0) hour = 12;
then,
FirstDigitHour = hour / 10;
secondDigitHour = hour % 10;
the time here is 6 pm so FirstDigitHour & secondDigitHour both = 6
the first digit should equal 0
If you're trying to format the time for display, I would advise you use the proper format string:
DateTime.Now.ToString("hh tt")
Which is the time in 2-digit 12-hour format (hh) with AM/PM (tt)
See the documentation:
http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx
Wouldn't this satisfy your need better
var x = DateTime.Now.ToString("hh");
it returns a string with hours in 12 hour format ( e.g. "01" or "02" ... "11" "12" )
Then you can just get the first and second digit like so
int firstDigit = Convert.ToInt32(x[0].ToString());
int secondDigit = Convert.ToInt32(x[1].ToString());
Seems to work fine for me.
int hr = 18; // 6pm
int hour = hr % 12;
if (hour == 0)
hour = 12;
int fd = hour/10;
int ld = hour%10;
in this case I have fd = 0 and ld = 6.
See it run.
Check if there is a second digit first... if DateTime.Now.Hour > 10. Then you have it.
Example: given two dates below, finish is always greater than or equal to start
start = 2001 Jan 01
finish = 2002 Mar 15
So from 2001 Jan 01 to the end of 2002 Feb
months = 12 + 2 = 14
For 2002 March
15/30 = 0.5
so grand total is 14.5 months difference.
It's very easy to work out by hand but how do I code it elegantly? At the moment I have the combination of a lot of if else and while loops to achieve what I want but I believe there are simpler solutions out there.
Update: the output needs to be precise (not approximation) for example:
if start 2001 Jan 01 and finish 2001 Apr 16, the output should be 1 + 1 + 1= 3 (for Jan, Feb and Mar) and 16 / 31 = 0.516 month, so the total is 3.516.
Another example would be if I start on 2001 Jul 5 and finish on 2002 Jul 10, the output should be 11 month up to the end of June 2002, and (31-5)/31 = 0.839 and 10/31 = 0.323 months, so the total is 11 + 0.839 + 0.323 = 12.162.
I extended Josh Stodola's code and Hightechrider's code:
public static decimal GetMonthsInRange(this IDateRange thisDateRange)
{
var start = thisDateRange.Start;
var finish = thisDateRange.Finish;
var monthsApart = Math.Abs(12*(start.Year - finish.Year) + start.Month - finish.Month) - 1;
decimal daysInStartMonth = DateTime.DaysInMonth(start.Year, start.Month);
decimal daysInFinishMonth = DateTime.DaysInMonth(finish.Year, finish.Month);
var daysApartInStartMonth = (daysInStartMonth - start.Day + 1)/daysInStartMonth;
var daysApartInFinishMonth = finish.Day/daysInFinishMonth;
return monthsApart + daysApartInStartMonth + daysApartInFinishMonth;
}
I gave an int answer before, and then realized what you asked for a more precise answer. I was tired, so I deleted and went to bed. So much for that, I was unable to fall asleep! For some reason, this question really bugged me, and I had to solve it. So here you go...
static void Main(string[] args)
{
decimal diff;
diff = monthDifference(new DateTime(2001, 1, 1), new DateTime(2002, 3, 15));
Console.WriteLine(diff.ToString("n2")); //14.45
diff = monthDifference(new DateTime(2001, 1, 1), new DateTime(2001, 4, 16));
Console.WriteLine(diff.ToString("n2")); //3.50
diff = monthDifference(new DateTime(2001, 7, 5), new DateTime(2002, 7, 10));
Console.WriteLine(diff.ToString("n2")); //12.16
Console.Read();
}
static decimal monthDifference(DateTime d1, DateTime d2)
{
if (d1 > d2)
{
DateTime hold = d1;
d1 = d2;
d2 = hold;
}
int monthsApart = Math.Abs(12 * (d1.Year-d2.Year) + d1.Month - d2.Month) - 1;
decimal daysInMonth1 = DateTime.DaysInMonth(d1.Year, d1.Month);
decimal daysInMonth2 = DateTime.DaysInMonth(d2.Year, d2.Month);
decimal dayPercentage = ((daysInMonth1 - d1.Day) / daysInMonth1)
+ (d2.Day / daysInMonth2);
return monthsApart + dayPercentage;
}
Now I shall have sweet dreams. Goodnight :)
What you want is probably something close to this ... which pretty much follows your explanation as to how to calculate it:
var startofd1 = d1.AddDays(-d1.Day + 1);
var startOfNextMonthAfterd1 = startofd1.AddMonths(1); // back to start of month and then to next month
int daysInFirstMonth = (startOfNextMonthAfterd1 - startofd1).Days;
double fraction1 = (double)(daysInFirstMonth - (d1.Day - 1)) / daysInFirstMonth; // fractional part of first month remaining
var startofd2 = d2.AddDays(-d2.Day + 1);
var startOfNextMonthAfterd2 = startofd2.AddMonths(1); // back to start of month and then to next month
int daysInFinalMonth = (startOfNextMonthAfterd2 - startofd2).Days;
double fraction2 = (double)(d2.Day - 1) / daysInFinalMonth; // fractional part of last month
// now find whole months in between
int monthsInBetween = (startofd2.Year - startOfNextMonthAfterd1.Year) * 12 + (startofd2.Month - startOfNextMonthAfterd1.Month);
return monthsInBetween + fraction1 + fraction2;
NB This has not been tested very well but it shows how to handle problems like this by finding well known dates at the start of months around the problem values and then working off them.
While loops for date time calculations are always a bad idea: see http://www.zuneboards.com/forums/zune-news/38143-cause-zune-30-leapyear-problem-isolated.html
Depending on how exactly you want your logic to work, this would at least give you a decent approximation:
// 365 days per year + 1 day per leap year = 1461 days every 4 years
// But years divisible by 100 are not leap years
// So 1461 days every 4 years - 1 day per 100th year = 36524 days every 100 years
// 12 months per year = 1200 months every 100 years
const double DaysPerMonth = 36524.0 / 1200.0;
double GetMonthsDifference(DateTime start, DateTime finish)
{
double days = (finish - start).TotalDays;
return days / DaysPerMonth;
}
One way to do this is that you'll see around quite a bit is:
private static int monthDifference(DateTime startDate, DateTime endDate)
{
int monthsApart = 12 * (startDate.Year - endDate.Year) + startDate.Month - endDate.Month;
return Math.Abs(monthsApart);
}
However, you want "partial months" which this doesn't give. But what is the point in comparing apples (January/March/May/July/August/October/December) with oranges (April/June/September/November) or even bananas that are sometimes coconuts (February)?
An alternative is to import Microsoft.VisualBasic and do this:
DateTime FromDate;
DateTime ToDate;
FromDate = DateTime.Parse("2001 Jan 01");
ToDate = DateTime.Parse("2002 Mar 15");
string s = DateAndTime.DateDiff (DateInterval.Month, FromDate,ToDate, FirstDayOfWeek.System, FirstWeekOfYear.System ).ToString();
However again:
The return value for
DateInterval.Month is calculated
purely from the year and month parts
of the arguments
[Source]
Just improved Josh's answer
static decimal monthDifference(DateTime d1, DateTime d2)
{
if (d1 > d2)
{
DateTime hold = d1;
d1 = d2;
d2 = hold;
}
decimal monthsApart = Math.Abs((12 * (d1.Year - d2.Year)) + d2.Month - d1.Month - 1);
decimal daysinStartingMonth = DateTime.DaysInMonth(d1.Year, d1.Month);
monthsApart = monthsApart + (1-((d1.Day - 1) / daysinStartingMonth));
// Replace (d1.Day - 1) with d1.Day incase you DONT want to have both inclusive difference.
decimal daysinEndingMonth = DateTime.DaysInMonth(d2.Year, d2.Month);
monthsApart = monthsApart + (d2.Day / daysinEndingMonth);
return monthsApart;
}
The answer works perfectly and while the terseness of the code makes it very small I had to break everything apart into smaller functions with named variables so that I could really understand what was going on... So, basically I just took Josh Stodola's code and Hightechrider's mentioned in Jeff's comment and made it smaller with comments explaining what was going on and why the calculations were being made, and hopefully this may help someone else:
[Test]
public void Calculate_Total_Months_Difference_Between_Two_Dates()
{
var startDate = DateTime.Parse( "10/8/1996" );
var finishDate = DateTime.Parse( "9/8/2012" ); // this should be now:
int numberOfMonthsBetweenStartAndFinishYears = getNumberOfMonthsBetweenStartAndFinishYears( startDate, finishDate );
int absMonthsApartMinusOne = getAbsMonthsApartMinusOne( startDate, finishDate, numberOfMonthsBetweenStartAndFinishYears );
decimal daysLeftToCompleteStartMonthPercentage = getDaysLeftToCompleteInStartMonthPercentage( startDate );
decimal daysCompletedSoFarInFinishMonthPercentage = getDaysCompletedSoFarInFinishMonthPercentage( finishDate );
// .77 + .26 = 1.04
decimal totalDaysDifferenceInStartAndFinishMonthsPercentage = daysLeftToCompleteStartMonthPercentage + daysCompletedSoFarInFinishMonthPercentage;
// 13 + 1.04 = 14.04 months difference.
decimal totalMonthsDifference = absMonthsApartMinusOne + totalDaysDifferenceInStartAndFinishMonthsPercentage;
//return totalMonths;
}
private static int getNumberOfMonthsBetweenStartAndFinishYears( DateTime startDate, DateTime finishDate )
{
int yearsApart = startDate.Year - finishDate.Year;
const int INT_TotalMonthsInAYear = 12;
// 12 * -1 = -12
int numberOfMonthsBetweenYears = INT_TotalMonthsInAYear * yearsApart;
return numberOfMonthsBetweenYears;
}
private static int getAbsMonthsApartMinusOne( DateTime startDate, DateTime finishDate, int numberOfMonthsBetweenStartAndFinishYears )
{
// This may be negative i.e. 7 - 9 = -2
int numberOfMonthsBetweenStartAndFinishMonths = startDate.Month - finishDate.Month;
// Absolute Value Of Total Months In Years Plus The Simple Months Difference Which May Be Negative So We Use Abs Function
int absDiffInMonths = Math.Abs( numberOfMonthsBetweenStartAndFinishYears + numberOfMonthsBetweenStartAndFinishMonths );
// Subtract one here because we are going to use a perecentage difference based on the number of days left in the start month
// and adding together the number of days that we've made it so far in the finish month.
int absMonthsApartMinusOne = absDiffInMonths - 1;
return absMonthsApartMinusOne;
}
/// <summary>
/// For example for 7/8/2012 there are 24 days left in the month so about .77 percentage of month is left.
/// </summary>
private static decimal getDaysLeftToCompleteInStartMonthPercentage( DateTime startDate )
{
// startDate = "7/8/2012"
// 31
decimal daysInStartMonth = DateTime.DaysInMonth( startDate.Year, startDate.Month );
// 31 - 8 = 23
decimal totalDaysInStartMonthMinusStartDay = daysInStartMonth - startDate.Day;
// add one to mark the day as being completed. 23 + 1 = 24
decimal daysLeftInStartMonth = totalDaysInStartMonthMinusStartDay + 1;
// 24 / 31 = .77 days left to go in the month
decimal daysLeftToCompleteInStartMonthPercentage = daysLeftInStartMonth / daysInStartMonth;
return daysLeftToCompleteInStartMonthPercentage;
}
/// <summary>
/// For example if the finish date were 9/8/2012 we've completed 8 days so far or .24 percent of the month
/// </summary>
private static decimal getDaysCompletedSoFarInFinishMonthPercentage( DateTime finishDate )
{
// for septebmer = 30 days in month.
decimal daysInFinishMonth = DateTime.DaysInMonth( finishDate.Year, finishDate.Month );
// 8 days divided by 30 = .26 days completed so far in finish month.
decimal daysCompletedSoFarInFinishMonthPercentage = finishDate.Day / daysInFinishMonth;
return daysCompletedSoFarInFinishMonthPercentage;
}
This solution calculates whole months and then adds the partial month based on the end of the time period. This way it always calculates full months between the dates' day-of-month and then calculates the partial month based on the number of remaining days.
public decimal getMonthDiff(DateTime date1, DateTime date2) {
// Make parameters agnostic
var earlyDate = (date1 < date2 ? date1 : date2);
var laterDate = (date1 > date2 ? date1 : date2);
// Calculate the change in full months
decimal months = ((laterDate.Year - earlyDate.Year) * 12) + (laterDate.Month - earlyDate.Month) - 1;
// Add partial months based on the later date
if (earlyDate.Day <= laterDate.Day) {
decimal laterMonthDays = DateTime.DaysInMonth(laterDate.Year, laterDate.Month);
decimal laterPartialMonth = ((laterDate.Day - earlyDate.Day) / laterMonthDays);
months += laterPartialMonth + 1;
} else {
var laterLastMonth = laterDate.AddMonths(-1);
decimal laterLastMonthDays = DateTime.DaysInMonth(laterLastMonth.Year, laterLastMonth.Month);
decimal laterPartialMonth = ((laterLastMonthDays - earlyDate.Day + laterDate.Day) / laterLastMonthDays);
months += laterPartialMonth;
}
return months;
}
The calculation below is one that is according the way the Dutch Tax Authority wants months calculated. This means that when the starts day is for example feb 22, march 23 should be result in something above 1 and not just something like 0.98.
private decimal GetMonthDiffBetter(DateTime date1, DateTime date2)
{
DateTime start = date1 < date2 ? date1 : date2;
DateTime end = date1 < date2 ? date2 : date1;
int totalYearMonths = (end.Year - start.Year) * 12;
int restMonths = end.Month - start.Month;
int totalMonths = totalYearMonths + restMonths;
decimal monthPart = (decimal)end.Day / (decimal)start.Day;
return totalMonths - 1 + monthPart;
}`
This should get you where you need to go:
DateTime start = new DateTime(2001, 1, 1);
DateTime finish = new DateTime(2002, 3, 15);
double diff = (finish - start).TotalDays / 30;
the framework as a TimeSpan object that is a result of subtracting two dates.
the subtraction is already considering the various option of February(28/29 days a month) so in my opinion this is the best practice
after you got it you can format it the way you like best
DateTime dates1 = new DateTime(2010, 1, 1);
DateTime dates2 = new DateTime(2010, 3, 15);
var span = dates1.Subtract(dates2);
span.ToString("your format here");
private Double GetTotalMonths(DateTime future, DateTime past)
{
Double totalMonths = 0.0;
while ((future - past).TotalDays > 28 )
{
past = past.AddMonths(1);
totalMonths += 1;
}
var daysInCurrent = DateTime.DaysInMonth(future.Year, future.Month);
var remaining = future.Day - past.Day;
totalMonths += ((Double)remaining / (Double)daysInCurrent);
return totalMonths;
}