diff --git a/usr.bin/cal/cal.1 b/usr.bin/cal/cal.1 index da83e602010a..f8074680081c 100644 --- a/usr.bin/cal/cal.1 +++ b/usr.bin/cal/cal.1 @@ -1,4 +1,4 @@ -.\" $NetBSD: cal.1,v 1.16 2003/06/30 19:12:31 wiz Exp $ +.\" $NetBSD: cal.1,v 1.17 2003/07/24 01:19:45 atatat Exp $ .\" .\" Copyright (c) 1989, 1990, 1993 .\" The Regents of the University of California. All rights reserved. @@ -36,7 +36,7 @@ .\" .\" @(#)cal.1 8.2 (Berkeley) 4/28/95 .\" -.Dd June 4, 2003 +.Dd July 23, 2003 .Dt CAL 1 .Os .Sh NAME @@ -44,9 +44,11 @@ .Nd displays a calendar .Sh SYNOPSIS .Nm -.Op Fl hjy3 +.Op Fl hjry3 .Op Fl A Ar after .Op Fl B Ar before +.Op Fl d Ar day-of-week +.Op Fl R Ar reform-spec .Op Oo Ar month Oc Ar year .Sh DESCRIPTION .Nm @@ -63,6 +65,11 @@ months after the specified month. Display .Ar before months before the specified month. +.It Fl d Ar day-of-week +Specifies the day of the week on which the calendar should start. +Valid values are 0 through 6, presenting Sunday through Saturday, +inclusively. +The default output starts on Sundays. .It Fl h Highlight the current day, if present in the displayed calendar. If output is to a terminal, then the appropriate terminal sequences @@ -73,6 +80,28 @@ is used and output is to a terminal, the current date will be highlighted in inverse video instead of bold. .It Fl j Display Julian dates (days one-based, numbered from January 1). +.It Fl R Ar reform-spec +Selects an alternate Gregorian reform point from the default of +September 3rd, 1752. +The +.Ar reform-spec +can be selected by one of the built-in names (see +.Sx NOTES +for a list) or by a date of the form YYYY/MM/DD. +The date and month may be omitted, provided that what is specified +uniquely selects a given built-in reform point. +If an exact date is specified, then that date is taken to be the first +missing date of the Gregorian Reform to be applied. +.It Fl r +Display the month in which the Gregorian Reform adjustment was +applied, if no other +.Ar month +or +.Ar year +information is given. +If used in conjunction with +.Fl y , +then the entire year is displayed. .It Fl y Display a calendar for the current year. .It Fl 3 @@ -91,17 +120,45 @@ If no parameters are specified, the current month's calendar is displayed. .Pp A year starts on Jan 1. -.Pp +.Sh NOTES In the USA and Great Britain the Gregorian Reformation occurred in 1752. By this time, most countries had recognized the reformation (although a -few did not recognize it until the early 1900's.) +few did not recognize it until the 1900's.) Eleven days following September 2, 1752 were eliminated by the reformation, so the calendar for that month is a bit unusual. -.Sh NOTES +.Pp In view of the chaotic way the Gregorian calendar was adopted throughout -the world in the years between 1582 and 1923 make sure to take into account +the world in the years between 1582 and 1928 make sure to take into account the date of the Gregorian Reformation in your region if you are checking a calendar for a very old date. +.Pp +.Nm +has a decent built-in list of Gregorian Reform dates and the names of +the countries where the reform was adopted: +.Pp +.Bd -literal + Italy Oct. 5, 1582 Denmark Feb. 19, 1700 + Spain Oct. 5, 1582 Great Britain Sep. 3, 1752 + Portugal Oct. 5, 1582 Sweden Feb. 18, 1753 + Poland Oct. 5, 1582 Finland Feb. 18, 1753 + France Dec. 12, 1582 Japan Dec. 20, 1872 + Luxembourg Dec. 22, 1582 China Nov. 7, 1911 + Netherlands Dec. 22, 1582 Bulgaria Apr. 1, 1916 + Bavaria Oct. 6, 1583 U.S.S.R. Feb. 1, 1918 + Austria Jan. 7, 1584 Serbia Jan. 19, 1919 + Switzerland Jan. 12, 1584 Romania Jan. 19, 1919 + Hungary Oct. 22, 1587 Greece Mar. 10, 1924 + Germany Feb. 19, 1700 Turkey Dec. 19, 1925 + Norway Feb. 19, 1700 Egypt Sep. 18, 1928 +.Ed +.Pp +The country known as +.Em Great Britain +can also be referred to as +.Em England +since that has less letters and no spaces in it. +This is meant only as a measure of expediency, not as a possible +slight to anyone involved. .Sh HISTORY A .Nm diff --git a/usr.bin/cal/cal.c b/usr.bin/cal/cal.c index a0a54078dc21..4566952fa768 100644 --- a/usr.bin/cal/cal.c +++ b/usr.bin/cal/cal.c @@ -1,4 +1,4 @@ -/* $NetBSD: cal.c,v 1.16 2003/07/22 14:22:09 yamt Exp $ */ +/* $NetBSD: cal.c,v 1.17 2003/07/24 01:19:45 atatat Exp $ */ /* * Copyright (c) 1989, 1993, 1994 @@ -46,7 +46,7 @@ __COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\n\ #if 0 static char sccsid[] = "@(#)cal.c 8.4 (Berkeley) 4/2/94"; #else -__RCSID("$NetBSD: cal.c,v 1.16 2003/07/22 14:22:09 yamt Exp $"); +__RCSID("$NetBSD: cal.c,v 1.17 2003/07/24 01:19:45 atatat Exp $"); #endif #endif /* not lint */ @@ -64,11 +64,10 @@ __RCSID("$NetBSD: cal.c,v 1.16 2003/07/22 14:22:09 yamt Exp $"); #include #include -#define THURSDAY 4 /* for reformation */ #define SATURDAY 6 /* 1 Jan 1 was a Saturday */ -#define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */ -#define NUMBER_MISSING_DAYS 11 /* 11 day correction */ +#define FIRST_MISSING_DAY reform->first_missing_day +#define NUMBER_MISSING_DAYS reform->missing_days #define MAXDAYS 42 /* max slots in a month array */ #define SPACE -1 /* used in day array */ @@ -78,21 +77,7 @@ static int days_in_month[2][13] = { {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, }; -int sep1752[MAXDAYS] = { - SPACE, SPACE, 1, 2, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, - SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, - SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, - SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, -}, j_sep1752[MAXDAYS] = { - SPACE, SPACE, 245, 246, 258, 259, 260, - 261, 262, 263, 264, 265, 266, 267, - 268, 269, 270, 271, 272, 273, 274, - SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, - SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, - SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, -}, empty[MAXDAYS] = { +int empty[MAXDAYS] = { SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, @@ -100,6 +85,7 @@ int sep1752[MAXDAYS] = { SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, }; +int shift_days[2][4][MAXDAYS + 1]; char *month_names[12] = { "January", "February", "March", "April", "May", "June", @@ -109,29 +95,119 @@ char *month_names[12] = { char *day_headings = " S M Tu W Th F S"; char *j_day_headings = " S M Tu W Th F S"; -/* leap year -- account for gregorian reformation in 1752 */ +/* leap years according to the julian calendar */ +#define j_leap_year(y, m, d) \ + (((m) > 2) && \ + !((y) % 4)) + +/* leap years according to the gregorian calendar */ +#define g_leap_year(y, m, d) \ + (((m) > 2) && \ + ((!((y) % 4) && ((y) % 100)) || \ + !((y) % 400))) + +/* leap year -- account for gregorian reformation at some point */ #define leap_year(yr) \ - ((yr) <= 1752 ? !((yr) % 4) : \ - (!((yr) % 4) && ((yr) % 100)) || !((yr) % 400)) + ((yr) <= reform->year ? j_leap_year((yr), 3, 1) : \ + g_leap_year((yr), 3, 1)) -/* number of centuries since 1700, not inclusive */ -#define centuries_since_1700(yr) \ - ((yr) > 1700 ? (yr) / 100 - 17 : 0) +/* number of julian leap days that have passed by a given date */ +#define j_leap_days(y, m, d) \ + ((((y) - 1) / 4) + j_leap_year(y, m, d)) -/* number of centuries since 1700 whose modulo of 400 is 0 */ -#define quad_centuries_since_1700(yr) \ - ((yr) > 1600 ? ((yr) - 1600) / 400 : 0) +/* number of gregorian leap days that have passed by a given date */ +#define g_leap_days(y, m, d) \ + ((((y) - 1) / 4) - (((y) - 1) / 100) + (((y) - 1) / 400) + \ + g_leap_year(y, m, d)) + +/* + * Subtracting the gregorian leap day count (for a given date) from + * the julian leap day count (for the same date) describes the number + * of days from the date before the shift to the next date that + * appears in the calendar. Since we want to know the number of + * *missing* days, not the number of days that the shift spans, we + * subtract 2. + * + * Alternately... + * + * There's a reason they call the Dark ages the Dark Ages. Part of it + * is that we don't have that many records of that period of time. + * One of the reasons for this is that a lot of the Dark Ages never + * actually took place. At some point in the first millenium A.D., a + * ruler of some power decided that he wanted the number of the year + * to be different than what it was, so he changed it to coincide + * nicely with some event (a birthday or anniversary, perhaps a + * wedding, or maybe a centennial for a largish city). One of the + * side effects of this upon the Gregorian reform is that two Julian + * leap years (leap days celebrated during centennial years that are + * not quatro-centennial years) were skipped. + */ +#define GREGORIAN_MAGIC 2 + +/* number of centuries since the reform, not inclusive */ +#define centuries_since_reform(yr) \ + ((yr) > reform->year ? ((yr) / 100) - (reform->year / 100) : 0) + +/* number of centuries since the reform whose modulo of 400 is 0 */ +#define quad_centuries_since_reform(yr) \ + ((yr) > reform->year ? ((yr) / 400) - (reform->year / 400) : 0) /* number of leap years between year 1 and this year, not inclusive */ #define leap_years_since_year_1(yr) \ - ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr)) + ((yr) / 4 - centuries_since_reform(yr) + quad_centuries_since_reform(yr)) + +struct reform { + const char *country; + int ambiguity, year, month, date; + long first_missing_day; + int missing_days; + /* + * That's 2 for standard/julian display, 4 for months possibly + * affected by the Gregorian shift, and MAXDAYS + 1 for the + * days that get displayed, plus a crib slot. + */ +} *reform, reforms[] = { + { "DEFAULT", 0, 1752, 9, 3 }, + { "Italy", 1, 1582, 10, 5 }, + { "Spain", 1, 1582, 10, 5 }, + { "Portugal", 1, 1582, 10, 5 }, + { "Poland", 1, 1582, 10, 5 }, + { "France", 2, 1582, 12, 10 }, + { "Luxembourg", 2, 1582, 12, 22 }, + { "Netherlands", 2, 1582, 12, 22 }, + { "Bavaria", 0, 1583, 10, 6 }, + { "Austria", 2, 1584, 1, 7 }, + { "Switzerland", 2, 1584, 1, 12 }, + { "Hungary", 0, 1587, 10, 22 }, + { "Germany", 0, 1700, 2, 19 }, + { "Norway", 0, 1700, 2, 19 }, + { "Denmark", 0, 1700, 2, 19 }, + { "Great Britain", 0, 1752, 9, 3 }, + { "England", 0, 1752, 9, 3 }, + { "America", 0, 1752, 9, 3 }, + { "Sweden", 0, 1753, 2, 18 }, + { "Finland", 0, 1753, 2, 18 }, + { "Japan", 0, 1872, 12, 20 }, + { "China", 0, 1911, 11, 7 }, + { "Bulgaria", 0, 1916, 4, 1 }, + { "U.S.S.R.", 0, 1918, 2, 1 }, + { "Serbia", 0, 1919, 1, 19 }, + { "Romania", 0, 1919, 1, 19 }, + { "Greece", 0, 1924, 3, 10 }, + { "Turkey", 0, 1925, 12, 19 }, + { "Egypt", 0, 1928, 9, 18 }, + { NULL, 0, 0, 0, 0 }, +}; int julian; +int dow; int hilite; char *md, *me; void init_hilite(void); int getnum(const char *); +void gregorian_reform(const char *); +void reform_day_array(int, int, int *, int *, int *,int *,int *,int *); int ascii_day(char *, int); void center(char *, int, int); void day_array(int, int, int *); @@ -148,12 +224,14 @@ main(int argc, char **argv) struct tm *local_time; time_t now; int ch, month, year, yflag; - int before, after; + int before, after, use_reform; int yearly = 0; + char *when; before = after = 0; - yflag = year = 0; - while ((ch = getopt(argc, argv, "A:B:hjy3")) != -1) { + use_reform = yflag = year = 0; + when = NULL; + while ((ch = getopt(argc, argv, "A:B:d:hjR:ry3")) != -1) { switch (ch) { case 'A': after = getnum(optarg); @@ -161,12 +239,23 @@ main(int argc, char **argv) case 'B': before = getnum(optarg); break; + case 'd': + dow = getnum(optarg); + if (dow < 0 || dow > 6) + errx(1, "illegal day of week value: use 0-6"); + break; case 'h': init_hilite(); break; case 'j': julian = 1; break; + case 'R': + when = optarg; + break; + case 'r': + use_reform = 1; + break; case 'y': yflag = 1; break; @@ -183,6 +272,11 @@ main(int argc, char **argv) argc -= optind; argv += optind; + if (when != NULL) + gregorian_reform(when); + if (reform == NULL) + gregorian_reform("DEFAULT"); + month = 0; switch (argc) { case 2: @@ -196,9 +290,16 @@ main(int argc, char **argv) case 0: (void)time(&now); local_time = localtime(&now); - year = local_time->tm_year + TM_YEAR_BASE; - if (!yflag) - month = local_time->tm_mon + 1; + if (use_reform) + year = reform->year; + else + year = local_time->tm_year + TM_YEAR_BASE; + if (!yflag) { + if (use_reform) + month = reform->month; + else + month = local_time->tm_mon + 1; + } break; default: usage(); @@ -316,8 +417,15 @@ monthrange(int month, int year, int before, int after, int yearly) break; sep = (i == month_per_row - 1) ? 0 : head_sep; - printf("%s%*s", - (julian) ? j_day_headings : day_headings, sep, ""); + if (dow) { + printf("%s ", (julian) ? + j_day_headings + 4 * dow : + day_headings + 3 * dow); + printf("%.*s", dow * (julian ? 4 : 3) - 1, + (julian) ? j_day_headings : day_headings); + } else + printf("%s", (julian) ? j_day_headings : day_headings); + printf("%*s", sep, ""); } printf("\n"); @@ -378,11 +486,14 @@ day_array(int month, int year, int *days) tm->tm_mon++; tm->tm_yday++; /* jan 1 is 1 for us, not 0 */ - if (month == 9 && year == 1752) { - memmove(days, - julian ? j_sep1752 : sep1752, MAXDAYS * sizeof(int)); - return; + for (dm = month + year * 12, dw = 0; dw < 4; dw++) { + if (dm == shift_days[julian][dw][MAXDAYS]) { + memmove(days, shift_days[julian][dw], + MAXDAYS * sizeof(int)); + return; + } } + memmove(days, empty, MAXDAYS * sizeof(int)); dm = days_in_month[leap_year(year)][month]; dw = day_in_week(1, month, year); @@ -391,7 +502,7 @@ day_array(int month, int year, int *days) if (hilite && year == tm->tm_year && (julian ? (day == tm->tm_yday) : (month == tm->tm_mon && day == tm->tm_mday))) - days[dw++] = -1 - day++; + days[dw++] = SPACE - day++; else days[dw++] = day++; } @@ -415,9 +526,8 @@ day_in_year(int day, int month, int year) /* * day_in_week * return the 0 based day number for any date from 1 Jan. 1 to - * 31 Dec. 9999. Assumes the Gregorian reformation eliminates - * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all - * missing days. + * 31 Dec. 9999. Returns the day of the week of the first + * missing day for any given Gregorian shift. */ int day_in_week(int day, int month, int year) @@ -427,10 +537,10 @@ day_in_week(int day, int month, int year) temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1) + day_in_year(day, month, year); if (temp < FIRST_MISSING_DAY) - return ((temp - 1 + SATURDAY) % 7); + return ((temp - dow + 6 + SATURDAY) % 7); if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS)) - return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7); - return (THURSDAY); + return (((temp - dow + 6 + SATURDAY) - NUMBER_MISSING_DAYS) % 7); + return ((FIRST_MISSING_DAY - dow + 6 + SATURDAY) % 7); } int @@ -451,9 +561,9 @@ ascii_day(char *p, int day) memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN); return (0); } - if (day < 0) { + if (day < SPACE) { b = p; - day = -1 - day; + day = SPACE - day; } else b = NULL; if (julian) { @@ -501,7 +611,6 @@ ascii_day(char *p, int day) *p++ = *t; } } - } *p = ' '; @@ -532,6 +641,214 @@ center(char *str, int len, int separate) (void)printf("%*s", separate, ""); } +/* + * gregorian_reform -- + * Given a description of date on which the Gregorian Reform was + * applied. The argument can be any of the "country" names + * listed in the reforms array (case insensitive) or a date of + * the form YYYY/MM/DD. The date and month can be omitted if + * doing so would not select more than one different built-in + * reform point. + */ +void +gregorian_reform(const char *p) +{ + int year, month, date; + int i, days, diw, diy; + char c; + + i = sscanf(p, "%d%*[/,-]%d%*[/,-]%d%c", &year, &month, &date, &c); + switch (i) { + case 4: + /* + * If the character was sscanf()ed, then there's more + * stuff than we need. + */ + errx(1, "date specifier %s invalid", p); + case 0: + /* + * Not a form we can sscanf(), so void these, and we + * can try matching "country" names later. + */ + year = month = date = -1; + break; + case 1: + month = 0; + /*FALLTHROUGH*/ + case 2: + date = 0; + /*FALLTHROUGH*/ + case 3: + /* + * At last, some sanity checking on the values we were + * given. + */ + if (year < 1 || year > 9999) + errx(1, "%d: illegal year value: use 1-9999", year); + if (i > 1 && (month < 1 || month > 12)) + errx(1, "%d: illegal month value: use 1-12", month); + if ((i == 3 && date < 1) || date < 0 || + date > days_in_month[1][month]) + /* + * What about someone specifying a leap day in + * a non-leap year? Well...that's a tricky + * one. We can't yet *say* whether the year + * in question is a leap year. What if the + * date given was, for example, 1700/2/29? is + * that a valid leap day? + * + * So...we punt, and hope that saying 29 in + * the case of February isn't too bad an idea. + */ + errx(1, "%d: illegal date value: use 1-%d", date, + days_in_month[1][month]); + break; + } + + /* + * A complete date was specified, so use the other pope. + */ + if (date > 0) { + static struct reform Goestheveezl; + + reform = &Goestheveezl; + reform->country = "Bompzidaize"; + reform->year = year; + reform->month = month; + reform->date = date; + } + + /* + * No date information was specified, so let's try to match on + * country name. + */ + else if (year == -1) { + for (reform = &reforms[0]; reform->year; reform++) { + if (strcasecmp(p, reform->country) == 0) + break; + } + } + + /* + * We have *some* date information, but not a complete date. + * Let's see if we have enough to pick a single entry from the + * list that's not ambiguous. + */ + else { + for (reform = &reforms[0]; reform->year; reform++) { + if ((year == 0 || year == reform->year) && + (month == 0 || month == reform->month) && + (date == 0 || month == reform->date)) + break; + } + + if (i <= reform->ambiguity) + errx(1, "%s: ambiguous short reform date specification", p); + } + + /* + * Oops...we reached the end of the list. + */ + if (reform->year == 0) + errx(1, "reform name %s invalid", p); + + /* + * + */ + reform->missing_days = + j_leap_days(reform->year, reform->month, reform->date) - + g_leap_days(reform->year, reform->month, reform->date) - + GREGORIAN_MAGIC; + + reform->first_missing_day = + (reform->year - 1) * 365 + + day_in_year(reform->date, reform->month, reform->year) + + date + + j_leap_days(reform->year, reform->month, reform->date); + + /* + * Once we know the day of the week of the first missing day, + * skip back to the first of the month's day of the week. + */ + diw = day_in_week(reform->date, reform->month, reform->year); + diw = (diw + 8 - (reform->date % 7)) % 7; + diy = day_in_year(1, reform->month, reform->year); + + /* + * We might need all four of these (if you switch from Julian + * to Gregorian at some point after 9900, you get a gap of 73 + * days, and that can affect four months), and it doesn't hurt + * all that much to precompute them, so there. + */ + date = 1; + days = 0; + for (i = 0; i < 4; i++) + reform_day_array(reform->month + i, reform->year, + &days, &date, &diw, &diy, + shift_days[0][i], + shift_days[1][i]); +} + +/* + * reform_day_array -- + * Pre-calculates the given month's calendar (in both "standard" + * and "julian day" representations) with respect for days + * skipped during a reform period. + */ +void +reform_day_array(int month, int year, int *done, int *date, int *diw, int *diy, + int *scal, int *jcal) +{ + int mdays; + + /* + * If the reform was in the month of october or later, then + * the month number from the caller could "overflow". + */ + if (month > 12) { + month -= 12; + year++; + } + + /* + * Erase months, and set crib number. The crib number is used + * later to determine if the month to be displayed is here or + * should be built on the fly with the generic routine + */ + memmove(scal, empty, MAXDAYS * sizeof(int)); + scal[MAXDAYS] = month + year * 12; + memmove(jcal, empty, MAXDAYS * sizeof(int)); + jcal[MAXDAYS] = month + year * 12; + + /* + * It doesn't matter what the actual month is when figuring + * out if this is a leap year or not, just so long as February + * gets the right number of days in it. + */ + mdays = days_in_month[g_leap_year(year, 3, 1)][month]; + + /* + * Bounce back to the first "row" in the day array, and fill + * in any days that actually occur. + */ + for (*diw %= 7; (*date - *done) <= mdays; (*date)++, (*diy)++) { + /* + * "date" doesn't get reset by the caller across calls + * to this routine, so we can actually tell that we're + * looking at April the 41st. Much easier than trying + * to calculate the absolute julian day for a given + * date and then checking that. + */ + if (*date < reform->date || + *date >= reform->date + reform->missing_days) { + scal[*diw] = *date - *done; + jcal[*diw] = *diy; + (*diw)++; + } + } + *done += mdays; +} + int getnum(const char *p) { @@ -586,6 +903,7 @@ usage(void) { (void)fprintf(stderr, - "usage: cal [-hjy3] [-B before] [-A after] [[month] year]\n"); + "usage: cal [-hjry3] [-d day-of-week] [-B before] [-A after] " + "[-R reform-spec]\n [[month] year]\n"); exit(1); }