#include #include #include #include extern const char *__progname; #define D_PER_400 146097 /* 365.2425 * 400, days in 400-year calendar cycle */ #define D_PER_100 36524 /* 365.24 * 100, days in ordinary century */ #define D_PER_4 1461 /* 365.25 * 4, days in ordinary 4-year cycle */ #define D_PER_1 365 /* days in ordinary year */ typedef struct date DATE; typedef struct time TIME; struct date { int y; int m; int d; } ; struct time { int h; int m; int s; } ; #define Cisdigit(x) isdigit((unsigned char)(x)) static void usage(void) __attribute__((__noreturn__)); static void usage(void) { fprintf(stderr,"Usage: %s yyyy-mm-dd (date -> number)\n",__progname); fprintf(stderr," %s hh:mm:ss (time -> number)\n",__progname); fprintf(stderr," %s -d nnnn (number -> date)\n",__progname); fprintf(stderr," %s -t nnnn (number -> time)\n",__progname); fprintf(stderr," %s -test (run selftests)\n",__progname); exit(1); } static int dval(char d) { switch (d) { case '0': return(0); break; case '1': return(1); break; case '2': return(2); break; case '3': return(3); break; case '4': return(4); break; case '5': return(5); break; case '6': return(6); break; case '7': return(7); break; case '8': return(8); break; case '9': return(9); break; } abort(); } inline static unsigned int ydays(unsigned int) __attribute__((__const__)); inline static unsigned int ydays(unsigned int y) { return( (y % 4) ? 365 : (y % 100) ? 366 : (y % 400) ? 365 : 366 ); } inline static unsigned int mdays(unsigned int, unsigned int) __attribute__((__const__)); inline static unsigned int mdays(unsigned int y, unsigned int m) { if (m > 12) abort(); return( (m==2) ? ydays(y)-365+28 : /*JanFebMarAprMayJunJulAugSepOctNovDec*/ "\0\37\00\37\36\37\36\37\37\36\37\36\37"[m] ); } static int argnumber(const char *s, int *vp) { int i; int v; v = 0; for (i=0;s[i];i++) { if (! isdigit((unsigned char)s[i])) return(0); v = (10 * v) + dval(s[i]); } *vp = v; return(1); } static int date_to_number(DATE date) { int y; int m; int d; int j; y = date.y; m = date.m; d = date.d; j = 0; for (m--;m>0;m--) j += mdays(y,m); j += (y / 400) * D_PER_400; y %= 400; if (y > 0) j ++; j += (y / 100) * D_PER_100; y %= 100; if (y > 0) j --; j += (y / 4) * D_PER_4; y %= 4; if (y > 0) j++; return((y*D_PER_1)+j+d-1); } static int time_to_number(TIME t) { return((t.h*3600)+(t.m*60)+t.s); } /* * This code contains some subtleties. Conceptually, it looks like * * y = (v / D_PER_400) * 400 * v %= D_PER_400 * if v > D_PER_100 * // Decrement v here because the arithmetic subtracts * // off ordinary centuries, and we have one day more * // than it's expecting. * v -- * y += (v / D_PER_100) * 100 * v %= D_PER_100 * // v is now a count of days into the century beginning with * // year y. * if y is a leap year * if v > D_PER_4 * y += (v / D_PER_4) * 4 * v %= D_PER_4 * else * if v >= D_PER_4 * v ++; * y += (v / D_PER_4) * 4 * v %= D_PER_4 * // v is now a number of days into the 4-year cycle beginning * // with year y. * if y is a leap year * if v > D_PER_1 * v --; * y += v / D_PER_1 * v %= D_PER_1 * else * if v >= D_PER_1 * y += v / D_PER_1 * v %= D_PER_1 * // v is now a number of days into year y. * loop over months to find month and day number * * * We optimize this a little for actual computation. */ static DATE number_to_date(int v) { int y; int yleap; int m; y = (v / D_PER_400) * 400; v %= D_PER_400; yleap = 1; if (v > D_PER_100) { v --; y += (v / D_PER_100) * 100; v %= D_PER_100; yleap = 0; } if (v >= D_PER_4+yleap) { if (! yleap) v ++; y += (v / D_PER_4) * 4; v %= D_PER_4; yleap = 1; } if (v >= D_PER_1+yleap) { v -= yleap; y += v / D_PER_1; v %= D_PER_1; } for (m=1;v>=mdays(y,m);m++) v -= mdays(y,m); return((DATE){.y=y,.m=m,.d=v+1}); } static TIME number_to_time(int v) { return((TIME){ .h=v/3600, .m=(v/60)%60, .s=v%60 }); } static void test_time(void) { int h; int m; int s; int v; int w; TIME t; w = 0; for (h=0;h<24;h++) for (m=0;m<60;m++) for (s=0;s<60;s++) { v = time_to_number((TIME){.h=h,.m=m,.s=s}); if (v != w) { printf("Time failure: %02d:%02d:%02d -> %d, wanted %d\n",h,m,s,v,w); exit(1); } t = number_to_time(v); if ((t.h != h) || (t.m != m) || (t.s != s)) { printf("Time failure: %d -> %02d:%02d:%02d, wanted %02d:%02d:%02d\n", v,t.h,t.m,t.s,h,m,s); exit(1); } w ++; } } static void test_date(void) { int y; int m; int nd; int d; int v; int w; DATE D; w = 547864; /* 1500-01-01 */ for (y=1500;y<2500;y++) { for (m=1;m<=12;m++) { nd = mdays(y,m); for (d=1;d<=nd;d++) { v = date_to_number((DATE){.y=y,.m=m,.d=d}); if (v != w) { printf("Date failure: %04d-%02d-%02d -> %d, wanted %d\n", y,m,d,v,w); exit(1); } D = number_to_date(v); if ((D.y != y) || (D.m != m) || (D.d != d)) { printf("Date failure: %d -> %04d-%02d-%02d, wanted %04d-%02d-%02d\n", v,D.y,D.m,D.d,y,m,d); exit(1); } w ++; } } } } static void run_tests(void) { test_time(); test_date(); } int main(int ac, char **av) { int v; if ( (ac == 2) && (strlen(av[1]) == 10) && (av[1][4] == '-') && (av[1][7] == '-') && Cisdigit(av[1][0]) && Cisdigit(av[1][1]) && Cisdigit(av[1][2]) && Cisdigit(av[1][3]) && Cisdigit(av[1][5]) && Cisdigit(av[1][6]) && Cisdigit(av[1][8]) && Cisdigit(av[1][9]) ) { printf("%d\n", date_to_number((DATE){ .y = (dval(av[1][0]) * 1000) + (dval(av[1][1]) * 100) + (dval(av[1][2]) * 10) + dval(av[1][3]), .m = (dval(av[1][5]) * 10) + dval(av[1][6]), .d = (dval(av[1][8]) * 10) + dval(av[1][9]) })); } else if ( (ac == 2) && (strlen(av[1]) == 8) && (av[1][2] == ':') && (av[1][5] == ':') && Cisdigit(av[1][0]) && Cisdigit(av[1][1]) && Cisdigit(av[1][3]) && Cisdigit(av[1][4]) && Cisdigit(av[1][6]) && Cisdigit(av[1][7]) ) { printf("%d\n", time_to_number((TIME) { .h = (dval(av[1][0]) * 10) + dval(av[1][1]), .m = (dval(av[1][3]) * 10) + dval(av[1][4]), .s = (dval(av[1][6]) * 10) + dval(av[1][7]) })); } else if ( (ac == 3) && !strcmp(av[1],"-d") && argnumber(av[2],&v) ) { DATE d; d = number_to_date(v); printf("%04d-%02d-%02d\n",d.y,d.m,d.d); } else if ( (ac == 3) && !strcmp(av[1],"-t") && argnumber(av[2],&v) ) { TIME t; t = number_to_time(v); printf("%02d:%02d:%02d\n",t.h,t.m,t.s); } else if ((ac == 2) && !strcmp(av[1],"-test")) { run_tests(); } else { usage(); } exit(0); }