実装してみると意外と難しい「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日)」から始まる、即ち「満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メソッドを使用します。
DateTime::diff https://www.php.net/manual/ja/datetime.diff.php
■ 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
この記事を書いた人
- 2013年にアーティスに入社。システムエンジニアとしてアーティスCMSを使用したWebサイトや受託システムの構築・保守に携わる。環境構築が好き。
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー