Skip to content

Instantly share code, notes, and snippets.

@akaegi
Created March 3, 2021 13:41
Show Gist options
  • Save akaegi/9291437f9b1d13b5930b1c20b2f21634 to your computer and use it in GitHub Desktop.
Save akaegi/9291437f9b1d13b5930b1c20b2f21634 to your computer and use it in GitHub Desktop.
Age calculation
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);
}
}
}
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