すのものの「いろいろ」(その19)

ANSI C で浮動小数点数を使わずに二つの時刻の差を求めるルーチン

ことしの夏、書いていたラインエディタに 「1970-01-01 (4) 09:00:00 +0900」のスタイルで現在の日時を取り込む機能をつけた。 最後の「+0900」は UTC との時差だが、 ANSI C にはこれを返す関数はないので、 自前で求める必要がある。

次のようにするのが普通かもしれない。 (以下のプログラムは、 短くて単独で動くようにこの機能だけを取り出してアレンジしたものである。)

#include <stdio.h>
#include <time.h>   /* time */
#include <string.h> /* strlen */

int main(void) {
    char str[] = "1970-01-01 (4) 09:00:00 +0900";   /* このスタイルで出力 */
    time_t now;
    struct tm gm, local;
    long sec;

    now = time(NULL);                   /* 現在時刻を取得 */
    gm = *gmtime(&now);                 /* UTC に変換 */
    local = *localtime(&now);           /* 地方時に変換 */

    sec = difftime(mktime(&local), mktime(&gm));    /* 時差を求める */

    strftime(str, sizeof(str), "%Y-%m-%d (%w) %H:%M:%S", &local);
    sprintf(str + strlen(str), " %+03ld%02ld", sec/60/60, sec/60%60);
    puts(str);
    return 0;
}
しかし「difftime() は double 型を返すから」というだけの理由で、 ここだけのために浮動小数点数関連のルーチンがリンクされてしまうのはくやしい。 difftime() の私家版で返り値が long のものを書けばよいが、 それではおおごとだと思ったので次のようにしてみた。
#include <stdio.h>
#include <time.h>   /* time */
#include <string.h> /* strlen */

int main(void) {
    char str[] = "1970-01-01 (4) 09:00:00 +0900";   /* このスタイルで出力 */
    time_t now;
    struct tm* tm;
    int min, wday;

    now = time(NULL);                   /* 現在時刻を取得 */
    tm = localtime(&now);               /* 地方時に変換 */
    strftime(str, sizeof(str), "%Y-%m-%d (%w) %H:%M:%S", tm);

    /* 以下、地方時と UTC の時間差を求める */

    min = tm->tm_hour*60 + tm->tm_min;  /* 正子からの時間(単位:分)*/
    wday = tm->tm_wday;                 /* 曜日 */

    tm = gmtime(&now);                  /* UTC に変換 */

    min -= tm->tm_hour*60 + tm->tm_min; /* 地方時と UTC の差(単位:分)*/
    switch (wday -= tm->tm_wday) {      /* 曜日の差で場合分け */
        case  1: case -6:    min += 24*60; break;
        case -1: case  6:    min -= 24*60; break;
        default:    ;
    }
    sprintf(str + strlen(str), " %+03d%02d", min/60, min%60);
    puts(str);
    return 0;
}
UTC との時差が 2 日以上になることはないから、 曜日で上のように場合分けをすればよいわけだ。

でも、いま落ち着いて考えてみたら、 私家版 difftime() を書くのはそれほど大変ではなかった。

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

long mydifftime(struct tm* t2, struct tm* t1);
long mytime(struct tm* t);

int main() {
    int i;
    char s1[128+1], s2[128+1];
    time_t t1, t2;
    struct tm tm1, tm2;
    long diff1, diff2;

    srand((unsigned)time(NULL));
    for (i=0; i<10000; i++) {
        t1 = (time_t)rand()*RAND_MAX+rand(); t1 = (t1 >= 0) ? t1 : -t1;
        t2 = (time_t)rand()*RAND_MAX+rand(); t2 = (t2 >= 0) ? t2 : -t2;
     /* printf("%ld %ld ", t1, t2); */
        tm1 = *gmtime(&t1); tm2 = *gmtime(&t2);
        strftime(s1, 128, "%Y-%m-%d (%w) %H:%M:%S", &tm1);
        strftime(s2, 128, "%Y-%m-%d (%w) %H:%M:%S", &tm2);
        printf("%s と %s ", s1, s2);
        diff1 = (long)difftime(t2, t1);
        diff2 = mydifftime(&tm2, &tm1);
     /* printf("%ld %ld", diff1, diff2); */
        if (diff1 == diff2) {
            printf("合っています.\n");
        } else {
            fprintf(stderr, "違っています.\n");
        }
    }
    return 0;
}

/* 以下の関数は、1970-01-01 00:00:00 から 2100-02-28 23:59:59 までの引数に   */
/* 対して使えると思います。ただし、long が 32 ビットだと、いわゆる UNIX 最後 */
/* の日までしか使えません。                                                  */

/* 私家版 difftime() です。引数と返り値の型が元のと違う点に注意。*/
long mydifftime(struct tm* t2, struct tm* t1) {
    return mytime(t2) - mytime(t1);
}

/* 1970-01-01 からの秒数を返す。閏秒には対応していない。*/
long mytime(struct tm* t) {
    long days;

    days = (t->tm_year - 70) * 365 + (t->tm_year - 69) / 4 + t->tm_yday;

        /* ↑ 1970-01-01 から数えて、その前日まで何日? */

    return ((days * 24 + t->tm_hour) * 60 + t->tm_min) * 60 + t->tm_sec;
}
このプログラムの main() はお試し用 main である。 乱数を利用して時刻を発生させ、 その差を元の difftime() と私家版とで比べている。 出力をファイルにリダイレクトさせて実行し、 画面に何も出力されなければ成功だ。 もしも RAND_MAX が 32767 で time_t 型には 1970-01-01 00:00:00 からの経過時間が秒を単位として格納されるようになっていると、 2004 年ぐらいまでの日時しか生成されないが、 テストとしては十分だろう。 気になるので、 t1, t2 の計算式をちょっと書き換え、 もっと後の日時も出てくるようにしての実験も行なった。

まとめ: 曜日を使うのはいい考えだと思ったのだが、 まとめてみると正攻法のほうが簡単だった。

2000-11-22 (3) 03:08:16 +0900

付記: 最後のプログラムで、 long とすべきところが unsigned long となっていたのを訂正した。

2000-11-24 (5) 02:23:20 +0900

二重引用符と & のエスケープを忘れていたので、した。

2004-05-04 (2) 02:31:39 +0900

「障害」の「障」は「障り(さわり)」の意、ということを思い出した

使徒行伝 8.36 に

視よ、水あり、我がバプテスマを受くるに何(なに)の障(さは)りかある

とあるのを見て思ったこと。

「障害」は「障碍」の書き換えだが、 「障害」「障害者」と書くと「害」 の字から受ける感じがよくないので昔のとおり「障碍」「障碍者」と書こう、 という話はときどき聞く。 新字源によれば「害」にも「碍」にも 「さまたげる」の意味があるので、あまりおかしな書き換えではない。 「害」という字は、 「害がある」などと一文字で日本語の語としても使われるので、 意味がわかるからより気になるのではあるまいか。

……ということは前から思っていたのだが、 「障」も「さわり」だから、あまりいい意味の字ではないはずだったのだ。

2000-11-21 (2) 19:46:04 +0900

乗り物の中で本を読む習慣は紀元前いつごろまでさかのぼる?

先日、いつものようにバス停で文語訳聖書を開いたら、 しおりがはさまっていたのは使徒行伝第 8 章だった。 この章の終わりには、 走る馬車の中でイザヤ書を読むエテオピヤの閹人が出てくるが、 乗り物の中で本を読む習慣はいつごろからあるのだろうか?  ちなみに、私は乗り物の中ではあまり本を読まない。 目が疲れるからだ。

2000-11-21 (2) 19:29:41 +0900

がんばればクリスマスツリーの絵が描けそうだが……

次のプログラムで作りました。 前の「これはなんでしょう?」のプログラムをほんの少しだけ変えたものです。

#include <stdio.h>

#define Min(X,Y) ((X)<(Y)?(X):(Y))
#define Max(X,Y) ((X)>(Y)?(X):(Y))
#define Nor(X) (Max(Min((X),255),0))    /* X を 0 以上 255 以下に normalize */

int main() {
    int i, j;

    printf("<TABLE cellspacing=\"0%%\">\n");
    printf("<TR>\n");
    printf("<TD height=\"1\" color=gray>\n");
    for (j=0; j<30; j++) {
        printf("<TD width=\"8\" color=gray>\n");
    }
    for (i=-7; i<=7; i++) {
        printf("<TR>\n");
        printf("<TD height=\"16\">\n");
        for (j=0; j<7-i; j++) {
            printf("<TD>\n");
        }
        for (j=-i; j<=7; j++) {
            printf("<TD bgcolor=\"#");
            printf("%02X%02X%02X", Nor(3*i), Nor(255-15*(i+j)), Nor(15*j));
            printf("\" colspan=2>");
            printf("<FONT color=\"#");
            printf("%02X%02X%02X", Nor(255-15*i), Nor(255-15*j), Nor(7*(i+j)));
            printf("\">■\n");
        }
    }
    printf("</TABLE>\n");
    return 0;
}

2000-11-21 (2) 19:08:40 +0900

二重引用符をエスケープするのを忘れていたので、した。

2004-05-04 (2) 02:21:15 +0900

これはなんでしょう? --- “悪乗り”バージョン

次のプログラムで作りました。 前の二つを組み合わせたものです。

#include <stdio.h>

#define Min(X,Y) ((X)<(Y)?(X):(Y))
#define Max(X,Y) ((X)>(Y)?(X):(Y))
#define Nor(X) (Max(Min((X),255),0))    /* X を 0 以上 255 以下に normalize */

int main() {
    int i, j;

    printf("<TABLE cellspacing=\"0%%\">\n");
    printf("<TR>\n");
    printf("<TD height=\"1\">\n");
    for (j=0; j<70; j++) {
        printf("<TD width=\"8\">\n");
    }
    for (i=-17; i<=17; i++) {
        printf("<TR>\n");
        printf("<TD height=\"16\">\n");
        for (j=0; j<17-i; j++) {
            printf("<TD>\n");
        }
        for (j=-i; j<=17; j++) {
            printf("<TD bgcolor=\"#");
            printf("%02X%02X%02X", Nor(15*i), Nor(15*j), Nor(255-15*(i+j)));
            printf("\" colspan=2>");
            printf("<FONT color=\"#");
            printf("%02X%02X%02X", Nor(255-15*i), Nor(255-15*j), Nor(15*(i+j)));
            printf("\">■\n");
        }
    }
    printf("</TABLE>\n");
    return 0;
}

2000-11-21 (2) 19:02:59 +0900

二重引用符をエスケープするのを忘れていたので、した。

2004-05-04 (2) 02:15:26 +0900


すのもの Sunomono