]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/cal.c
cal: Highlight today even when month or year specified
[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 <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <time.h>
65 #include <unistd.h>
66 #include <err.h>
67 #include "nls.h"
68
69 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
70
71 #ifdef HAVE_NCURSES_H
72 #include <ncurses.h>
73 #elif defined(HAVE_NCURSES_NCURSES_H)
74 #include <ncurses/ncurses.h>
75 #endif
76
77 #include <term.h> /* include after <curses.h> */
78
79 static void
80 my_setupterm(const char *term, int fildes, int *errret) {
81 setupterm((char*)term, fildes, errret);
82 }
83
84 static void
85 my_putstring(char *s) {
86 putp(s);
87 }
88
89 static const char *
90 my_tgetstr(char *s, char *ss) {
91 const char* ret = tigetstr(ss);
92 if (!ret || ret==(char*)-1)
93 return "";
94 else
95 return ret;
96 }
97
98 #elif defined(HAVE_LIBTERMCAP)
99
100 #include <termcap.h>
101
102 char termbuffer[4096];
103 char tcbuffer[4096];
104 char *strbuf = termbuffer;
105
106 static void
107 my_setupterm(const char *term, int fildes, int *errret) {
108 *errret = tgetent(tcbuffer, term);
109 }
110
111 static void
112 my_putstring(char *s) {
113 tputs (s, 1, putchar);
114 }
115
116 static const char *
117 my_tgetstr(char *s, char *ss) {
118 const char* ret = tgetstr(s, &strbuf);
119 if (!ret)
120 return "";
121 else
122 return ret;
123 }
124
125 #else /* ! (HAVE_LIBTERMCAP || HAVE_LIBNCURSES || HAVE_LIBNCURSESW) */
126
127 static void
128 my_putstring(char *s) {
129 fputs(s, stdout);
130 }
131
132 #endif
133
134
135 const char *term="";
136 const char *Senter="", *Sexit="";/* enter and exit standout mode */
137 int Slen; /* strlen of Senter+Sexit */
138 char *Hrow; /* pointer to highlighted row in month */
139
140 #ifdef HAVE_LANGINFO_H
141 # include <langinfo.h>
142 #else
143 # include <localeinfo.h> /* libc4 only */
144 #endif
145
146 #include "widechar.h"
147
148 #define SIZE(a) (sizeof(a)/sizeof((a)[0]))
149
150 /* allow compile-time define to over-ride default */
151 #ifndef NUM_MONTHS
152 #define NUM_MONTHS 1
153 #endif
154
155 #if ( NUM_MONTHS != 1 && NUM_MONTHS !=3 )
156 #error NUM_MONTHS must be 1 or 3
157 #endif
158
159 #define THURSDAY 4 /* for reformation */
160 #define SATURDAY 6 /* 1 Jan 1 was a Saturday */
161
162 #define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */
163 #define NUMBER_MISSING_DAYS 11 /* 11 day correction */
164
165 #define MAXDAYS 43 /* max slots in a month array */
166 #define SPACE -1 /* used in day array */
167
168 static int days_in_month[2][13] = {
169 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
170 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
171 };
172
173 int sep1752[MAXDAYS] = {
174 SPACE, SPACE, 1, 2, 14, 15, 16,
175 17, 18, 19, 20, 21, 22, 23,
176 24, 25, 26, 27, 28, 29, 30,
177 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
178 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
179 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
180 SPACE
181 }, j_sep1752[MAXDAYS] = {
182 SPACE, SPACE, 245, 246, 258, 259, 260,
183 261, 262, 263, 264, 265, 266, 267,
184 268, 269, 270, 271, 272, 273, 274,
185 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
186 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
187 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
188 SPACE
189 }, empty[MAXDAYS] = {
190 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
191 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
192 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
193 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
194 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
195 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE,
196 SPACE
197 };
198
199 #define DAY_LEN 3 /* 3 spaces per day */
200 #define J_DAY_LEN 4 /* 4 spaces per day */
201 #define WEEK_LEN 21 /* 7 days * 3 characters */
202 #define J_WEEK_LEN 28 /* 7 days * 4 characters */
203 #define HEAD_SEP 2 /* spaces between day headings */
204 #define J_HEAD_SEP 2
205
206 /* utf-8 can have up to 6 bytes per char; and an extra byte for ending \0 */
207 char day_headings[WEEK_LEN*6+1];
208 /* weekstart = 1 => " M Tu W Th F S S " */
209 char j_day_headings[J_WEEK_LEN*6+1];
210 /* weekstart = 1 => " M Tu W Th F S S " */
211 const char *full_month[12];
212
213 /* leap year -- account for gregorian reformation in 1752 */
214 #define leap_year(yr) \
215 ((yr) <= 1752 ? !((yr) % 4) : \
216 (!((yr) % 4) && ((yr) % 100)) || !((yr) % 400))
217
218 /* number of centuries since 1700, not inclusive */
219 #define centuries_since_1700(yr) \
220 ((yr) > 1700 ? (yr) / 100 - 17 : 0)
221
222 /* number of centuries since 1700 whose modulo of 400 is 0 */
223 #define quad_centuries_since_1700(yr) \
224 ((yr) > 1600 ? ((yr) - 1600) / 400 : 0)
225
226 /* number of leap years between year 1 and this year, not inclusive */
227 #define leap_years_since_year_1(yr) \
228 ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr))
229
230 /* 0 => sunday, 1 => monday */
231 int weekstart=0;
232 int julian;
233
234 #define TODAY_FLAG 0x400 /* flag day for highlighting */
235
236 #define FMT_ST_LINES 8
237 #define FMT_ST_CHARS 300 /* 90 suffices in most locales */
238 struct fmt_st
239 {
240 char s[FMT_ST_LINES][FMT_ST_CHARS];
241 };
242
243 char * ascii_day(char *, int);
244 int center_str(const char* src, char* dest, size_t dest_size, int width);
245 void center(const char *, int, int);
246 void day_array(int, int, int, int *);
247 int day_in_week(int, int, int);
248 int day_in_year(int, int, int);
249 void yearly(int, int);
250 void j_yearly(int, int);
251 void do_monthly(int, int, int, struct fmt_st*);
252 void monthly(int, int, int);
253 void monthly3(int, int, int);
254 void trim_trailing_spaces(char *);
255 void usage(void);
256 void headers_init(void);
257 extern char *__progname;
258
259 int
260 main(int argc, char **argv) {
261 struct tm *local_time;
262 time_t now;
263 int ch, day, month, year, yflag;
264 char *progname, *p;
265 int num_months = NUM_MONTHS;
266
267 progname = argv[0];
268 if ((p = strrchr(progname, '/')) != NULL)
269 progname = p+1;
270 __progname = progname;
271
272 setlocale(LC_ALL, "");
273 bindtextdomain(PACKAGE, LOCALEDIR);
274 textdomain(PACKAGE);
275
276 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
277 if ((term = getenv("TERM"))) {
278 int ret;
279 my_setupterm(term, 1, &ret);
280 if (ret > 0) {
281 Senter = my_tgetstr("so","smso");
282 Sexit = my_tgetstr("se","rmso");
283 Slen = strlen(Senter) + strlen(Sexit);
284 }
285 }
286 #endif
287
288 /*
289 * The traditional Unix cal utility starts the week at Sunday,
290 * while ISO 8601 starts at Monday. We read the start day from
291 * the locale database, which can be overridden with the
292 * -s (Sunday) or -m (Monday) options.
293 */
294 #ifdef HAVE_LANGINFO_H
295 /*
296 * You need to use 2 locale variables to get the first day of the week.
297 * This is needed to support first_weekday=2 and first_workday=1 for
298 * the rare case where working days span across 2 weeks.
299 * This shell script shows the combinations and calculations involved:
300
301 for LANG in en_US ru_RU fr_FR csb_PL POSIX; do
302 printf "%s:\t%s + %s -1 = " $LANG $(locale week-1stday first_weekday)
303 date -d"$(locale week-1stday) +$(($(locale first_weekday)-1))day" +%w
304 done
305
306 en_US: 19971130 + 1 -1 = 0 #0 = sunday
307 ru_RU: 19971130 + 2 -1 = 1
308 fr_FR: 19971201 + 1 -1 = 1
309 csb_PL: 19971201 + 2 -1 = 2
310 POSIX: 19971201 + 7 -1 = 0
311 */
312 {
313 int wfd = (int)(intptr_t) nl_langinfo(_NL_TIME_WEEK_1STDAY);
314 wfd = day_in_week(wfd % 100, (wfd / 100) % 100, wfd / (100 * 100));
315 weekstart = (wfd + *nl_langinfo(_NL_TIME_FIRST_WEEKDAY) - 1) % 7;
316 }
317 #endif
318
319 yflag = 0;
320 while ((ch = getopt(argc, argv, "13mjsyV")) != -1)
321 switch(ch) {
322 case '1':
323 num_months = 1; /* default */
324 break;
325 case '3':
326 num_months = 3;
327 break;
328 case 's':
329 weekstart = 0; /* default */
330 break;
331 case 'm':
332 weekstart = 1;
333 break;
334 case 'j':
335 julian = 1;
336 break;
337 case 'y':
338 yflag = 1;
339 break;
340 case 'V':
341 printf(_("%s from %s\n"),
342 progname, PACKAGE_STRING);
343 return 0;
344 case '?':
345 default:
346 usage();
347 }
348 argc -= optind;
349 argv += optind;
350
351 time(&now);
352 local_time = localtime(&now);
353
354 day = month = year = 0;
355 switch(argc) {
356 case 3:
357 if ((day = atoi(*argv++)) < 1 || month > 31)
358 errx(1, _("illegal day value: use 1-%d"), 31);
359 /* FALLTHROUGH */
360 case 2:
361 if ((month = atoi(*argv++)) < 1 || month > 12)
362 errx(1, _("illegal month value: use 1-12"));
363 /* FALLTHROUGH */
364 case 1:
365 if ((year = atoi(*argv)) < 1 || year > 9999)
366 errx(1, _("illegal year value: use 1-9999"));
367 if (day) {
368 int dm = days_in_month[leap_year(year)][month];
369 if (day > dm)
370 errx(1, _("illegal day value: use 1-%d"), dm);
371 day = day_in_year(day, month, year);
372 } else if ((local_time->tm_year + 1900) == year) {
373 day = local_time->tm_yday + 1;
374 }
375 if (!month)
376 yflag=1;
377 break;
378 case 0:
379 day = local_time->tm_yday + 1;
380 year = local_time->tm_year + 1900;
381 month = local_time->tm_mon + 1;
382 break;
383 default:
384 usage();
385 }
386 headers_init();
387
388 if (!isatty(1))
389 day = 0; /* don't highlight */
390
391 if (yflag && julian)
392 j_yearly(day, year);
393 else if (yflag)
394 yearly(day, year);
395 else if (num_months == 1)
396 monthly(day, month, year);
397 else if (num_months == 3)
398 monthly3(day, month, year);
399 exit(0);
400 }
401
402 void headers_init(void)
403 {
404 int i, wd;
405 char *cur_dh = day_headings, *cur_j_dh = j_day_headings;
406
407 strcpy(day_headings,"");
408 strcpy(j_day_headings,"");
409
410 #ifdef HAVE_LANGINFO_H
411 # define weekday(wd) nl_langinfo(ABDAY_1+wd)
412 #else
413 # define weekday(wd) _time_info->abbrev_wkday[wd]
414 #endif
415
416 for(i = 0 ; i < 7 ; i++ ) {
417 ssize_t space_left;
418 wd = (i + weekstart) % 7;
419
420 if (i)
421 strcat(cur_dh++, " ");
422 space_left = sizeof(day_headings) - (cur_dh - day_headings);
423 if(space_left <= 2)
424 break;
425 cur_dh += center_str(weekday(wd), cur_dh, space_left, 2);
426
427 if (i)
428 strcat(cur_j_dh++, " ");
429 space_left = sizeof(j_day_headings) - (cur_j_dh - j_day_headings);
430 if(space_left <= 3)
431 break;
432 cur_j_dh += center_str(weekday(wd), cur_j_dh, space_left, 3);
433 }
434
435 #undef weekday
436
437 for (i = 0; i < 12; i++) {
438 #ifdef HAVE_LANGINFO_H
439 full_month[i] = nl_langinfo(MON_1+i);
440 #else
441 full_month[i] = _time_info->full_month[i];
442 #endif
443 }
444 }
445
446 void
447 do_monthly(int day, int month, int year, struct fmt_st *out) {
448 int col, row, days[MAXDAYS];
449 char *p, lineout[FMT_ST_CHARS];
450 int width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1;
451
452 day_array(day, month, year, days);
453
454 /*
455 * %s is the month name, %d the year number.
456 * you can change the order and/or add something here; eg for
457 * Basque the translation should be: "%2$dko %1$s", and
458 * the Vietnamese should be "%s na(m %d", etc.
459 */
460 snprintf(lineout, sizeof(lineout), _("%s %d"),
461 full_month[month - 1], year);
462 center_str(lineout, out->s[0], SIZE(out->s[0]), width);
463
464 snprintf(out->s[1], FMT_ST_CHARS, "%s",
465 julian ? j_day_headings : day_headings);
466 for (row = 0; row < 6; row++) {
467 int has_hl = 0;
468 for (col = 0, p = lineout; col < 7; col++) {
469 int xd = days[row * 7 + col];
470 if (xd != SPACE && (xd & TODAY_FLAG))
471 has_hl = 1;
472 p = ascii_day(p, xd);
473 }
474 *p = '\0';
475 trim_trailing_spaces(lineout);
476 snprintf(out->s[row+2], FMT_ST_CHARS, "%s", lineout);
477 if (has_hl)
478 Hrow = out->s[row+2];
479 }
480 }
481
482 void
483 monthly(int day, int month, int year) {
484 int i;
485 struct fmt_st out;
486
487 do_monthly(day, month, year, &out);
488 for (i = 0; i < FMT_ST_LINES; i++) {
489 my_putstring(out.s[i]);
490 putchar('\n');
491 }
492 }
493
494 void
495 monthly3(int day, int month, int year) {
496 char lineout[FMT_ST_CHARS];
497 int i;
498 int width;
499 struct fmt_st out_prev;
500 struct fmt_st out_curm;
501 struct fmt_st out_next;
502 int prev_month, prev_year;
503 int next_month, next_year;
504
505 if (month == 1) {
506 prev_month = 12;
507 prev_year = year - 1;
508 } else {
509 prev_month = month - 1;
510 prev_year = year;
511 }
512 if (month == 12) {
513 next_month = 1;
514 next_year = year + 1;
515 } else {
516 next_month = month + 1;
517 next_year = year;
518 }
519
520 do_monthly(day, prev_month, prev_year, &out_prev);
521 do_monthly(day, month, year, &out_curm);
522 do_monthly(day, next_month, next_year, &out_next);
523
524 width = (julian ? J_WEEK_LEN : WEEK_LEN) -1;
525 for (i = 0; i < 2; i++)
526 printf("%s %s %s\n", out_prev.s[i], out_curm.s[i], out_next.s[i]);
527 for (i = 2; i < FMT_ST_LINES; i++) {
528 int w1, w2, w3;
529 w1 = w2 = w3 = width;
530
531 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
532 /* adjust width to allow for non printable characters */
533 w1 += (out_prev.s[i] == Hrow ? Slen : 0);
534 w2 += (out_curm.s[i] == Hrow ? Slen : 0);
535 w3 += (out_next.s[i] == Hrow ? Slen : 0);
536 #endif
537 snprintf(lineout, SIZE(lineout), "%-*s %-*s %-*s\n",
538 w1, out_prev.s[i],
539 w2, out_curm.s[i],
540 w3, out_next.s[i]);
541
542 my_putstring(lineout);
543 }
544 }
545
546 void
547 j_yearly(int day, int year) {
548 int col, *dp, i, month, row, which_cal;
549 int days[12][MAXDAYS];
550 char *p, lineout[80];
551
552 snprintf(lineout, sizeof(lineout), "%d", year);
553 center(lineout, J_WEEK_LEN*2 + J_HEAD_SEP - 1, 0);
554 printf("\n\n");
555
556 for (i = 0; i < 12; i++)
557 day_array(day, i + 1, year, days[i]);
558 memset(lineout, ' ', sizeof(lineout) - 1);
559 lineout[sizeof(lineout) - 1] = '\0';
560 for (month = 0; month < 12; month += 2) {
561 center(full_month[month], J_WEEK_LEN-1, J_HEAD_SEP+1);
562 center(full_month[month + 1], J_WEEK_LEN-1, 0);
563 printf("\n%s%*s %s\n", j_day_headings, J_HEAD_SEP, "",
564 j_day_headings);
565 for (row = 0; row < 6; row++) {
566 p = lineout;
567 for (which_cal = 0; which_cal < 2; which_cal++) {
568 dp = &days[month + which_cal][row * 7];
569 for (col = 0; col < 7; col++)
570 p = ascii_day(p, *dp++);
571 p += sprintf(p, " ");
572 }
573 *p = '\0';
574 trim_trailing_spaces(lineout);
575 my_putstring(lineout);
576 putchar('\n');
577 }
578 }
579 printf("\n");
580 }
581
582 void
583 yearly(int day, int year) {
584 int col, *dp, i, month, row, which_cal;
585 int days[12][MAXDAYS];
586 char *p, lineout[100];
587
588 snprintf(lineout, sizeof(lineout), "%d", year);
589 center(lineout, WEEK_LEN*3 + HEAD_SEP*2 - 1, 0);
590 printf("\n\n");
591
592 for (i = 0; i < 12; i++)
593 day_array(day, i + 1, year, days[i]);
594 memset(lineout, ' ', sizeof(lineout) - 1);
595 lineout[sizeof(lineout) - 1] = '\0';
596 for (month = 0; month < 12; month += 3) {
597 center(full_month[month], WEEK_LEN-1, HEAD_SEP+1);
598 center(full_month[month + 1], WEEK_LEN-1, HEAD_SEP+1);
599 center(full_month[month + 2], WEEK_LEN-1, 0);
600 printf("\n%s%*s %s%*s %s\n", day_headings, HEAD_SEP,
601 "", day_headings, HEAD_SEP, "", day_headings);
602 for (row = 0; row < 6; row++) {
603 p = lineout;
604 for (which_cal = 0; which_cal < 3; which_cal++) {
605 dp = &days[month + which_cal][row * 7];
606 for (col = 0; col < 7; col++)
607 p = ascii_day(p, *dp++);
608 p += sprintf(p, " ");
609 }
610 *p = '\0';
611 trim_trailing_spaces(lineout);
612 my_putstring(lineout);
613 putchar('\n');
614 }
615 }
616 putchar('\n');
617 }
618
619 /*
620 * day_array --
621 * Fill in an array of 42 integers with a calendar. Assume for a moment
622 * that you took the (maximum) 6 rows in a calendar and stretched them
623 * out end to end. You would have 42 numbers or spaces. This routine
624 * builds that array for any month from Jan. 1 through Dec. 9999.
625 */
626 void
627 day_array(int day, int month, int year, int *days) {
628 int julday, daynum, dw, dm;
629 int *d_sep1752;
630
631 if (month == 9 && year == 1752) {
632 d_sep1752 = julian ? j_sep1752 : sep1752;
633 memcpy(days, d_sep1752 + weekstart, MAXDAYS * sizeof(int));
634 for (dm=0; dm<MAXDAYS; dm++)
635 if (j_sep1752[dm] == day)
636 days[dm] |= TODAY_FLAG;
637 return;
638 }
639 memcpy(days, empty, MAXDAYS * sizeof(int));
640 dm = days_in_month[leap_year(year)][month];
641 dw = (day_in_week(1, month, year) - weekstart + 7) % 7;
642 julday = day_in_year(1, month, year);
643 daynum = julian ? julday : 1;
644 while (dm--) {
645 days[dw] = daynum++;
646 if (julday++ == day)
647 days[dw] |= TODAY_FLAG;
648 dw++;
649 }
650 }
651
652 /*
653 * day_in_year --
654 * return the 1 based day number within the year
655 */
656 int
657 day_in_year(int day, int month, int year) {
658 int i, leap;
659
660 leap = leap_year(year);
661 for (i = 1; i < month; i++)
662 day += days_in_month[leap][i];
663 return day;
664 }
665
666 /*
667 * day_in_week
668 * return the 0 based day number for any date from 1 Jan. 1 to
669 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
670 * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all
671 * missing days.
672 */
673 int
674 day_in_week(int day, int month, int year) {
675 long temp;
676
677 temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
678 + day_in_year(day, month, year);
679 if (temp < FIRST_MISSING_DAY)
680 return ((temp - 1 + SATURDAY) % 7);
681 if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
682 return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
683 return (THURSDAY);
684 }
685
686 char *
687 ascii_day(char *p, int day) {
688 int display, val;
689 int highlight = 0;
690 static char *aday[] = {
691 "",
692 " 1", " 2", " 3", " 4", " 5", " 6", " 7",
693 " 8", " 9", "10", "11", "12", "13", "14",
694 "15", "16", "17", "18", "19", "20", "21",
695 "22", "23", "24", "25", "26", "27", "28",
696 "29", "30", "31",
697 };
698
699 if (day == SPACE) {
700 int len = julian ? J_DAY_LEN : DAY_LEN;
701 memset(p, ' ', len);
702 return p+len;
703 }
704 if (day & TODAY_FLAG) {
705 day &= ~TODAY_FLAG;
706 p += sprintf(p, "%s", Senter);
707 highlight = 1;
708 }
709 if (julian) {
710 if ((val = day / 100)) {
711 day %= 100;
712 *p++ = val + '0';
713 display = 1;
714 } else {
715 *p++ = ' ';
716 display = 0;
717 }
718 val = day / 10;
719 if (val || display)
720 *p++ = val + '0';
721 else
722 *p++ = ' ';
723 *p++ = day % 10 + '0';
724 } else {
725 *p++ = aday[day][0];
726 *p++ = aday[day][1];
727 }
728 if (highlight)
729 p += sprintf(p, "%s", Sexit);
730 *p++ = ' ';
731 return p;
732 }
733
734 void
735 trim_trailing_spaces(s)
736 char *s;
737 {
738 char *p;
739
740 for (p = s; *p; ++p)
741 continue;
742 while (p > s && isspace(*--p))
743 continue;
744 if (p > s)
745 ++p;
746 *p = '\0';
747 }
748
749 #ifdef HAVE_WIDECHAR
750 /* replace non printable chars.
751 * return 1 if replacement made, 0 otherwise */
752 int wc_ensure_printable(wchar_t* wchars)
753 {
754 int replaced=0;
755 wchar_t* wc = wchars;
756 while (*wc) {
757 if (!iswprint((wint_t) *wc)) {
758 *wc=L'\uFFFD';
759 replaced=1;
760 }
761 wc++;
762 }
763 return replaced;
764 }
765
766 /* truncate wchar string to width cells.
767 * returns number of cells used. */
768 size_t wc_truncate(wchar_t* wchars, size_t width, size_t minchars)
769 {
770 int wc=0;
771 int cells=0;
772 while (*(wchars+wc)) {
773 cells = wcswidth(wchars, wc+1);
774 if (cells > width) {
775 if (wc >= minchars) {
776 break;
777 }
778 }
779 wc++;
780 }
781 wchars[wc]=L'\0';
782 return cells;
783 }
784 #endif
785
786 /*
787 * Center string, handling multibyte characters appropriately.
788 * In addition if the string is too large for the width it's truncated.
789 * The number of trailing spaces may be 1 less than the number of leading spaces.
790 */
791 int
792 center_str(const char* src, char* dest, size_t dest_size, int width)
793 {
794 #ifdef HAVE_WIDECHAR
795 wchar_t str_wc[FMT_ST_CHARS];
796 #endif
797 char str[FMT_ST_CHARS];
798 const char* str_to_print=src;
799 int used, spaces, wc_conversion=0, wc_enabled=0;
800
801 #ifdef HAVE_WIDECHAR
802 if (mbstowcs(str_wc, src, SIZE(str_wc)) > 0) {
803 str_wc[SIZE(str_wc)-1]=L'\0';
804 wc_enabled=1;
805 wc_conversion = wc_ensure_printable(str_wc);
806 used = wcswidth(str_wc, SIZE(str_wc));
807 }
808 else
809 #endif
810 used = strlen(src);
811
812 if (wc_conversion || used > width) {
813 str_to_print=str;
814 if (wc_enabled) {
815 #ifdef HAVE_WIDECHAR
816 used = wc_truncate(str_wc, width, 1);
817 wcstombs(str, str_wc, SIZE(str));
818 #endif
819 } else {
820 memcpy(str, src, width);
821 str[width]='\0';
822 }
823 }
824
825 spaces = width - used;
826 spaces = ( spaces < 0 ? 0 : spaces );
827
828 return snprintf(dest, dest_size, "%*s%s%*s",
829 spaces / 2 + spaces % 2, "",
830 str_to_print,
831 spaces / 2, "" );
832 }
833
834 void
835 center(str, len, separate)
836 const char *str;
837 int len;
838 int separate;
839 {
840 char lineout[FMT_ST_CHARS];
841 center_str(str, lineout, SIZE(lineout), len);
842 fputs(lineout, stdout);
843 if (separate)
844 printf("%*s", separate, "");
845 }
846
847 void
848 usage()
849 {
850
851 fprintf(stderr, _("usage: cal [-13smjyV] [[[day] month] year]\n"));
852 exit(1);
853 }