https による web サイトや、ftps サーバなどで使用する自己証明書を作るとき、証明書有効期限を日数で与える場合があります。
こんなとき例えば「3年後の12月31日までの日数」なんてのを簡単に算出したいと思い、UNIX / Linux で、ある基準日から指定日までの通算日数を表示するコマンドを探したのですが、これが見つからない。
date コマンドのオプションで何とかならないかと調べてみたけど、UNIX時間の通算「秒」を表示することはできるのですが、通算「日」はムリそう。
そこで、自分で作ることにしました。
基準日は、充分な過去であれば何年何月何日でも良いのですが、
グレゴリウス暦1年1月1日からの通算日を求める、ちょっとおもしろいロジックを掲載しているサイトを見つけたので、そのロジックをまるまるお借りすることにしました。
(ユリウス暦を基準とした通算日を「ユリウス通日(つうじつ)」と呼ぶのに倣って、この通算日のことを「グレゴリウス通日」と呼ぶことにします)
このロジック、どこが面白いかというと、ある月(m月)における年内通算経過日数(d日)、すなわち「その年の1月1日からm月1日までの経過日数d日」を、単純なmの一次関数で求めちゃう、というところ。
d = int(a・m + b)
このようにして一次式の勾配aと切片bをうまく調整すると、式の値の小数点以下を切り捨てることで、年初からその月の1日までの通算日が得られる、というものです。
勾配・切片を調整するうえでクセモノなのが、他の月よりも日数が少なく、年により28日 or 29日と変動する2月。 これを1月とともに後ろに回して、それぞれ前年の13月、14月として扱うことで、2月の影響を排除するという工夫も盛り込まれています。
これが求められれば、あとはうるう年を考慮してその前年の12月31日までの通算日を求め、それにこの関数で求められた年内通算日数と、月内の日付を足して1日減ずれば、グレゴリウス通日が求められます。
以下、プログラムソース。
そこそこ長くなってしまいましたが、前段の大部分はコマンドライン引数の評価であり、実際に通日を算出しているのは最後の10行ほど。 前述したロジックのお陰で、極めて簡潔です。
(インデントつけるために全角スペースを使用しています。 コピペしてコンパイルする際には、全角スペースを適宜半角スペース/タブなどに変換してください)
$ cat gregorianDay.c ↓
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
static void usage();
static int gregorianDay(int year, int month, int day);
int
main(int argc, char *argv[])
{
time_t t =time(NULL);
struct tm *tm =localtime(&t);
char s_date[9];
char s_year[5], s_month[3], s_day[3];
int gd;
//
// arguments check
//
switch(argc){
case 1:
strftime(s_date, sizeof(s_date), "%Y%m%d", tm);
break;
case 2:
if(strlen(argv[1]) != 8){
usage();
if(!strcmp(argv[1], "-h")) return 0; else return -1;
}
strcpy(s_date, argv[1]);
break;
default:
usage();
return -1;
}
strcpy(s_year, &s_date[0], 4), s_year[4] ='\0';
strncpy(s_month, &s_date[4], 2), s_month[2] ='\0';
strncpy(s_day, &s_date[6], 2), s_day[2] ='\0';
if((gd =gregorianDay(atoi(s_year), atoi(s_month), atoi(s_day))) < 0){
fputs("bad date\n", stderr);
usage();
return -1;
}
printf("%d\n", gd);
return 0;
}
void
usage()
{
fputs("usage : gd <date>\n", stderr);
fputs(" <date> must be in the format \"yyyymmdd\"\n", stderr);
fputs(" if no <date> is given, gd prints greg.day of today\n", stderr);
}
int
gregorianDay(int year, int month, int day)
{
bool ly =false;
int dy, dl, dm;
//
// check validity of given date
//
if(year < 1) return -1;
// check if given year is leap
if((year % 4) == 0){
ly =true;
if((year % 100) == 0){
ly =false;
if((year % 400) == 0) ly =true;
}
}
// check validity of given day of each month
if(day < 1) return -1;
switch(month){
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
if(31 < day) return -1;
break;
case 4: case 6: case 9: case 11:
if(30 < day) return -1;
break;
case 2:
if(ly){
if(29 < day) return -1;
}else{
if(28 < day) return -1;
}
break;
default:
return -1;
}
//
// calculate gregorian day
//
// assume Jan. & Feb. to 13th & 14th month last year
if(month <= 2){
year--;
month +=12;
}
// days in years
dy =365 * (year -1);
// days of leap years
dl =year/4 -year/100 +year/400;
// days before 1st in given month from 1st Jan. in given year
dm =(month * 979 - 1033) >> 5;
return(dy + dl + dm + day -1);
}
ビルドは
$ gcc gregorianDay.c -o gd ↓
にて。 出力される実行形式ファイルのファイル名は gd
$ gd -h ↓
usage : gd <date>
<date> must be in the format "yyyymmdd"
if no <date> is given, gd prints greg.day of today
で usage を表示。
引数なしの
$ gd ↓
737155
で、本日のグレゴリウス通日を表示。
引数として yyyymmdd フォーマットで日付を与えると、
$ gd 19600716 ↓
715706
与えたその日におけるグレゴリウス通日を表示。
シェル環境であれば、こんな風にすることで、2つの日付間の日数差を求めることもできます。
$ expr `gd` - `gd 19600716` ↓
21449
(1960年7月16日から本日までの経過日数)
ちなみに 1960/7/16 はわきたの誕生日。 つまり、2019年4月7日現在、わきたは生後21,449日だそうです (笑
centOS 7 と msys2 on windows10 上でビルド&動作を確認してあります。
| http://blog.wakita.cc/index.php?e=96 |
|
サーバ・Linux | 01:29 PM |
comments (0) |
trackback (0) |