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