年齢


いし  2004-01-08 10:06:51  No: 52981  IP: [192.*.*.*]

現在の時間(年・月・日)を求めて、生年月日(年・月・日)を引いて年齢を求めたいのですが可能なのでしょうか?

よかったら教えてください

編集 削除
...  2004-01-08 10:56:50  No: 52982  IP: [192.*.*.*]

年は引き算して、月日を比較すればいいのでは?

現在の時間(年・月・日) - 生年月日(年・月・日) から
年齢を出すのは無理があるような。(一年の日数は?とか)
無理とは言わないですが、面倒になりそうです。

#別の掲示板でも同じような質問を見ましたが何かの課題ですか?

編集 削除
RiSK  2004-01-08 10:59:07  No: 52983  IP: [192.*.*.*]

ここには初カキコです。m(_ _)m

同一人物か、同一学校の問題ですね。
http://www.mtakahashi.com/cgi.cgi?10408

編集 削除
Toshi  2004-01-08 16:26:46  No: 52984  IP: [192.*.*.*]

ん〜・・・これってVC++とは全く関係ない質問だと思うのですが・・・
まぁ、一応、サンプルあげます。このサンプルでは誕生日の文字列を受取って
年齢を返しますが、受け取りパラメータをint3つでやれば冒頭の文字列解析処理は
削れます。

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// birthday strings YYYY/MM/DD → int age
int  getAge(char *bd)
{
  int  i,fix,age;
  int  ymd[3];
  char  wbuf[16];
  char  *tkn;
  struct  tm ttime, *stime;
  time_t  ntime,btime,dtime;

  strcpy(wbuf,(const char *)bd);
  i = fix = 0;
  tkn = strtok(wbuf,"/");
  while (tkn != NULL){
    ymd[i++] = atoi(tkn);
    tkn = strtok(NULL,"/");
  }
  if(ymd[0] < 1970){
    fix = 1970 - ymd[0];
    ymd[0] = 1970;
  }
  ttime.tm_sec = 59;
  ttime.tm_min = 59;
  ttime.tm_hour = 23;
  ttime.tm_mday = ymd[2];
  ttime.tm_mon = ymd[1] - 1;
  ttime.tm_year = ymd[0] - 1900;
  ttime.tm_isdst = 0;
  time(&ntime);
  btime = mktime(&ttime);
  dtime = (time_t)difftime(ntime,btime);
  stime = gmtime(&dtime);
  age = stime->tm_year - 70 + fix;
  return(age);
}

編集 削除
なーめ  2004-01-10 02:05:37  No: 52985  IP: [192.*.*.*]

これでは、
誕生日が 1970年1月1日より前の人は正しく求められません。
mktime() は tm_year が 70 未満だと -1 を返しますよね。
質問者には関係ないかもしれませんが。(^^;;
gmtime と localtime の区別も気をつけなければならないですね。

編集 削除
...  2004-01-10 19:42:40  No: 52986  IP: [192.*.*.*]

>誕生日が 1970年1月1日より前の人は正しく求められません。
私はぎりぎりセーフでした。(ちょっとかすりました。)

#ゴミレスすいません。

編集 削除
Toshi  2004-01-13 10:18:21  No: 52987  IP: [192.*.*.*]

>>>なーめ さん

しっかりコードを見てください。なんの為のFIXなのか・・

編集 削除
Toshi  2004-01-13 14:45:17  No: 52988  IP: [192.*.*.*]

ってゆーか、学生相手っぽかったので、敢えて多少誤差の出る
「サンプル」を掲載したのですが、また突っ込まれるとアレなので・・・
誤差無しで正確に年齢を得られるソースを再掲しときます。

// birthday YYYY/MM/DD → age
int    getAge(char *birth)
{
    int     i,fix,ret;
    int     ymd[3];
    char    wbuf[16];
    char    *tkn;
    struct  tm  tmBthTime, *tmRsltTime, *tmJpnTime;
    time_t  nowTime,bthTime,diffTime;

    strcpy(wbuf,(const char *)birth);
    i = fix = 0;
    tkn = strtok(wbuf,"/");
    while (tkn != NULL){
        ymd[i++] = atoi(tkn);
        tkn = strtok(NULL,"/");
    }
    if(ymd[0] < 1970){
        fix = 1970 - ymd[0];
        ymd[0] = 1970;
    }
    time(&nowTime);
    tmJpnTime = localtime(&nowTime);
    tmJpnTime->tm_sec  = 0;
    tmJpnTime->tm_min  = 0;
    tmJpnTime->tm_hour = 0;
    nowTime = mktime(tmJpnTime);
    tmBthTime.tm_sec  = 0;
    tmBthTime.tm_min  = 0;
    tmBthTime.tm_hour = 0;
    tmBthTime.tm_mday = ymd[2];
    tmBthTime.tm_mon  = ymd[1] - 1;
    tmBthTime.tm_year = ymd[0] - 1900;
    tmBthTime.tm_isdst = 0;
    bthTime = mktime(&tmBthTime);
    diffTime = (time_t)difftime(nowTime,bthTime);
    tmRsltTime = gmtime(&diffTime);
    ret = tmRsltTime->tm_year - 70 + fix;
    return(ret);
}

// 使用例
void main(void)
{
    int    age;

    age = getAge("1954/01/14");
    printf("Age = %d\n",age);
}

編集 削除
YuO  2004-01-14 14:13:20  No: 52989  IP: [192.*.*.*]

> 誤差無しで正確に年齢を得られるソースを再掲しときます。

処理系依存する上に長く,使い勝手が悪いです。
さらに,突っ込み所が結構ある……。


> // birthday YYYY/MM/DD → age
> int    getAge(char *birth)

int getAge (const char * birth)
変更しないのであればconstを付けること。
特に,文字列を受け入れる場合はconstを付けるか否かは重要です。

CにしろC++にしろ,文字列リテラルを変更すると未定義動作となります。
#C++では元々const charの配列だが。
故に,char *に文字列リテラルを渡すことは未定義動作を引き渡す原因となり得るため,
Cがわかっているプログラマであれば,char *に文字列リテラルを渡すことは躊躇うでしょう。
例えば,
char * p;
int age;

p = malloc(strlen("1954/01/14") + 1);
if (!p) abort();
strcpy(p, "1954/01/14");
age = getAge(p);
free(p);
のように使う可能性があります。


>     strcpy(wbuf,(const char *)birth);

char *からconst char *へのキャストは不要です。
#constがわかっていない?

このキャストは最悪,型の不一致というコンパイラの警告/エラーを握りつぶすという問題を持っています。


>     i = fix = 0;
>     tkn = strtok(wbuf,"/");
>     while (tkn != NULL){
>         ymd[i++] = atoi(tkn);
>         tkn = strtok(NULL,"/");
>     }

sscanfを使えばよい問題ですし,このコードでは2003/1/5/4のような場合にバッファオーバーランを引き起こします。


>     diffTime = (time_t)difftime(nowTime,bthTime);
>     tmRsltTime = gmtime(&diffTime);
>     ret = tmRsltTime->tm_year - 70 + fix;

現状のVC++では実行できますが,将来実行できるとは限りませんよ。

time_tが1970年1月1日起点というのはあくまで現在のVC++での話です。
将来にわたってその日が起点であるとは限りませんし,
他の処理系においても同じ起点である保証はありません。


time_tの内部表現などという処理系依存項目に頼らず,
自分で日付を演算するのが,正しくかつ確実に年齢を計算する唯一の方法です。

エラー処理をほとんど端折っていますが,
int age (const char * str)
{
    int d0, d1, m0, m1, y0, y1;
    struct tm * today;
    time_t t;

    if (sscanf(str, "%u/%u/%u", &y1, &m1, &d1) != 3) return -1;
    time(&t);
    today = localtime(&t);
    y0 = today->tm_year + 1900;
    m0 = today->tm_mon + 1;
    d0 = today->tm_mday;

    if (d0 < d1) m0--;
    if (m0 < m1) y0--;
    return y0 - y1;
}
で実行当日の年齢を得ることができます。

編集 削除
Toshi  2004-01-14 15:38:15  No: 52990  IP: [192.*.*.*]

って書くと、突っ込む人が現れるのね・・^^;

年齢算出ロジックは確かにYuOさんの示したサンプルの方が
スマートで賢いですね。

文字列解析や、constに関しては、好みの問題で突っ込み所では無いと
思ってますが・・・
文字列解析は、過去にscanf系で嫌なトラウマを持ってて、それ以来、
必ずstrtok()でやってます。
const に関しては、int getAge(const char *) にしちゃうと、結局
上位側で使い方によってキャストしてやる必要性が出てくるし、
strcpy()系の関数に渡す時に、いずれにせよどっかしらでキャスト
してやらないといけない訳だから、いいんじゃないかと。
(別にポインタを直に操作する処理系では無いですしね)

逆に、int getAge(char *) がいけないとなると、 int getAge(void *)
なんかとんでもないって事になるのでしょう。
私は滅多に void * は使いませんが、どうしても汎用性を求める時は
使っちゃいます。
YuOさんも、処理系依存で汎用性が無い!って事で突っ込まれてるのでしょう
から、だったらなぜに const で突っ込むかちょっと不思議。

編集 削除
YuO  2004-01-14 16:17:29  No: 52991  IP: [192.*.*.*]

> って書くと、突っ込む人が現れるのね・・^^;
> 年齢算出ロジックは確かにYuOさんの示したサンプルの方が
> スマートで賢いですね。

2000年2月29日生まれの人の年齢を2001年2月28日にToshiさんのプログラムで算出すると1歳になってしまいます。
つまり,間違いが存在します。


> const に関しては、int getAge(const char *) にしちゃうと、結局
> 上位側で使い方によってキャストしてやる必要性が出てくるし、

なんでですか?
char *はキャスト無しでconst char *に変換することが出来ます。
#変換(Conversion)とキャスト(Cast)は別物。


> strcpy()系の関数に渡す時に、いずれにせよどっかしらでキャスト
> してやらないといけない訳だから、いいんじゃないかと。

constに関する勉強不足です。

T *をconst T *にする変換は自動で行われます。キャストする必要はありません。

このことは,JIS X 3010-1993の6.2.2.3や,
ISO/IEC 9899:1999の6.3.2.3の第2パラグラフ,
ISO/IEC 14882:1998の4.4に記述されていることです。


> 逆に、int getAge(char *) がいけないとなると、 int getAge(void *)
> なんかとんでもないって事になるのでしょう。
> 私は滅多に void * は使いませんが、どうしても汎用性を求める時は
> 使っちゃいます。

本来const char *が欲しいところにvoid *を使うのは問題外です。
void *ってのは「記憶域の塊」を受け取るためのものですから。


> YuOさんも、処理系依存で汎用性が無い!って事で突っ込まれてるのでしょう
> から、だったらなぜに const で突っ込むかちょっと不思議。

constは書いたとおり,文字列リテラルを渡せなくなるためです。

例えば,C++だと,例えば
getAge(flag ? "2000/02/28" : "2001/02/28")
という部分式はコンパイル時にエラーになります。


ちなみに,
void main (void)
も処理系に依存する書き方です。
#ここの過去ログに書いたことがあるから省略。

編集 削除
Toshi  2004-01-15 11:07:50  No: 52992  IP: [192.*.*.*]

ん〜、difftime()はそんなにあてにならないのかぁ・・・


strcpy()に関しては、const キャストを逆に勘違いしてた様です。
つまり簡単に言えば、char * → const char * へのキャストはいらないけど、
const char * → char * のキャストは必要だから、getAge()は
const char * で受取った方がいいって事がいいたい訳ですよね?

YuOさん、教訓になりました。
今度からこの掲示板にレスる時は、事前に近代C言語規定に沿ってるか、
処理系依存なコードになってないか、バグは無いかを十分に吟味してから
にしますよ。(って事は俺は一生レスれないな・・きっと(笑))

編集 削除
tetrapod  2004-01-15 11:51:50  No: 52993  IP: [192.*.*.*]

> ん〜、difftime()はそんなにあてにならないのかぁ・・・
ちょっと違うと思う。
difftime() は時間差を求める関数であるのに対し、
年齢は時間差で求めるものではないから。

あっちの掲示板にも書いたけど「法律上の年齢」は別なので注意。
http://www.eonet.ne.jp/~matuura/kisanbi_to_nenreikeisan.html

編集 削除