]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/cal.c
cal: limit year to 32 bit value
[thirdparty/util-linux.git] / misc-utils / cal.c
1 /*
2 * Copyright (c) 1989, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Kim Letkeman.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37 /* 1999-02-01 Jean-Francois Bignolles: added option '-m' to display
38 * monday as the first day of the week.
39 * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
40 * - added Native Language Support
41 *
42 * 2000-09-01 Michael Charles Pruznick <dummy@netwiz.net>
43 * Added "-3" option to print prev/next month with current.
44 * Added over-ridable default NUM_MONTHS and "-1" option to
45 * get traditional output when -3 is the default. I hope that
46 * enough people will like -3 as the default that one day the
47 * product can be shipped that way.
48 *
49 * 2001-05-07 Pablo Saratxaga <pablo@mandrakesoft.com>
50 * Fixed the bugs with multi-byte charset (zg: cjk, utf-8)
51 * displaying. made the 'month year' ("%s %d") header translatable
52 * so it can be adapted to conventions used by different languages
53 * added support to read "first_weekday" locale information
54 * still to do: support for 'cal_direction' (will require a major
55 * rewrite of the displaying) and proper handling of RTL scripts
56 */
57
58 #include <sys/types.h>
59
60 #include <ctype.h>
61 #include <getopt.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <string.h>
65 #include <time.h>
66 #include <unistd.h>
67 #include <errno.h>
68
69 #include "c.h"
70 #include "closestream.h"
71 #include "colors.h"
72 #include "nls.h"
73 #include "mbsalign.h"
74 #include "strutils.h"
75
76 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
77 # ifdef HAVE_NCURSES_H
78 # include <ncurses.h>
79 # elif defined(HAVE_NCURSES_NCURSES_H)
80 # include <ncurses/ncurses.h>
81 # endif
82 # include <term.h>
83
84 static void my_setupterm(const char *term, int fildes, int *errret)
85 {
86 setupterm((char *)term, fildes, errret);
87 }
88
89 static void my_putstring(char *s)
90 {
91 putp(s);
92 }
93
94 static const char *my_tgetstr(char *s __attribute__((__unused__)), char *ss)
95 {
96 const char *ret = tigetstr(ss);
97 if (!ret || ret == (char *)-1)
98 return "";
99
100 return ret;
101 }
102
103 #elif defined(HAVE_LIBTERMCAP)
104 # include <termcap.h>
105
106 static char termbuffer[4096];
107 static char tcbuffer[4096];
108 static char *strbuf = termbuffer;
109
110 static void my_setupterm(const char *term, int fildes __attribute__((__unused__)), int *errret)
111 {
112 *errret = tgetent(tcbuffer, term);
113 }
114
115 static void my_putstring(char *s)
116 {
117 tputs(s, 1, putchar);
118 }
119
120 static const char *my_tgetstr(char *s, char *ss __attribute__((__unused__)))
121 {
122 const char *ret = tgetstr(s, &strbuf);
123 if (!ret)
124 return "";
125
126 return ret;
127 }
128
129 #else /* ! (HAVE_LIBTERMCAP || HAVE_LIBNCURSES || HAVE_LIBNCURSESW) */
130
131 static void my_putstring(char *s)
132 {
133 fputs(s, stdout);
134 }
135
136 #endif /* end of LIBTERMCAP / NCURSES */
137
138 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
139 static const char *term="";
140 static int Slen; /* strlen of Senter+Sexit */
141 #endif
142
143 static const char *Senter="", *Sexit="";/* enter and exit standout mode */
144
145 #include "widechar.h"
146
147 /* allow compile-time define to over-ride default */
148 #ifndef NUM_MONTHS
149 # define NUM_MONTHS 1
150 #endif
151
152 #if ( NUM_MONTHS != 1 && NUM_MONTHS !=3 )
153 # error NUM_MONTHS must be 1 or 3
154 #endif
155
156 enum {
157 SUNDAY = 0,
158 MONDAY,
159 TUESDAY,
160 WEDNESDAY,
161 THURSDAY,
162 FRIDAY,
163 SATURDAY,
164 DAYS_IN_WEEK,
165 NONEDAY
166 };
167
168 #define FIRST_WEEKDAY SATURDAY /* Jan 1st, 1 was a Saturday */
169 #define REFORMATION_YEAR 1752 /* Signed-off-by: Lord Chesterfield */
170 #define REFORMATION_MONTH 9 /* September */
171 #define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */
172 #define NUMBER_MISSING_DAYS 11 /* 11 day correction */
173 #define YDAY_AFTER_MISSING 258 /* 14th in Sep 1752 */
174
175 #define DAYS_IN_YEAR 365 /* the common case, leap years are calculated */
176 #define MONTHS_IN_YEAR 12
177 #define DAYS_IN_MONTH 31
178 #define MAXDAYS 42 /* slots in a month array */
179 #define SPACE -1 /* used in day array */
180
181 #define SMALLEST_YEAR 1
182
183 #define DAY_LEN 3 /* 3 spaces per day */
184 #define WEEK_LEN (DAYS_IN_WEEK * DAY_LEN)
185 #define HEAD_SEP 2
186 #define MONTH_COLS 3 /* month columns in year view */
187 #define WNUM_LEN 3
188
189 #define TODAY_FLAG 0x400 /* flag day for highlighting */
190
191 #define FMT_ST_LINES 9
192 #define FMT_ST_CHARS 300 /* 90 suffices in most locales */
193 struct fmt_st
194 {
195 char s[FMT_ST_LINES][FMT_ST_CHARS];
196 };
197
198 static const int days_in_month[2][13] = {
199 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
200 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
201 };
202
203 /* September 1752 is special, and has static assignments for both date
204 * and Julian representations. */
205 static const int d_sep1752[MAXDAYS / 2] = {
206 SPACE, SPACE, 1, 2, 14, 15, 16,
207 17, 18, 19, 20, 21, 22, 23,
208 24, 25, 26, 27, 28, 29, 30
209 }, j_sep1752[MAXDAYS / 2] = {
210 SPACE, SPACE, 245, 246, 258, 259, 260,
211 261, 262, 263, 264, 265, 266, 267,
212 268, 269, 270, 271, 272, 273, 274
213 }, empty[MAXDAYS] = {
214 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
215 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
216 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
217 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
218 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
219 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE
220 };
221
222 enum {
223 WEEK_NUM_DISABLED = 0,
224 WEEK_NUM_MASK=0xff,
225 WEEK_NUM_ISO=0x100,
226 WEEK_NUM_US=0x200,
227 };
228
229 /* utf-8 can have up to 6 bytes per char; and an extra byte for ending \0 */
230 static char day_headings[(WEEK_LEN + 1) * 6 + 1];
231
232 struct cal_request {
233 int day;
234 int month;
235 int32_t year;
236 int week;
237 };
238
239 struct cal_control {
240 const char *full_month[MONTHS_IN_YEAR]; /* month names */
241 int colormode; /* day and week number highlight */
242 int num_months; /* number of months horizontally in print out */
243 int weekstart; /* day the week starts, often Sun or Mon */
244 int weektype; /* WEEK_TYPE_{NONE,ISO,US} */
245 size_t day_width; /* day width in characters in printout */
246 size_t week_width; /* 7 * day_width + possible week num */
247 int gutter_width; /* spaces in between horizontal month outputs */
248 struct cal_request req; /* the times user is interested */
249 unsigned int julian:1, /* julian output */
250 yflag:1, /* print whole year */
251 header_hint:1; /* does month name + year need two lines to fit */
252 };
253
254 struct cal_month {
255 int days[MAXDAYS]; /* the day numbers, or SPACE */
256 int weeks[MAXDAYS / DAYS_IN_WEEK];
257 int month;
258 int32_t year;
259 struct cal_month *next;
260 };
261
262 /* function prototypes */
263 static int leap_year(int32_t year);
264 static void headers_init(struct cal_control *ctl);
265 static void set_consecutive_months(struct cal_month *month, int m, int32_t y);
266 static void cal_fill_month(struct cal_month *month, const struct cal_control *ctl);
267 static void cal_output_header(struct cal_month *month, const struct cal_control *ctl);
268 static void cal_output_months(struct cal_month *month, const struct cal_control *ctl);
269 static void monthly(const struct cal_control *ctl);
270 static void monthly3(const struct cal_control *ctl);
271 static void yearly(const struct cal_control *ctl);
272 static int day_in_year(int day, int month, int32_t year);
273 static int day_in_week(int day, int month, int32_t year);
274 static int week_number(int day, int month, int32_t year, const struct cal_control *ctl);
275 static int week_to_day(const struct cal_control *ctl);
276 static int center_str(const char *src, char *dest, size_t dest_size, size_t width);
277 static void center(const char *str, size_t len, int separate);
278 static void __attribute__((__noreturn__)) usage(FILE *out);
279
280 int main(int argc, char **argv)
281 {
282 struct tm *local_time;
283 time_t now;
284 int ch;
285 static struct cal_control ctl = {
286 .weekstart = SUNDAY,
287 .num_months = NUM_MONTHS,
288 .colormode = UL_COLORMODE_AUTO,
289 .weektype = WEEK_NUM_DISABLED,
290 .day_width = DAY_LEN,
291 .gutter_width = 2,
292 .req.day = 0,
293 .req.month = 0
294 };
295
296 enum {
297 OPT_COLOR = CHAR_MAX + 1
298 };
299
300 static const struct option longopts[] = {
301 {"one", no_argument, NULL, '1'},
302 {"three", no_argument, NULL, '3'},
303 {"sunday", no_argument, NULL, 's'},
304 {"monday", no_argument, NULL, 'm'},
305 {"julian", no_argument, NULL, 'j'},
306 {"year", no_argument, NULL, 'y'},
307 {"week", optional_argument, NULL, 'w'},
308 {"color", optional_argument, NULL, OPT_COLOR},
309 {"version", no_argument, NULL, 'V'},
310 {"help", no_argument, NULL, 'h'},
311 {NULL, 0, NULL, 0}
312 };
313
314 setlocale(LC_ALL, "");
315 bindtextdomain(PACKAGE, LOCALEDIR);
316 textdomain(PACKAGE);
317 atexit(close_stdout);
318
319 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
320 if ((term = getenv("TERM"))) {
321 int ret;
322 my_setupterm(term, STDOUT_FILENO, &ret);
323 if (ret > 0) {
324 Senter = my_tgetstr("so","smso");
325 Sexit = my_tgetstr("se","rmso");
326 Slen = strlen(Senter) + strlen(Sexit);
327 }
328 }
329 #endif
330
331 /*
332 * The traditional Unix cal utility starts the week at Sunday,
333 * while ISO 8601 starts at Monday. We read the start day from
334 * the locale database, which can be overridden with the
335 * -s (Sunday) or -m (Monday) options.
336 */
337 #if HAVE_DECL__NL_TIME_WEEK_1STDAY
338 /*
339 * You need to use 2 locale variables to get the first day of the week.
340 * This is needed to support first_weekday=2 and first_workday=1 for
341 * the rare case where working days span across 2 weeks.
342 * This shell script shows the combinations and calculations involved:
343 *
344 * for LANG in en_US ru_RU fr_FR csb_PL POSIX; do
345 * printf "%s:\t%s + %s -1 = " $LANG $(locale week-1stday first_weekday)
346 * date -d"$(locale week-1stday) +$(($(locale first_weekday)-1))day" +%w
347 * done
348 *
349 * en_US: 19971130 + 1 -1 = 0 #0 = sunday
350 * ru_RU: 19971130 + 2 -1 = 1
351 * fr_FR: 19971201 + 1 -1 = 1
352 * csb_PL: 19971201 + 2 -1 = 2
353 * POSIX: 19971201 + 7 -1 = 0
354 */
355 {
356 int wfd;
357 union { unsigned int word; char *string; } val;
358 val.string = nl_langinfo(_NL_TIME_WEEK_1STDAY);
359
360 wfd = val.word;
361 wfd = day_in_week(wfd % 100, (wfd / 100) % 100, wfd / (100 * 100));
362 ctl.weekstart = (wfd + *nl_langinfo(_NL_TIME_FIRST_WEEKDAY) - 1) % DAYS_IN_WEEK;
363 }
364 #endif
365
366 while ((ch = getopt_long(argc, argv, "13mjsywVh", longopts, NULL)) != -1)
367 switch(ch) {
368 case '1':
369 ctl.num_months = 1; /* default */
370 break;
371 case '3':
372 ctl.num_months = 3;
373 break;
374 case 's':
375 ctl.weekstart = SUNDAY; /* default */
376 break;
377 case 'm':
378 ctl.weekstart = MONDAY;
379 break;
380 case 'j':
381 ctl.julian = 1;
382 ctl.day_width = DAY_LEN + 1;
383 break;
384 case 'y':
385 ctl.yflag = 1;
386 break;
387 case 'w':
388 if (optarg) {
389 ctl.req.week = strtos32_or_err(optarg,
390 _("invalid week argument"));
391 if (ctl.req.week < 1 || 53 < ctl.req.week)
392 errx(EXIT_FAILURE,_("illegal week value: use 1-53"));
393 }
394 ctl.weektype = WEEK_NUM_US; /* default per weekstart */
395 break;
396 case OPT_COLOR:
397 if (optarg)
398 ctl.colormode = colormode_or_err(optarg,
399 _("unsupported color mode"));
400 break;
401 case 'V':
402 printf(UTIL_LINUX_VERSION);
403 return EXIT_SUCCESS;
404 case 'h':
405 usage(stdout);
406 case '?':
407 default:
408 usage(stderr);
409 }
410 argc -= optind;
411 argv += optind;
412
413 if (ctl.weektype) {
414 ctl.weektype = ctl.req.week & WEEK_NUM_MASK;
415 ctl.weektype |= (ctl.weekstart == MONDAY ? WEEK_NUM_ISO : WEEK_NUM_US);
416 ctl.week_width = (ctl.day_width * DAYS_IN_WEEK) + WNUM_LEN;
417 } else
418 ctl.week_width = ctl.day_width * DAYS_IN_WEEK;
419
420 time(&now);
421 local_time = localtime(&now);
422
423 switch(argc) {
424 case 3:
425 ctl.req.day = strtos32_or_err(*argv++, _("illegal day value"));
426 if (ctl.req.day < 1 || DAYS_IN_MONTH < ctl.req.day)
427 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), DAYS_IN_MONTH);
428 /* FALLTHROUGH */
429 case 2:
430 ctl.req.month = strtos32_or_err(*argv++, _("illegal month value: use 1-12"));
431 if (ctl.req.month < 1 || MONTHS_IN_YEAR < ctl.req.month)
432 errx(EXIT_FAILURE, _("illegal month value: use 1-12"));
433 /* FALLTHROUGH */
434 case 1:
435 ctl.req.year = strtos32_or_err(*argv++, _("illegal year value"));
436 if (ctl.req.year < SMALLEST_YEAR)
437 errx(EXIT_FAILURE, _("illegal year value: use positive integer"));
438 if (ctl.req.year == INT32_MAX)
439 errx(EXIT_FAILURE, _("illegal year value"));
440 if (ctl.req.day) {
441 int dm = days_in_month[leap_year(ctl.req.year)][ctl.req.month];
442 if (ctl.req.day > dm)
443 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), dm);
444 ctl.req.day = day_in_year(ctl.req.day, ctl.req.month, ctl.req.year);
445 } else if ((int32_t) (local_time->tm_year + 1900) == ctl.req.year) {
446 ctl.req.day = local_time->tm_yday + 1;
447 }
448 if (!ctl.req.month && !ctl.req.week) {
449 ctl.req.month = local_time->tm_mon + 1;
450 ctl.yflag = 1;
451 }
452 break;
453 case 0:
454 ctl.req.day = local_time->tm_yday + 1;
455 ctl.req.year = local_time->tm_year + 1900;
456 ctl.req.month = local_time->tm_mon + 1;
457 break;
458 default:
459 usage(stderr);
460 }
461
462 if (0 < ctl.req.week) {
463 int yday = week_to_day(&ctl);
464 int leap = leap_year(ctl.req.year);
465 int m = 1;
466
467 if (yday < 1)
468 errx(EXIT_FAILURE, _("illegal week value: year %d "
469 "doesn't have week %d"),
470 ctl.req.year, ctl.req.week);
471 while (m <= 12 && yday > days_in_month[leap][m])
472 yday -= days_in_month[leap][m++];
473 if (m > 12) {
474 /* In some years (e.g. 2010 in ISO mode) it's possible
475 * to have a remnant of week 53 starting the year yet
476 * the year in question ends during 52, in this case
477 * we're assuming that early remnant is being referred
478 * to if 53 is given as argument. */
479 if (ctl.req.week != week_number(31, 12, ctl.req.year - 1, &ctl))
480 errx(EXIT_FAILURE,
481 _("illegal week value: year %d "
482 "doesn't have week %d"),
483 ctl.req.year, ctl.req.week);
484 }
485 if (!ctl.req.month)
486 ctl.req.month = 12 < m ? 1 : m;
487 }
488
489 headers_init(&ctl);
490
491 if (!colors_init(ctl.colormode)) {
492 ctl.req.day = 0;
493 ctl.weektype &= ~WEEK_NUM_MASK;
494 }
495
496 if (ctl.yflag) {
497 if (ctl.julian)
498 ctl.num_months = MONTH_COLS - 1;
499 else
500 ctl.num_months = MONTH_COLS;
501 ctl.gutter_width = 3;
502 yearly(&ctl);
503 } else if (ctl.num_months == 1)
504 monthly(&ctl);
505 else if (ctl.num_months == 3)
506 monthly3(&ctl);
507
508 return EXIT_SUCCESS;
509 }
510
511 /* leap year -- account for gregorian reformation in 1752 */
512 static int leap_year(int32_t year)
513 {
514 if (year <= REFORMATION_YEAR)
515 return !(year % 4);
516 else
517 return ( !(year % 4) && (year % 100) ) || !(year % 400);
518 }
519
520 static void headers_init(struct cal_control *ctl)
521 {
522 size_t i, wd;
523 char *cur_dh = day_headings;
524 char tmp[FMT_ST_CHARS];
525 size_t year_len;
526
527 year_len = snprintf(tmp, sizeof(tmp), "%d", ctl->req.year);
528
529 for (i = 0; i < DAYS_IN_WEEK; i++) {
530 size_t space_left;
531 wd = (i + ctl->weekstart) % DAYS_IN_WEEK;
532
533 if (i)
534 strcat(cur_dh++, " ");
535 space_left = sizeof(day_headings) - (cur_dh - day_headings);
536
537 if (space_left <= (ctl->day_width - 1))
538 break;
539 cur_dh += center_str(nl_langinfo(ABDAY_1 + wd), cur_dh,
540 space_left, ctl->day_width - 1);
541 }
542
543 for (i = 0; i < MONTHS_IN_YEAR; i++) {
544 ctl->full_month[i] = nl_langinfo(MON_1 + i);
545 /* The +1 after year_len is space in between month and year. */
546 if (ctl->week_width < strlen(ctl->full_month[i]) + year_len + 1)
547 ctl->header_hint = 1;
548 }
549 }
550
551 static void set_consecutive_months(struct cal_month *month, int m, int32_t y)
552 {
553 struct cal_month *i;
554 for (i = month; i; i = i->next) {
555 i->month = m++;
556 i->year = y;
557 if (MONTHS_IN_YEAR < m) {
558 m = 1;
559 y++;
560 }
561 }
562 }
563
564 static void cal_fill_month(struct cal_month *month, const struct cal_control *ctl)
565 {
566 int first_week_day = day_in_week(1, month->month, month->year);
567 int month_days;
568 int i, j, weeklines = 0;
569
570 if (ctl->julian)
571 j = day_in_year(1, month->month, month->year);
572 else
573 j = 1;
574 month_days = j + days_in_month[leap_year(month->year)][month->month];
575
576 /* True when Sunday is not first day in the output week. */
577 if (ctl->weekstart) {
578 first_week_day -= ctl->weekstart;
579 if (first_week_day < 0)
580 first_week_day = DAYS_IN_WEEK - ctl->weekstart;
581 month_days += ctl->weekstart - 1;
582 }
583
584 /* Fill day array. */
585 for (i = 0; i < MAXDAYS; i++) {
586 if (0 < first_week_day) {
587 month->days[i] = SPACE;
588 first_week_day--;
589 continue;
590 }
591 if (j < month_days) {
592 if (month->year == 1752 && month->month == 9 && (j == 3 || j == 247))
593 j += NUMBER_MISSING_DAYS;
594 month->days[i] = j;
595 j++;
596 continue;
597 }
598 month->days[i] = SPACE;
599 weeklines++;
600 }
601
602 /* Add week numbers */
603 if (ctl->weektype) {
604 int weeknum = week_number(1, month->month, month->year, ctl);
605 weeklines = MAXDAYS / DAYS_IN_WEEK - weeklines / DAYS_IN_WEEK;
606 for (i = 0; i < MAXDAYS / DAYS_IN_WEEK; i++) {
607 if (0 < weeklines)
608 month->weeks[i] = weeknum++;
609 else
610 month->weeks[i] = SPACE;
611 weeklines--;
612 if (52 < weeknum && i == 0)
613 weeknum = week_number(month->days[DAYS_IN_WEEK * (i + 1)], 1, month->year, ctl);
614 else if (52 < weeknum)
615 weeknum = week_number(31, 12, month->year, ctl);
616 }
617 }
618 }
619
620 static void cal_output_header(struct cal_month *month, const struct cal_control *ctl)
621 {
622 char out[FMT_ST_CHARS];
623 struct cal_month *i;
624
625 if (ctl->header_hint || ctl->yflag) {
626 for (i = month; i; i = i->next) {
627 sprintf(out, _("%s"), ctl->full_month[i->month - 1]);
628 center(out, ctl->week_width - 1, i->next == NULL ? 0 : ctl->gutter_width);
629 }
630 if (!ctl->yflag) {
631 fputs("\n", stdout);
632 for (i = month; i; i = i->next) {
633 sprintf(out, _("%d"), i->year);
634 center(out, ctl->week_width - 1, i->next == NULL ? 0 : ctl->gutter_width);
635 }
636 }
637 } else {
638 for (i = month; i; i = i->next) {
639 sprintf(out, _("%s %d"), ctl->full_month[i->month - 1], i->year);
640 center(out, ctl->week_width - 1, i->next == NULL ? 0 : ctl->gutter_width);
641 }
642 }
643 puts("");
644 for (i = month; i; i = i->next) {
645 if (ctl->weektype) {
646 if (ctl->julian)
647 printf("%*s%s", (int)ctl->day_width - 1, "", day_headings);
648 else
649 printf("%*s%s", (int)ctl->day_width, "", day_headings);
650 } else
651 fputs(day_headings, stdout);
652 if (i->next != NULL)
653 printf("%*s", ctl->gutter_width, "");
654 }
655 puts("");
656 }
657
658 static void cal_output_months(struct cal_month *month, const struct cal_control *ctl)
659 {
660 int reqday, week_line, d;
661 int skip;
662 struct cal_month *i;
663
664 for (week_line = 0; week_line < MAXDAYS / DAYS_IN_WEEK; week_line++) {
665 for (i = month; i; i = i->next) {
666 /* Determine the day that should be highlighted. */
667 reqday = 0;
668 if (i->month == ctl->req.month && i->year == ctl->req.year) {
669 if (ctl->julian)
670 reqday = ctl->req.day;
671 else
672 reqday =
673 ctl->req.day + 1 - day_in_year(1, i->month,
674 i->year);
675 }
676
677 if (ctl->weektype) {
678 if (0 < i->weeks[week_line]) {
679 if ((ctl->weektype & WEEK_NUM_MASK) ==
680 i->weeks[week_line])
681 printf("%s%2d%s", Senter, i->weeks[week_line],
682 Sexit);
683 else
684 printf("%2d", i->weeks[week_line]);
685 } else
686 printf("%2s", "");
687 skip = ctl->day_width;
688 } else
689 /* First day of the week is one char narrower than the other days,
690 * unless week number is printed. */
691 skip = ctl->day_width - 1;
692
693 for (d = DAYS_IN_WEEK * week_line;
694 d < DAYS_IN_WEEK * week_line + DAYS_IN_WEEK; d++) {
695 if (0 < i->days[d]) {
696 if (reqday == i->days[d])
697 printf("%*s%s%*d%s", skip - (ctl->julian ? 3 : 2),
698 "", Senter, (ctl->julian ? 3 : 2),
699 i->days[d], Sexit);
700 else
701 printf("%*d", skip, i->days[d]);
702 } else
703 printf("%*s", skip, "");
704 if (skip < (int)ctl->day_width)
705 skip++;
706 }
707 if (i->next != NULL)
708 printf("%*s", ctl->gutter_width, "");
709 }
710 if (i == NULL)
711 printf("%*s\n", ctl->gutter_width - (ctl->yflag ? 0 : 1), "");
712 }
713 }
714
715 static void monthly(const struct cal_control *ctl)
716 {
717 struct cal_month month;
718
719 month.month = ctl->req.month;
720 month.year = ctl->req.year;
721 month.next = NULL;
722
723 cal_fill_month(&month, ctl);
724
725 cal_output_header(&month, ctl);
726 cal_output_months(&month, ctl);
727 }
728
729 static void monthly3(const struct cal_control *ctl)
730 {
731 struct cal_month m1, m2, m3, *i;
732 int first_month;
733 int32_t first_year;
734
735 m1.next = &m2;
736 m2.next = &m3;
737 m3.next = NULL;
738
739 if (ctl->req.month == 1) {
740 first_month = MONTHS_IN_YEAR;
741 first_year = ctl->req.year - 1;
742 } else {
743 first_month = ctl->req.month - 1;
744 first_year = ctl->req.year;
745 }
746
747 set_consecutive_months(&m1, first_month, first_year);
748 for (i = &m1; i; i = i->next)
749 cal_fill_month(i, ctl);
750 cal_output_header(&m1, ctl);
751 cal_output_months(&m1, ctl);
752 }
753
754 static void yearly(const struct cal_control *ctl)
755 {
756 struct cal_month m1, m2, m3, *i;
757 int month;
758 char out[FMT_ST_CHARS];
759 int year_width = 0;
760
761 m1.next = &m2;
762 if (ctl->julian)
763 m2.next = NULL;
764 else {
765 m2.next = &m3;
766 m3.next = NULL;
767 }
768
769 /* year header */
770 for (i = &m1; i; i = i->next)
771 year_width += ctl->week_width + 1;
772 if (ctl->julian)
773 year_width--;
774 sprintf(out, "%d", ctl->req.year);
775 center(out, year_width, 0);
776 fputs("\n\n", stdout);
777
778 for (month = 1; month < MONTHS_IN_YEAR; month += ctl->julian ? 2 : 3) {
779 set_consecutive_months(&m1, month, ctl->req.year);
780 for (i = &m1; i; i = i->next)
781 cal_fill_month(i, ctl);
782 cal_output_header(&m1, ctl);
783 cal_output_months(&m1, ctl);
784 }
785 /* Is empty line at the end year output really needed? */
786 puts("");
787 }
788
789 /*
790 * day_in_year --
791 * return the 1 based day number within the year
792 */
793 static int day_in_year(int day, int month, int32_t year)
794 {
795 int i, leap;
796
797 leap = leap_year(year);
798 for (i = 1; i < month; i++)
799 day += days_in_month[leap][i];
800 return day;
801 }
802
803 /*
804 * day_in_week
805 * return the 0 based day number for any date from 1 Jan. 1 to
806 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
807 * 3 Sep. 1752 through 13 Sep. 1752, and returns invalid weekday
808 * during the period of 11 days.
809 */
810 static int day_in_week(int day, int month, int32_t year)
811 {
812 static const int reform[] = {
813 SUNDAY, WEDNESDAY, TUESDAY, FRIDAY, SUNDAY, WEDNESDAY,
814 FRIDAY, MONDAY, THURSDAY, SATURDAY, TUESDAY, THURSDAY
815 };
816 static const int old[] = {
817 FRIDAY, MONDAY, SUNDAY, WEDNESDAY, FRIDAY, MONDAY,
818 WEDNESDAY, SATURDAY, TUESDAY, THURSDAY, SUNDAY, TUESDAY
819 };
820 if (year != 1753)
821 year -= month < 3;
822 else
823 year -= (month < 3) + 14;
824 if (REFORMATION_YEAR < year
825 || (year == REFORMATION_YEAR && 9 < month)
826 || (year == REFORMATION_YEAR && month == 9 && 13 < day))
827 return (year + (year / 4) - (year / 100) + (year / 400) + reform[month - 1] +
828 day) % 7;
829 if (year < REFORMATION_YEAR
830 || (year == REFORMATION_YEAR && month < 9)
831 || (year == REFORMATION_YEAR && month == 9 && day < 3))
832 return (year + year / 4 + old[month - 1] + day) % 7;
833 return NONEDAY;
834 }
835
836 /*
837 * week_number
838 * return the week number of a given date, 1..53.
839 * Supports ISO-8601 and North American modes.
840 * Day may be given as Julian day of the year mode, in which
841 * case the month is disregarded entirely.
842 */
843 static int week_number(int day, int month, int32_t year, const struct cal_control *ctl)
844 {
845 int fday = 0, yday;
846 int wday = day_in_week(1, 1, year);
847
848 if (ctl->weektype & WEEK_NUM_ISO)
849 fday = wday + (wday >= FRIDAY ? -2 : 5);
850 else
851 /* WEEK_NUM_US
852 * - according to gcal, the first Sun is in the first week
853 * - according to wikipedia, the first Sat is in the first week
854 */
855 fday = wday + (wday == SUNDAY ? 6 : -1);
856
857 /* For julian dates the month can be set to 1, the global julian
858 * variable cannot be relied upon here, because we may recurse
859 * internally for 31.12. which would not work. */
860 if (day > 31)
861 month = 1;
862
863 yday = day_in_year(day,month,year);
864 if (year == REFORMATION_YEAR) {
865 if (yday >= YDAY_AFTER_MISSING)
866 fday -= NUMBER_MISSING_DAYS;
867 }
868
869 /* Last year is last year */
870 if (yday + fday < 7)
871 return week_number(31, 12, year - 1, ctl);
872
873 /* Or it could be part of the next year. The reformation year had less
874 * days than 365 making this check invalid, but reformation year ended
875 * on Sunday and in week 51, so it's ok here. */
876 if (ctl->weektype == WEEK_NUM_ISO && yday >= 363
877 && day_in_week(day, month, year) >= MONDAY
878 && day_in_week(day, month, year) <= WEDNESDAY
879 && day_in_week(31, 12, year) >= MONDAY
880 && day_in_week(31, 12, year) <= WEDNESDAY)
881 return week_number(1, 1, year + 1, ctl);
882
883 return (yday + fday) / 7;
884 }
885
886 /*
887 * week_to_day
888 * return the yday of the first day in a given week inside
889 * the given year. This may be something other than Monday
890 * for ISO-8601 modes. For North American numbering this
891 * always returns a Sunday.
892 */
893 static int week_to_day(const struct cal_control *ctl)
894 {
895 int yday, wday;
896
897 wday = day_in_week(1, 1, ctl->req.year);
898 yday = ctl->req.week * 7 - wday;
899
900 if (ctl->weektype & WEEK_NUM_ISO)
901 yday -= (wday >= FRIDAY ? -2 : 5);
902 else
903 yday -= (wday == SUNDAY ? 6 : -1); /* WEEK_NUM_US */
904 if (yday <= 0)
905 return 1;
906
907 return yday;
908 }
909
910 /*
911 * Center string, handling multibyte characters appropriately.
912 * In addition if the string is too large for the width it's truncated.
913 * The number of trailing spaces may be 1 less than the number of leading spaces.
914 */
915 static int center_str(const char* src, char* dest,
916 size_t dest_size, size_t width)
917 {
918 return mbsalign(src, dest, dest_size, &width,
919 MBS_ALIGN_CENTER, MBA_UNIBYTE_FALLBACK);
920 }
921
922 static void center(const char *str, size_t len, int separate)
923 {
924 char lineout[FMT_ST_CHARS];
925
926 center_str(str, lineout, ARRAY_SIZE(lineout), len);
927 my_putstring(lineout);
928
929 if (separate) {
930 snprintf(lineout, sizeof(lineout), "%*s", separate, "");
931 my_putstring(lineout);
932 }
933 }
934
935 static void __attribute__ ((__noreturn__)) usage(FILE * out)
936 {
937 fputs(USAGE_HEADER, out);
938 fprintf(out, _(" %s [options] [[[day] month] year]\n"), program_invocation_short_name);
939
940 fputs(USAGE_SEPARATOR, out);
941 fputs(_("Display a calendar, or some part of it.\n"), out);
942 fputs(_("Without any arguments, display the current month.\n"), out);
943
944 fputs(USAGE_OPTIONS, out);
945 fputs(_(" -1, --one show only a single month (default)\n"), out);
946 fputs(_(" -3, --three show three months spanning the date\n"), out);
947 fputs(_(" -s, --sunday Sunday as first day of week\n"), out);
948 fputs(_(" -m, --monday Monday as first day of week\n"), out);
949 fputs(_(" -j, --julian output Julian dates\n"), out);
950 fputs(_(" -y, --year show the whole year\n"), out);
951 fputs(_(" -w, --week[=<num>] show US or ISO-8601 week numbers\n"), out);
952 fputs(_(" --color[=<when>] colorize messages (auto, always or never)\n"), out);
953
954 fputs(USAGE_SEPARATOR, out);
955 fputs(USAGE_HELP, out);
956 fputs(USAGE_VERSION, out);
957 fprintf(out, USAGE_MAN_TAIL("cal(1)"));
958
959 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
960 }