グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



実装してみると意外と難しい「PHPで年齢を計算する方法」を紹介します

弊社では、健診予約システム等の構築を制作させていただく機会があります。
予約システムでは、ユーザの生年月日をもとに年齢を算出する必要があるのですが、考慮しないといけないポイントがいくつかあり、意外とややこしいです。

そこで今回は、PHPで年齢を計算する方法を紹介します。

なお、今回の検証で使用した環境は以下の通りです。

使用環境
  • Docker desktop for Windows: 3.1.0
  • docker: 20.10.2
  • docker-compose: 1.27.4, build 40524192
  • PHP: 8.0.2 (Docker image: php:8.0.2-cli-alpine3.13)
  • composer: 2.0.11 (Docker image: composer:2.0.11)

年齢を扱うのは結構難しい

まずは愚直に年齢の値を引数に取り、値を保持するAgeクラスを設計してみます。

>class Age
{
    private int $age;

    public function __construct(int $age)
    {
        $this->age = $age;
    }

    public function valueOf(): int
    {
        return $this->age;
    }
}
$age = new Age(19);
$age->valueOf(); // int(19)

これで、年齢が取得できます。となればいいのですが、
ここで何かしらの予約申込みで予約したい日に年齢が20歳以上の方には割引特典をするといったアプリケーションを考えてみます。

2021年03月31日の時、生年月日が2001年04月01日の年齢は19歳です。
2021年04月01日の時、生年月日が2001年04月01日の年齢は20歳です。

年齢は基点とする日付によって変わり得るので、ドメインによってはただ年齢の値を保持する実装の場合に対応できないケースがでてきてしまいます。

その場合は基準日から、生年月日の日付を減算して年齢を求めることができます。

(基準日 - 生年月日) / 10000
(int)((20210419 - 20010420) / 10000) // int(19)
(int)((20210420 - 20010420) / 10000) // int(20)

これで年齢計算ができるようになりました。となればいいのですが、
ドメインによっては上記の計算方法でも対応できないケースが存在します。

年齢に関わる用語集

年齢計算ニ関スル法律

日本では年齢の計算方法を定める法律が存在します。

これに対し、年齢計算ニ関スル法律は、年齢は出生の日から起算するものとし、初日不算入の例外を定めている(年齢計算ニ関スル法律第1項)。そして、その期間は起算日応当日の前日に満了する(年齢計算ニ関スル法律第2項、民法143条準用(同条2項参照)) 以上の条文から、年齢は生まれた日を0歳とし、生まれた年の翌年以降、起算日に応当する日の前日が満了するたびに1歳ずつ加算する。つまり、加齢する時刻は誕生日前日が満了する「午後12時」(24時0分0秒)と解されている(「前日午後12時」と「当日午前0時」は時刻としては同じだが、属する日は異なることに注意)。wikipedia

参考: 明治三十五年法律第五十号(年齢計算ニ関スル法律)

満年齢

生まれた日や基点となる最初の年を「0歳」、「0年」から数え始め、以後1年間の満了ごとにそれぞれ1歳、1年ずつ年を加えていく考え方。ある基点からの経過年数を表す基数年である。wikipedia

私たちが普段年齢と呼んでいる表現方法が満年齢です。
満年齢以外には生まれた年を「1歳」から数え始める数え年がありますが本記事では割愛します。

起算日、満了

期間計算の原則
まず「満了」とは、期間中の全時間が満たされ、その期間が終了することである。よって、「1年間の満了」とは1年間の最後の日の全24時間まで経過することである。「最後の日」とは起算日応当日からみると前日であるため、期間の満了は起算日応当日の前日となる。次に「起算日」は、初日を省いて翌日とするのが原則である。つまり、期間は「初日の翌日に応当する日の前日」に満了するため、結果的に1年間の満了は初日と同月同日になる。結婚記念日や創立記念日など、日常生活上の多くの記念日が「n年間の満了日」と一致するのはこのためである。wikipedia
期間計算の例外
年齢計算にあっては例外的に初日(出生日)を起算日とするwikipedia

生年月日が2021年03月31日であれば以下のようになります。

用語 説明 日付
起算日 生年月日(出生日) 2021年03月31日
最後の日 起算日応当日からみると前日 2022年03月31日
1年間の満了 1年間の最後の日の全24時間まで経過すること 2022年03月31日 24:00:00

1年間の満了が時間で見ると2022年04月01日 00:00:00と同じですが、日付で見ると2022年03月31日と違っているのが重要です。

学齢

学齢(がくれい)とは学校に就学して教育を受けることが適切とされる年齢のことである。日本では、満6歳の誕生日以後の最初の4月1日から9年間(満15歳に達した日以後の最初の3月31日まで)が該当する。wikipedia
早生まれ 日本において、誕生日が1月1日から4月1日までの間にある場合、俗に「早生まれ」と呼ばれる。これは日本における小中学校の学齢期の制度が次のようになっている事に由来する。

日本における学齢期は、法律で「満6歳の誕生日以後(注:基準日を含む)における最初の学年の初め(最初の4月1日)」から始まる、即ち「満6歳の誕生日を含む日以降に最初に到来する4月1日から就学する」こととなる。よって学校法上の小中学校は原則として、同じ学齢(1学年)は4月2日生まれから翌4月1日生まれまでの児童、生徒で構成される事になる。 wikipedia

年齢 学年
6〜7歳 1年
7〜8歳 2年
8〜9歳 3年
9〜10歳 4年
10〜11歳 5年
11〜12歳 6年
12〜13歳 1年
13〜14歳 2年
14〜15歳 3年

2015年04月01日生まれの人は2021年03月31日 24:00:00に満6歳を迎えるので、2021年04月01日から小学校に就学しますが、
2015年04月02日生まれの人は2021年04月01日 24:00:00に満6歳を迎えるので、2022年04月01日から小学校に就学します。

環境構築

まずはPHPの環境をDockerで構築します。

■ docker-compose.yml

version: '3.8'

services:

  php:
    image: php:8.0.2-cli-alpine3.13
    working_dir: /app
    volumes:
      - type: bind
        source: .
        target: /app
      - type: bind
        source: .composer
        target: /.composer

  composer:
    image: composer:2.0.11
    working_dir: /app
    volumes:
      - type: bind
        source: .
        target: /app
      - type: bind
        source: .composer
        target: /.composer

■ composer.json

{
    "require": {
        "nesbot/carbon": "^2.45"
    },
    "require-dev": {
        "phpunit/phpunit": "^9.5"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    }
}

日付を扱うライブラリの nesbot/carbon とテスト用に phpunit/phpunit をインストールします。

$ docker-compose run --rm -u $(id -u):$(id -g) composer install

続けて、phpunit.xml.distを追加します。

■ phpunit.xml.dist

<?xml version="1.0"?>
<phpunit
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
    backupGlobals="false"
    backupStaticAttributes="false"
    beStrictAboutTestsThatDoNotTestAnything="true"
    beStrictAboutChangesToGlobalState="true"
    beStrictAboutOutputDuringTests="true"
    colors="true"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    processIsolation="false"
    stopOnFailure="false"
    stderr="true"
    bootstrap="tests/bootstrap.php"
>
  <coverage processUncoveredFiles="true">
    <include>
      <directory suffix=".php">./src/</directory>
    </include>
  </coverage>
  <testsuites>
    <testsuite name="Test Suite">
      <directory>./tests/</directory>
    </testsuite>
  </testsuites>
</phpunit>

続けて、テスト用にboostrap.phpを追加します。

■ tests/bootstrap.php

<?php

declare(strict_types=1);

require __DIR__ . '/../vendor/autoload.php';

環境構築は以上です。

基準日と生年月日から満年齢を計算する

年齢計算で必要になる用語をまとめます。

用語 用語(英名) 説明
生年月日 BirthDate 起算日となる生年月日(誕生日)
基準日 ReferenceDate 今日、予約したい日等

基準日から生年月日の年の差分で年齢を取得する設計でいきます。
差分の取得にはCarbonが継承しているDateTimeクラスのdiffメソッドを使用します。

■ src/Age.php

<?php

declare(strict_types=1);

namespace App;

use Carbon\CarbonImmutable;

class Age
{
    private CarbonImmutable $birthDate;

    public function __construct(CarbonImmutable $birthDate)
    {
        $this->birthDate = $birthDate;
    }

    public function calc(CarbonImmutable $referenceDate): int
    {
        return $referenceDate->diff($this->birthDate)->y;
    }
}

やっていることは 基準日 – 生年月日 / 10000 と同じです。

以下の要件でテストができそうです。

  • 基準日が2021年03月31日 23時59分59秒の時、生年月日が2015年04月01日の年齢は5歳として扱う
  • 基準日が2021年04月01日 00時00分00秒の時、生年月日が2015年04月01日の年齢は6歳として扱う

基準日が2021年03月31日 23時59分59秒の時、生年月日が2015年04月01日の年齢は5歳として扱う

■ tests/AgeTest.php

<?php

declare(strict_types=1);

namespace Tests;

use App\Age;
use Carbon\CarbonImmutable;
use PHPUnit\Framework\TestCase;

class AgeTest extends TestCase
{
    public function test_基準日が2021年03月31日_23時59分59秒の時、生年月日が2015年04月01日の年齢は5歳として扱う(): void
    {
        $birthDate = new CarbonImmutable('2015-04-01');
        $today = new CarbonImmutable('2021-03-31 23:59:59');
        $sut = new Age($birthDate);

        $result = $sut->calc($today);

        $this->assertSame(5, $result);
    }
}

実行結果

$ docker-compose run --rm -u $(id -u):$(id -g) php ./vendor/bin/phpunit --testdox
Creating calculate-age_php_run ... done
PHPUnit 9.5.2 by Sebastian Bergmann and contributors.

Age (Tests\Age)
 ✔ 基準日が2021年03月31日 23時59分59秒の時、生年月日が2015年04月01日の年齢は5歳として扱う

基準日が2021年04月01日 00時00分00秒の時、生年月日が2015年04月01日の年齢は6歳として扱う

テストケースに追加してテストを実行します。

@@ -20,4 +20,15 @@ class AgeTest extends TestCase

         $this->assertSame(5, $result);
     }
+
+    public function test_基準日が2021年04月01日_00時00分00秒の時、生年月日が2015年04月01日の年齢は6歳として扱う(): void
+    {
+        $birthDate = new CarbonImmutable('2015-04-01');
+        $today = new CarbonImmutable('2021-04-01 00:00:00');
+        $sut = new Age($birthDate);
+
+        $result = $sut->calc($today);
+
+        $this->assertSame(6, $result);
+    }
 }

実行結果

$ docker-compose run --rm -u $(id -u):$(id -g) php ./vendor/bin/phpunit --testdox
Creating calculate-age_php_run ... done
PHPUnit 9.5.2 by Sebastian Bergmann and contributors.

Age (Tests\Age)
 ✔ 基準日が2021年03月31日 23時59分59秒の時、生年月日が2015年04月01日の年齢は5歳として扱う
 ✔ 基準日が2021年04月01日 00時00分00秒の時、生年月日が2015年04月01日の年齢は6歳として扱う

Time: 00:00.014, Memory: 8.00 MB

OK (2 tests, 2 assertions)

テストが通りました。

基準日と生年月日から学齢の年齢を計算する

ドメインによっては基準日によって満年齢では満n歳でも、満n+1歳として取り扱うケースがあります。

先述の学齢では基準日を時刻を含まない03月31日としたとき、生年月日が04月01日の人は満n+1歳として取り扱いたいといったケースはあり得るケースです。 e.g.) 基準日が2021年03月31日の時、生年月日が2015年04月01日の人は満6歳 ((20210331 – 20150401) / 10000で計算すると満5歳)

生年月日の年と月の前日に歳を加算するといった要件で対応できそうです。

年齢計算で必要になる用語をまとめます。

用語 用語(英名) 説明
生年月日 BirthDate 起算日となる生年月日(誕生日)
基準日 ReferenceDate 基準日 (xxxx年03月31日, xxxx年04月1日等)

■ src/SchoolAge.php

<?php

declare(strict_types=1);

namespace App;

use Carbon\CarbonImmutable;

class SchoolAge
{
    private CarbonImmutable $birthDate;

    public function __construct(CarbonImmutable $birthDate)
    {
        $this->birthDate = $birthDate;
    }

    public function calc(CarbonImmutable $referenceDate): int
    {
        return $referenceDate->setTime(24, 00, 00)->diff($this->birthDate)->y;
    }
}

以下の要件でテストができそうです。

  • 基準日が2021年03月30日の時、生年月日が2015年04月01日の年齢は5歳として扱う
  • 基準日が2021年03月31日の時、生年月日が2015年04月01日の年齢は6歳として扱う

基準日が2021年03月30日の時、生年月日が2015年04月01日の年齢は5歳として扱う

■ tests/SchooleAgeTest.php

<?php

declare(strict_types=1);

namespace Tests;

use App\SchoolAge;
use Carbon\CarbonImmutable;
use PHPUnit\Framework\TestCase;

class SchoolAgeTest extends TestCase
{
    public function test_基準日が2021年03月30日の時、生年月日が2015年04月01日の年齢は5歳として扱う(): void
    {
        $birthDate = new CarbonImmutable('2015-04-01');
        $referenceDate = new CarbonImmutable('2021-03-30');
        $sut = new SchoolAge($birthDate);

        $result = $sut->calc($referenceDate);

        $this->assertSame(5, $result);
    }
}

実行結果

$ docker-compose run --rm -u $(id -u):$(id -g) php ./vendor/bin/phpunit --testdox
Creating calculate-age_php_run ... done
PHPUnit 9.5.2 by Sebastian Bergmann and contributors.

Age (Tests\Age)
 ✔ 基準日が2021年03月31日 23時59分59秒の時、生年月日が2015年04月01日の年齢は5歳として扱う
 ✔ 基準日が2021年04月01日 00時00分00秒の時、生年月日が2015年04月01日の年齢は6歳として扱う

School Age (Tests\SchoolAge)
 ✔ 基準日が 2021年 03月 30日の時、生年月日が 2015年 04月 01日の年齢は 5歳として扱う

Time: 00:00.018, Memory: 8.00 MB

OK (3 tests, 3 assertions)

基準日が2021年03月31日の時、生年月日が2015年04月01日の年齢は6歳として扱う

■ tests/SchooleAgeTest.php

@@ -20,4 +20,15 @@ class SchoolAgeTest extends TestCase

         $this->assertSame(5, $result);
     }
+
+    public function test_基準日が2021年03月31日の時、生年月日が2015年04月01日の年齢は6歳として扱う(): void
+    {
+        $birthDate = new CarbonImmutable('2015-04-01');
+        $referenceDate = new CarbonImmutable('2021-03-31');
+        $sut = new SchoolAge($birthDate);
+
+        $result = $sut->calc($referenceDate);
+
+        $this->assertSame(6, $result);
+    }
 }

実行結果

$ docker-compose run --rm -u $(id -u):$(id -g) php ./vendor/bin/phpunit --testdox
Creating calculate-age_php_run ... done
PHPUnit 9.5.2 by Sebastian Bergmann and contributors.

Age (Tests\Age)
 ✔ 基準日が2021年03月31日 23時59分59秒の時、生年月日が2015年04月01日の年齢は5歳として扱う
 ✔ 基準日が2021年04月01日 00時00分00秒の時、生年月日が2015年04月01日の年齢は6歳として扱う

School Age (Tests\SchoolAge)
 ✔ 基準日が 2021年 03月 30日の時、生年月日が 2015年 04月 01日の年齢は 5歳として扱う
 ✔ 基準日が 2021年 03月 31日の時、生年月日が 2015年 04月 01日の年齢は 6歳として扱う

Time: 00:00.014, Memory: 8.00 MB

OK (4 tests, 4 assertions)

テストが通りました。

まとめ

  • 愚直に年齢の値を状態として保持するケース
  • 基準日から生年月日を減算して年齢を計算するケース
  • 学齢のようなドメインによって特別な年齢計算が必要なケース

以上のケースをPHPで実装する方法について紹介しました。

学齢以外にも特別な年齢計算というものは存在するようです。
興味のある方は年齢計算の練習に試してみてください。

満年齢のうち年未満の端数処理は切り捨てが一般的だが、生命保険の分野では、これを四捨五入した保険年齢と呼ばれる年齢で保険料を算出する会社又は商品もある。 人事労務の分野では、新規学卒者に対し、学歴に応じて一定の年齢とみなす学卒年齢という考え方がある。
健康診査や人間ドックの分野では、年度年齢あるいは検診(健診)年齢と呼ばれる、当該会計年度の末日(3月31日)現在の満年齢が用いられる。
日本の法令の場合、ほとんどは満年齢を基準にしているが、法律上の資格の有効期間・更新期間の中には、資格者の誕生日を基準とするものもある wikipedia

この記事を書いた人

美髭公
美髭公事業開発部 web application engineer
2013年にアーティスに入社。システムエンジニアとしてアーティスCMSを使用したWebサイトや受託システムの構築・保守に携わる。環境構築が好き。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします