Created
March 3, 2021 13:41
-
-
Save akaegi/9291437f9b1d13b5930b1c20b2f21634 to your computer and use it in GitHub Desktop.
Age calculation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics; | |
using System.Globalization; | |
namespace ToBeDefined | |
{ | |
public static class Age | |
{ | |
/// <summary> | |
/// 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) | |
/// </summary> | |
/// <example>Calculate("2020-02-29", "2021-03-01") == 1.0</example> | |
/// <example>Calculate("2020-01-01", "2021-03-15") == 1.2 (1 year and 73 days old)</example> | |
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; | |
} | |
/// <summary> | |
/// Parses a given string in the format "yyyy-MM-dd" as a DateTime value in UTC. | |
/// </summary> | |
private static DateTime ParseUtcDate(string s) | |
{ | |
return DateTime.ParseExact( | |
s, | |
"yyyy-MM-dd", | |
CultureInfo.InvariantCulture.DateTimeFormat).AssumeUtc(); | |
} | |
/// <summary> | |
/// Creates a UTC date in the given <paramref name="year"/> 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. | |
/// </summary> | |
/// <example>CreateAnniversary(2021, "1983-13-07") ==> "2021-13-07"</example> | |
/// <example>CreateAnniversary(2020, "1991-02-29") ==> "2020-02-29"</example> | |
/// <example>CreateAnniversary(2021, "1991-02-29") ==> "2021-03-01"</example> | |
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(); | |
} | |
/// <summary> | |
/// Sets kind to UTC (interpreting DateTime as value in UTC). | |
/// </summary> | |
private static DateTime AssumeUtc(this DateTime me) | |
{ | |
return DateTime.SpecifyKind(me, DateTimeKind.Utc); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics; | |
using System.Globalization; | |
using ToBeDefined; | |
using XFSpike.Tests; | |
namespace ToBeDefined | |
{ | |
public class AgeCalculatorTest | |
{ | |
static void Main(string[] args) | |
{ | |
var age1 = Age.Calculate("1983-07-13", "1984-07-13"); | |
Debug.Assert(age1 == 1); | |
var age2 = Age.Calculate("1983-07-13", "1984-07-12"); | |
Debug.Assert(age2 >= 0.9 && age2 < 1); | |
var age3 = Age.Calculate("1983-07-13", "1984-09-13"); | |
Debug.Assert(age3 > 1 && age3 <= 1.2); | |
var age4 = Age.Calculate("2020-02-29", "2021-02-28"); | |
Debug.Assert(age4 > 0 && age4 < 1); | |
var age5 = Age.Calculate("2020-02-29", "2021-03-01"); | |
Debug.Assert(age5 == 1); | |
var age6 = Age.Calculate("2020-02-29", "2020-02-29"); | |
Debug.Assert(age6 == 0); | |
bool thrown7 = false; | |
try | |
{ | |
Age.Calculate("2020-02-29", "2020-02-28"); | |
} | |
catch (Exception e) | |
{ | |
thrown7 = true; | |
} | |
Debug.Assert(thrown7); | |
var a = DateTime.Parse("2021-01-01").AddDays(73); | |
var age8 = Age.Calculate("2020-01-01", a.ToString("yyyy-MM-dd")); | |
Debug.Assert(age8 == 1.2); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment