Reputation: 2321
Hello so I've implemented this solution to attain the user's birthday from a birthday date input: Calculate age in C#
This works great however I do need to interpret birthday's for ages less than a year (babies, infants). It will current just give me an age of "0" if there are less than 365 days between the bdate and current date.
What I'm thinking is something like this:
public string calculateAge(DateTime birthDate, DateTime now)
{
//BDay is in different year (age > 1)
int age = now.Year - birthDate.Year;
if (now.Month < birthDate.Month || (now.Month == birthDate.Month && now.Day < birthDate.Day)) age--;
if (age == 0)
{
//Bday is in same year
age = now.Month - birthDate.Month;
if (now.Month < birthDate.Month || (now.Month == birthDate.Month && now.Day < birthDate.Day)) age--;
return age.ToString() + " months";
}
if (age == 0)
{
//Bday is in the same month
age = now.Day - birthDate.Day;
if (now.Month < birthDate.Month || (now.Month == birthDate.Month && now.Day < birthDate.Day)) age--;
return age.ToString() + " days";
}
return age.ToString();
}
However some of my test Bdays give me this:
(Today's date: 3/6/2012)
Bday1 = 3/5/2012
Age result = -1
Expected result = 1 day
Bday2 = 3/1/2012
Age result = 0 months
Expected result = 5 days
Bday3 = 1/1/2012
Age result = 2 months
Expected result = 2 months (this is fine)
Bday4 = 3/7/2011
Age result = -1 months
Expected result = 11 months
Bday5 = 3/1/2011
Age result = 1
Expected result = 1 (this is fine)
You can see that because of how it is currently setup the issue is stemming from when the bday month is less than the current month some negative numbers can result.
I also see the error about not being able to get to the "days" loop, but I think that's a moot point right now. Let me know if you have any insight on what I can do to get the desired results. Also if you need more info like test bdays too. Thanks!
Upvotes: 1
Views: 12966
Reputation:
You can use the DateDiff class of Time Period Library for .NET:
// ----------------------------------------------------------------------
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 );
// 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
} // DateDiffSample
Upvotes: 3
Reputation: 1
int[] getAge(DateTime dt)
{
DateTime today = DateTime.Now;
int years = 0;
int days = 0;
int months = 0;
int[] age = new int[3];
while (dt.Year != today.Year || dt.Month != today.Month || dt.Day != today.Day)
{
if (dt.AddYears(1).CompareTo(today) <= 0)
{
years++;
dt = dt.AddYears(1);
}
else
{
if (dt.AddMonths(1).CompareTo(today) <= 0)
{
months++;
dt = dt.AddMonths(1);
}
else
{
if (dt.AddDays(1).CompareTo(today) <= 0)
{
days++;
dt = dt.AddDays(1);
}
else
{
dt = today;
}
}
}
}
age[0] = years;
age[1] = months;
age[2] = days;
return age;
}
Upvotes: 0
Reputation: 74277
Here's one way, that computes the age the way a normal human being in a western culture would do it for CE dates. Different cultures and calendards reckon age in different ways. For instance, China and other countries in Asia reckon a newborn baby to be 1 year old on the day he is born and his age ticks up on each subsequent Lunar New Year in the Chinese calendar. So if a child was born, say, a month before the Lunar New Year, he would be 1 year old for that month and then would tick up to 2 years old a month after his birth.
This algorithm doesn't work for BCE dates or for dates spanning the transition from the Julian to the Gregorian calendar. That's dicey proposition anyway, no matter how you slice it, since different locations, even within the same country, switched at different times: Russia didn't switch to the Gregorian calendar until after the Bolshevik revolution, for instance).
So Unless you have the locale for the start date and the locale for the end date, you can't accurately compute a time span across the Julian/Gregorian divide.
The algorithm is:
Find the reference date, the most recent monthly birthday on or before the current date.
If the current day-of-the-month is prior to the actual day of birth, use the prior month. If the actual day of birth is later than the last day of the month, cap it at the last day of the month.
For instance, if the current date is 7 March, 2012 and the actual birthday is '31 March, 1990', your reference date is 29 February, 2012.
Compute the difference between your reference date and the date of birth in years and months. This is easy because in the western calendar, years have a consistent number of months. You can use integer division:
int totalMonths = ( 12 * endYear + endMonth ) - ( 12 * startYear + startMonth ) ; int years = totalMonths / 12 ; int months = totalMonths % 12 ;
Or you can subtract and carry if necessary.
int years = endYear - startYear ;
int months = endMonth - startMonth ;
if ( months < 0 )
{
months += 12 ;
years -= 1 ;
}
The results should be identical in either case.
The days component is the number of days from the reference date to the current date.
Using this algorithm, one is 0 days old on the day of birth.
Here's my code:
static class HumanAgeFactory
{
public static HumanAge ComputeAge( this DateTime dob )
{
return dob.ComputeAgeAsOf( DateTime.Now ) ;
}
public static HumanAge ComputeAgeAsOf( this DateTime dob , DateTime now )
{
dob = dob.Date ; // toss the time component
now = now.Date ; // toss the time component
if ( dob > now ) throw new ArgumentOutOfRangeException( "dob" , "'now' must be on or after 'dob'" ) ;
DateTime mostRecentBirthDay = MostRecentNthDayOfTheMonthOnOrBefore( dob.Day , now ) ;
int years = mostRecentBirthDay.Year - dob.Year ;
int months = mostRecentBirthDay.Month - dob.Month ;
int days = (int) ( now - mostRecentBirthDay ).TotalDays ;
if ( months < 0 )
{
months += 12 ;
years -= 1 ;
}
if ( days < 0 ) throw new InvalidOperationException() ;
if ( months < 0 ) throw new InvalidOperationException() ;
if ( years < 0 ) throw new InvalidOperationException() ;
HumanAge instance = new HumanAge( years , months , days ) ;
return instance ;
}
private static DateTime MostRecentNthDayOfTheMonthOnOrBefore( int nthDay , DateTime now )
{
if ( nthDay < 1 ) throw new ArgumentOutOfRangeException( "dayOfBirth" ) ;
int year = now.Year ;
int month = now.Month ;
if ( nthDay > now.Day )
{
--month ;
if ( month < 1 )
{
month += 12 ;
year -= 1 ;
}
}
int daysInMonth = CultureInfo.CurrentCulture.Calendar.GetDaysInMonth( year , month ) ;
int day = ( nthDay > daysInMonth ? daysInMonth : nthDay ) ;
DateTime instance = new DateTime( year , month , day ) ;
return instance ;
}
}
public class HumanAge
{
public int Years { get ; private set ; }
public int Months { get ; private set ; }
public int Days { get ; private set ; }
public override string ToString()
{
string instance = string.Format( "{0} {1} , {2} {3} , {4} {5}" ,
Years , Years == 1 ? "year" : "years" ,
Months , Months == 1 ? "month" : "months" ,
Days , Days == 1 ? "day" : "days"
) ;
return instance ;
}
public HumanAge( int years , int months , int days )
{
if ( years < 0 ) throw new ArgumentOutOfRangeException( "years" ) ;
if ( months < 0 || months > 12 ) throw new ArgumentOutOfRangeException( "months" ) ;
if ( days < 0 || days > 31 ) throw new ArgumentOutOfRangeException( "days" ) ;
this.Years = years ;
this.Months = months ;
this.Days = days ;
return ;
}
}
Upvotes: 1
Reputation: 16981
static public string calculateAge(DateTime birthDate, DateTime now)
{
birthDate = birthDate.Date;
now = now.Date;
var days = now.Day - birthDate.Day;
if (days < 0)
{
var newNow = now.AddMonths(-1);
days += (int)(now - newNow).TotalDays;
now = newNow;
}
var months = now.Month - birthDate.Month;
if (months < 0)
{
months += 12;
now = now.AddYears(-1);
}
var years = now.Year - birthDate.Year;
if (years == 0)
{
if (months == 0)
return days.ToString() + " days";
else
return months.ToString() + " months";
}
return years.ToString();
}
results (for now - 3/7/2012):
3/5/2012: 2 days
3/1/2012: 6 days
1/1/2012: 2 months
3/8/2011: 11 months
3/1/2011: 1
Upvotes: 2
Reputation: 15579
We actually have a "DateSpan" structure in our framework for doing similar calculations... The crux of it related to what you're after is below where by for a defined "Start" and "End" DateTime
variable it has properties such as:
public int WholeMonths
{
get
{
var startInEndsYear = Start.AddYears(End.Year - Start.Year);
// Are within a month of each other if EITHER:
// 1. Month is the same
// 2. Month period is within 1
// AND
// The difference between days of the year is less than the number of days in the start's month
var sameMonth = End.Month == startInEndsYear.Month || (End.Month - 1 == Start.Month && (End.DayOfYear - startInEndsYear.DayOfYear) / (double)DateTime.DaysInMonth(startInEndsYear.Year, startInEndsYear.Month) < 1.0d );
var sameMonthAndDay = sameMonth && End.Day == Start.Day;
var res = (End.Year - Start.Year) * 12;
if (sameMonth && !sameMonthAndDay)
{
res -= (startInEndsYear > End) ? 1 : 0;
}
else if (sameMonthAndDay)
{
res -= (End.TimeOfDay < Start.TimeOfDay ? 1 : 0);
}
else
{
res -= Start.Month;
res += End.Month;
}
return res;
}
}
And dependent properties on that:
public int WholeYears
{
get
{
return (int) Math.Floor(WholeMonths/12d);
}
}
public int PartMonths
{
get
{
return WholeMonths % 12;
}
}
I'll leave it as a further exercise for you to convert to words based on those properties.
EDIT: Here's the calc for the Days:
public TimeSpan PartDays
{
get
{
var startInEndsMonth = Start.AddMonths(WholeMonths);
return End.Subtract(startInEndsMonth);
}
}
Upvotes: 1
Reputation: 1732
You can get somewhat close with this function I found:
/// <summary>
/// Converts a timespan value to a string representation.
/// </summary>
/// <param name="time_span">the amount of time to convert to words</param>
/// <param name="whole_seconds">round up the seconds</param>
/// <returns>4 minutes, 58 seconds, etc</returns>
/// <remarks>If it can't convert to a string, it returns "Calculating time remaining..."</remarks>
public string TimespanToWords(TimeSpan time_span, bool whole_seconds = true)
{
TimeSpan span;
string str2 = "";
if (time_span.Days > 0)
{
str2 = str2 + ", " + time_span.Days.ToString() + " days";
span = new TimeSpan(time_span.Days, 0, 0, 0);
time_span = time_span.Subtract(span);
}
if (time_span.Hours > 0)
{
str2 = str2 + ", " + time_span.Hours.ToString() + " hours";
span = new TimeSpan(0, time_span.Hours, 0, 0);
time_span = time_span.Subtract(span);
}
if (time_span.Minutes > 0)
{
str2 = str2 + ", " + time_span.Minutes.ToString() + " minutes";
span = new TimeSpan(0, 0, time_span.Minutes, 0);
time_span = time_span.Subtract(span);
}
if (whole_seconds)
{
if (time_span.Seconds > 0)
{
str2 = str2 + ", " + time_span.Seconds.ToString() + " seconds";
}
}
else
{
str2 = str2 + ", " + time_span.TotalSeconds.ToString() + " seconds";
}
if (str2.Length > 0)
{
str2 = str2.Substring(2);
}
if (string.IsNullOrEmpty(str2))
{
return "Calculating time remaining...";
}
return str2;
}
Here's how to use it:
var date1 = System.DateTime.Parse("01/01/1999");
var date2 = System.DateTime.Parse("03/07/2012");
var ts = date2 - date1;
var timeString = TimespanToWords(ts, true);
Upvotes: 0