- 技術 💻
生年月日から年齢を計算|SQL・Excel・Java・JS・Pythonと2月29日
どの言語・どの 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つです。
- 日数÷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)。 - YEARFRAC は年齢の計算用ではない。 年の割合(利息計算などに使う)を求める関数なので、誕生日当日に1つ足りないことがあります。
- 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期連続の黒字決算。