Consider the following 2 scenarios: Scenario 1). Today is May 1st 2012, and Scenario 2). Today is September 1st 2012.
Now, consider that we write on our webpage the
What you are looking for is indeed not what TimeSpan
represents. TimeSpan
represents an interval as a count of ticks, without respect to a base DateTime
or Calendar
.
A new DateDifference
type might make more sense here, with a constructor or factory method taking a base DateTime
, a target DateTime
, and optionally a Calendar
(defaulting to CultureInfo.CurrentCulture) with which to compute the various difference components (years, months, etc.)
EDIT: It looks to me like Noda Time may have the tools you need for this — the Period class "[r]epresents a period of time expressed in human chronological terms: hours, days, weeks, months and so on", and in particular Period.Between(then, now, PeriodUnits.AllUnits)
seems to be the precise calculation you're asking for — but it's necessarily a much more complex class than TimeSpan
. The Key Concepts page on the Noda Time wiki explains how "humans make time messy":
Leaving aside the tricky bits of astronomy and relativity, mankind has still made time hard to negotiate. If we all used ticks from the Unix epoch to talk about time, there wouldn't be a need for a library like Noda Time.
But no, we like to talk in years, months, days, weeks - and for some reason we like 12pm (which confusingly comes before 1pm) to be roughly the time at which the sun is highest... so we have time zones.
Not only that, but we don't all agree on how many months there are. Different civilizations have come up with different ways of splitting up the year, and different numbers for the years to start with. These are calendar systems.
I would say that the current TimeSpan is a real timespan object, i.e., the amount of time between Jan 1 2008 1:31 a.m. and Feb. 3, 2008 at 6:45 a.m. is the same as the amount of time between Feb. 5, 2008 at 1:45 p.m. and March 9, 2008 at 6:59 p.m.. What you are looking for is in actuality the difference between two datetimes.
As for the .MakeMagicHappen.gimmeSomethingPretty.surelyMShasThoughtAboutThisDilema to fulfill the specific needs of your system, that's why people hire you as a programmer. If the framework you are using does absolutely everything, your company would just be able to presss a single button and their system would pop out fully formed and you'd be on the unemployment line along with the rest of us programmers.
I took the accepted answer and converted it from VB.Net to C# and made a few modification/improvements as well. I got rid of the string reversals, which were being used to replace the last instance of a string, and used an extension method that more directly finds and replaces the last instance of a string.
Example of how to call the method:
PeriodBetween(#2/28/2011#, DateTime.UtcNow, 6)
Main Method:
public static string PeriodBetween(DateTime then, DateTime now, byte numberOfPeriodUnits = 2)
{
// Translated from VB.Net to C# from: https://stackoverflow.com/a/1956265
// numberOfPeriodUnits identifies how many time period units to show.
// If numberOfPeriodUnits = 3, function would return:
// "3 years, 2 months and 13 days"
// If numberOfPeriodUnits = 2, function would return:
// "3 years and 2 months"
// If numberOfPeriodUnits = 6, (maximum value), function would return:
// "3 years, 2 months, 13 days, 13 hours, 29 minutes and 9 seconds"
if (numberOfPeriodUnits > 6 || numberOfPeriodUnits < 1)
{
throw new ArgumentOutOfRangeException($"Parameter [{nameof(numberOfPeriodUnits)}] is out of bounds. Valid range is 1 to 6.");
}
short Years = 0;
short Months = 0;
short Days = 0;
short Hours = 0;
short Minutes = 0;
short Seconds = 0;
short DaysInBaseMonth = (short)(DateTime.DaysInMonth(then.Year, then.Month));
Years = (short)(now.Year - then.Year);
Months = (short)(now.Month - then.Month);
if (Months < 0)
{
Months += 12;
Years--; // add 1 year to months, and remove 1 year from years.
}
Days = (short)(now.Day - then.Day);
if (Days < 0)
{
Days += DaysInBaseMonth;
Months--;
}
Hours = (short)(now.Hour - then.Hour);
if (Hours < 0)
{
Hours += 24;
Days--;
}
Minutes = (short)(now.Minute - then.Minute);
if (Minutes < 0)
{
Minutes += 60;
Hours--;
}
Seconds = (short)(now.Second - then.Second);
if (Seconds < 0)
{
Seconds += 60;
Minutes--;
}
// This is the display functionality.
StringBuilder TimePeriod = new StringBuilder();
short NumberOfPeriodUnitsAdded = 0;
if (Years > 0)
{
TimePeriod.Append(Years);
TimePeriod.Append(" year" + (Years != 1 ? "s" : "") + ", ");
NumberOfPeriodUnitsAdded++;
}
if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
{
goto ParseAndReturn;
}
if (Months > 0)
{
TimePeriod.AppendFormat(Months.ToString());
TimePeriod.Append(" month" + (Months != 1 ? "s" : "") + ", ");
NumberOfPeriodUnitsAdded++;
}
if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
{
goto ParseAndReturn;
}
if (Days > 0)
{
TimePeriod.Append(Days);
TimePeriod.Append(" day" + (Days != 1 ? "s" : "") + ", ");
NumberOfPeriodUnitsAdded++;
}
if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
{
goto ParseAndReturn;
}
if (Hours > 0)
{
TimePeriod.Append(Hours);
TimePeriod.Append(" hour" + (Hours != 1 ? "s" : "") + ", ");
NumberOfPeriodUnitsAdded++;
}
if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
{
goto ParseAndReturn;
}
if (Minutes > 0)
{
TimePeriod.Append(Minutes);
TimePeriod.Append(" minute" + (Minutes != 1 ? "s" : "") + ", ");
NumberOfPeriodUnitsAdded++;
}
if (numberOfPeriodUnits == NumberOfPeriodUnitsAdded)
{
goto ParseAndReturn;
}
if (Seconds > 0)
{
TimePeriod.Append(Seconds);
TimePeriod.Append(" second" + (Seconds != 1 ? "s" : "") + "");
NumberOfPeriodUnitsAdded++;
}
ParseAndReturn:
// If the string is empty, that means the datetime is less than a second in the past.
// An empty string being passed will cause an error, so we construct our own meaningful
// string which will still fit into the "Posted * ago " syntax.
if (TimePeriod.ToString() == "")
{
TimePeriod.Append("less than 1 second");
}
return TimePeriod.ToString().TrimEnd(' ', ',').ToString().ReplaceLast(",", " and");
}
ReplaceLast Extension Method:
public static string ReplaceLast(this string source, string search, string replace)
{
int pos = source.LastIndexOf(search);
if (pos == -1)
{
return source;
}
return source.Remove(pos, search.Length).Insert(pos, replace);
}
Using .Net 4.5 and the CultureInfo
class, one can add months and years to a given date.
DateTime datetime = DateTime.UtcNow;
int years = 15;
int months = 7;
DateTime yearsAgo = CultureInfo.InvariantCulture.Calendar.AddYears(datetime, -years);
DateTime monthsInFuture = CultureInfo.InvariantCulture.Calendar.AddMonths(datetime, months);
Since that's a lot of typing, I prefer to create extension methods:
public static DateTime AddYears(this DateTime datetime, int years)
{
return CultureInfo.InvariantCulture.Calendar.AddYears(datetime, years);
}
public static DateTime AddMonths(this DateTime datetime, int months)
{
return CultureInfo.InvariantCulture.Calendar.AddMonths(datetime, months);
}
DateTime yearsAgo = datetime.AddYears(-years);
DateTime monthsInFuture = datetime.AddMonths(months);
Well, better late then nothing I suppose ;)
C# function giving everything
And this is my modified version :
private string GetElapsedTime(DateTime from_date, DateTime to_date) {
int years;
int months;
int days;
int hours;
int minutes;
int seconds;
int milliseconds;
//------------------
// Handle the years.
//------------------
years = to_date.Year - from_date.Year;
//------------------------
// See if we went too far.
//------------------------
DateTime test_date = from_date.AddMonths(12 * years);
if (test_date > to_date)
{
years--;
test_date = from_date.AddMonths(12 * years);
}
//--------------------------------
// Add months until we go too far.
//--------------------------------
months = 0;
while (test_date <= to_date)
{
months++;
test_date = from_date.AddMonths(12 * years + months);
}
months--;
//------------------------------------------------------------------
// Subtract to see how many more days, hours, minutes, etc. we need.
//------------------------------------------------------------------
from_date = from_date.AddMonths(12 * years + months);
TimeSpan remainder = to_date - from_date;
days = remainder.Days;
hours = remainder.Hours;
minutes = remainder.Minutes;
seconds = remainder.Seconds;
milliseconds = remainder.Milliseconds;
return (years > 0 ? years.ToString() + " years " : "") +
(months > 0 ? months.ToString() + " months " : "") +
(days > 0 ? days.ToString() + " days " : "") +
(hours > 0 ? hours.ToString() + " hours " : "") +
(minutes > 0 ? minutes.ToString() + " minutes " : "");}
Here's how to add some extension methods for this with C# using mean values:
public static class TimeSpanExtensions
{
public static int GetYears(this TimeSpan timespan)
{
return (int)(timespan.Days/365.2425);
}
public static int GetMonths(this TimeSpan timespan)
{
return (int)(timespan.Days/30.436875);
}
}