どの言語・どの DB でも使える年齢の求め方は、生年月日と基準日を「20261010」のような8桁の数にして引き、10000で割って小数を切り捨てる方法です。 誕生日の当日に1つ加わり、2月29日生まれでも正しく動きます。

年齢 = floor( (基準日のyyyymmdd − 生年月日のyyyymmdd) ÷ 10000 )
例:(20261010 − 19901010) ÷ 10000 = 36.0000 → 36歳
    (20261009 − 19901010) ÷ 10000 = 35.9999 → 35歳

一方で、「経過日数 ÷ 365」で求める書き方は、誕生日の前日にもう1つ加わってしまうなど、年に数日ずれます。この記事では、各言語・DB の標準的な書き方を、2月29日生まれを含む7通りの日付で動かし、どれが正しく、どれがずれるかを一覧にしました。

確認環境:MySQL 8.0.46、PostgreSQL 16.15、SQLite 3.45.1、OpenJDK 21.0.10、Node.js 22.22.2、Python 3.11.15(python-dateutil)、LibreOffice Calc 24.2(Excel と同名の DATEDIF・YEARFRAC 関数)。SQL Server と Oracle は各社の公式リファレンスで確認(確認日 2026年10月10日)。

早見表:環境別の正しい書き方

環境 書き方 2月29日生まれ
MySQL TIMESTAMPDIFF(YEAR, 生年月日, 基準日) 正しい
PostgreSQL EXTRACT(YEAR FROM age(基準日, 生年月日)) 正しい
SQLite (CAST(strftime('%Y%m%d', 基準日) AS INTEGER) - CAST(strftime('%Y%m%d', 生年月日) AS INTEGER)) / 10000 正しい
SQL Server (CONVERT(int, CONVERT(char(8), 基準日, 112)) - CONVERT(int, CONVERT(char(8), 生年月日, 112))) / 10000 正しい
Oracle TRUNC((TO_NUMBER(TO_CHAR(基準日,'YYYYMMDD')) - TO_NUMBER(TO_CHAR(生年月日,'YYYYMMDD'))) / 10000) 正しい
Excel =DATEDIF(生年月日, 基準日, "Y") 正しい
Java Period.between(生年月日, 基準日).getYears() 正しい
JavaScript 年の差から、誕生日前なら1を引く(下のコード) 正しい
Python 年の差から、誕生日前なら1を引く(下のコード) 正しい

避けたい書き方:経過日数÷365(MySQL の DATEDIFF/Excel の引き算)、経過日数÷365.25、SQL Server の DATEDIFF(YEAR, ...)、Excel の YEARFRAC。理由は下の比較表に載せています。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「生年月日から年齢を計算|SQL・Excel・Java・JS・Pythonと2月29日」

7通りの日付で比べた結果

SQL

生年月日 → 基準日 正しい年齢 MySQL TIMESTAMPDIFF 日数÷365
2000-02-29 → 2024-02-28 23 23 24
2000-02-29 → 2024-02-29 24 24 24
2000-02-29 → 2025-02-28 24 24 25
2000-02-29 → 2025-03-01 25 25 25
1990-10-10 → 2026-10-09 35 35 36
1990-10-10 → 2026-10-10 36 36 36
2000-03-01 → 2003-03-01 3 3 3

表計算と Python ライブラリ

生年月日 → 基準日 正しい年齢 DATEDIF YEARFRAC Python dateutil
2000-02-29 → 2024-02-28 23 23 23 23
2000-02-29 → 2024-02-29 24 24 23 24
2000-02-29 → 2025-02-28 24 24 24 25
2000-02-29 → 2025-03-01 25 25 25 25
1990-10-10 → 2026-10-09 35 35 35 35
1990-10-10 → 2026-10-10 36 36 36 36
2000-03-01 → 2003-03-01 3 3 2 3

太字が正しい年齢とずれた結果です。TIMESTAMPDIFF は MySQL、日数÷365 は MySQL の FLOOR(DATEDIFF(t, b) / 365)、DATEDIF・YEARFRAC は LibreOffice Calc、dateutil は Python の relativedelta(t, b).years の結果です。PostgreSQL の age()、SQLite の8桁の式、Java の Period、JavaScript と Python の自作関数は、7通りすべて正しい年齢になりました。

読み方は3つです。

  1. 日数÷365 は、うるう年の分だけ早く年を取る。 1990-10-10 生まれが誕生日の前日(2026-10-09)に36歳になっています。÷365.25 にしても、2000-03-01 生まれの3歳の誕生日(2003-03-01)が2歳になるずれが残ります(FLOOR(DATEDIFF('2003-03-01','2000-03-01')/365.25) は 2)。
  2. YEARFRAC は年齢の計算用ではない。 年の割合(利息計算などに使う)を求める関数なので、誕生日当日に1つ足りないことがあります。
  3. 2月29日生まれの「うるう年でない年の2月28日」は、ライブラリで答えが分かれる。 MySQL・PostgreSQL・Java・DATEDIF は24歳(3月1日に25歳)、Python の dateutil は2月28日に25歳とします。どちらを採るかは次の章で整理します。

2月29日生まれと「誕生日当日」の考え方

年齢がいつ1つ加わるかは、「年齢計算ニ関スル法律」で、出生の日から起算し、期間の計算は民法の規定によると定められています。民法143条では、年で定めた期間は「起算日に応当する日の前日」に満了し、応当する日が無いときは「その月の末日」に満了します。

生年月日 年齢が加わるタイミング(法律の考え方) 日常の数え方
10月10日 10月9日の終わり(24時) 10月10日から1つ上
2月29日(うるう年) 2月28日の終わり(24時) 2月29日から1つ上
2月29日(うるう年でない年) 2月28日の終わり(24時) 3月1日から1つ上

「前日の24時」は「誕生日の0時」と同じ時刻なので、日付単位で見れば「誕生日当日から1つ上」になります。 2月29日生まれでうるう年でない年は、2月28日の終わりに加わるので、日付で見ると3月1日から1つ上です。この記事の8桁の式・MySQL・PostgreSQL・Java の結果はこの考え方と一致します。

例外は学校の学年で、「満6歳に達した日の翌日以後における最初の学年の初め」に入学するため、4月1日生まれは前の学年に入ります(章の最後「学年を求める」を参照)。

SQL

MySQL:TIMESTAMPDIFF

SELECT name, birthday,
       TIMESTAMPDIFF(YEAR, birthday, CURDATE()) AS age
FROM members;

基準日を指定するなら CURDATE() を '2026-10-10' のような日付に替えます。TIMESTAMPDIFF(YEAR, ...) は月日まで比べて「満年齢」を返すので、7通りすべて正しい結果でした。

+------------+------------+---------------+--------------+----------------+----------+
| birth      | base       | timestampdiff | days_div_365 | days_div_36525 | yyyymmdd |
+------------+------------+---------------+--------------+----------------+----------+
| 2000-02-29 | 2024-02-28 |            23 |           24 |             23 |       23 |
| 2000-02-29 | 2024-02-29 |            24 |           24 |             24 |       24 |
| 2000-02-29 | 2025-02-28 |            24 |           25 |             24 |       24 |
| 2000-02-29 | 2025-03-01 |            25 |           25 |             25 |       25 |
| 1990-10-10 | 2026-10-09 |            35 |           36 |             35 |       35 |
| 1990-10-10 | 2026-10-10 |            36 |           36 |             36 |       36 |
| 2010-12-31 | 2011-01-01 |             0 |            0 |              0 |        0 |
+------------+------------+---------------+--------------+----------------+----------+

8桁の式で書くなら (DATE_FORMAT(基準日,'%Y%m%d') - DATE_FORMAT(生年月日,'%Y%m%d')) DIV 10000 です(右端の列)。

PostgreSQL:age 関数

SELECT name, birthday,
       EXTRACT(YEAR FROM age(CURRENT_DATE, birthday)) AS age
FROM members;

age(基準日, 生年月日) は「○年○か月○日」の間隔(interval)を返すので、EXTRACT(YEAR FROM ...) で年だけを取り出します。引数の順番は「基準日(後の日付)、生年月日(前の日付)」です。

   birth    |    base    |       age_interval       | age_years | yyyymmdd
------------+------------+--------------------------+-----------+----------
 2000-02-29 | 2024-02-28 | 23 years 11 mons 28 days |        23 |       23
 2000-02-29 | 2024-02-29 | 24 years                 |        24 |       24
 2000-02-29 | 2025-02-28 | 24 years 11 mons 28 days |        24 |       24
 2000-02-29 | 2025-03-01 | 25 years 1 day           |        25 |       25
 1990-10-10 | 2026-10-09 | 35 years 11 mons 30 days |        35 |       35
 1990-10-10 | 2026-10-10 | 36 years                 |        36 |       36
 2010-12-31 | 2011-01-01 | 1 day                    |         0 |        0

引数を1つだけ渡す age(birthday) は「今日の0時」との差になります。

SQLite:8桁の式

SQLite には年齢の関数が無いので、strftime で8桁にして引きます。

SELECT name, birthday,
       (CAST(strftime('%Y%m%d', 'now', 'localtime') AS INTEGER)
        - CAST(strftime('%Y%m%d', birthday) AS INTEGER)) / 10000 AS age
FROM members;

整数どうしの割り算は小数が切り捨てられるので、/ 10000 だけで満年齢になります。'now' は UTC なので、日本時間で数えるなら 'localtime' を付けます。

SQL Server:DATEDIFF(YEAR) は使わない

SQL Server の DATEDIFF(YEAR, 開始, 終了) は「年の境目を何回またいだか」を数える関数で、月日を見ません。Microsoft のリファレンスでも、指定した日付の部分の境界を越えた数を返すと説明されています。

-- 2010-12-31 生まれ、2011-01-01 時点で 1 が返る(実際は0歳)
SELECT DATEDIFF(YEAR, '2010-12-31', '2011-01-01');

満年齢は8桁の式で求めます。スタイル 112 は yyyymmdd 形式です。

SELECT name, birthday,
       (CONVERT(int, CONVERT(char(8), GETDATE(), 112))
        - CONVERT(int, CONVERT(char(8), birthday, 112))) / 10000 AS age
FROM members;

Oracle:8桁の式か MONTHS_BETWEEN

SELECT name, birthday,
       TRUNC((TO_NUMBER(TO_CHAR(SYSDATE, 'YYYYMMDD'))
              - TO_NUMBER(TO_CHAR(birthday, 'YYYYMMDD'))) / 10000) AS age
FROM members;

よく使われる TRUNC(MONTHS_BETWEEN(基準日, 生年月日) / 12) は、Oracle のリファレンスで「両方の日付が月末日なら月数は整数になる」と定められています。2月29日生まれと「うるう年でない年の2月28日」はどちらも月末日なので、2月28日に1つ上になります。法律の考え方と合わせたい場合は8桁の式を使います。

Excel:DATEDIF 関数

=DATEDIF(B2, TODAY(), "Y")

B2 に生年月日、2番目の引数に基準日を入れます。"Y" は満年数です。LibreOffice Calc で7通りを計算した結果は、すべて正しい年齢でした。

生年月日 基準日 DATEDIF(…,"Y") INT(YEARFRAC(…,1)) INT((基準−生)/365)
2000-02-29 2024-02-28 23 23 24
2000-02-29 2024-02-29 24 23 24
2000-02-29 2025-02-28 24 24 25
2000-02-29 2025-03-01 25 25 25
1990-10-10 2026-10-09 35 35 36
1990-10-10 2026-10-10 36 36 36
2000-03-01 2003-03-01 3 2 3
やりたいこと 数式
満年齢 =DATEDIF(B2, TODAY(), "Y")
○歳○か月 =DATEDIF(B2,TODAY(),"Y") & "歳" & DATEDIF(B2,TODAY(),"YM") & "か月"
4月1日時点の年齢 =DATEDIF(B2, DATE(2026,4,1), "Y")
関数を使わない形 =INT((TEXT(TODAY(),"yyyymmdd")-TEXT(B2,"yyyymmdd"))/10000)

DATEDIF は関数の一覧や入力候補に出ませんが、Excel で使えます(Microsoft のサポートページに説明があります)。開始日が終了日より後だと #NUM! になります。

Java:Period.between

import java.nio.file.*;
import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;

public class Age {
    /** 誕生日当日に1つ加える(一般的な数え方) */
    static int age(LocalDate birth, LocalDate base) {
        int b = birth.getYear() * 10000 + birth.getMonthValue() * 100 + birth.getDayOfMonth();
        int t = base.getYear() * 10000 + base.getMonthValue() * 100 + base.getDayOfMonth();
        return (t - b) / 10000;
    }

    public static void main(String[] args) throws Exception {
        System.out.println("生年月日    基準日      Period  ChronoUnit  yyyymmdd式");
        for (String line : Files.readAllLines(Path.of("cases.txt"))) {
            String[] p = line.split(" ");
            LocalDate birth = LocalDate.parse(p[0]), base = LocalDate.parse(p[1]);
            System.out.printf("%s  %s  %6d  %10d  %10d%n", birth, base,
                Period.between(birth, base).getYears(),
                ChronoUnit.YEARS.between(birth, base),
                age(birth, base));
        }
    }
}
$ java -Dstdout.encoding=UTF-8 Age.java
生年月日    基準日      Period  ChronoUnit  yyyymmdd式
2000-02-29  2024-02-28      23          23          23
2000-02-29  2024-02-29      24          24          24
2000-02-29  2025-02-28      24          24          24
2000-02-29  2025-03-01      25          25          25
1990-10-10  2026-10-09      35          35          35
1990-10-10  2026-10-10      36          36          36
2010-12-31  2011-01-01       0           0           0

Period.between(生年月日, 基準日).getYears() と ChronoUnit.YEARS.between(生年月日, 基準日) はどちらも満年齢です。今日の年齢は LocalDate.now(ZoneId.of("Asia/Tokyo")) を基準日にします(サーバーが UTC だと、日本の朝9時までは前日の日付になります)。

JavaScript

Date の差をミリ秒で取って割る書き方は、日数÷365 と同じずれが出ます。年・月・日を比べる関数にします。

// 誕生日当日に1つ加える(一般的な数え方)。日付は 'YYYY-MM-DD' の文字列で受け取る
function age(birth, base) {
  const [by, bm, bd] = birth.split('-').map(Number);
  const [ty, tm, td] = base.split('-').map(Number);
  return ty - by - (tm < bm || (tm === bm && td < bd) ? 1 : 0);
}

// 今日(日本時間)の日付を 'YYYY-MM-DD' で得る
const todayJst = () =>
  new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Tokyo' }).format(new Date());

module.exports = { age, todayJst };
if (require.main === module) {
  require('fs').readFileSync('cases.txt', 'utf8').trim().split('\n')
    .forEach((l) => { const [b, t] = l.split(' '); console.log(b, t, age(b, t)); });
  console.log('今日', todayJst());
}
$ node age.js
2000-02-29 2024-02-28 23
2000-02-29 2024-02-29 24
2000-02-29 2025-02-28 24
2000-02-29 2025-03-01 25
1990-10-10 2026-10-09 35
1990-10-10 2026-10-10 36
2010-12-31 2011-01-01 0
今日 2026-10-10

new Date('1990-10-10') は UTC の0時として解釈され、日本時間では朝9時になります。getDate() などを使うとタイムゾーンで1日ずれることがあるため、この関数は日付を 'YYYY-MM-DD' の文字列のまま比べています。今日の日付は Intl.DateTimeFormat に timeZone: 'Asia/Tokyo' を付けて取ります(sv-SE ロケールは YYYY-MM-DD 形式で出力されるため使っています)。

Python

from datetime import date

def age(birth: date, base: date) -> int:
    """誕生日当日に1つ加える(一般的な数え方)"""
    return base.year - birth.year - ((base.month, base.day) < (birth.month, birth.day))

if __name__ == '__main__':
    for line in open('cases.txt'):
        b, t = map(date.fromisoformat, line.split())
        print(b, t, age(b, t))
$ python3 age.py
2000-02-29 2024-02-28 23
2000-02-29 2024-02-29 24
2000-02-29 2025-02-28 24
2000-02-29 2025-03-01 25
1990-10-10 2026-10-09 35
1990-10-10 2026-10-10 36
2010-12-31 2011-01-01 0

(base.month, base.day) < (birth.month, birth.day) はタプルの比較で、誕生日前なら True(=1)になり、年の差から引かれます。

python-dateutil の relativedelta(基準日, 生年月日).years も満年齢を返しますが、2月29日生まれは「うるう年でない年の2月28日」に1つ上になります(比較表の dateutil 列)。法律の考え方に合わせたいなら、上の関数を使います。

応用:基準日・学年・○歳以上の判定

「○歳以上」をSQLで絞り込む(索引が効く書き方)

年齢を計算してから比べると、全行で計算が走り、生年月日の索引が使えません。基準日から年数を引いた日付と比べると索引が効きます。

-- 2026年10月10日時点で18歳以上(2008年10月10日以前に生まれた人)
SELECT * FROM members
WHERE birthday <= DATE '2026-10-10' - INTERVAL '18 years';   -- PostgreSQL

SELECT * FROM members
WHERE birthday <= DATE_SUB('2026-10-10', INTERVAL 18 YEAR);  -- MySQL

2月29日生まれの扱いは、引き算の結果(2008-10-10 のような日付)で決まるので、境界の人を1件確認しておくと安心です。

学年を求める

学校の学年は4月2日〜翌年4月1日生まれが同じ学年です。4月1日生まれは前の年度の生まれとして数えます。

from datetime import date

def school_grade(birth: date, fiscal_year: int) -> int:
    """fiscal_year 年度の学年(1なら小学1年、0以下は未就学)"""
    school_birth_year = birth.year - (1 if (birth.month, birth.day) <= (4, 1) else 0)
    return fiscal_year - school_birth_year - 6

print(school_grade(date(2019, 4, 2), 2026))   # 1(小学1年)
print(school_grade(date(2020, 4, 1), 2026))   # 1(4月1日生まれは前の学年)
print(school_grade(date(2020, 4, 2), 2026))   # 0(未就学)

中学1年なら 7、高校1年なら 10 になります。

よくある質問

Q. DB に年齢の列を持つべきですか。 A. 持たずに、生年月日から計算するのが一般的です。年齢は毎日変わるため、列で持つと更新し続ける必要があります。集計で頻繁に使う場合は、基準日を決めた集計用のテーブルを別に作ります。

Q. 数え年を求めたいです。 A. 数え年は「生まれた年を1歳とし、元日に1つ加える」数え方なので、基準日の年 − 生まれた年 + 1 です。

Q. 「○歳○か月」を求めたいです。 A. Java は Period.between(...) の getYears() と getMonths()、PostgreSQL は age() の結果から EXTRACT(MONTH FROM ...)、Excel は DATEDIF の "Y" と "YM" を組み合わせます。

Q. 今日の日付はどこで取るのがよいですか。 A. タイムゾーンをそろえた1か所で取ります。DB サーバー・アプリサーバー・ブラウザで時刻設定が違うと、日本の0時〜9時のあいだに年齢が1日ずれることがあります。

まとめ

  • どこでも使える式は floor((基準日yyyymmdd − 生年月日yyyymmdd) ÷ 10000)。誕生日当日に1つ加わり、2月29日生まれも正しい
  • MySQL は TIMESTAMPDIFF(YEAR, …)、PostgreSQL は EXTRACT(YEAR FROM age(…))、Excel は DATEDIF(…,"Y")、Java は Period.between で正しい年齢になる
  • 日数÷365・÷365.25・SQL Server の DATEDIFF(YEAR)・YEARFRAC はずれる(誕生日の前日に加わる、年の境目で加わる、当日に足りない)
  • 2月29日生まれは、うるう年でない年は3月1日から1つ上(法律の考え方)。Python の dateutil と Oracle の MONTHS_BETWEEN は2月28日に1つ上になる
  • 「○歳以上」の絞り込みは、基準日から年数を引いた日付と生年月日を比べると索引が効く
  • 今日の日付はタイムゾーンを決めて1か所で取る

参考・出典

確認日はいずれも 2026年10月10日です。

  • e-Gov 法令検索「年齢計算ニ関スル法律」:https://laws.e-gov.go.jp/law/135AC0000000050
  • e-Gov 法令検索「民法(第143条 暦による期間の計算)」:https://laws.e-gov.go.jp/law/129AC0000000089
  • e-Gov 法令検索「学校教育法(第17条)」:https://laws.e-gov.go.jp/law/322AC0000000026
  • MySQL 8.0 Reference Manual「Date and Time Functions(TIMESTAMPDIFF)」:https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html
  • PostgreSQL 16 文書「日付/時刻関数と演算子(age)」:https://www.postgresql.org/docs/16/functions-datetime.html
  • SQLite「Date And Time Functions」:https://www.sqlite.org/lang_datefunc.html
  • Microsoft Learn「DATEDIFF (Transact-SQL)」:https://learn.microsoft.com/ja-jp/sql/t-sql/functions/datediff-transact-sql
  • Oracle Database SQL Language Reference「MONTHS_BETWEEN」:https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/MONTHS_BETWEEN.html
  • Microsoft サポート「DATEDIF 関数」:https://support.microsoft.com/ja-jp/excel/functions/datedif-function
  • Java SE 21 API「java.time.Period」:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/Period.html
  • python-dateutil「relativedelta」:https://dateutil.readthedocs.io/en/stable/relativedelta.html

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「生年月日から年齢を計算|SQL・Excel・Java・JS・Pythonと2月29日」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?