1*6a5a56ceSkim /* $NetBSD: cal.c,v 1.30 2020/06/29 14:01:14 kim Exp $ */
22db3772cSglass
361f28255Scgd /*
42db3772cSglass * Copyright (c) 1989, 1993, 1994
52db3772cSglass * The Regents of the University of California. All rights reserved.
661f28255Scgd *
761f28255Scgd * This code is derived from software contributed to Berkeley by
861f28255Scgd * Kim Letkeman.
961f28255Scgd *
1061f28255Scgd * Redistribution and use in source and binary forms, with or without
1161f28255Scgd * modification, are permitted provided that the following conditions
1261f28255Scgd * are met:
1361f28255Scgd * 1. Redistributions of source code must retain the above copyright
1461f28255Scgd * notice, this list of conditions and the following disclaimer.
1561f28255Scgd * 2. Redistributions in binary form must reproduce the above copyright
1661f28255Scgd * notice, this list of conditions and the following disclaimer in the
1761f28255Scgd * documentation and/or other materials provided with the distribution.
1889aaa1bbSagc * 3. Neither the name of the University nor the names of its contributors
1961f28255Scgd * may be used to endorse or promote products derived from this software
2061f28255Scgd * without specific prior written permission.
2161f28255Scgd *
2261f28255Scgd * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2361f28255Scgd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2461f28255Scgd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2561f28255Scgd * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2661f28255Scgd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2761f28255Scgd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2861f28255Scgd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2961f28255Scgd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3061f28255Scgd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3161f28255Scgd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3261f28255Scgd * SUCH DAMAGE.
3361f28255Scgd */
3461f28255Scgd
35b4d27c3aSlukem #include <sys/cdefs.h>
3661f28255Scgd #ifndef lint
3798e5374cSlukem __COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
3898e5374cSlukem The Regents of the University of California. All rights reserved.");
3961f28255Scgd #endif /* not lint */
4061f28255Scgd
4161f28255Scgd #ifndef lint
422db3772cSglass #if 0
432db3772cSglass static char sccsid[] = "@(#)cal.c 8.4 (Berkeley) 4/2/94";
442db3772cSglass #else
45*6a5a56ceSkim __RCSID("$NetBSD: cal.c,v 1.30 2020/06/29 14:01:14 kim Exp $");
462db3772cSglass #endif
4761f28255Scgd #endif /* not lint */
4861f28255Scgd
4961f28255Scgd #include <sys/types.h>
502db3772cSglass
5161f28255Scgd #include <ctype.h>
522db3772cSglass #include <err.h>
53c81873c7Syamt #include <errno.h>
54c81873c7Syamt #include <limits.h>
552db3772cSglass #include <stdio.h>
562db3772cSglass #include <stdlib.h>
572db3772cSglass #include <string.h>
5898eb8895Sroy #include <term.h>
592db3772cSglass #include <time.h>
607bc244d5Schristos #include <tzfile.h>
612db3772cSglass #include <unistd.h>
6261f28255Scgd
6361f28255Scgd #define SATURDAY 6 /* 1 Jan 1 was a Saturday */
6461f28255Scgd
65cfda7110Satatat #define FIRST_MISSING_DAY reform->first_missing_day
66cfda7110Satatat #define NUMBER_MISSING_DAYS reform->missing_days
6761f28255Scgd
6861f28255Scgd #define MAXDAYS 42 /* max slots in a month array */
6961f28255Scgd #define SPACE -1 /* used in day array */
7061f28255Scgd
7161f28255Scgd static int days_in_month[2][13] = {
7261f28255Scgd {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
7361f28255Scgd {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
7461f28255Scgd };
7561f28255Scgd
7649dbe234Sjoerg static int empty[MAXDAYS] = {
7761f28255Scgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
7861f28255Scgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
7961f28255Scgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
8061f28255Scgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
8161f28255Scgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
8261f28255Scgd SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
8361f28255Scgd };
8449dbe234Sjoerg static int shift_days[2][4][MAXDAYS + 1];
8561f28255Scgd
8649dbe234Sjoerg static const char *month_names[12] = {
8761f28255Scgd "January", "February", "March", "April", "May", "June",
8861f28255Scgd "July", "August", "September", "October", "November", "December",
8961f28255Scgd };
9061f28255Scgd
91*6a5a56ceSkim static const char *day_headings = "Su Mo Tu We Th Fr Sa";
92*6a5a56ceSkim static const char *j_day_headings = " Su Mo Tu We Th Fr Sa";
9361f28255Scgd
94cfda7110Satatat /* leap years according to the julian calendar */
95cfda7110Satatat #define j_leap_year(y, m, d) \
96cfda7110Satatat (((m) > 2) && \
97cfda7110Satatat !((y) % 4))
98cfda7110Satatat
99cfda7110Satatat /* leap years according to the gregorian calendar */
100cfda7110Satatat #define g_leap_year(y, m, d) \
101cfda7110Satatat (((m) > 2) && \
102cfda7110Satatat ((!((y) % 4) && ((y) % 100)) || \
103cfda7110Satatat !((y) % 400)))
104cfda7110Satatat
105cfda7110Satatat /* leap year -- account for gregorian reformation at some point */
10661f28255Scgd #define leap_year(yr) \
107cfda7110Satatat ((yr) <= reform->year ? j_leap_year((yr), 3, 1) : \
108cfda7110Satatat g_leap_year((yr), 3, 1))
10961f28255Scgd
110cfda7110Satatat /* number of julian leap days that have passed by a given date */
111cfda7110Satatat #define j_leap_days(y, m, d) \
112cfda7110Satatat ((((y) - 1) / 4) + j_leap_year(y, m, d))
11361f28255Scgd
114cfda7110Satatat /* number of gregorian leap days that have passed by a given date */
115cfda7110Satatat #define g_leap_days(y, m, d) \
116cfda7110Satatat ((((y) - 1) / 4) - (((y) - 1) / 100) + (((y) - 1) / 400) + \
117cfda7110Satatat g_leap_year(y, m, d))
118cfda7110Satatat
119cfda7110Satatat /*
120cfda7110Satatat * Subtracting the gregorian leap day count (for a given date) from
121cfda7110Satatat * the julian leap day count (for the same date) describes the number
122cfda7110Satatat * of days from the date before the shift to the next date that
123cfda7110Satatat * appears in the calendar. Since we want to know the number of
124cfda7110Satatat * *missing* days, not the number of days that the shift spans, we
125cfda7110Satatat * subtract 2.
126cfda7110Satatat *
127cfda7110Satatat * Alternately...
128cfda7110Satatat *
129cfda7110Satatat * There's a reason they call the Dark ages the Dark Ages. Part of it
130cfda7110Satatat * is that we don't have that many records of that period of time.
131cfda7110Satatat * One of the reasons for this is that a lot of the Dark Ages never
132cfda7110Satatat * actually took place. At some point in the first millenium A.D., a
133cfda7110Satatat * ruler of some power decided that he wanted the number of the year
134cfda7110Satatat * to be different than what it was, so he changed it to coincide
135cfda7110Satatat * nicely with some event (a birthday or anniversary, perhaps a
136cfda7110Satatat * wedding, or maybe a centennial for a largish city). One of the
137cfda7110Satatat * side effects of this upon the Gregorian reform is that two Julian
138cfda7110Satatat * leap years (leap days celebrated during centennial years that are
139cfda7110Satatat * not quatro-centennial years) were skipped.
140cfda7110Satatat */
141cfda7110Satatat #define GREGORIAN_MAGIC 2
142cfda7110Satatat
143cfda7110Satatat /* number of centuries since the reform, not inclusive */
144cfda7110Satatat #define centuries_since_reform(yr) \
145cfda7110Satatat ((yr) > reform->year ? ((yr) / 100) - (reform->year / 100) : 0)
146cfda7110Satatat
147cfda7110Satatat /* number of centuries since the reform whose modulo of 400 is 0 */
148cfda7110Satatat #define quad_centuries_since_reform(yr) \
149cfda7110Satatat ((yr) > reform->year ? ((yr) / 400) - (reform->year / 400) : 0)
15061f28255Scgd
15161f28255Scgd /* number of leap years between year 1 and this year, not inclusive */
15261f28255Scgd #define leap_years_since_year_1(yr) \
153cfda7110Satatat ((yr) / 4 - centuries_since_reform(yr) + quad_centuries_since_reform(yr))
154cfda7110Satatat
15549dbe234Sjoerg static struct reform {
156cfda7110Satatat const char *country;
157cfda7110Satatat int ambiguity, year, month, date;
158cfda7110Satatat long first_missing_day;
159cfda7110Satatat int missing_days;
160cfda7110Satatat /*
161cfda7110Satatat * That's 2 for standard/julian display, 4 for months possibly
162cfda7110Satatat * affected by the Gregorian shift, and MAXDAYS + 1 for the
163cfda7110Satatat * days that get displayed, plus a crib slot.
164cfda7110Satatat */
165cfda7110Satatat } *reform, reforms[] = {
16653d5aab9Slukem { "DEFAULT", 0, 1752, 9, 3, 0, 0 },
16753d5aab9Slukem { "Italy", 1, 1582, 10, 5, 0, 0 },
16853d5aab9Slukem { "Spain", 1, 1582, 10, 5, 0, 0 },
16953d5aab9Slukem { "Portugal", 1, 1582, 10, 5, 0, 0 },
17053d5aab9Slukem { "Poland", 1, 1582, 10, 5, 0, 0 },
17153d5aab9Slukem { "France", 2, 1582, 12, 10, 0, 0 },
17253d5aab9Slukem { "Luxembourg", 2, 1582, 12, 22, 0, 0 },
17353d5aab9Slukem { "Netherlands", 2, 1582, 12, 22, 0, 0 },
17453d5aab9Slukem { "Bavaria", 0, 1583, 10, 6, 0, 0 },
17553d5aab9Slukem { "Austria", 2, 1584, 1, 7, 0, 0 },
17653d5aab9Slukem { "Switzerland", 2, 1584, 1, 12, 0, 0 },
17753d5aab9Slukem { "Hungary", 0, 1587, 10, 22, 0, 0 },
17853d5aab9Slukem { "Germany", 0, 1700, 2, 19, 0, 0 },
17953d5aab9Slukem { "Norway", 0, 1700, 2, 19, 0, 0 },
18053d5aab9Slukem { "Denmark", 0, 1700, 2, 19, 0, 0 },
18153d5aab9Slukem { "Great Britain", 0, 1752, 9, 3, 0, 0 },
18253d5aab9Slukem { "England", 0, 1752, 9, 3, 0, 0 },
18353d5aab9Slukem { "America", 0, 1752, 9, 3, 0, 0 },
18453d5aab9Slukem { "Sweden", 0, 1753, 2, 18, 0, 0 },
18553d5aab9Slukem { "Finland", 0, 1753, 2, 18, 0, 0 },
18653d5aab9Slukem { "Japan", 0, 1872, 12, 20, 0, 0 },
18753d5aab9Slukem { "China", 0, 1911, 11, 7, 0, 0 },
18853d5aab9Slukem { "Bulgaria", 0, 1916, 4, 1, 0, 0 },
18953d5aab9Slukem { "U.S.S.R.", 0, 1918, 2, 1, 0, 0 },
19053d5aab9Slukem { "Serbia", 0, 1919, 1, 19, 0, 0 },
19153d5aab9Slukem { "Romania", 0, 1919, 1, 19, 0, 0 },
19253d5aab9Slukem { "Greece", 0, 1924, 3, 10, 0, 0 },
19353d5aab9Slukem { "Turkey", 0, 1925, 12, 19, 0, 0 },
19453d5aab9Slukem { "Egypt", 0, 1928, 9, 18, 0, 0 },
19553d5aab9Slukem { NULL, 0, 0, 0, 0, 0, 0 },
196cfda7110Satatat };
19761f28255Scgd
19849dbe234Sjoerg static int julian;
19949dbe234Sjoerg static int dow;
20049dbe234Sjoerg static int hilite;
20149dbe234Sjoerg static const char *md, *me;
20261f28255Scgd
20349dbe234Sjoerg static void init_hilite(void);
20449dbe234Sjoerg static int getnum(const char *);
20549dbe234Sjoerg static void gregorian_reform(const char *);
20649dbe234Sjoerg static void reform_day_array(int, int, int *, int *, int *,int *,int *,int *);
20749dbe234Sjoerg static int ascii_day(char *, int);
20849dbe234Sjoerg static void center(const char *, int, int);
20949dbe234Sjoerg static void day_array(int, int, int *);
21049dbe234Sjoerg static int day_in_week(int, int, int);
21149dbe234Sjoerg static int day_in_year(int, int, int);
21249dbe234Sjoerg static void monthrange(int, int, int, int, int);
21349dbe234Sjoerg static void trim_trailing_spaces(char *);
21449dbe234Sjoerg __dead static void usage(void);
2152db3772cSglass
2162db3772cSglass int
main(int argc,char ** argv)217534ffc6dSperry main(int argc, char **argv)
21861f28255Scgd {
21961f28255Scgd struct tm *local_time;
2202db3772cSglass time_t now;
221c98485dbSjoerg int ch, yflag;
222c98485dbSjoerg long month, year;
223cfda7110Satatat int before, after, use_reform;
224c81873c7Syamt int yearly = 0;
225c98485dbSjoerg char *when, *eoi;
22661f28255Scgd
227c81873c7Syamt before = after = 0;
228cfda7110Satatat use_reform = yflag = year = 0;
229cfda7110Satatat when = NULL;
230819d4545Schristos while ((ch = getopt(argc, argv, "A:B:C:d:hjR:ry3")) != -1) {
23161f28255Scgd switch (ch) {
232c81873c7Syamt case 'A':
233c81873c7Syamt after = getnum(optarg);
2345e6732ebSdholland if (after < 0)
2355e6732ebSdholland errx(1, "Argument to -A must be positive");
236c81873c7Syamt break;
237c81873c7Syamt case 'B':
238c81873c7Syamt before = getnum(optarg);
2395e6732ebSdholland if (before < 0)
2405e6732ebSdholland errx(1, "Argument to -B must be positive");
241c81873c7Syamt break;
242819d4545Schristos case 'C':
243819d4545Schristos after = before = getnum(optarg);
244819d4545Schristos if (after < 0)
245819d4545Schristos errx(1, "Argument to -C must be positive");
246819d4545Schristos break;
247cfda7110Satatat case 'd':
248cfda7110Satatat dow = getnum(optarg);
249cfda7110Satatat if (dow < 0 || dow > 6)
250cfda7110Satatat errx(1, "illegal day of week value: use 0-6");
251cfda7110Satatat break;
25285cee2b4Satatat case 'h':
25385cee2b4Satatat init_hilite();
25485cee2b4Satatat break;
25561f28255Scgd case 'j':
25661f28255Scgd julian = 1;
25761f28255Scgd break;
258cfda7110Satatat case 'R':
259cfda7110Satatat when = optarg;
260cfda7110Satatat break;
261cfda7110Satatat case 'r':
262cfda7110Satatat use_reform = 1;
263cfda7110Satatat break;
26461f28255Scgd case 'y':
26561f28255Scgd yflag = 1;
26661f28255Scgd break;
267037dfc0eSperry case '3':
268c81873c7Syamt before = after = 1;
269037dfc0eSperry break;
27061f28255Scgd case '?':
27161f28255Scgd default:
27261f28255Scgd usage();
273534ffc6dSperry /* NOTREACHED */
274534ffc6dSperry }
27561f28255Scgd }
276037dfc0eSperry
27761f28255Scgd argc -= optind;
27861f28255Scgd argv += optind;
27961f28255Scgd
280cfda7110Satatat if (when != NULL)
281cfda7110Satatat gregorian_reform(when);
282cfda7110Satatat if (reform == NULL)
283cfda7110Satatat gregorian_reform("DEFAULT");
284cfda7110Satatat
28561f28255Scgd month = 0;
28661f28255Scgd switch (argc) {
28761f28255Scgd case 2:
288c98485dbSjoerg month = strtol(*argv++, &eoi, 10);
289c98485dbSjoerg if (month < 1 || month > 12 || *eoi != '\0')
2902db3772cSglass errx(1, "illegal month value: use 1-12");
291c98485dbSjoerg year = strtol(*argv, &eoi, 10);
292c98485dbSjoerg if (year < 1 || year > 9999 || *eoi != '\0')
2932db3772cSglass errx(1, "illegal year value: use 1-9999");
29461f28255Scgd break;
295c98485dbSjoerg case 1:
296c98485dbSjoerg year = strtol(*argv, &eoi, 10);
297358c956dSjoerg if (year < 1 || year > 9999 || (*eoi != '\0' && *eoi != '/' && *eoi != '-'))
298c98485dbSjoerg errx(1, "illegal year value: use 1-9999");
299358c956dSjoerg if (*eoi != '\0') {
300c98485dbSjoerg month = strtol(eoi + 1, &eoi, 10);
301c98485dbSjoerg if (month < 1 || month > 12 || *eoi != '\0')
302c98485dbSjoerg errx(1, "illegal month value: use 1-12");
303c98485dbSjoerg }
304c98485dbSjoerg break;
30561f28255Scgd case 0:
30661f28255Scgd (void)time(&now);
30761f28255Scgd local_time = localtime(&now);
308cfda7110Satatat if (use_reform)
309cfda7110Satatat year = reform->year;
310cfda7110Satatat else
3117bc244d5Schristos year = local_time->tm_year + TM_YEAR_BASE;
312cfda7110Satatat if (!yflag) {
313cfda7110Satatat if (use_reform)
314cfda7110Satatat month = reform->month;
315cfda7110Satatat else
31661f28255Scgd month = local_time->tm_mon + 1;
317cfda7110Satatat }
31861f28255Scgd break;
31961f28255Scgd default:
32061f28255Scgd usage();
32161f28255Scgd }
32261f28255Scgd
323c81873c7Syamt if (!month) {
324c81873c7Syamt /* yearly */
325c81873c7Syamt month = 1;
326c81873c7Syamt before = 0;
327c81873c7Syamt after = 11;
328c81873c7Syamt yearly = 1;
329c81873c7Syamt }
330c81873c7Syamt
331c81873c7Syamt monthrange(month, year, before, after, yearly);
332037dfc0eSperry
33361f28255Scgd exit(0);
33461f28255Scgd }
33561f28255Scgd
33661f28255Scgd #define DAY_LEN 3 /* 3 spaces per day */
33761f28255Scgd #define J_DAY_LEN 4 /* 4 spaces per day */
33861f28255Scgd #define WEEK_LEN 20 /* 7 * 3 - one space at the end */
33961f28255Scgd #define J_WEEK_LEN 27 /* 7 * 4 - one space at the end */
34061f28255Scgd #define HEAD_SEP 2 /* spaces between day headings */
34161f28255Scgd #define J_HEAD_SEP 2
342c81873c7Syamt #define MONTH_PER_ROW 3 /* how many monthes in a row */
343c81873c7Syamt #define J_MONTH_PER_ROW 2
34461f28255Scgd
34549dbe234Sjoerg static void
monthrange(int month,int year,int before,int after,int yearly)346c81873c7Syamt monthrange(int month, int year, int before, int after, int yearly)
34761f28255Scgd {
348c81873c7Syamt int startmonth, startyear;
349c81873c7Syamt int endmonth, endyear;
350c81873c7Syamt int i, row;
351c81873c7Syamt int days[3][MAXDAYS];
35285cee2b4Satatat char lineout[256];
353c81873c7Syamt int inayear;
354c81873c7Syamt int newyear;
355c81873c7Syamt int day_len, week_len, head_sep;
356c81873c7Syamt int month_per_row;
35785cee2b4Satatat int skip, r_off, w_off;
35861f28255Scgd
359c81873c7Syamt if (julian) {
360c81873c7Syamt day_len = J_DAY_LEN;
361c81873c7Syamt week_len = J_WEEK_LEN;
362c81873c7Syamt head_sep = J_HEAD_SEP;
363c81873c7Syamt month_per_row = J_MONTH_PER_ROW;
36461f28255Scgd }
365c81873c7Syamt else {
366c81873c7Syamt day_len = DAY_LEN;
367c81873c7Syamt week_len = WEEK_LEN;
368c81873c7Syamt head_sep = HEAD_SEP;
369c81873c7Syamt month_per_row = MONTH_PER_ROW;
37061f28255Scgd }
37161f28255Scgd
372037dfc0eSperry month--;
373037dfc0eSperry
374c81873c7Syamt startyear = year - (before + 12 - 1 - month) / 12;
375c81873c7Syamt startmonth = 12 - 1 - ((before + 12 - 1 - month) % 12);
376c81873c7Syamt endyear = year + (month + after) / 12;
377c81873c7Syamt endmonth = (month + after) % 12;
378037dfc0eSperry
379c81873c7Syamt if (startyear < 0 || endyear > 9999) {
380ff532697Schristos errx(1, "year should be in 1-9999");
381c81873c7Syamt }
382037dfc0eSperry
383c81873c7Syamt year = startyear;
384c81873c7Syamt month = startmonth;
385c81873c7Syamt inayear = newyear = (year != endyear || yearly);
386c81873c7Syamt if (inayear) {
387c81873c7Syamt skip = month % month_per_row;
388c81873c7Syamt month -= skip;
389c81873c7Syamt }
390c81873c7Syamt else {
391c81873c7Syamt skip = 0;
392c81873c7Syamt }
393c81873c7Syamt
394c81873c7Syamt do {
395c81873c7Syamt if (newyear) {
396c81873c7Syamt (void)snprintf(lineout, sizeof(lineout), "%d", year);
397c81873c7Syamt center(lineout, week_len * month_per_row +
398c81873c7Syamt head_sep * (month_per_row - 1), 0);
399c81873c7Syamt (void)printf("\n\n");
400c81873c7Syamt newyear = 0;
401c81873c7Syamt }
402c81873c7Syamt
403c81873c7Syamt for (i = 0; i < skip; i++)
404c81873c7Syamt center("", week_len, head_sep);
405c81873c7Syamt
406c81873c7Syamt for (; i < month_per_row; i++) {
407c81873c7Syamt int sep;
408c81873c7Syamt
409c81873c7Syamt if (year == endyear && month + i > endmonth)
410c81873c7Syamt break;
411c81873c7Syamt
412c81873c7Syamt sep = (i == month_per_row - 1) ? 0 : head_sep;
413c81873c7Syamt day_array(month + i + 1, year, days[i]);
414c81873c7Syamt if (inayear) {
415c81873c7Syamt center(month_names[month + i], week_len, sep);
416c81873c7Syamt }
417c81873c7Syamt else {
418c81873c7Syamt snprintf(lineout, sizeof(lineout), "%s %d",
419c81873c7Syamt month_names[month + i], year);
420c81873c7Syamt center(lineout, week_len, sep);
421c81873c7Syamt }
422c81873c7Syamt }
423c81873c7Syamt printf("\n");
424c81873c7Syamt
425c81873c7Syamt for (i = 0; i < skip; i++)
426c81873c7Syamt center("", week_len, head_sep);
427c81873c7Syamt
428c81873c7Syamt for (; i < month_per_row; i++) {
429c81873c7Syamt int sep;
430c81873c7Syamt
431c81873c7Syamt if (year == endyear && month + i > endmonth)
432c81873c7Syamt break;
433c81873c7Syamt
434c81873c7Syamt sep = (i == month_per_row - 1) ? 0 : head_sep;
435cfda7110Satatat if (dow) {
436cfda7110Satatat printf("%s ", (julian) ?
437cfda7110Satatat j_day_headings + 4 * dow :
438cfda7110Satatat day_headings + 3 * dow);
439cfda7110Satatat printf("%.*s", dow * (julian ? 4 : 3) - 1,
440cfda7110Satatat (julian) ? j_day_headings : day_headings);
441cfda7110Satatat } else
442cfda7110Satatat printf("%s", (julian) ? j_day_headings : day_headings);
443cfda7110Satatat printf("%*s", sep, "");
444c81873c7Syamt }
445c81873c7Syamt printf("\n");
446c81873c7Syamt
447037dfc0eSperry for (row = 0; row < 6; row++) {
448658ed336Slukem char *p = NULL;
449808b43aeSyamt
450808b43aeSyamt memset(lineout, ' ', sizeof(lineout));
451c81873c7Syamt for (i = 0; i < skip; i++) {
452808b43aeSyamt /* nothing */
453c81873c7Syamt }
45485cee2b4Satatat w_off = 0;
455c81873c7Syamt for (; i < month_per_row; i++) {
456c81873c7Syamt int col, *dp;
457c81873c7Syamt
458c81873c7Syamt if (year == endyear && month + i > endmonth)
459c81873c7Syamt break;
460c81873c7Syamt
46185cee2b4Satatat p = lineout + i * (week_len + 2) + w_off;
462c81873c7Syamt dp = &days[i][row * 7];
46385cee2b4Satatat for (col = 0; col < 7;
46485cee2b4Satatat col++, p += day_len + r_off) {
46585cee2b4Satatat r_off = ascii_day(p, *dp++);
46685cee2b4Satatat w_off += r_off;
46785cee2b4Satatat }
468037dfc0eSperry }
469037dfc0eSperry *p = '\0';
470037dfc0eSperry trim_trailing_spaces(lineout);
471037dfc0eSperry (void)printf("%s\n", lineout);
472037dfc0eSperry }
473c81873c7Syamt
474c81873c7Syamt skip = 0;
475c81873c7Syamt month += month_per_row;
476c81873c7Syamt if (month >= 12) {
477c81873c7Syamt month -= 12;
478c81873c7Syamt year++;
479c81873c7Syamt newyear = 1;
480c81873c7Syamt }
481c81873c7Syamt } while (year < endyear || (year == endyear && month <= endmonth));
482037dfc0eSperry }
483037dfc0eSperry
48461f28255Scgd /*
48561f28255Scgd * day_array --
48661f28255Scgd * Fill in an array of 42 integers with a calendar. Assume for a moment
48761f28255Scgd * that you took the (maximum) 6 rows in a calendar and stretched them
48861f28255Scgd * out end to end. You would have 42 numbers or spaces. This routine
48961f28255Scgd * builds that array for any month from Jan. 1 through Dec. 9999.
49061f28255Scgd */
49149dbe234Sjoerg static void
day_array(int month,int year,int * days)492534ffc6dSperry day_array(int month, int year, int *days)
49361f28255Scgd {
4942db3772cSglass int day, dw, dm;
49585cee2b4Satatat time_t t;
49685cee2b4Satatat struct tm *tm;
49785cee2b4Satatat
49885cee2b4Satatat t = time(NULL);
49985cee2b4Satatat tm = localtime(&t);
50085cee2b4Satatat tm->tm_year += TM_YEAR_BASE;
50185cee2b4Satatat tm->tm_mon++;
50285cee2b4Satatat tm->tm_yday++; /* jan 1 is 1 for us, not 0 */
50361f28255Scgd
504cfda7110Satatat for (dm = month + year * 12, dw = 0; dw < 4; dw++) {
505cfda7110Satatat if (dm == shift_days[julian][dw][MAXDAYS]) {
506cfda7110Satatat memmove(days, shift_days[julian][dw],
507cfda7110Satatat MAXDAYS * sizeof(int));
50861f28255Scgd return;
50961f28255Scgd }
510cfda7110Satatat }
511cfda7110Satatat
5122db3772cSglass memmove(days, empty, MAXDAYS * sizeof(int));
51361f28255Scgd dm = days_in_month[leap_year(year)][month];
51461f28255Scgd dw = day_in_week(1, month, year);
51561f28255Scgd day = julian ? day_in_year(1, month, year) : 1;
51685cee2b4Satatat while (dm--) {
51785cee2b4Satatat if (hilite && year == tm->tm_year &&
51885cee2b4Satatat (julian ? (day == tm->tm_yday) :
51985cee2b4Satatat (month == tm->tm_mon && day == tm->tm_mday)))
520cfda7110Satatat days[dw++] = SPACE - day++;
52185cee2b4Satatat else
52261f28255Scgd days[dw++] = day++;
52361f28255Scgd }
52485cee2b4Satatat }
52561f28255Scgd
52661f28255Scgd /*
52761f28255Scgd * day_in_year --
52861f28255Scgd * return the 1 based day number within the year
52961f28255Scgd */
53049dbe234Sjoerg static int
day_in_year(int day,int month,int year)531534ffc6dSperry day_in_year(int day, int month, int year)
53261f28255Scgd {
5332db3772cSglass int i, leap;
53461f28255Scgd
53561f28255Scgd leap = leap_year(year);
53661f28255Scgd for (i = 1; i < month; i++)
53761f28255Scgd day += days_in_month[leap][i];
53861f28255Scgd return (day);
53961f28255Scgd }
54061f28255Scgd
54161f28255Scgd /*
54261f28255Scgd * day_in_week
54361f28255Scgd * return the 0 based day number for any date from 1 Jan. 1 to
544cfda7110Satatat * 31 Dec. 9999. Returns the day of the week of the first
545cfda7110Satatat * missing day for any given Gregorian shift.
54661f28255Scgd */
54749dbe234Sjoerg static int
day_in_week(int day,int month,int year)548534ffc6dSperry day_in_week(int day, int month, int year)
54961f28255Scgd {
55061f28255Scgd long temp;
55161f28255Scgd
55261f28255Scgd temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
55361f28255Scgd + day_in_year(day, month, year);
55461f28255Scgd if (temp < FIRST_MISSING_DAY)
555cfda7110Satatat return ((temp - dow + 6 + SATURDAY) % 7);
55661f28255Scgd if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
557cfda7110Satatat return (((temp - dow + 6 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
558cfda7110Satatat return ((FIRST_MISSING_DAY - dow + 6 + SATURDAY) % 7);
55961f28255Scgd }
56061f28255Scgd
56149dbe234Sjoerg static int
ascii_day(char * p,int day)562534ffc6dSperry ascii_day(char *p, int day)
56361f28255Scgd {
56485cee2b4Satatat int display, val, rc;
56585cee2b4Satatat char *b;
56653d5aab9Slukem static const char *aday[] = {
56761f28255Scgd "",
56861f28255Scgd " 1", " 2", " 3", " 4", " 5", " 6", " 7",
56961f28255Scgd " 8", " 9", "10", "11", "12", "13", "14",
57061f28255Scgd "15", "16", "17", "18", "19", "20", "21",
57161f28255Scgd "22", "23", "24", "25", "26", "27", "28",
57261f28255Scgd "29", "30", "31",
57361f28255Scgd };
57461f28255Scgd
57561f28255Scgd if (day == SPACE) {
57661f28255Scgd memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN);
57785cee2b4Satatat return (0);
57861f28255Scgd }
579cfda7110Satatat if (day < SPACE) {
58085cee2b4Satatat b = p;
581cfda7110Satatat day = SPACE - day;
58285cee2b4Satatat } else
58385cee2b4Satatat b = NULL;
58461f28255Scgd if (julian) {
585b4d27c3aSlukem if ((val = day / 100) != 0) {
58661f28255Scgd day %= 100;
58761f28255Scgd *p++ = val + '0';
58861f28255Scgd display = 1;
58961f28255Scgd } else {
59061f28255Scgd *p++ = ' ';
59161f28255Scgd display = 0;
59261f28255Scgd }
59361f28255Scgd val = day / 10;
59461f28255Scgd if (val || display)
59561f28255Scgd *p++ = val + '0';
59661f28255Scgd else
59761f28255Scgd *p++ = ' ';
59861f28255Scgd *p++ = day % 10 + '0';
59961f28255Scgd } else {
60061f28255Scgd *p++ = aday[day][0];
60161f28255Scgd *p++ = aday[day][1];
60261f28255Scgd }
60385cee2b4Satatat
60485cee2b4Satatat rc = 0;
60585cee2b4Satatat if (b != NULL) {
60698eb8895Sroy const char *t;
60798eb8895Sroy char h[64];
60885cee2b4Satatat int l;
60985cee2b4Satatat
61085cee2b4Satatat l = p - b;
61185cee2b4Satatat memcpy(h, b, l);
61285cee2b4Satatat p = b;
61385cee2b4Satatat
61485cee2b4Satatat if (md != NULL) {
61585cee2b4Satatat for (t = md; *t; rc++)
61685cee2b4Satatat *p++ = *t++;
61785cee2b4Satatat memcpy(p, h, l);
61885cee2b4Satatat p += l;
61985cee2b4Satatat for (t = me; *t; rc++)
62085cee2b4Satatat *p++ = *t++;
62185cee2b4Satatat } else {
62285cee2b4Satatat for (t = &h[0]; l--; t++) {
62385cee2b4Satatat *p++ = *t;
62485cee2b4Satatat rc++;
62585cee2b4Satatat *p++ = '\b';
62685cee2b4Satatat rc++;
62785cee2b4Satatat *p++ = *t;
62885cee2b4Satatat }
62985cee2b4Satatat }
63085cee2b4Satatat }
63185cee2b4Satatat
63261f28255Scgd *p = ' ';
63385cee2b4Satatat return (rc);
63461f28255Scgd }
63561f28255Scgd
63649dbe234Sjoerg static void
trim_trailing_spaces(char * s)637534ffc6dSperry trim_trailing_spaces(char *s)
63861f28255Scgd {
6392db3772cSglass char *p;
64061f28255Scgd
6412db3772cSglass for (p = s; *p; ++p)
6422db3772cSglass continue;
643ac193186Schristos while (p > s && isspace((unsigned char)*--p))
6442db3772cSglass continue;
64561f28255Scgd if (p > s)
64661f28255Scgd ++p;
64761f28255Scgd *p = '\0';
64861f28255Scgd }
64961f28255Scgd
65049dbe234Sjoerg static void
center(const char * str,int len,int separate)65153d5aab9Slukem center(const char *str, int len, int separate)
65261f28255Scgd {
6532db3772cSglass
65461f28255Scgd len -= strlen(str);
65561f28255Scgd (void)printf("%*s%s%*s", len / 2, "", str, len / 2 + len % 2, "");
65661f28255Scgd if (separate)
65761f28255Scgd (void)printf("%*s", separate, "");
65861f28255Scgd }
65961f28255Scgd
660cfda7110Satatat /*
661cfda7110Satatat * gregorian_reform --
662cfda7110Satatat * Given a description of date on which the Gregorian Reform was
663cfda7110Satatat * applied. The argument can be any of the "country" names
664cfda7110Satatat * listed in the reforms array (case insensitive) or a date of
665cfda7110Satatat * the form YYYY/MM/DD. The date and month can be omitted if
666cfda7110Satatat * doing so would not select more than one different built-in
667cfda7110Satatat * reform point.
668cfda7110Satatat */
66949dbe234Sjoerg static void
gregorian_reform(const char * p)670cfda7110Satatat gregorian_reform(const char *p)
671cfda7110Satatat {
672cfda7110Satatat int year, month, date;
673cfda7110Satatat int i, days, diw, diy;
674cfda7110Satatat char c;
675cfda7110Satatat
676cfda7110Satatat i = sscanf(p, "%d%*[/,-]%d%*[/,-]%d%c", &year, &month, &date, &c);
677cfda7110Satatat switch (i) {
678cfda7110Satatat case 4:
679cfda7110Satatat /*
680cfda7110Satatat * If the character was sscanf()ed, then there's more
681cfda7110Satatat * stuff than we need.
682cfda7110Satatat */
683cfda7110Satatat errx(1, "date specifier %s invalid", p);
684cfda7110Satatat case 0:
685cfda7110Satatat /*
686cfda7110Satatat * Not a form we can sscanf(), so void these, and we
687cfda7110Satatat * can try matching "country" names later.
688cfda7110Satatat */
689cfda7110Satatat year = month = date = -1;
690cfda7110Satatat break;
691cfda7110Satatat case 1:
692cfda7110Satatat month = 0;
693cfda7110Satatat /*FALLTHROUGH*/
694cfda7110Satatat case 2:
695cfda7110Satatat date = 0;
696cfda7110Satatat /*FALLTHROUGH*/
697cfda7110Satatat case 3:
698cfda7110Satatat /*
699cfda7110Satatat * At last, some sanity checking on the values we were
700cfda7110Satatat * given.
701cfda7110Satatat */
702cfda7110Satatat if (year < 1 || year > 9999)
703cfda7110Satatat errx(1, "%d: illegal year value: use 1-9999", year);
704cfda7110Satatat if (i > 1 && (month < 1 || month > 12))
705cfda7110Satatat errx(1, "%d: illegal month value: use 1-12", month);
706cfda7110Satatat if ((i == 3 && date < 1) || date < 0 ||
707cfda7110Satatat date > days_in_month[1][month])
708cfda7110Satatat /*
709cfda7110Satatat * What about someone specifying a leap day in
710cfda7110Satatat * a non-leap year? Well...that's a tricky
711cfda7110Satatat * one. We can't yet *say* whether the year
712cfda7110Satatat * in question is a leap year. What if the
713cfda7110Satatat * date given was, for example, 1700/2/29? is
714cfda7110Satatat * that a valid leap day?
715cfda7110Satatat *
716cfda7110Satatat * So...we punt, and hope that saying 29 in
717cfda7110Satatat * the case of February isn't too bad an idea.
718cfda7110Satatat */
719cfda7110Satatat errx(1, "%d: illegal date value: use 1-%d", date,
720cfda7110Satatat days_in_month[1][month]);
721cfda7110Satatat break;
722cfda7110Satatat }
723cfda7110Satatat
724cfda7110Satatat /*
725cfda7110Satatat * A complete date was specified, so use the other pope.
726cfda7110Satatat */
727cfda7110Satatat if (date > 0) {
728cfda7110Satatat static struct reform Goestheveezl;
729cfda7110Satatat
730cfda7110Satatat reform = &Goestheveezl;
731cfda7110Satatat reform->country = "Bompzidaize";
732cfda7110Satatat reform->year = year;
733cfda7110Satatat reform->month = month;
734cfda7110Satatat reform->date = date;
735cfda7110Satatat }
736cfda7110Satatat
737cfda7110Satatat /*
738cfda7110Satatat * No date information was specified, so let's try to match on
739cfda7110Satatat * country name.
740cfda7110Satatat */
741cfda7110Satatat else if (year == -1) {
742cfda7110Satatat for (reform = &reforms[0]; reform->year; reform++) {
743cfda7110Satatat if (strcasecmp(p, reform->country) == 0)
744cfda7110Satatat break;
745cfda7110Satatat }
746cfda7110Satatat }
747cfda7110Satatat
748cfda7110Satatat /*
749cfda7110Satatat * We have *some* date information, but not a complete date.
750cfda7110Satatat * Let's see if we have enough to pick a single entry from the
751cfda7110Satatat * list that's not ambiguous.
752cfda7110Satatat */
753cfda7110Satatat else {
754cfda7110Satatat for (reform = &reforms[0]; reform->year; reform++) {
755cfda7110Satatat if ((year == 0 || year == reform->year) &&
756cfda7110Satatat (month == 0 || month == reform->month) &&
757cfda7110Satatat (date == 0 || month == reform->date))
758cfda7110Satatat break;
759cfda7110Satatat }
760cfda7110Satatat
761cfda7110Satatat if (i <= reform->ambiguity)
762cfda7110Satatat errx(1, "%s: ambiguous short reform date specification", p);
763cfda7110Satatat }
764cfda7110Satatat
765cfda7110Satatat /*
766cfda7110Satatat * Oops...we reached the end of the list.
767cfda7110Satatat */
768cfda7110Satatat if (reform->year == 0)
769cfda7110Satatat errx(1, "reform name %s invalid", p);
770cfda7110Satatat
771cfda7110Satatat /*
772cfda7110Satatat *
773cfda7110Satatat */
774cfda7110Satatat reform->missing_days =
775cfda7110Satatat j_leap_days(reform->year, reform->month, reform->date) -
776cfda7110Satatat g_leap_days(reform->year, reform->month, reform->date) -
777cfda7110Satatat GREGORIAN_MAGIC;
778cfda7110Satatat
779cfda7110Satatat reform->first_missing_day =
780cfda7110Satatat (reform->year - 1) * 365 +
781cfda7110Satatat day_in_year(reform->date, reform->month, reform->year) +
782cfda7110Satatat date +
783cfda7110Satatat j_leap_days(reform->year, reform->month, reform->date);
784cfda7110Satatat
785cfda7110Satatat /*
786cfda7110Satatat * Once we know the day of the week of the first missing day,
787cfda7110Satatat * skip back to the first of the month's day of the week.
788cfda7110Satatat */
789cfda7110Satatat diw = day_in_week(reform->date, reform->month, reform->year);
790cfda7110Satatat diw = (diw + 8 - (reform->date % 7)) % 7;
791cfda7110Satatat diy = day_in_year(1, reform->month, reform->year);
792cfda7110Satatat
793cfda7110Satatat /*
794cfda7110Satatat * We might need all four of these (if you switch from Julian
795cfda7110Satatat * to Gregorian at some point after 9900, you get a gap of 73
796cfda7110Satatat * days, and that can affect four months), and it doesn't hurt
797cfda7110Satatat * all that much to precompute them, so there.
798cfda7110Satatat */
799cfda7110Satatat date = 1;
800cfda7110Satatat days = 0;
801cfda7110Satatat for (i = 0; i < 4; i++)
802cfda7110Satatat reform_day_array(reform->month + i, reform->year,
803cfda7110Satatat &days, &date, &diw, &diy,
804cfda7110Satatat shift_days[0][i],
805cfda7110Satatat shift_days[1][i]);
806cfda7110Satatat }
807cfda7110Satatat
808cfda7110Satatat /*
809cfda7110Satatat * reform_day_array --
810cfda7110Satatat * Pre-calculates the given month's calendar (in both "standard"
811cfda7110Satatat * and "julian day" representations) with respect for days
812cfda7110Satatat * skipped during a reform period.
813cfda7110Satatat */
81449dbe234Sjoerg static void
reform_day_array(int month,int year,int * done,int * date,int * diw,int * diy,int * scal,int * jcal)815cfda7110Satatat reform_day_array(int month, int year, int *done, int *date, int *diw, int *diy,
816cfda7110Satatat int *scal, int *jcal)
817cfda7110Satatat {
818cfda7110Satatat int mdays;
819cfda7110Satatat
820cfda7110Satatat /*
821cfda7110Satatat * If the reform was in the month of october or later, then
822cfda7110Satatat * the month number from the caller could "overflow".
823cfda7110Satatat */
824cfda7110Satatat if (month > 12) {
825cfda7110Satatat month -= 12;
826cfda7110Satatat year++;
827cfda7110Satatat }
828cfda7110Satatat
829cfda7110Satatat /*
830cfda7110Satatat * Erase months, and set crib number. The crib number is used
831cfda7110Satatat * later to determine if the month to be displayed is here or
832cfda7110Satatat * should be built on the fly with the generic routine
833cfda7110Satatat */
834cfda7110Satatat memmove(scal, empty, MAXDAYS * sizeof(int));
835cfda7110Satatat scal[MAXDAYS] = month + year * 12;
836cfda7110Satatat memmove(jcal, empty, MAXDAYS * sizeof(int));
837cfda7110Satatat jcal[MAXDAYS] = month + year * 12;
838cfda7110Satatat
839cfda7110Satatat /*
840cfda7110Satatat * It doesn't matter what the actual month is when figuring
841cfda7110Satatat * out if this is a leap year or not, just so long as February
842cfda7110Satatat * gets the right number of days in it.
843cfda7110Satatat */
844cfda7110Satatat mdays = days_in_month[g_leap_year(year, 3, 1)][month];
845cfda7110Satatat
846cfda7110Satatat /*
847cfda7110Satatat * Bounce back to the first "row" in the day array, and fill
848cfda7110Satatat * in any days that actually occur.
849cfda7110Satatat */
850cfda7110Satatat for (*diw %= 7; (*date - *done) <= mdays; (*date)++, (*diy)++) {
851cfda7110Satatat /*
852cfda7110Satatat * "date" doesn't get reset by the caller across calls
853cfda7110Satatat * to this routine, so we can actually tell that we're
854cfda7110Satatat * looking at April the 41st. Much easier than trying
855cfda7110Satatat * to calculate the absolute julian day for a given
856cfda7110Satatat * date and then checking that.
857cfda7110Satatat */
858cfda7110Satatat if (*date < reform->date ||
859cfda7110Satatat *date >= reform->date + reform->missing_days) {
860cfda7110Satatat scal[*diw] = *date - *done;
861cfda7110Satatat jcal[*diw] = *diy;
862cfda7110Satatat (*diw)++;
863cfda7110Satatat }
864cfda7110Satatat }
865cfda7110Satatat *done += mdays;
866cfda7110Satatat }
867cfda7110Satatat
86849dbe234Sjoerg static int
getnum(const char * p)869c81873c7Syamt getnum(const char *p)
870c81873c7Syamt {
87153d5aab9Slukem unsigned long result;
872c81873c7Syamt char *ep;
873c81873c7Syamt
874c81873c7Syamt errno = 0;
875c81873c7Syamt result = strtoul(p, &ep, 10);
876c81873c7Syamt if (p[0] == '\0' || *ep != '\0')
877c81873c7Syamt goto error;
878c81873c7Syamt if (errno == ERANGE && result == ULONG_MAX)
879c81873c7Syamt goto error;
880c81873c7Syamt if (result > INT_MAX)
881c81873c7Syamt goto error;
882c81873c7Syamt
883c81873c7Syamt return (int)result;
884c81873c7Syamt
885c81873c7Syamt error:
886c81873c7Syamt errx(1, "bad number: %s", p);
887c81873c7Syamt /*NOTREACHED*/
888c81873c7Syamt }
889c81873c7Syamt
89049dbe234Sjoerg static void
init_hilite(void)89185cee2b4Satatat init_hilite(void)
89285cee2b4Satatat {
89353d5aab9Slukem const char *term;
89498eb8895Sroy int errret;
89585cee2b4Satatat
89685cee2b4Satatat hilite++;
89785cee2b4Satatat
89885cee2b4Satatat if (!isatty(fileno(stdout)))
89985cee2b4Satatat return;
90085cee2b4Satatat
90153d5aab9Slukem term = getenv("TERM");
90253d5aab9Slukem if (term == NULL)
90353d5aab9Slukem term = "dumb";
90498eb8895Sroy if (setupterm(term, fileno(stdout), &errret) != 0 && errret != 1)
90585cee2b4Satatat return;
90685cee2b4Satatat
90798eb8895Sroy if (hilite > 1)
90898eb8895Sroy md = enter_reverse_mode;
90998eb8895Sroy else
91098eb8895Sroy md = enter_bold_mode;
91198eb8895Sroy me = exit_attribute_mode;
91285cee2b4Satatat if (me == NULL || md == NULL)
91385cee2b4Satatat md = me = NULL;
91485cee2b4Satatat }
91585cee2b4Satatat
91649dbe234Sjoerg static void
usage(void)917534ffc6dSperry usage(void)
91861f28255Scgd {
9192db3772cSglass
920c81873c7Syamt (void)fprintf(stderr,
921819d4545Schristos "usage: cal [-3hjry] [-A after] [-B before] [-C context] [-d day-of-week] "
922cfda7110Satatat "[-R reform-spec]\n [[month] year]\n");
92361f28255Scgd exit(1);
92461f28255Scgd }
925