1 /*        $NetBSD: cal.c,v 1.30 2020/06/29 14:01:14 kim Exp $         */
2 
3 /*
4  * Copyright (c) 1989, 1993, 1994
5  *        The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Kim Letkeman.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 #ifndef lint
37 __COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\
38  The Regents of the University of California.  All rights reserved.");
39 #endif /* not lint */
40 
41 #ifndef lint
42 #if 0
43 static char sccsid[] = "@(#)cal.c       8.4 (Berkeley) 4/2/94";
44 #else
45 __RCSID("$NetBSD: cal.c,v 1.30 2020/06/29 14:01:14 kim Exp $");
46 #endif
47 #endif /* not lint */
48 
49 #include <sys/types.h>
50 
51 #include <ctype.h>
52 #include <err.h>
53 #include <errno.h>
54 #include <limits.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #include <term.h>
59 #include <time.h>
60 #include <tzfile.h>
61 #include <unistd.h>
62 
63 #define   SATURDAY            6                   /* 1 Jan 1 was a Saturday */
64 
65 #define   FIRST_MISSING_DAY   reform->first_missing_day
66 #define   NUMBER_MISSING_DAYS           reform->missing_days
67 
68 #define   MAXDAYS                       42                  /* max slots in a month array */
69 #define   SPACE                         -1                  /* used in day array */
70 
71 static int days_in_month[2][13] = {
72           {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
73           {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
74 };
75 
76 static int empty[MAXDAYS] = {
77           SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,
78           SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,
79           SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,
80           SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,
81           SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,
82           SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,    SPACE,
83 };
84 static int shift_days[2][4][MAXDAYS + 1];
85 
86 static const char *month_names[12] = {
87           "January", "February", "March", "April", "May", "June",
88           "July", "August", "September", "October", "November", "December",
89 };
90 
91 static const char *day_headings = "Su Mo Tu We Th Fr Sa";
92 static const char *j_day_headings = " Su  Mo  Tu  We  Th  Fr  Sa";
93 
94 /* leap years according to the julian calendar */
95 #define j_leap_year(y, m, d) \
96           (((m) > 2) && \
97            !((y) % 4))
98 
99 /* leap years according to the gregorian calendar */
100 #define g_leap_year(y, m, d) \
101           (((m) > 2) && \
102            ((!((y) % 4) && ((y) % 100)) || \
103             !((y) % 400)))
104 
105 /* leap year -- account for gregorian reformation at some point */
106 #define   leap_year(yr) \
107           ((yr) <= reform->year ? j_leap_year((yr), 3, 1) : \
108           g_leap_year((yr), 3, 1))
109 
110 /* number of julian leap days that have passed by a given date */
111 #define j_leap_days(y, m, d) \
112           ((((y) - 1) / 4) + j_leap_year(y, m, d))
113 
114 /* number of gregorian leap days that have passed by a given date */
115 #define g_leap_days(y, m, d) \
116           ((((y) - 1) / 4) - (((y) - 1) / 100) + (((y) - 1) / 400) + \
117           g_leap_year(y, m, d))
118 
119 /*
120  * Subtracting the gregorian leap day count (for a given date) from
121  * the julian leap day count (for the same date) describes the number
122  * of days from the date before the shift to the next date that
123  * appears in the calendar.  Since we want to know the number of
124  * *missing* days, not the number of days that the shift spans, we
125  * subtract 2.
126  *
127  * Alternately...
128  *
129  * There's a reason they call the Dark ages the Dark Ages.  Part of it
130  * is that we don't have that many records of that period of time.
131  * One of the reasons for this is that a lot of the Dark Ages never
132  * actually took place.  At some point in the first millenium A.D., a
133  * ruler of some power decided that he wanted the number of the year
134  * to be different than what it was, so he changed it to coincide
135  * nicely with some event (a birthday or anniversary, perhaps a
136  * wedding, or maybe a centennial for a largish city).  One of the
137  * side effects of this upon the Gregorian reform is that two Julian
138  * leap years (leap days celebrated during centennial years that are
139  * not quatro-centennial years) were skipped.
140  */
141 #define GREGORIAN_MAGIC 2
142 
143 /* number of centuries since the reform, not inclusive */
144 #define   centuries_since_reform(yr) \
145           ((yr) > reform->year ? ((yr) / 100) - (reform->year / 100) : 0)
146 
147 /* number of centuries since the reform whose modulo of 400 is 0 */
148 #define   quad_centuries_since_reform(yr) \
149           ((yr) > reform->year ? ((yr) / 400) - (reform->year / 400) : 0)
150 
151 /* number of leap years between year 1 and this year, not inclusive */
152 #define   leap_years_since_year_1(yr) \
153           ((yr) / 4 - centuries_since_reform(yr) + quad_centuries_since_reform(yr))
154 
155 static struct reform {
156           const char *country;
157           int ambiguity, year, month, date;
158           long first_missing_day;
159           int missing_days;
160           /*
161            * That's 2 for standard/julian display, 4 for months possibly
162            * affected by the Gregorian shift, and MAXDAYS + 1 for the
163            * days that get displayed, plus a crib slot.
164            */
165 } *reform, reforms[] = {
166           { "DEFAULT",                  0, 1752,  9,  3, 0, 0 },
167           { "Italy",                    1, 1582, 10,  5, 0, 0 },
168           { "Spain",                    1, 1582, 10,  5, 0, 0 },
169           { "Portugal",                 1, 1582, 10,  5, 0, 0 },
170           { "Poland",                   1, 1582, 10,  5, 0, 0 },
171           { "France",                   2, 1582, 12, 10, 0, 0 },
172           { "Luxembourg",               2, 1582, 12, 22, 0, 0 },
173           { "Netherlands",    2, 1582, 12, 22, 0, 0 },
174           { "Bavaria",                  0, 1583, 10,  6, 0, 0 },
175           { "Austria",                  2, 1584,  1,  7, 0, 0 },
176           { "Switzerland",    2, 1584,  1, 12, 0, 0 },
177           { "Hungary",                  0, 1587, 10, 22, 0, 0 },
178           { "Germany",                  0, 1700,  2, 19, 0, 0 },
179           { "Norway",                   0, 1700,  2, 19, 0, 0 },
180           { "Denmark",                  0, 1700,  2, 19, 0, 0 },
181           { "Great Britain",  0, 1752,  9,  3, 0, 0 },
182           { "England",                  0, 1752,  9,  3, 0, 0 },
183           { "America",                  0, 1752,  9,  3, 0, 0 },
184           { "Sweden",                   0, 1753,  2, 18, 0, 0 },
185           { "Finland",                  0, 1753,  2, 18, 0, 0 },
186           { "Japan",                    0, 1872, 12, 20, 0, 0 },
187           { "China",                    0, 1911, 11,  7, 0, 0 },
188           { "Bulgaria",                 0, 1916,  4,  1, 0, 0 },
189           { "U.S.S.R.",                 0, 1918,  2,  1, 0, 0 },
190           { "Serbia",                   0, 1919,  1, 19, 0, 0 },
191           { "Romania",                  0, 1919,  1, 19, 0, 0 },
192           { "Greece",                   0, 1924,  3, 10, 0, 0 },
193           { "Turkey",                   0, 1925, 12, 19, 0, 0 },
194           { "Egypt",                    0, 1928,  9, 18, 0, 0 },
195           { NULL,                       0,    0,  0,  0, 0, 0 },
196 };
197 
198 static int julian;
199 static int dow;
200 static int hilite;
201 static const char *md, *me;
202 
203 static void         init_hilite(void);
204 static int          getnum(const char *);
205 static void         gregorian_reform(const char *);
206 static void         reform_day_array(int, int, int *, int *, int *,int *,int *,int *);
207 static int          ascii_day(char *, int);
208 static void         center(const char *, int, int);
209 static void         day_array(int, int, int *);
210 static int          day_in_week(int, int, int);
211 static int          day_in_year(int, int, int);
212 static void         monthrange(int, int, int, int, int);
213 static void         trim_trailing_spaces(char *);
214 __dead static void  usage(void);
215 
216 int
main(int argc,char ** argv)217 main(int argc, char **argv)
218 {
219           struct tm *local_time;
220           time_t now;
221           int ch, yflag;
222           long month, year;
223           int before, after, use_reform;
224           int yearly = 0;
225           char *when, *eoi;
226 
227           before = after = 0;
228           use_reform = yflag = year = 0;
229           when = NULL;
230           while ((ch = getopt(argc, argv, "A:B:C:d:hjR:ry3")) != -1) {
231                     switch (ch) {
232                     case 'A':
233                               after = getnum(optarg);
234                               if (after < 0)
235                                         errx(1, "Argument to -A must be positive");
236                               break;
237                     case 'B':
238                               before = getnum(optarg);
239                               if (before < 0)
240                                         errx(1, "Argument to -B must be positive");
241                               break;
242                     case 'C':
243                               after = before = getnum(optarg);
244                               if (after < 0)
245                                         errx(1, "Argument to -C must be positive");
246                               break;
247                     case 'd':
248                               dow = getnum(optarg);
249                               if (dow < 0 || dow > 6)
250                                         errx(1, "illegal day of week value: use 0-6");
251                               break;
252                     case 'h':
253                               init_hilite();
254                               break;
255                     case 'j':
256                               julian = 1;
257                               break;
258                     case 'R':
259                               when = optarg;
260                               break;
261                     case 'r':
262                               use_reform = 1;
263                               break;
264                     case 'y':
265                               yflag = 1;
266                               break;
267                     case '3':
268                               before = after = 1;
269                               break;
270                     case '?':
271                     default:
272                               usage();
273                               /* NOTREACHED */
274                     }
275           }
276 
277           argc -= optind;
278           argv += optind;
279 
280           if (when != NULL)
281                     gregorian_reform(when);
282           if (reform == NULL)
283                     gregorian_reform("DEFAULT");
284 
285           month = 0;
286           switch (argc) {
287           case 2:
288                     month = strtol(*argv++, &eoi, 10);
289                     if (month < 1 || month > 12 || *eoi != '\0')
290                               errx(1, "illegal month value: use 1-12");
291                     year = strtol(*argv, &eoi, 10);
292                     if (year < 1 || year > 9999 || *eoi != '\0')
293                               errx(1, "illegal year value: use 1-9999");
294                     break;
295           case 1:
296                     year = strtol(*argv, &eoi, 10);
297                     if (year < 1 || year > 9999 || (*eoi != '\0' && *eoi != '/' && *eoi != '-'))
298                               errx(1, "illegal year value: use 1-9999");
299                     if (*eoi != '\0') {
300                               month = strtol(eoi + 1, &eoi, 10);
301                               if (month < 1 || month > 12 || *eoi != '\0')
302                                         errx(1, "illegal month value: use 1-12");
303                     }
304                     break;
305           case 0:
306                     (void)time(&now);
307                     local_time = localtime(&now);
308                     if (use_reform)
309                               year = reform->year;
310                     else
311                               year = local_time->tm_year + TM_YEAR_BASE;
312                     if (!yflag) {
313                               if (use_reform)
314                                         month = reform->month;
315                               else
316                                         month = local_time->tm_mon + 1;
317                     }
318                     break;
319           default:
320                     usage();
321           }
322 
323           if (!month) {
324                     /* yearly */
325                     month = 1;
326                     before = 0;
327                     after = 11;
328                     yearly = 1;
329           }
330 
331           monthrange(month, year, before, after, yearly);
332 
333           exit(0);
334 }
335 
336 #define   DAY_LEN             3                   /* 3 spaces per day */
337 #define   J_DAY_LEN 4                   /* 4 spaces per day */
338 #define   WEEK_LEN  20                  /* 7 * 3 - one space at the end */
339 #define   J_WEEK_LEN          27                  /* 7 * 4 - one space at the end */
340 #define   HEAD_SEP  2                   /* spaces between day headings */
341 #define   J_HEAD_SEP          2
342 #define   MONTH_PER_ROW       3                   /* how many monthes in a row */
343 #define   J_MONTH_PER_ROW     2
344 
345 static void
monthrange(int month,int year,int before,int after,int yearly)346 monthrange(int month, int year, int before, int after, int yearly)
347 {
348           int startmonth, startyear;
349           int endmonth, endyear;
350           int i, row;
351           int days[3][MAXDAYS];
352           char lineout[256];
353           int inayear;
354           int newyear;
355           int day_len, week_len, head_sep;
356           int month_per_row;
357           int skip, r_off, w_off;
358 
359           if (julian) {
360                     day_len = J_DAY_LEN;
361                     week_len = J_WEEK_LEN;
362                     head_sep = J_HEAD_SEP;
363                     month_per_row = J_MONTH_PER_ROW;
364           }
365           else {
366                     day_len = DAY_LEN;
367                     week_len = WEEK_LEN;
368                     head_sep = HEAD_SEP;
369                     month_per_row = MONTH_PER_ROW;
370           }
371 
372           month--;
373 
374           startyear = year - (before + 12 - 1 - month) / 12;
375           startmonth = 12 - 1 - ((before + 12 - 1 - month) % 12);
376           endyear = year + (month + after) / 12;
377           endmonth = (month + after) % 12;
378 
379           if (startyear < 0 || endyear > 9999) {
380                     errx(1, "year should be in 1-9999");
381           }
382 
383           year = startyear;
384           month = startmonth;
385           inayear = newyear = (year != endyear || yearly);
386           if (inayear) {
387                     skip = month % month_per_row;
388                     month -= skip;
389           }
390           else {
391                     skip = 0;
392           }
393 
394           do {
395                     if (newyear) {
396                               (void)snprintf(lineout, sizeof(lineout), "%d", year);
397                               center(lineout, week_len * month_per_row +
398                                   head_sep * (month_per_row - 1), 0);
399                               (void)printf("\n\n");
400                               newyear = 0;
401                     }
402 
403                     for (i = 0; i < skip; i++)
404                               center("", week_len, head_sep);
405 
406                     for (; i < month_per_row; i++) {
407                               int sep;
408 
409                               if (year == endyear && month + i > endmonth)
410                                         break;
411 
412                               sep = (i == month_per_row - 1) ? 0 : head_sep;
413                               day_array(month + i + 1, year, days[i]);
414                               if (inayear) {
415                                         center(month_names[month + i], week_len, sep);
416                               }
417                               else {
418                                         snprintf(lineout, sizeof(lineout), "%s %d",
419                                             month_names[month + i], year);
420                                         center(lineout, week_len, sep);
421                               }
422                     }
423                     printf("\n");
424 
425                     for (i = 0; i < skip; i++)
426                               center("", week_len, head_sep);
427 
428                     for (; i < month_per_row; i++) {
429                               int sep;
430 
431                               if (year == endyear && month + i > endmonth)
432                                         break;
433 
434                               sep = (i == month_per_row - 1) ? 0 : head_sep;
435                               if (dow) {
436                                         printf("%s ", (julian) ?
437                                             j_day_headings + 4 * dow :
438                                             day_headings + 3 * dow);
439                                         printf("%.*s", dow * (julian ? 4 : 3) - 1,
440                                                (julian) ? j_day_headings : day_headings);
441                               } else
442                                         printf("%s", (julian) ? j_day_headings : day_headings);
443                               printf("%*s", sep, "");
444                     }
445                     printf("\n");
446 
447                     for (row = 0; row < 6; row++) {
448                               char *p = NULL;
449 
450                               memset(lineout, ' ', sizeof(lineout));
451                               for (i = 0; i < skip; i++) {
452                                         /* nothing */
453                               }
454                               w_off = 0;
455                               for (; i < month_per_row; i++) {
456                                         int col, *dp;
457 
458                                         if (year == endyear && month + i > endmonth)
459                                                   break;
460 
461                                         p = lineout + i * (week_len + 2) + w_off;
462                                         dp = &days[i][row * 7];
463                                         for (col = 0; col < 7;
464                                              col++, p += day_len + r_off) {
465                                                   r_off = ascii_day(p, *dp++);
466                                                   w_off += r_off;
467                                         }
468                               }
469                               *p = '\0';
470                               trim_trailing_spaces(lineout);
471                               (void)printf("%s\n", lineout);
472                     }
473 
474                     skip = 0;
475                     month += month_per_row;
476                     if (month >= 12) {
477                               month -= 12;
478                               year++;
479                               newyear = 1;
480                     }
481           } while (year < endyear || (year == endyear && month <= endmonth));
482 }
483 
484 /*
485  * day_array --
486  *        Fill in an array of 42 integers with a calendar.  Assume for a moment
487  *        that you took the (maximum) 6 rows in a calendar and stretched them
488  *        out end to end.  You would have 42 numbers or spaces.  This routine
489  *        builds that array for any month from Jan. 1 through Dec. 9999.
490  */
491 static void
day_array(int month,int year,int * days)492 day_array(int month, int year, int *days)
493 {
494           int day, dw, dm;
495           time_t t;
496           struct tm *tm;
497 
498           t = time(NULL);
499           tm = localtime(&t);
500           tm->tm_year += TM_YEAR_BASE;
501           tm->tm_mon++;
502           tm->tm_yday++; /* jan 1 is 1 for us, not 0 */
503 
504           for (dm = month + year * 12, dw = 0; dw < 4; dw++) {
505                     if (dm == shift_days[julian][dw][MAXDAYS]) {
506                               memmove(days, shift_days[julian][dw],
507                                         MAXDAYS * sizeof(int));
508                               return;
509                     }
510           }
511 
512           memmove(days, empty, MAXDAYS * sizeof(int));
513           dm = days_in_month[leap_year(year)][month];
514           dw = day_in_week(1, month, year);
515           day = julian ? day_in_year(1, month, year) : 1;
516           while (dm--) {
517                     if (hilite && year == tm->tm_year &&
518                         (julian ? (day == tm->tm_yday) :
519                          (month == tm->tm_mon && day == tm->tm_mday)))
520                               days[dw++] = SPACE - day++;
521                     else
522                               days[dw++] = day++;
523           }
524 }
525 
526 /*
527  * day_in_year --
528  *        return the 1 based day number within the year
529  */
530 static int
day_in_year(int day,int month,int year)531 day_in_year(int day, int month, int year)
532 {
533           int i, leap;
534 
535           leap = leap_year(year);
536           for (i = 1; i < month; i++)
537                     day += days_in_month[leap][i];
538           return (day);
539 }
540 
541 /*
542  * day_in_week
543  *        return the 0 based day number for any date from 1 Jan. 1 to
544  *        31 Dec. 9999.  Returns the day of the week of the first
545  *        missing day for any given Gregorian shift.
546  */
547 static int
day_in_week(int day,int month,int year)548 day_in_week(int day, int month, int year)
549 {
550           long temp;
551 
552           temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
553               + day_in_year(day, month, year);
554           if (temp < FIRST_MISSING_DAY)
555                     return ((temp - dow + 6 + SATURDAY) % 7);
556           if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
557                     return (((temp - dow + 6 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
558           return ((FIRST_MISSING_DAY - dow + 6 + SATURDAY) % 7);
559 }
560 
561 static int
ascii_day(char * p,int day)562 ascii_day(char *p, int day)
563 {
564           int display, val, rc;
565           char *b;
566           static const char *aday[] = {
567                     "",
568                     " 1", " 2", " 3", " 4", " 5", " 6", " 7",
569                     " 8", " 9", "10", "11", "12", "13", "14",
570                     "15", "16", "17", "18", "19", "20", "21",
571                     "22", "23", "24", "25", "26", "27", "28",
572                     "29", "30", "31",
573           };
574 
575           if (day == SPACE) {
576                     memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN);
577                     return (0);
578           }
579           if (day < SPACE) {
580                     b = p;
581                     day = SPACE - day;
582           } else
583                     b = NULL;
584           if (julian) {
585                     if ((val = day / 100) != 0) {
586                               day %= 100;
587                               *p++ = val + '0';
588                               display = 1;
589                     } else {
590                               *p++ = ' ';
591                               display = 0;
592                     }
593                     val = day / 10;
594                     if (val || display)
595                               *p++ = val + '0';
596                     else
597                               *p++ = ' ';
598                     *p++ = day % 10 + '0';
599           } else {
600                     *p++ = aday[day][0];
601                     *p++ = aday[day][1];
602           }
603 
604           rc = 0;
605           if (b != NULL) {
606                     const char *t;
607                     char h[64];
608                     int l;
609 
610                     l = p - b;
611                     memcpy(h, b, l);
612                     p = b;
613 
614                     if (md != NULL) {
615                               for (t = md; *t; rc++)
616                                         *p++ = *t++;
617                               memcpy(p, h, l);
618                               p += l;
619                               for (t = me; *t; rc++)
620                                         *p++ = *t++;
621                     } else {
622                               for (t = &h[0]; l--; t++) {
623                                         *p++ = *t;
624                                         rc++;
625                                         *p++ = '\b';
626                                         rc++;
627                                         *p++ = *t;
628                               }
629                     }
630           }
631 
632           *p = ' ';
633           return (rc);
634 }
635 
636 static void
trim_trailing_spaces(char * s)637 trim_trailing_spaces(char *s)
638 {
639           char *p;
640 
641           for (p = s; *p; ++p)
642                     continue;
643           while (p > s && isspace((unsigned char)*--p))
644                     continue;
645           if (p > s)
646                     ++p;
647           *p = '\0';
648 }
649 
650 static void
center(const char * str,int len,int separate)651 center(const char *str, int len, int separate)
652 {
653 
654           len -= strlen(str);
655           (void)printf("%*s%s%*s", len / 2, "", str, len / 2 + len % 2, "");
656           if (separate)
657                     (void)printf("%*s", separate, "");
658 }
659 
660 /*
661  * gregorian_reform --
662  *        Given a description of date on which the Gregorian Reform was
663  *        applied.  The argument can be any of the "country" names
664  *        listed in the reforms array (case insensitive) or a date of
665  *        the form YYYY/MM/DD.  The date and month can be omitted if
666  *        doing so would not select more than one different built-in
667  *        reform point.
668  */
669 static void
gregorian_reform(const char * p)670 gregorian_reform(const char *p)
671 {
672           int year, month, date;
673           int i, days, diw, diy;
674           char c;
675 
676           i = sscanf(p, "%d%*[/,-]%d%*[/,-]%d%c", &year, &month, &date, &c);
677           switch (i) {
678           case 4:
679                     /*
680                      * If the character was sscanf()ed, then there's more
681                      * stuff than we need.
682                      */
683                     errx(1, "date specifier %s invalid", p);
684           case 0:
685                     /*
686                      * Not a form we can sscanf(), so void these, and we
687                      * can try matching "country" names later.
688                      */
689                     year = month = date = -1;
690                     break;
691           case 1:
692                     month = 0;
693                     /*FALLTHROUGH*/
694           case 2:
695                     date = 0;
696                     /*FALLTHROUGH*/
697               case 3:
698                     /*
699                      * At last, some sanity checking on the values we were
700                      * given.
701                      */
702                     if (year < 1 || year > 9999)
703                               errx(1, "%d: illegal year value: use 1-9999", year);
704                     if (i > 1 && (month < 1 || month > 12))
705                               errx(1, "%d: illegal month value: use 1-12", month);
706                     if ((i == 3 && date < 1) || date < 0 ||
707                         date > days_in_month[1][month])
708                               /*
709                                * What about someone specifying a leap day in
710                                * a non-leap year?  Well...that's a tricky
711                                * one.  We can't yet *say* whether the year
712                                * in question is a leap year.  What if the
713                                * date given was, for example, 1700/2/29?  is
714                                * that a valid leap day?
715                                *
716                                * So...we punt, and hope that saying 29 in
717                                * the case of February isn't too bad an idea.
718                                */
719                               errx(1, "%d: illegal date value: use 1-%d", date,
720                                    days_in_month[1][month]);
721                     break;
722           }
723 
724           /*
725            * A complete date was specified, so use the other pope.
726            */
727           if (date > 0) {
728                     static struct reform Goestheveezl;
729 
730                     reform = &Goestheveezl;
731                     reform->country = "Bompzidaize";
732                     reform->year = year;
733                     reform->month = month;
734                     reform->date = date;
735           }
736 
737           /*
738            * No date information was specified, so let's try to match on
739            * country name.
740            */
741           else if (year == -1) {
742                     for (reform = &reforms[0]; reform->year; reform++) {
743                               if (strcasecmp(p, reform->country) == 0)
744                                         break;
745                     }
746           }
747 
748           /*
749            * We have *some* date information, but not a complete date.
750            * Let's see if we have enough to pick a single entry from the
751            * list that's not ambiguous.
752            */
753           else {
754                     for (reform = &reforms[0]; reform->year; reform++) {
755                               if ((year == 0 || year == reform->year) &&
756                                   (month == 0 || month == reform->month) &&
757                                   (date == 0 || month == reform->date))
758                                         break;
759                     }
760 
761                     if (i <= reform->ambiguity)
762                               errx(1, "%s: ambiguous short reform date specification", p);
763           }
764 
765           /*
766            * Oops...we reached the end of the list.
767            */
768           if (reform->year == 0)
769                     errx(1, "reform name %s invalid", p);
770 
771           /*
772            *
773            */
774           reform->missing_days =
775                     j_leap_days(reform->year, reform->month, reform->date) -
776                     g_leap_days(reform->year, reform->month, reform->date) -
777                     GREGORIAN_MAGIC;
778 
779           reform->first_missing_day =
780                     (reform->year - 1) * 365 +
781                     day_in_year(reform->date, reform->month, reform->year) +
782                     date +
783                     j_leap_days(reform->year, reform->month, reform->date);
784 
785           /*
786            * Once we know the day of the week of the first missing day,
787            * skip back to the first of the month's day of the week.
788            */
789           diw = day_in_week(reform->date, reform->month, reform->year);
790           diw = (diw + 8 - (reform->date % 7)) % 7;
791           diy = day_in_year(1, reform->month, reform->year);
792 
793           /*
794            * We might need all four of these (if you switch from Julian
795            * to Gregorian at some point after 9900, you get a gap of 73
796            * days, and that can affect four months), and it doesn't hurt
797            * all that much to precompute them, so there.
798            */
799           date = 1;
800           days = 0;
801           for (i = 0; i < 4; i++)
802                     reform_day_array(reform->month + i, reform->year,
803                                          &days, &date, &diw, &diy,
804                                          shift_days[0][i],
805                                          shift_days[1][i]);
806 }
807 
808 /*
809  * reform_day_array --
810  *        Pre-calculates the given month's calendar (in both "standard"
811  *        and "julian day" representations) with respect for days
812  *        skipped during a reform period.
813  */
814 static void
reform_day_array(int month,int year,int * done,int * date,int * diw,int * diy,int * scal,int * jcal)815 reform_day_array(int month, int year, int *done, int *date, int *diw, int *diy,
816           int *scal, int *jcal)
817 {
818           int mdays;
819 
820           /*
821            * If the reform was in the month of october or later, then
822            * the month number from the caller could "overflow".
823            */
824           if (month > 12) {
825                     month -= 12;
826                     year++;
827           }
828 
829           /*
830            * Erase months, and set crib number.  The crib number is used
831            * later to determine if the month to be displayed is here or
832            * should be built on the fly with the generic routine
833            */
834           memmove(scal, empty, MAXDAYS * sizeof(int));
835           scal[MAXDAYS] = month + year * 12;
836           memmove(jcal, empty, MAXDAYS * sizeof(int));
837           jcal[MAXDAYS] = month + year * 12;
838 
839           /*
840            * It doesn't matter what the actual month is when figuring
841            * out if this is a leap year or not, just so long as February
842            * gets the right number of days in it.
843            */
844           mdays = days_in_month[g_leap_year(year, 3, 1)][month];
845 
846           /*
847            * Bounce back to the first "row" in the day array, and fill
848            * in any days that actually occur.
849            */
850           for (*diw %= 7; (*date - *done) <= mdays; (*date)++, (*diy)++) {
851                     /*
852                      * "date" doesn't get reset by the caller across calls
853                      * to this routine, so we can actually tell that we're
854                      * looking at April the 41st.  Much easier than trying
855                      * to calculate the absolute julian day for a given
856                      * date and then checking that.
857                      */
858                     if (*date < reform->date ||
859                         *date >= reform->date + reform->missing_days) {
860                               scal[*diw] = *date - *done;
861                               jcal[*diw] = *diy;
862                               (*diw)++;
863                     }
864           }
865           *done += mdays;
866 }
867 
868 static int
getnum(const char * p)869 getnum(const char *p)
870 {
871           unsigned long result;
872           char *ep;
873 
874           errno = 0;
875           result = strtoul(p, &ep, 10);
876           if (p[0] == '\0' || *ep != '\0')
877                     goto error;
878           if (errno == ERANGE && result == ULONG_MAX)
879                     goto error;
880           if (result > INT_MAX)
881                     goto error;
882 
883           return (int)result;
884 
885 error:
886           errx(1, "bad number: %s", p);
887           /*NOTREACHED*/
888 }
889 
890 static void
init_hilite(void)891 init_hilite(void)
892 {
893           const char *term;
894           int errret;
895 
896           hilite++;
897 
898           if (!isatty(fileno(stdout)))
899                     return;
900 
901           term = getenv("TERM");
902           if (term == NULL)
903                     term = "dumb";
904           if (setupterm(term, fileno(stdout), &errret) != 0 && errret != 1)
905                     return;
906 
907           if (hilite > 1)
908                     md = enter_reverse_mode;
909           else
910                     md = enter_bold_mode;
911           me = exit_attribute_mode;
912           if (me == NULL || md == NULL)
913                     md = me = NULL;
914 }
915 
916 static void
usage(void)917 usage(void)
918 {
919 
920           (void)fprintf(stderr,
921               "usage: cal [-3hjry] [-A after] [-B before] [-C context] [-d day-of-week] "
922               "[-R reform-spec]\n           [[month] year]\n");
923           exit(1);
924 }
925