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); } } }