using System;
using System.Diagnostics;
using System.Globalization;
namespace ToBeDefined
{
public static class Age
{
///
/// Calculates the age given birthday and referenceDate strings in the form "yyyy-MM-dd".
/// Age is given in years with one decimal place (floored).
/// The calculation assumes
/// - age starts at 0 at date of birth
/// - a gregorian calendar
/// - birthdates "February 29" have anniversary at "March 1" in non-leap years.
/// - calculation ignores exact times of date of birth and reference date (assumes 00:00)
///
/// Calculate("2020-02-29", "2021-03-01") == 1.0
/// Calculate("2020-01-01", "2021-03-15") == 1.2 (1 year and 73 days old)
public static double Calculate(string birthdateString, string referenceDate)
{
var dateOfBirth = ParseUtcDate(birthdateString);
var today = ParseUtcDate(referenceDate);
if (dateOfBirth > today)
{
throw new ArgumentException("Birthdate must not lie in the future.");
}
var anniversary = CreateAnniversary(today.Year, dateOfBirth);
double age;
if (today >= anniversary)
{
// anniversary of birthdate already passed this year
var nextAnniversary = CreateAnniversary(today.Year + 1, dateOfBirth);
age = (today.Year - dateOfBirth.Year) +
(today - anniversary).TotalDays / (nextAnniversary - anniversary).TotalDays;
}
else
{
// anniversary of birthday not passed this year
DateTime prevAnniversary = CreateAnniversary(today.Year - 1, dateOfBirth);
age = (prevAnniversary.Year - dateOfBirth.Year) +
(today - prevAnniversary).TotalDays / (anniversary - prevAnniversary).TotalDays;
}
return Math.Floor(10 * age) / 10;
}
///
/// Parses a given string in the format "yyyy-MM-dd" as a DateTime value in UTC.
///
private static DateTime ParseUtcDate(string s)
{
return DateTime.ParseExact(
s,
"yyyy-MM-dd",
CultureInfo.InvariantCulture.DateTimeFormat).AssumeUtc();
}
///
/// Creates a UTC date in the given that represents the anniversary date for
/// the given birthdate.
/// For date of births "February 29" the anniversary is assumed to be "March 1" in non leap years.
///
/// CreateAnniversary(2021, "1983-13-07") ==> "2021-13-07"
/// CreateAnniversary(2020, "1991-02-29") ==> "2020-02-29"
/// CreateAnniversary(2021, "1991-02-29") ==> "2021-03-01"
private static DateTime CreateAnniversary(int year, DateTime dateOfBirth)
{
Debug.Assert(dateOfBirth.Kind == DateTimeKind.Utc);
int month, day;
if (!DateTime.IsLeapYear(year) && dateOfBirth.Month == 2 && dateOfBirth.Day == 29)
{
month = 3;
day = 1;
}
else
{
month = dateOfBirth.Month;
day = dateOfBirth.Day;
}
return new DateTime(year, month, day).AssumeUtc();
}
///
/// Sets kind to UTC (interpreting DateTime as value in UTC).
///
private static DateTime AssumeUtc(this DateTime me)
{
return DateTime.SpecifyKind(me, DateTimeKind.Utc);
}
}
}