/* * 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 (which is j=0) */ 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 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 ++; /* And 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 --; /* And 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 ++; /* And 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); /* And 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) { long int 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); }