はじめに 「年齢計算」は“日付ロジックの入門にして落とし穴だらけのテーマ”
「この人は今何歳か」「○日時点で何歳だったか」。
業務システムでも、顧客管理・会員管理・保険・医療・学校など、年齢計算は本当によく出てきます。
でも、
単純に「今年 − 生まれた年」ではダメです。
誕生日を迎えているかどうかで、年齢は 1 歳変わります。
ここでは、
一番基本的な「満年齢」の計算から始めて、
基準日を指定するパターン、DateOnly の活用、注意すべき落とし穴まで、
初心者向けにかみ砕いて説明していきます。
基本:満年齢の考え方「誕生日を過ぎているかどうか」
「年だけ引く」となぜ間違うのか
まず、よくやってしまう間違いから。
int age = today.Year - birth.Year;
C#一見それっぽいですが、
例えば「2000/12/31 生まれ」の人を「2026/02/10 時点」で計算するとどうなるか。
today.Year = 2026
birth.Year = 2000
age = 2026 − 2000 = 26
でも、2026/02/10 の時点では、まだ 2026 年の誕生日(12/31)を迎えていません。
正しい満年齢は 25 歳です。
つまり、
「年だけ引く」と、誕生日を迎えていない期間は 1 歳多く出てしまいます。
正しい満年齢のロジック
満年齢の基本ロジックはこうです。
- まず「年だけ引いた値」を仮の年齢とする
- その年の誕生日を作る
- 基準日がその誕生日より前なら、1 歳引く
これを C# で書くと、こうなります。
using System;
public static class AgeUtil
{
public static int GetAge(DateTime birthDate, DateTime onDate)
{
if (onDate < birthDate)
{
throw new ArgumentException("基準日が生年月日より前です。");
}
int age = onDate.Year - birthDate.Year;
DateTime birthdayThisYear = new DateTime(onDate.Year, birthDate.Month, birthDate.Day);
if (onDate < birthdayThisYear)
{
age--;
}
return age;
}
}
C#使い方の例です。
DateTime birth = new DateTime(2000, 12, 31);
DateTime d1 = new DateTime(2026, 2, 10);
DateTime d2 = new DateTime(2026, 12, 31);
DateTime d3 = new DateTime(2027, 1, 1);
Console.WriteLine(AgeUtil.GetAge(birth, d1)); // 25
Console.WriteLine(AgeUtil.GetAge(birth, d2)); // 26
Console.WriteLine(AgeUtil.GetAge(birth, d3)); // 27
C#ここでの重要ポイントは、
「その年の誕生日を作って、基準日と比較する」というステップを必ず入れることです。
これを入れないと、誕生日を迎える前後で 1 歳ズレ続けます。
「今日時点の年齢」を求めるユーティリティ
DateTime.Today を使ったシンプル版
「今この瞬間の年齢」ではなく、「今日時点の年齢」でよければ、DateTime.Today を基準日に使うのが分かりやすいです。
public static class AgeUtil
{
public static int GetAgeToday(DateTime birthDate)
{
return GetAge(birthDate, DateTime.Today);
}
}
C#使い方はとてもシンプルです。
DateTime birth = new DateTime(1990, 5, 10);
int age = AgeUtil.GetAgeToday(birth);
Console.WriteLine(age); // 今日時点の満年齢
C#「画面に表示する年齢」「プロフィールの年齢」など、
多くのケースではこの GetAgeToday で足ります。
基準日を変えたいケース:「○日時点で何歳だったか」
過去や未来のある日を基準にする
業務では、「今日」以外を基準にしたいこともよくあります。
例えば、
「契約開始日時点での年齢」
「入社日時点での年齢」
「イベント開催日時点での年齢」
などです。
こういうときは、先ほどの GetAge をそのまま使えば OK です。
DateTime birth = new DateTime(1990, 5, 10);
DateTime joinedDate = new DateTime(2015, 4, 1);
int ageAtJoin = AgeUtil.GetAge(birth, joinedDate);
Console.WriteLine(ageAtJoin); // 入社日時点の年齢
C#ここでのポイントは、
「年齢は常に“ある基準日時点での満年齢”として扱う」
という考え方を徹底することです。
「今何歳?」と「入社時何歳だった?」は別物なので、
基準日を引数で渡せるようにしておくと、ロジックがブレません。
DateOnly を使った、日付だけの年齢計算
時刻を無視したいなら DateOnly が素直
.NET 6 以降なら、日付だけを扱う DateOnly が使えます。
年齢計算は「日付」だけ分かればよく、時刻は不要なので、DateOnly ベースで書くとより意図がはっきりします。
public static class AgeUtilDateOnly
{
public static int GetAge(DateOnly birthDate, DateOnly onDate)
{
if (onDate < birthDate)
{
throw new ArgumentException("基準日が生年月日より前です。");
}
int age = onDate.Year - birthDate.Year;
DateOnly birthdayThisYear = new DateOnly(onDate.Year, birthDate.Month, birthDate.Day);
if (onDate < birthdayThisYear)
{
age--;
}
return age;
}
}
C#使い方の例です。
DateOnly birth = new DateOnly(2000, 12, 31);
DateOnly on = new DateOnly(2026, 2, 10);
int age = AgeUtilDateOnly.GetAge(birth, on);
Console.WriteLine(age); // 25
C#DateTime だと「時刻」「タイムゾーン」の話が絡んできますが、
年齢計算ではそこまで要らないことが多いので、DateOnly を使うとシンプルで安全です。
落とし穴1:うるう年(2/29 生まれ)の扱い
2/29 生まれの人は、うるう年以外どうカウントするか
2/29 生まれの人は、うるう年以外には「その年の誕生日」が存在しません。
現実世界では、「2/28 か 3/1 を誕生日扱いにする」など、
国や制度によって扱いが分かれることがあります。
先ほどの実装だと、new DateTime(onDate.Year, 2, 29) がうるう年以外で例外になります。
この問題をどう扱うかは、
システムの要件次第です。
例えば、「2/28 を誕生日扱いにする」ルールにするなら、
こんな感じで調整できます。
public static int GetAgeWithLeapSupport(DateTime birthDate, DateTime onDate)
{
if (onDate < birthDate)
{
throw new ArgumentException("基準日が生年月日より前です。");
}
int age = onDate.Year - birthDate.Year;
int month = birthDate.Month;
int day = birthDate.Day;
if (month == 2 && day == 29 && !DateTime.IsLeapYear(onDate.Year))
{
day = 28;
}
DateTime birthdayThisYear = new DateTime(onDate.Year, month, day);
if (onDate < birthdayThisYear)
{
age--;
}
return age;
}
C#ここは「業務ルール」として決める必要があるので、
仕様書や関係者との合意が必須です。
落とし穴2:タイムゾーンと「いつ年齢が変わるか」
UTC とローカルで日付がズレる問題
サーバー側で UTC を使っている場合、
「日本時間では誕生日を迎えているのに、UTC ではまだ前日」
という状況が普通に起こります。
例えば、
日本時間 2026/05/10 0:30 は、UTC だと 2026/05/09 15:30 です。
「日本のユーザーの年齢」を扱うのに、
UTC の日付で年齢計算をしてしまうと、
誕生日当日に「まだ年齢が変わっていない」ように見えてしまいます。
実務的には、
- ユーザーの居住国・タイムゾーンを決める
- UTC からそのタイムゾーンに変換してから、日付ベースで年齢計算する
という方針を取るのが安全です。
イメージとしては、こんな感じです。
public static int GetAgeInTimeZone(DateTime birthDateLocal, DateTime utcNow, TimeZoneInfo timeZone)
{
DateTime nowLocal = TimeZoneInfo.ConvertTimeFromUtc(utcNow, timeZone);
DateOnly birth = DateOnly.FromDateTime(birthDateLocal);
DateOnly on = DateOnly.FromDateTime(nowLocal);
return AgeUtilDateOnly.GetAge(birth, on);
}
C#「どのタイムゾーンの“今日”を基準に年齢を決めるか」を
ユーティリティ名やコメントに明示しておくと、
後から読んだ人が迷わずに済みます。
まとめ 「年齢計算ユーティリティ」は“日付の境界をきちんと扱う練習台”
年齢計算は、一見シンプルに見えて、
誕生日の前後、うるう年、タイムゾーンなど、
日付ロジックの典型的な落とし穴が全部詰まっています。
押さえておきたいポイントを整理すると、こうなります。
- 満年齢は「年だけ引く」のではなく、「その年の誕生日を過ぎているかどうか」で 1 歳調整する。
- 「今日時点の年齢」と「ある日時点の年齢」は別物なので、基準日を引数で渡せる
GetAge(birth, onDate)という形にしておく。 - 時刻やタイムゾーンを気にしたくないなら、
DateOnlyベースで年齢計算を書くとシンプルで安全になる。 - 2/29 生まれの扱いは業務ルール次第なので、「うるう年以外は 2/28 扱い」など、仕様として決めてから実装する。
- UTC とローカルのズレを意識し、「どのタイムゾーンの“今日”で年齢を決めるか」を最初に設計しておく。
ここを押さえておけば、
「なんとなく Year を引いている」状態から一歩進んで、
“現実のルールにちゃんと沿った、実務で使える年齢計算ユーティリティ”を
自分の C# コードの中に自信を持って組み込めるようになります。
