【Java初心者用】今年の年齢の求め方
様々なシステムで今年の年齢を求めたい場合があります。例えば、データベースに生年月日を登録しておき、プログラムを使って今年の年齢を調べたい、といった場面です。年齢は生年月日などから簡単に求めれられるので、データベースに保存することはありません。その代わり、今年の年齢を求めるプログラムを作成できるスキルが必要です。
今回は今年の年齢を求めるプログラムをいくつか紹介します。これらのプログラムから年齢を求めるプログラムの考え方を習得してください。
なお、この記事では4つのサンプルプログラムを紹介しているので長文となります。
まず、この記事は以下のような人を対象としています。
対象者・Javaの勉強を始めたばかりの人
・今年の年齢を求めるプログラムの作り方を知りたい人
・日付クラスの使い方を知りたい人
この記事を読むと、次のようなことが理解できるようになります。
この記事を読むとできること・年齢の計算方法を知ることができる
・日付クラスの使い方を知ることができる
・色々なプログラムの作り方を知ることができる
生年月日から今年の年齢を求めるプログラムを作成したいのですが、どう考えたら良いのでしょうか?
生まれてから今年まで何年経過したかが今年の年齢です。
簡単な計算式で求めることができますよ!!
Javaを勉強したばかりで、まだまだプログラムをどうやって作れば良いか分かりません。
では、今回は簡単なプログラムから本格的なプログラムまでいくつかサンプルプログラムを使って解説します。
今年の年齢の求め方
今年の年齢は「今年の西暦-生まれた西暦」の計算式で簡単に求めることができます。わざわざ、ここで解説するまでもないかもしれませんが、初心者にとっては最初は分からずに戸惑ってしまう可能性もあります。
今回は、以下の3つのパターンでサンプルプログラムを作って解説します。
- キーボードから西暦(数値)のみを入力して年齢を求める
- キーボードから西暦のみを入力して年齢を求める。但し、数値以外の場合と範囲外の西暦(1900年以前と来年以降)の場合は再度入力させる
- キーボードから生年月日を「yyyy/mm/dd」形式で入力し、年齢を正確に求める
何もチェックしないサンプル
このサンプルでは、キーボードから西暦が整数として入力されるという前提でプログラムを作成します。英字などのデータを入力すると例外が発生するので、単純に年齢を計算する入門用のプログラムとしてとらえて下さい。まずは、プログラムの作り方を確認していきます。
その後、残りのサンプルで入力されたデータのチェック機能やより正確な年齢の計算方法などを説明していきます。
プログラムのコード
プログラムのコードは以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
package currentAge; import java.time.LocalDateTime; import java.util.Scanner; public class CurrentAgeSimple { public static void main(String[] args) { // 生まれた西暦の取得 int bornYear = getBornYear(); // 今年の西暦の取得 int thisYear = getThisYear(); // 今年の年齢の計算(取得) int currentAge = calcCurrentAge(bornYear,thisYear); // 結果の表示 showResult(bornYear,thisYear,currentAge); } public static int getBornYear() { // キーボードの準備 Scanner stdin = new Scanner(System.in); System.out.println("あなたの生まれた西暦の入力"); // 入力された整数を返す return stdin.nextInt(); } public static int getThisYear() { // 現在の日時を取得 LocalDateTime today = LocalDateTime.now(); // 西暦のデータを取り出し返す return today.getYear(); } public static int calcCurrentAge(int born,int today) { // 今年の年齢(今年の西暦ー生まれた西暦)を返す return today-born; } public static void showResult(int born, int now, int age) { System.out.printf("%s年生まれのあなたは今年[%d年]、%d歳です%n", born,now,age); } } |
コードの説明
このサンプルはmain()メソッド内には利用するメソッド名のみを記述し、プログラムの全体像を理解できるようにしています。
24行目 return stdin.nextInt();
この行ではキーボードから入力されたデータを整数値として読み込み、main()メソッドに返しています。ここで「abc」などの整数以外のデータを入力すると、「InputMissmatchException例外」が発生します。本来は例外処理を実装しなければなりませんが、このサンプルでは「必ず整数のデータが入力される」という前提でプログラムを作成しています(もちろん、このプログラムは練習用で現場などでは使えないものです)。
29行目 LacalDateTime today = LocalDateTime.now();
Java8から新しく用意されたDate Time APIを利用して現在の日時などが格納されているLocalDateTimeオブジェクトを取得しています。なお、年齢を求める場合、時間データは使わないので、LocalDateオブジェクトを取得しても構いません。
31行目 return today.getYear();
作成したLocalDateTimeオブジェクトから西暦のデータを取り出し、main()メソッドに返しています。
36行目 return today – born;
引数として受け取った今年の西暦から生まれた西暦を引き、今年に年齢を計算しています。年齢の計算式自体は非常に簡単なものです。求めた今年の年齢をmain()メソッドに返しています。
実行結果
以下の実行結果のように、整数以外のデータを入力すると例外が発生してしまいます。
入力されたデータが正しいかをチェックするサンプル
次のプログラムでは以下のチェック機能を実装します。
- 入力されたデータが整数かどうか。整数以外の場合は再入力させる
- 1900年未満と今年以上の整数が入力された場合は再入力させる
プログラムのコード
各種チェック機能を実装したサンプルプログラムは以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
package currentAge; import java.time.LocalDateTime; import java.util.Scanner; public class CurrentAgeCheck { public static void main(String[] args) { // 生まれた西暦の取得 int bornYear = getBornYear(); // 今年の西暦の取得 int thisYear = getThisYear(); // 今年の年齢の計算(取得) int currentAge = calcCurrentAge(bornYear, thisYear); // 結果の表示 showResult(bornYear, thisYear, currentAge); } public static int getBornYear() { // キーボードの準備 Scanner stdin = new Scanner(System.in); int born; // 入力された西暦(整数)を格納 // 入力回数が未定なので無限ループ while (true) { // 整数以外が入力された時の例外処理を実装 try { System.out.println("あなたの生まれた西暦の入力"); born=stdin.nextInt(); // 入力された整数の範囲をチェック if(born<1900 || born>getThisYear()) { // 範囲外の場合 System.out.println("西暦は1900年から今年までの範囲で入力してください"); continue; // while文のやり直し }else { // 範囲内の場合 break; // while文の終了 } } catch (Exception e) { // 整数以外の例外が発生した場合 System.out.println("西暦は整数で入力して下さい"); // 入力バッファのクリア(残っている整数以外のデータの除去) stdin.next(); } } // 入力された西暦(整数)を返す return born; } public static int getThisYear() { // 現在の日時を取得 LocalDateTime today = LocalDateTime.now(); // 西暦のデータを取り出し返す return today.getYear(); } public static int calcCurrentAge(int born, int today) { // 今年の年齢(今年の西暦ー生まれた西暦)を返す return today - born; } public static void showResult(int born, int now, int age) { System.out.printf("%s年生まれのあなたは今年[%d年]、%d歳です%n", born, now, age); } } |
コードの説明
1つ前のサンプルプログラムとの違いは、キーボードから西暦を入力する「getBornYear()メソッド」の内容のみです。それ以外は一切変更していないので、このメソッドの内容について解説します。
25行目 while(true){
このサンプルでは適切な範囲の西暦(整数)が入力されるまで何度でも入力処理をおこないます。この時、プログラムを作成する時点では何回実行されるか予測できません。このように実行する回数が不定の場合は一般的に無限ループで対応します。
while文は()内の条件式をチェックし、trueの時に{ }内の処理をおこない、再度条件式をチェックします。このサンプルのように()の条件式に「true」を指定すると永遠に条件式の結果が成り立つので、無限に繰り返し処理をおこないます。なお、無限ループを利用する場合は必ず終了するために命令文(if文を利用することが多い)を記述してください。
27行目 try {
キーボードから西暦を入力する際、整数として読み込もうとしますが、文字が入力される場合があります。その場合、例外が発生するため、try~catchの例外処理を実装しています。try{ }内には例外が発生する可能性のある命令を全て記述します。
29行目 born = stdin.nextInt();
キーボードから西暦を整数として読み込み、変数bornに代入します。この行を実行する時に整数以外のデータが入力されていれば例外が発生し、39行目のcatch()文に処理が飛びます。
31行目 if(born<1900 || born>getThisYear() ) {
整数として読み込むことができたら、今度は適切な範囲(1900年以上今年以下)かどうかのチェックをおこなっています。ここで、今年の西暦には「2021」のような具体的な値は入力しないように注意しましょう。もし「2021」と入力すると、2021年の時は問題ないのですが、2022年になった場合はこの部分の値を訂正しなければなりません。訂正するたびに再コンパイルが必要となり手間がかかります。したがって、このサンプルのように変化する値は他のメソッドや計算式で求められるようにしておきましょう。
34行目 continue;
適切な範囲かどうかのチェックをおこない、不適切な西暦の場合は再度入力をやり直さなければなりません。そのために「continue」で次の繰り返し処理を実行させます。continueが実行されると処理は25行目のwhile()文に戻ります。
37行目 break;
入力された西暦の値が適切な範囲の場合、入力をおこなっている無限ループを終了させる必要があります。そこで、「break」を記述し、無限ループを終了させています。この行が実行されると、処理は46行目に移動します。
39行目 } catch(Exception e) {
もし、try{ }部分のどこかで何らかの例外が発生した場合、この行に処理が移ってきます。catch()の()内には捕捉する例外の種類(正確にはクラス)を指定します。例外の種類ごとに別々の処理を実行させたい場合は例外の種類ごとにcatch( ){ }を記述します。
このサンプルでは「Exceptionクラスの例外」を補足しています。このExceptionクラスは各種例外のスーパークラスなので、ほとんどの例外を捕捉することができます。通常は例外の種類ごとに対応を分けて記述しますが、このサンプルではそこまでの対応は実装していません。
42行目 stdin.next();
整数以外のデータが入力された場合、netInt()メソッドが実行できないため入力バッファにデータがまだ残っています。このままでは新しいデータをキーボードから読み込むことができないので、バッファをクリアするためにnext()メソッドを実行しています。このメソッドは入力バッファから文字列としてデータを読み込むので、ほとんどのデータの読み込みができます。
なお、読み込んだデータを変数に代入していないので、読み込んだデータはプログラム内では使用できません。不要なデータなので変数に代入せず、そのまま捨てています。
実行結果
以下が実行結果です。整数以外や範囲外のデータを入力した場合、エラー画面などにならずに再度入力させていることが確認できます。
yyyy/mm/dd形式のデータで正確な年齢を求める方法
ここまで説明したサンプルプログラムは西暦のみ入力しているだけなので正確な年齢は求められません。例えば、誕生日が2000年10月10日で、現在の日付が2020年10月9日だと正確にはまだ誕生日が来ていないので19歳なのですが、サンプルプログラムでは20歳と表示されてしまいます。
より正確に年齢を求めるためには「今年の誕生日が過ぎているかどうか」をチェックしなければなりません。誕生日が過ぎていれば単純な西暦の引き算で求められますが、まだ誕生日が来ていなければ「-1」しなければなりません。
正確な年齢を求めるためには西暦の他に月と日のデータも利用する必要があります。ここからはより正確な年齢を求めるためのプログラムの作り方について説明します。
自分で経過日数を調べるプログラム
最も原始的な方法は入力された年、月、日をそれぞれ独立した整数として取り出し、自分で経過日数を計算する方法です。この方法は手間がかかりますが、どの言語でも利用できる汎用的なアルゴリズムです。
年齢の計算は生まれた月と今の月を比較し、その結果によって今年の誕生日が来ているかどうかをチェックします。
- 今の月の方が生まれた月より大きければ、今年の誕生日は過ぎている
- 今の月の方が小さければ、まだ今年の誕生日は来ていない
- 今の月と生まれた月が同じ場合、生まれた日と今の日を比較し、今の日が生まれた日以降の場合は今年の誕生日は過ぎている
年齢は「今年の西暦-生まれた西暦」で求め、上の判定結果によってまだ今年の誕生日が来ていない場合は求めた年齢から「-1」します。
生年月日が「2000年10月10日」とし、現在の日付が「①2021年11月1日」、「②2021年9月1日」、「③2021年10月1日」、「④2021年10月20日」の4つの場合について具体的に確認してみます。なお、元となる年齢は「2021-2000」で「21歳」となります。
①今の月が生まれた月よりも大きい場合
生まれた月が「10月」、今の月が「11月」で2021年の誕生日は既に過ぎているので年齢は21歳となります。したがって、西暦の引き算で求めた年齢はそのままとなります。
②今の月が生まれた月よりも小さい場合
生まれた月が「10月」、今の月が「9月」で2021年の誕生日はまだきていません。したがって、まだ21歳になっていないので、西暦の引き算で求めた年齢を「-1」し、20歳となります。
③今の月が生まれた月と同じで今の日が生まれた日よりも小さい場合
生まれた月と今の月が「10月」で等しい場合、今年の誕生日が過ぎているかどうかの判定は月だけではおこなえません。この場合、日を使って判断する必要があります。生まれた日が「10日」、今の日が「1日」でまだ今年の誕生日は来ていません。したがって、まだ21歳になっていないので、西暦の引き算で求めた年齢を「-1」し、20歳となります。
④今の月が生まれた月と同じで今の日が生まれた日以降の場合
生まれた月と今の月が「10月」で等しく、生まれた日が「10日」、今の日が「20日」で2021年の誕生日が過ぎているので、年齢は計算結果のまま21歳となります。
このように、その年の誕生日が過ぎているかどうかをif文などを利用してチェックし、正確な年齢を求める必要があります。なお、これ以外にも色々な方法があるので、是非自分でも考えてみて下さい。
まずはmain()メソッドで行うプログラムのフローチャートを以下に示します。
プログラムのコード
では、この考え方をプログラムにしたものが以下になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
package currentAge; import java.time.LocalDate; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; public class CurrentAgeHassle { public static void main(String[] args) { // 生年月日(文字列)の取得 String birthday = getBirthday(); // 入力した生年月日が適切かどうかのチェック if (isBirthday(birthday)) { // 今年の年齢の計算(取得) int currentAge = calcCurrentAge(birthday); // 結果の表示 showResult(birthday, currentAge); } else { // 不適切な場合 System.exit(0); // プログラムの終了 } } public static String getBirthday() { // キーボードの準備 Scanner stdin = new Scanner(System.in); System.out.println("あなたの生年月日(yyyy/mm/dd形式)の入力"); // 文字列を1行読み込む String birth = stdin.nextLine(); // 整数以外が入力されたかどうかのチェック(正規表現) Pattern pattern = Pattern.compile("^\\d{4}/\\d{1,2}/\\d{1,2}$"); Matcher matcher = pattern.matcher(birth); // 生年月日の書式と一致するかどうかのチェック if (!matcher.matches()) { // 一致しない場合 System.out.println("入力が不適切です。再度やり直してください"); System.exit(0); // プログラムの終了 } return birth; // 読み込んだ文字列を返す } public static boolean isBirthday(String birth) { boolean flag = true; // 適切(デフォルト)か不適切かを保存 // 生年月日を「/」で西暦、月、日に分割 String[] array = birth.split("/"); // 分割したそれぞれのデータをint型に変換 int year = Integer.parseInt(array[0]); // 西暦の取得 int month = Integer.parseInt(array[1]); // 月の取得 int date = Integer.parseInt(array[2]); // 日の取得 // 西暦が適切な範囲外かどうかのチェック if (year < 1900 || year > LocalDate.now().getYear()) { System.out.println("入力できる西暦は1900年から今年までです"); flag = false; // 不適切なデータ } // 月や日が範囲外かどうかのチェック if (month <= 0 || month >= 13 || date <= 0 || date >= 32) { System.out.println("月日の指定が不適切です"); flag = false; // 不適切なデータ } return flag; // 判定結果を返す } public static int calcCurrentAge(String birth) { // 生年月日を「/」で西暦、月、日に分割 String[] array = birth.split("/"); // 分割したそれぞれのデータをint型に変換 int bornYear = Integer.parseInt(array[0]); // 生まれた西暦 int bornMonth = Integer.parseInt(array[1]); // 生まれた月 int bornDate = Integer.parseInt(array[2]); // 生まれた日 // 現在の日付をLocalDateオブジェクトとして取得 LocalDate today = LocalDate.now(); int thisYear = today.getYear(); // 今の西暦 int thisMonth = today.getMonthValue(); // 今の月 int thisDate = today.getDayOfMonth(); // 今の日 // 西暦から今の年齢を計算 int age = thisYear - bornYear; // まだ今年の誕生日の月になっていない場合 if (thisMonth < bornMonth) { age--; // 年齢を「-1」 } // 今月は誕生月だが、また誕生日になっていない場合 if (thisMonth == bornMonth && thisDate > bornDate) { age--; // 年齢を「-1」 } return age; // 求めた年齢を返す } public static void showResult(String birth, int age) { System.out.printf("生年月日が%sの人は今年現在%d歳です%n", birth, age); } } |
コードの解説
プログラムが複雑になっていますが、データの有効性をチェックするコードが追加されています。コメント文があるのですが、主要なコードを説明します。
12行目 String birthday = getBirthday();
このプログラムでは、誕生日を「yyyy/mm/dd形式」の文字列で取得します。メソッドを実行すると文字列が返されるので、受け取った文字列を変数に代入します。
15行目 if(isBirthday(birthday)) {
入力された生年月日が「yyyy/mm/dd形式」になっているか、また不適切なデータ(13月や45日など)が入力されていないかなどのチェックをisBirthday()メソッドで行います。このメソッドは正しい書式の場合は「true」、不適切な書式の場合は「false」を返すので、その結果を条件式として判定しています。
17行目 int currentAge = calcCurrentAge(birthday);
書式をチェックした結果、正しい(true)場合はこの行が実行されます。この行では受け取った生年月日をメソッドに渡し、今年の年齢を求め、返された年齢を変数に代入しています。
21行目 System.exit(0);
書式が不適切な場合は年齢の計算をおこなわず、プログラムを終了しています。プログラムを途中で終了させる場合は「System.exit(0)」と入力します。なお、()内の値は「終了ステータス」と呼ばれるものでOSに終了状態を通知します。「0」は適切な処理をおこなって、正常にプログラムを終了したことを意味します。
33行目 Pattern pattern = Pattern.compile(“^\\d{4}/\\d{1,2}/\\d{1,2}$”);
キーボードから入力する生年月日の書式を正規表現で指定します。それぞれの記号の意味は次の通りです。
- 「^記号」は行の先頭
- 「\d」は0から9の数字([0-9]と同じ)。ただし「\」を打ち消すためにもう1文字「\」を付ける
- 「{4}」は直前の文字を4回繰り返す
- 「{1,2}」は直前の文字を1回または2回繰り返す
- 「$記号」は行の末尾
以上のことから、「^\\d{4}/\\d{1,2}/\\d{12}$」は「数字4桁で始まり、5文字目が「/」、その後に数字が1文字または2文字あり、次の文字が「/」で、その後が数字1文字または2文字で終わるデータ」を意味することになります。生年月日は「西暦4桁(数字)/月(数字1または2桁)/日(数字1または2桁)」となるので、このような書式を正規表現で指定します。
34行目 Matcher matcher = pattern.matcher(birth);
作成した正規表現のPatternオブジェクトに変数birthに代入されているデータを当てはめます。
37行目 if( !matcher.matches() ) {
当てはめた結果、データが正規表現の書式と完全一致しているかどうかをチェックします。matchers()メソッドは完全一致する場合にtrueを返します。「!」は指定した条件の否定を意味するので、「完全に一致しない場合」をこの行でチェックしています。
39行目 System.exit(0);
正規表現と完全一致しない場合、不適切な生年月日が指定されているので、プログラムを強制終了させます。
48行目 String[ ] array = birth.split(“/”);
正規表現に完全一致していても、適切な数字が指定されているかどうかはチェックできません。例えば、「1000/20/99」というデータを入力しても正規表現では完全一致してしまいます。
正規表現で完全一致した後は、適切な西暦、月、日のデータが入力されているかチェックしなければなりません。そこで、「/」を区切り文字として生年月日を分割し、配列に代入します。
50行目~52行目
配列に分割されたデータは文字列なので、Integer.parseInt()メソッドを使って整数に変換します。
55行目 if( year < 1900 || year> LocalDate.now().getYear() ) {
今回のサンプルプログラムでは、「入力できる西暦は1900年から今年まで」としていますので、そのチェックをおこないます。なお、LocalDate.now()メソッドで現在の日付オブジェクトを生成し、getYear()メソッドでその中から西暦のみを取得しています。これで今年の西暦が分かります。
60行目 if( month <= 0 || month >= 13 || date <= 0 || date >=32 ) {
更に存在しない月や日が指定されていないかどうかをチェックします。月は1月から12月まで、日は1日から31日までとしています。なお、このプログラムでは正確に日のチェックはおこなえません。例えば、2021年9月は30日までしかありませんが、「2021/09/31」と存在しない年月日が入力されてもエラー判定はできません。
57行目、62行目
西暦、月、日で不適切な値が入力されている場合は、正しい書式か不適切な書式かの情報を格納する変数にfalseを代入します。このフラグの値がfalseの場合、不適切なデータを意味します。
71行目~73行目
今年の年齢を求めるためにまず、入力された生年月日のデータを「/」を区切り文字として分割し、それぞれのデータを数値に変換しています。
76行目~79行目
今の西暦、月、日をLocalDateクラスを利用して取得しています。
85行目 if( thisMonth < bornMonth ) {
今年の誕生日が過ぎたかどうかのチェックをしています。まず、今の月(thisMonth)が生まれた月(bornMonth)より小さい場合は、まだ今年の誕生日が来ていません。
89行目 if(thisMonth == bornMonth && thisDate > bornDate ) {
次に誕生月でも、まだ誕生日(日にち)が来ていない場合もあるので、そのチェックをしています。
86行目、90行目 age- -;
85行目と89行目のif文が成り立つ場合は、まだ今年の誕生日が来ていないので、西暦の差で求めた年齢から「-1」します。
以上が生年月日を「yyyy/mm/dd形式」で指定した場合の年齢を計算するプログラムです。正しいデータが指定されているかどうかのチェック部分が面倒です。
なお、今日の日付を求めるLocalDateクラスの使い方は以下の記事を参考にして下さい。
経過日数を求めるPeriodクラスを利用するプログラム
上記のサンプルでは指定したyyyy/mm/dd形式のデータが日付として正しいかどうかのチェックを自分で実装する必要がありました。これはサンプルプログラムを見てもらえばわかりますが、かなり面倒です。しかし、プログラム言語では一般的に、文字列で指定した日付データが実際の日付として正しいかどうかを簡単にチェックできる機能が用意されています。
Java8以降では日時を扱う専用の「java.timeパッケージ」が用意されており、その中には便利なクラスが多数定義されています。今回、Periodクラスを利用して年齢を求める方法を説明します。
Periodクラス
Periodクラスは日付データの各種解析をおこなうためのクラスです。2つの期間の経過年数や経過日数、ある日付から何日後、何日前など色々な情報を簡単なメソッドで求めることができます。
年齢は誕生日と現在の日付の2つの期間の経過年数を調べることで求められます。この方法を利用すると今年の誕生日が来ているかどうかなどのチェックも自動的におこなってくれます。
なお、時間に関する各種解析をおこないたい場合は、Durationクラスを利用します。
プログラムのコード
Periodクラスを利用したサンプルコードは以下の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
package currentAge; import java.time.LocalDate; import java.time.Period; import java.time.format.DateTimeFormatter; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; public class CurrentAgeDetail { public static void main(String[] args) { // 生年月日の取得 String birthday = getBirthday(); // 今年の年齢の計算(取得) int currentAge = calcCurrentAge(birthday); // 結果の表示 showResult(birthday, currentAge); } public static String getBirthday() { // キーボードの準備 Scanner stdin = new Scanner(System.in); System.out.println("あなたの生年月日(yyyy/mm/dd形式)の入力"); // 文字列を1行読み込む String birth = stdin.nextLine(); // 整数以外が入力されたかどうかのチェック(正規表現) Pattern pattern = Pattern.compile("^\\d{4}/\\d{1,2}/\\d{1,2}$"); Matcher matcher = pattern.matcher(birth); // 生年月日の書式と一致するかどうかのチェック if (!matcher.matches()) { // 一致しない場合 System.out.println("入力が不適切です。再度やり直してください"); System.exit(0); // プログラムの終了 } return birth; // 読み込んだ文字列を返す } public static int calcCurrentAge(String birth) { Period period = null; // DateTimeFormatterオブジェクトの作成(誕生日の書式を指定) DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/M/d"); try { // 文字列から書式を指定してLocaDateオブジェクトを作成 LocalDate birthday = LocalDate.parse(birth, formatter); String[] array = birth.split("/"); // 西暦を取得 int year = Integer.parseInt(array[0]); // 西暦を整数に変換 // 現在のLocaDateオブジェクトを取得 LocalDate today = LocalDate.now(); // 適切な範囲かどうかのチェック if (year < 1900 || year > today.getYear()) { System.out.println("入力できる西暦は1900年から今年までです"); System.exit(0); // プログラムの終了 } // 2つのLocaDateオブジェクトの差を求める period = Period.between(birthday, today); } catch (Exception e) { // 不適切なデータによりLocaDateオブジェクトが作成できない場合 System.out.println("月日の指定が不適切です"); System.exit(0); // プログラムの終了 } // Periodオブジェクトから経過年数を取り出し返す return period.getYears(); } public static void showResult(String born, int age) { System.out.printf("誕生日が%sのあなたは今年[%d年]、%d歳です%n", born,LocalDate.now().getYear(),age); } } |
コードの解説
29行目から35行目
キーボードから入力される生年月日が「yyyy/mm/dd形式」に一致するが正規表現を利用してチェックしています。内容は前のサンプルと同じです。
42行目 DateTimeFormatter formatter = DateTimeFormatter.ofPattern(“yyyy/m/d”);
このサンプルでは「yyyy/mm/dd形式」の文字列から直接LocalDateオブジェクトを作成します。まずは、どのような形式で日付データが入力されているのか、その形式を設定します。
46行目 LocalDate birthday = LocalDate.parse(birth,formatter);
LocalDate.parse()メソッドは()内に指定した文字列からLocalDateオブジェクトを直接作成します。第1引数には文字列、第2引数には日付の書式を指定します。変数birthに代入されている文字列(キーボードから入力された生年月日)をformatterオブジェクトに設定されている書式(yyyy/mm/dd形式)を元にLocalDateオブジェクトに変換します。
なお、このメソッドでは存在しない日付データが指定された場合は例外が発生します。例えば、「2000/13/45」という生年月日が入力された場合は例外が発生します。そのため、44行目でtry文を記述しています。
このメソッドを利用することで、if文を使って月や日が適切な範囲内のデータかどうかを自分で実装する必要はありません。
48行目から57行目
今回のサンプルでは西暦は1900年から今年まで、という条件があるので、その条件に合うかどうかのチェックをしています。プログラムは上で説明したサンプルと同じなので詳細な説明は省略します。
60行目 period = Period.between(birthday , today);
この行がこのサンプルで重要な処理となります。Periodクラスのbetween()メソッドは引数として指定した2つのLocalDateオブジェクトの経過情報(Periodオブジェクト)を取得します。経過情報には、経過年数、経過月数、経過日数などがあります。
67行目 return period.getYears();
Periodオブジェクトから、getYear()メソッドを実行し、経過した年数を取得しています。Periodクラスを利用すると、今年の誕生日が来ているかどうかのチェックも自動的におこなってくれます。
なお、このサンプルでも実は存在しない日付を指定してもエラーにはなりません。明らかに指定したデータが不適切な場合(例えば、「2000/13/45」など)は、例外が発生します。例外が発生しないのは、月が1から12、日が1から31を指定した場合です。もし、その月に31日が存在しないにもかかわらず、31日を指定すると自動的に30日に補正されてしまいます。
より正確に日付を求めたい場合は、厳格にチェックする機能をオンにする必要があります。具体的には42行目を以下のように修正します。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(“uuuu/M/d”).withResolverStyle(ResolverStyle.STRICT);
データの書式を「yyyy/M/d」から「uuuu/M/d」に変更します。「y」は西暦ですが、正確には「紀元年」を意味します。一方「u」は単なる西暦を示します。一般的には西暦を示す記号は「y」で良いのですが、厳格にチェックしたい場合は「u」を指定しなければなりません。もし、「y」をそのまま使用していると、存在する日付を生年月日で入力しても、例外が発生してしまいます。
withResolverStyle()メソッドは文字列から書式を使ってLocalDateオブジェクトを作成する際に厳密に日付のチェックをおこなうかどうかを指定します。デフォルトは月なら1から12、日なら1から31しかチェックしませんが、「ResolverStyle.STRICT」を指定すると、日付として実際に存在するかどうかまで厳密にチェックします。
まとめ
今回は、今年の年齢を計算するプログラムの作り方について説明しました。
「誕生日の西暦だけを入力させる」場合と「誕生日を西暦/月/日で入力させる場合」の2つの場面に分けて説明してきました。どのようなデータをユーザに入力してもらいたいかによって、作成されるプログラムは変わってきます。また、どこまでエラーチェックをおこなうかによっても記述する内容は変わります。
プログラムを勉強したての初心者の場合、まずは正しい結果が得られるプログラムを作ることが大事ですが、慣れてきたらエラー対策をどのようにするかも考えるようにしましょう。実際に他の人にプログラムを利用させる場合、間違ったデータを入力するかもしれません。エラー対策をきちんと実装していないと、例外などのエラーメッセージを大量に出力しプログラムが強制終了してしまいます。このような状況になると、ユーザはプログラムを自分が壊してしまったのか非常に不安になります。ユーザに安心して使用してもらうためにも、エラー対策はきちんと行うべきです。
・今年の年齢は「今年の西暦ー生まれた西暦」で簡単に求められる
・入力したデータが適切な範囲のデータかをチェックする機能を実装したほうが良い
・入力したデータのチェックは正規表現を利用すると簡単におこなえる
・文字列から日付データ(オブジェクト)を直接作成するメソッドが用意されている
・Periodクラスを利用すると正確に経過年数(年齢)を求めることできる
年齢を求める処理は同じなのですが、データに何を使うか、エラー対策をどこまでするかで、こんなにも色々なプログラムがあるんですね。
まずは、動くプログラムを作ることが大事ですが、ぜひエラー対策を実装して他の人にやさしいプログラムを作るように心がけて下さい!!
せっかく日付専用のクラスが用意されているので、それを使いこなせるようになるといいのでしょうか。
最初は難しいかもしれませんが、日付専用のクラスは便利なクラスやメソッドがあるので、是非チャレンジしてみて下さい!!