Reform the Gregorian Reform. This means that the previously hard

coded meaning of 1752/09/03 is only a default, and that everything is
now calculated dynamically.

You can now use -R reform-spec to specify an alternate reform.  Read
the fine (new) man page for details on this.  There is also a new -r
option which will make cal print the month (or year, if -y is also
given) in which the Gregorian Reform started.  I say started only
because if you apply the reform at 9999/1/22, a chunk of January is
knocked out, February and March are missing entirely, and April starts
on the 5th.  The use of -r with -y does pretty much what you'd expect.

Also, implement -d day-of-week so that you can tell cal to start the
week on something other that a Sunday.  This addresses PR bin/8539 at
long last.
This commit is contained in:
atatat 2003-07-24 01:19:45 +00:00
parent c9c88988f6
commit cfda71101a
2 changed files with 435 additions and 60 deletions

View File

@ -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

View File

@ -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 <tzfile.h>
#include <unistd.h>
#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);
}