/* * ndate [-e] [-ei] [-eo] [-d] [-di] [-do] [-add ndays] [-sub ndays] [date] * * -ei specifies day-since-epoch input; -eo specifies day-since-epoch * output. -e means both -ei and -eo. -d forms are the converse of * the -e forms, specifying date format instead of epoch format. For * any given bit (ie, epoch-in or epoch-out), last specifier on the * command line wins. * * -add and -sub options specify a number of days to add or subtract * from the date that would otherwise be output. Multiple such * options may be given and the resulting adjustments accumulate. * * Note that partial date specs work only for -di style input. * * Also, undocumented -test option runs tests: * ndate -test [-v] [j-start [j-interval]] */ #include #include #include #include extern const char *__progname; static int eflag_i = 0; static int eflag_o = 0; typedef struct date DATE; struct date { int y; // Gregorian year int m; // month, 1-12 int d; // day within month, 1-31 int j; // day since 0000-01-01 Gregorian unsigned int v_ymd; // y/m/d are valid unsigned int v_j; // j is valid } ; /* * Return number of days in a year, given year number. */ 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 ); } /* * Return number of days in a month of a year, assuming month in range. */ inline static unsigned int mdays(unsigned int, unsigned int) __attribute__ ((const)); inline static unsigned int mdays(unsigned int y, unsigned int m) { return( (m == 2) ? ( (y % 4) ? 28 : (y % 100) ? 29 : (y % 400) ? 28 : 29 ) /*JanFebMarAprMayJunJulAugSepOctNovDec*/ : "\0\37\00\37\36\37\36\37\37\36\37\36\37"[m] ); } /* * Assume y/m/d are set, adjust to valid values, assuming someone did * arithmetic on the thing as an 8-digit integer (but do assume no * more than about 30 days were added or subtracted). ymd must be * valid; if this changes any of them, it invalidates j, otherwise it * does not change j's validity. */ static void adjust_ymd(DATE *d) { if (d->d > 70) { d->m ++; d->d -= 100; } while (d->d < 1) { d->m --; if (d->m < 1) { d->m = 12; d->y --; } d->d += mdays(d->y,d->m); } while (d->d > mdays(d->y,d->m)) { d->d -= mdays(d->y,d->m); d->m ++; if (d->m > 12) { d->m = 1; d->y ++; } } } /* * Compute d->y, d->m, d->d. d->j must be valid. */ static void convert_j_ymd(DATE *d) { int n; int j; if (! d->v_j) abort(); /* First cut, compute the correct 400-year cycle */ j = d->j; d->y = 400 * (j / 146097); /* 146097d = 400y */ j %= 146097; /* * Get to within "a few" years of correct, making sure we don't * overshoot. This is a bit icky. We assume 365-day years, but that * may overshoot by as much as about 100 days (one day per leap year, * but we know <400y at this point); we subtract off 500 days (a nice * round number more than the greatest possible overshoot) and then * add them back at the end. We do this only when n will be at least * 1 within the if, because only then is it correct to add back 499 * days after taking off 500. This apparent peculiarity occurs * because the corrections for leap years and century non-leap years * does not take into account the fact that the first year of the * 400-year cycle is a leap year in spite of being a century year. * The difference between 500 and 499 corrects for this day. This is * why n must be at least 1 for this computation to be correct: it is * correct only when j is high enough that the leap-year day in that * first year of the cycle is actually included. */ if (j > 1100) { j -= 500; n = j / 365; j %= 365; d->y += n; j += 499 + ((n + 99) / 100) - ((n + 3) / 4); } /* * Now, j is small enough we can just loop through the remaining * years, since there will be only "a few" of them (three or four, * max). */ while (1) { n = ydays(d->y); if (j < n) break; d->y ++; j -= n; } /* * Now step past whole months. Abort if we fall off the end of the * year, because that "can't happen" (it would mean mdays() thinks * some year is shorter than ydays() does). */ d->m = 1; while (1) { n = mdays(d->y,d->m); if (j < n) break; d->m ++; j -= n; if (d->m > 12) abort(); } /* * Now, just set the day number within the month. */ d->d = j + 1; // y, m, d are now valid! d->v_ymd = 1; } /* * Compute d->j. d->y, d->m, d->d must be valid. */ static void convert_ymd_j(DATE *d) { int j; int y; int m; if (! d->v_ymd) abort(); /* * Compute figure for years. First, 4-century cycles. */ y = d->y; j = 146097 * (y / 400); y %= 400; /* * Now, if there's at least one more year, correct for the first year * of the cycle being a leap year even though it's a century year. */ if (y > 0) j ++; /* * Count off centuries. */ j += 36524 * (y / 100); y %= 100; /* * Now, if there's at least one more year, correct for the first year * of the century not being a leap year even though it's divisible by * 4. */ if (y > 0) j --; /* * Count off 4-year leap-year cycles. */ j += 1461 * (y / 4); y %= 4; /* * Now, if there's at least one more year, add a day because the first * year of the 4-year cycle has 366 days instead of 365. */ if (y > 0) j ++; /* * Count off remaining years. */ j += 365 * y; /* * Now, count off whole months, if any. */ y = d->y; for (m=d->m-1;m>0;m--) j += mdays(y,m); /* * Add in the number of days into the current month. */ d->j = j + d->d - 1; // j is now valid! d->v_j = 1; } /* * Test convert_ymd_j and convert_j_ymd. */ static void do_tests(int ac, char **av) { DATE d; DATE ld; int vflag; int j; int n; int min; int max; int diff; vflag = 0; min = 600000; max = 800000; while ((ac > 0) && (av[0][0] == '-')) { if (!strcmp(av[0],"-v")) { vflag = 1; } else { fprintf(stderr,"%s: invalid flag %s to -test\n",__progname,av[0]); exit(1); } ac --; av ++; } switch (ac) { case 0: break; case 1: min = atoi(av[0]); break; case 2: min = atoi(av[0]); max = min + atoi(av[1]); break; default: fprintf(stderr,"%s: bad args to -test\n",__progname); exit(1); break; } ld.j = min; ld.v_j = 1; ld.v_ymd = 0; convert_j_ymd(&ld); n = min - (min % 2000); for (j=min+1;j%04d%02d%02d, %04d%02d%02d->%d\n",d.j,d.y,d.m,d.d,ld.y,ld.m,ld.d,ld.j); if (diff) abort(); if (j >= n) { printf("%d %04d%02d%02d\n",j,d.y,d.m,d.d); n += 2000; } } } static void curtime_ymd(DATE *d) { time_t now; struct tm *tm; time(&now); tm = localtime(&now); d->y = tm->tm_year + 1900; d->m = tm->tm_mon + 1; d->d = tm->tm_mday; d->v_ymd = 1; d->v_j = 0; } static void needarg(const char *flag) { fprintf(stderr,"%s: %s needs another argument\n",__progname,flag); exit(1); } int main(int, char **); int main(int ac, char **av) { char tmp[9]; DATE d; int offset; int skip; ac --; av ++; offset = 0; while ((ac > 0) && (av[0][0] == '-')) { skip = 0; if (!strcmp(av[0],"-e")) { eflag_i = 1; eflag_o = 1; } else if (!strcmp(av[0],"-ei")) { eflag_i = 1; } else if (!strcmp(av[0],"-eo")) { eflag_o = 1; } else if (!strcmp(av[0],"-d")) { eflag_i = 0; eflag_o = 0; } else if (!strcmp(av[0],"-di")) { eflag_i = 0; } else if (!strcmp(av[0],"-do")) { eflag_o = 0; } else if (!strcmp(av[0],"-add")) { if (! av[1]) needarg(av[0]); offset += atoi(av[1]); skip = 1; } else if (!strcmp(av[0],"-sub")) { if (! av[1]) needarg(av[0]); offset -= atoi(av[1]); skip = 1; } else if (!strcmp(av[0],"-test")) { do_tests(ac-1,av+1); exit(0); } else { fprintf(stderr,"%s: invalid flag %s\n",__progname,av[0]); exit(1); } do { ac --; av ++; } while (skip-- > 0); } switch (ac) { case 0: curtime_ymd(&d); break; case 1: if (eflag_i) { d.j = atoi(av[0]); d.v_j = 1; d.v_ymd = 0; } else { curtime_ymd(&d); sprintf(&tmp[0],"%04d%02d%02d",d.y,d.m,d.d); switch (strlen(av[0])) { case 0: break; case 1: tmp[6] = '0'; tmp[7] = av[0][0]; break; case 2: tmp[6] = av[0][0]; tmp[7] = av[0][1]; break; case 3: tmp[4] = '0'; bcopy(av[0],&tmp[5],3); break; case 4: bcopy(av[0],&tmp[4],4); break; case 5: case 6: case 7: case 8: strcpy(&tmp[8-strlen(av[0])],av[0]); break; default: fprintf(stderr,"%s: invalid date `%s'\n",__progname,av[0]); exit(1); break; } d.d = atoi(&tmp[6]); tmp[6] = '\0'; d.m = atoi(&tmp[4]); tmp[4] = '\0'; d.y = atoi(&tmp[0]); adjust_ymd(&d); d.v_ymd = 1; d.v_j = 0; } break; default: fprintf(stderr,"Usage: %s [-e] [-ei] [-eo] [-d] [-di] [-do] [date]\n",__progname); exit(1); break; } if (offset) { if (! d.v_j) convert_ymd_j(&d); d.j += offset; d.v_ymd = 0; } if (eflag_o) { if (! d.v_j) convert_ymd_j(&d); printf("%d\n",d.j); } else { if (! d.v_ymd) convert_j_ymd(&d); printf("%04d%02d%02d\n",d.y,d.m,d.d); } exit(0); }