What you have seems on the right track, perhaps the following might be of use, something I put together maybe ten years ago and added, today the code for displaying date information.
By no means perfect in regards to logic for displaying but the underlying logic is sound.
Usage
var from = new DateTime(2023, 10, 19);
Age result = from.Age(DateTime.Now);
var posted = $"Posted {result.Posted()}";
In the following model, Full, Posted and ToString where just added. Posted method is what I call dirty and cheap.
public class Age
{
public int Years { get; set; }
public int Months { get; set; }
public int Days { get; set; }
public int Hours { get; set; }
public int Minutes { get; set; }
public int Seconds { get; set; }
public int Milliseconds { get; set; }
/// <summary>
/// Date to calculate off of a later date
/// </summary>
public DateTime From { get; set; }
/// <summary>
/// Date to calculate off a earlier date
/// </summary>
public DateTime To { get; set; }
/// <summary>
/// No logic if any member is 0
/// </summary>
public string Full(string text = "Posted ")
=> $"{text}" +
$"{Years} years " +
$"{Months} months " +
$"{Days} days " +
$"{Hours} hours " +
$"{Minutes} minutes " +
$"{Seconds} ago";
public string Posted()
{
var data =
$"{Years} years " +
$"{Months} months " +
$"{Days} days " +
$"{Hours} hours " +
$"{Minutes} minutes " +
$"{Seconds} seconds ago";
return data
.Replace("0 years", "")
.Replace("0 months", "")
.Replace("0 days", "")
.Replace("0 hours", "")
.Replace("0 minutes", "")
.TrimStart();
}
/// <summary>
/// No logic for 0 on time portions
/// </summary>
public override string ToString()
{
if (Years > 0 && Months > 0 && Days > 0)
{
return $"{Years} years {Months} months {Days} days ago";
}
else if (Months > 0 && Days > 0)
{
return $"{Months} months {Days} days ago";
}
else if (Days > 0)
{
return $"{Days} days ago";
}
else
{
return "";
}
}
}
Then we have a language extension to keep client code clean.
public static class DateTimeExtensions
{
/// <summary>
/// Provides a wrapper for <see cref="GetElapsedTime"/> to keep client code clean
/// </summary>
/// <param name="fromDateTime">Pass date</param>
/// <param name="toDate">Typically DateTime.Now</param>
/// <returns><see cref="Age"/> instance</returns>
/// <remarks>
/// * Minimal assertion is done
/// * milliseconds are provides but not much use
/// </remarks>
public static Age Age(this DateTime fromDateTime, DateTime toDate)
{
fromDateTime.GetElapsedTime(toDate,
out var years, out var months, out var days,
out var hours, out var minutes, out var seconds,
out _);
return new Age()
{
Years = years,
Months = months,
Days = days,
Hours = hours,
Minutes = minutes,
Seconds = seconds,
From = fromDateTime,
To = toDate
};
}
/// <summary>
/// Get elapsed time in years, months, days, hours, seconds
/// </summary>
/// <param name="fromDate">Date in past</param>
/// <param name="toDate">Date pass fromDate</param>
/// <param name="years"></param>
/// <param name="months"></param>
/// <param name="days"></param>
/// <param name="hours"></param>
/// <param name="minutes"></param>
/// <param name="seconds"></param>
/// <param name="milliseconds"></param>
public static void GetElapsedTime(this DateTime fromDate, DateTime toDate, out int years, out int months, out int days, out int hours, out int minutes, out int seconds, out int milliseconds)
{
// If from_date > to_date, switch them around.
if (fromDate > toDate)
{
GetElapsedTime(
toDate,
fromDate,
out years,
out months,
out days,
out hours,
out minutes,
out seconds,
out milliseconds);
years = -years;
months = -months;
days = -days;
hours = -hours;
minutes = -minutes;
seconds = -seconds;
milliseconds = -milliseconds;
}
else
{
// Handle the years.
years = toDate.Year - fromDate.Year;
// See if we went too far.
DateTime testDate = fromDate.AddMonths(12 * years);
if (testDate > toDate)
{
years--;
testDate = fromDate.AddMonths(12 * years);
}
// Add months until we go too far.
months = 0;
while (testDate <= toDate)
{
months++;
testDate = fromDate.AddMonths(12 * years + months);
}
months--;
// Subtract to see how many more days,
// hours, minutes, etc. we need.
fromDate = fromDate.AddMonths(12 * years + months);
TimeSpan remainder = toDate - fromDate;
days = remainder.Days;
hours = remainder.Hours;
minutes = remainder.Minutes;
seconds = remainder.Seconds;
milliseconds = remainder.Milliseconds;
}
}
}