]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/cal.c
cal: determine the first day of week from the locale
[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;
314 union { unsigned int word; char *string; } val;
315 val.string = nl_langinfo(_NL_TIME_WEEK_1STDAY);
316
317 wfd = val.word;
318 wfd = day_in_week(wfd % 100, (wfd / 100) % 100, wfd / (100 * 100));
319 weekstart = (wfd + *nl_langinfo(_NL_TIME_FIRST_WEEKDAY) - 1) % 7;
320 }
321 #endif
322
323 yflag = 0;
324 while ((ch = getopt(argc, argv, "13mjsyV")) != -1)
325 switch(ch) {
326 case '1':
327 num_months = 1; /* default */
328 break;
329 case '3':
330 num_months = 3;
331 break;
332 case 's':
333 weekstart = 0; /* default */
334 break;
335 case 'm':
336 weekstart = 1;
337 break;
338 case 'j':
339 julian = 1;
340 break;
341 case 'y':
342 yflag = 1;
343 break;
344 case 'V':
345 printf(_("%s from %s\n"),
346 progname, PACKAGE_STRING);
347 return 0;
348 case '?':
349 default:
350 usage();
351 }
352 argc -= optind;
353 argv += optind;
354
355 day = month = year = 0;
356 switch(argc) {
357 case 3:
358 if ((day = atoi(*argv++)) < 1 || month > 31)
359 errx(1, _("illegal day value: use 1-%d"), 31);
360 /* FALLTHROUGH */
361 case 2:
362 if ((month = atoi(*argv++)) < 1 || month > 12)
363 errx(1, _("illegal month value: use 1-12"));
364 /* FALLTHROUGH */
365 case 1:
366 if ((year = atoi(*argv)) < 1 || year > 9999)
367 errx(1, _("illegal year value: use 1-9999"));
368 if (day) {
369 int dm = days_in_month[leap_year(year)][month];
370 if (day > dm)
371 errx(1, _("illegal day value: use 1-%d"), dm);
372 day = day_in_year(day, month, year);
373 }
374 if (!month)
375 yflag=1;
376 break;
377 case 0:
378 time(&now);
379 local_time = localtime(&now);
380 day = local_time->tm_yday + 1;
381 year = local_time->tm_year + 1900;
382 month = local_time->tm_mon + 1;
383 break;
384 default:
385 usage();
386 }
387 headers_init();
388
389 if (!isatty(1))
390 day = 0; /* don't highlight */
391
392 if (yflag && julian)
393 j_yearly(day, year);
394 else if (yflag)
395 yearly(day, year);
396 else if (num_months == 1)
397 monthly(day, month, year);
398 else if (num_months == 3)
399 monthly3(day, month, year);
400 exit(0);
401 }
402
403 void headers_init(void)
404 {
405 int i, wd;
406 char *cur_dh = day_headings, *cur_j_dh = j_day_headings;
407
408 strcpy(day_headings,"");
409 strcpy(j_day_headings,"");
410
411 #ifdef HAVE_LANGINFO_H
412 # define weekday(wd) nl_langinfo(ABDAY_1+wd)
413 #else
414 # define weekday(wd) _time_info->abbrev_wkday[wd]
415 #endif
416
417 for(i = 0 ; i < 7 ; i++ ) {
418 ssize_t space_left;
419 wd = (i + weekstart) % 7;
420
421 if (i)
422 strcat(cur_dh++, " ");
423 space_left = sizeof(day_headings) - (cur_dh - day_headings);
424 if(space_left <= 2)
425 break;
426 cur_dh += center_str(weekday(wd), cur_dh, space_left, 2);
427
428 if (i)
429 strcat(cur_j_dh++, " ");
430 space_left = sizeof(j_day_headings) - (cur_j_dh - j_day_headings);
431 if(space_left <= 3)
432 break;
433 cur_j_dh += center_str(weekday(wd), cur_j_dh, space_left, 3);
434 }
435
436 #undef weekday
437
438 for (i = 0; i < 12; i++) {
439 #ifdef HAVE_LANGINFO_H
440 full_month[i] = nl_langinfo(MON_1+i);
441 #else
442 full_month[i] = _time_info->full_month[i];
443 #endif
444 }
445 }
446
447 void
448 do_monthly(int day, int month, int year, struct fmt_st *out) {
449 int col, row, days[MAXDAYS];
450 char *p, lineout[FMT_ST_CHARS];
451 int width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1;
452
453 day_array(day, month, year, days);
454
455 /*
456 * %s is the month name, %d the year number.
457 * you can change the order and/or add something here; eg for
458 * Basque the translation should be: "%2$dko %1$s", and
459 * the Vietnamese should be "%s na(m %d", etc.
460 */
461 snprintf(lineout, sizeof(lineout), _("%s %d"),
462 full_month[month - 1], year);
463 center_str(lineout, out->s[0], SIZE(out->s[0]), width);
464
465 snprintf(out->s[1], FMT_ST_CHARS, "%s",
466 julian ? j_day_headings : day_headings);
467 for (row = 0; row < 6; row++) {
468 int has_hl = 0;
469 for (col = 0, p = lineout; col < 7; col++) {
470 int xd = days[row * 7 + col];
471 if (xd != SPACE && (xd & TODAY_FLAG))
472 has_hl = 1;
473 p = ascii_day(p, xd);
474 }
475 *p = '\0';
476 trim_trailing_spaces(lineout);
477 snprintf(out->s[row+2], FMT_ST_CHARS, "%s", lineout);
478 if (has_hl)
479 Hrow = out->s[row+2];
480 }
481 }
482
483 void
484 monthly(int day, int month, int year) {
485 int i;
486 struct fmt_st out;
487
488 do_monthly(day, month, year, &out);
489 for (i = 0; i < FMT_ST_LINES; i++) {
490 my_putstring(out.s[i]);
491 putchar('\n');
492 }
493 }
494
495 void
496 monthly3(int day, int month, int year) {
497 char lineout[FMT_ST_CHARS];
498 int i;
499 int width;
500 struct fmt_st out_prev;
501 struct fmt_st out_curm;
502 struct fmt_st out_next;
503 int prev_month, prev_year;
504 int next_month, next_year;
505
506 if (month == 1) {
507 prev_month = 12;
508 prev_year = year - 1;
509 } else {
510 prev_month = month - 1;
511 prev_year = year;
512 }
513 if (month == 12) {
514 next_month = 1;
515 next_year = year + 1;
516 } else {
517 next_month = month + 1;
518 next_year = year;
519 }
520
521 do_monthly(day, prev_month, prev_year, &out_prev);
522 do_monthly(day, month, year, &out_curm);
523 do_monthly(day, next_month, next_year, &out_next);
524
525 width = (julian ? J_WEEK_LEN : WEEK_LEN) -1;
526 for (i = 0; i < 2; i++)
527 printf("%s %s %s\n", out_prev.s[i], out_curm.s[i], out_next.s[i]);
528 for (i = 2; i < FMT_ST_LINES; i++) {
529 int w1, w2, w3;
530 w1 = w2 = w3 = width;
531
532 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
533 /* adjust width to allow for non printable characters */
534 w1 += (out_prev.s[i] == Hrow ? Slen : 0);
535 w2 += (out_curm.s[i] == Hrow ? Slen : 0);
536 w3 += (out_next.s[i] == Hrow ? Slen : 0);
537 #endif
538 snprintf(lineout, SIZE(lineout), "%-*s %-*s %-*s\n",
539 w1, out_prev.s[i],
540 w2, out_curm.s[i],
541 w3, out_next.s[i]);
542
543 my_putstring(lineout);
544 }
545 }
546
547 void
548 j_yearly(int day, int year) {
549 int col, *dp, i, month, row, which_cal;
550 int days[12][MAXDAYS];
551 char *p, lineout[80];
552
553 snprintf(lineout, sizeof(lineout), "%d", year);
554 center(lineout, J_WEEK_LEN*2 + J_HEAD_SEP - 1, 0);
555 printf("\n\n");
556
557 for (i = 0; i < 12; i++)
558 day_array(day, i + 1, year, days[i]);
559 memset(lineout, ' ', sizeof(lineout) - 1);
560 lineout[sizeof(lineout) - 1] = '\0';
561 for (month = 0; month < 12; month += 2) {
562 center(full_month[month], J_WEEK_LEN-1, J_HEAD_SEP+1);
563 center(full_month[month + 1], J_WEEK_LEN-1, 0);
564 printf("\n%s%*s %s\n", j_day_headings, J_HEAD_SEP, "",
565 j_day_headings);
566 for (row = 0; row < 6; row++) {
567 p = lineout;
568 for (which_cal = 0; which_cal < 2; which_cal++) {
569 dp = &days[month + which_cal][row * 7];
570 for (col = 0; col < 7; col++)
571 p = ascii_day(p, *dp++);
572 p += sprintf(p, " ");
573 }
574 *p = '\0';
575 trim_trailing_spaces(lineout);
576 my_putstring(lineout);
577 putchar('\n');
578 }
579 }
580 printf("\n");
581 }
582
583 void
584 yearly(int day, int year) {
585 int col, *dp, i, month, row, which_cal;
586 int days[12][MAXDAYS];
587 char *p, lineout[100];
588
589 snprintf(lineout, sizeof(lineout), "%d", year);
590 center(lineout, WEEK_LEN*3 + HEAD_SEP*2 - 1, 0);
591 printf("\n\n");
592
593 for (i = 0; i < 12; i++)
594 day_array(day, i + 1, year, days[i]);
595 memset(lineout, ' ', sizeof(lineout) - 1);
596 lineout[sizeof(lineout) - 1] = '\0';
597 for (month = 0; month < 12; month += 3) {
598 center(full_month[month], WEEK_LEN-1, HEAD_SEP+1);
599 center(full_month[month + 1], WEEK_LEN-1, HEAD_SEP+1);
600 center(full_month[month + 2], WEEK_LEN-1, 0);
601 printf("\n%s%*s %s%*s %s\n", day_headings, HEAD_SEP,
602 "", day_headings, HEAD_SEP, "", day_headings);
603 for (row = 0; row < 6; row++) {
604 p = lineout;
605 for (which_cal = 0; which_cal < 3; which_cal++) {
606 dp = &days[month + which_cal][row * 7];
607 for (col = 0; col < 7; col++)
608 p = ascii_day(p, *dp++);
609 p += sprintf(p, " ");
610 }
611 *p = '\0';
612 trim_trailing_spaces(lineout);
613 my_putstring(lineout);
614 putchar('\n');
615 }
616 }
617 putchar('\n');
618 }
619
620 /*
621 * day_array --
622 * Fill in an array of 42 integers with a calendar. Assume for a moment
623 * that you took the (maximum) 6 rows in a calendar and stretched them
624 * out end to end. You would have 42 numbers or spaces. This routine
625 * builds that array for any month from Jan. 1 through Dec. 9999.
626 */
627 void
628 day_array(int day, int month, int year, int *days) {
629 int julday, daynum, dw, dm;
630 int *d_sep1752;
631
632 if (month == 9 && year == 1752) {
633 d_sep1752 = julian ? j_sep1752 : sep1752;
634 memcpy(days, d_sep1752 + weekstart, MAXDAYS * sizeof(int));
635 for (dm=0; dm<MAXDAYS; dm++)
636 if (j_sep1752[dm] == day)
637 days[dm] |= TODAY_FLAG;
638 return;
639 }
640 memcpy(days, empty, MAXDAYS * sizeof(int));
641 dm = days_in_month[leap_year(year)][month];
642 dw = (day_in_week(1, month, year) - weekstart + 7) % 7;
643 julday = day_in_year(1, month, year);
644 daynum = julian ? julday : 1;
645 while (dm--) {
646 days[dw] = daynum++;
647 if (julday++ == day)
648 days[dw] |= TODAY_FLAG;
649 dw++;
650 }
651 }
652
653 /*
654 * day_in_year --
655 * return the 1 based day number within the year
656 */
657 int
658 day_in_year(int day, int month, int year) {
659 int i, leap;
660
661 leap = leap_year(year);
662 for (i = 1; i < month; i++)
663 day += days_in_month[leap][i];
664 return day;
665 }
666
667 /*
668 * day_in_week
669 * return the 0 based day number for any date from 1 Jan. 1 to
670 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
671 * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all
672 * missing days.
673 */
674 int
675 day_in_week(int day, int month, int year) {
676 long temp;
677
678 temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
679 + day_in_year(day, month, year);
680 if (temp < FIRST_MISSING_DAY)
681 return ((temp - 1 + SATURDAY) % 7);
682 if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
683 return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
684 return (THURSDAY);
685 }
686
687 char *
688 ascii_day(char *p, int day) {
689 int display, val;
690 int highlight = 0;
691 static char *aday[] = {
692 "",
693 " 1", " 2", " 3", " 4", " 5", " 6", " 7",
694 " 8", " 9", "10", "11", "12", "13", "14",
695 "15", "16", "17", "18", "19", "20", "21",
696 "22", "23", "24", "25", "26", "27", "28",
697 "29", "30", "31",
698 };
699
700 if (day == SPACE) {
701 int len = julian ? J_DAY_LEN : DAY_LEN;
702 memset(p, ' ', len);
703 return p+len;
704 }
705 if (day & TODAY_FLAG) {
706 day &= ~TODAY_FLAG;
707 p += sprintf(p, "%s", Senter);
708 highlight = 1;
709 }
710 if (julian) {
711 if ((val = day / 100)) {
712 day %= 100;
713 *p++ = val + '0';
714 display = 1;
715 } else {
716 *p++ = ' ';
717 display = 0;
718 }
719 val = day / 10;
720 if (val || display)
721 *p++ = val + '0';
722 else
723 *p++ = ' ';
724 *p++ = day % 10 + '0';
725 } else {
726 *p++ = aday[day][0];
727 *p++ = aday[day][1];
728 }
729 if (highlight)
730 p += sprintf(p, "%s", Sexit);
731 *p++ = ' ';
732 return p;
733 }
734
735 void
736 trim_trailing_spaces(s)
737 char *s;
738 {
739 char *p;
740
741 for (p = s; *p; ++p)
742 continue;
743 while (p > s && isspace(*--p))
744 continue;
745 if (p > s)
746 ++p;
747 *p = '\0';
748 }
749
750 #ifdef HAVE_WIDECHAR
751 /* replace non printable chars.
752 * return 1 if replacement made, 0 otherwise */
753 int wc_ensure_printable(wchar_t* wchars)
754 {
755 int replaced=0;
756 wchar_t* wc = wchars;
757 while (*wc) {
758 if (!iswprint((wint_t) *wc)) {
759 *wc=L'\uFFFD';
760 replaced=1;
761 }
762 wc++;
763 }
764 return replaced;
765 }
766
767 /* truncate wchar string to width cells.
768 * returns number of cells used. */
769 size_t wc_truncate(wchar_t* wchars, size_t width, size_t minchars)
770 {
771 int wc=0;
772 int cells=0;
773 while (*(wchars+wc)) {
774 cells = wcswidth(wchars, wc+1);
775 if (cells > width) {
776 if (wc >= minchars) {
777 break;
778 }
779 }
780 wc++;
781 }
782 wchars[wc]=L'\0';
783 return cells;
784 }
785 #endif
786
787 /*
788 * Center string, handling multibyte characters appropriately.
789 * In addition if the string is too large for the width it's truncated.
790 * The number of trailing spaces may be 1 less than the number of leading spaces.
791 */
792 int
793 center_str(const char* src, char* dest, size_t dest_size, int width)
794 {
795 #ifdef HAVE_WIDECHAR
796 wchar_t str_wc[FMT_ST_CHARS];
797 #endif
798 char str[FMT_ST_CHARS];
799 const char* str_to_print=src;
800 int used, spaces, wc_conversion=0, wc_enabled=0;
801
802 #ifdef HAVE_WIDECHAR
803 if (mbstowcs(str_wc, src, SIZE(str_wc)) > 0) {
804 str_wc[SIZE(str_wc)-1]=L'\0';
805 wc_enabled=1;
806 wc_conversion = wc_ensure_printable(str_wc);
807 used = wcswidth(str_wc, SIZE(str_wc));
808 }
809 else
810 #endif
811 used = strlen(src);
812
813 if (wc_conversion || used > width) {
814 str_to_print=str;
815 if (wc_enabled) {
816 #ifdef HAVE_WIDECHAR
817 used = wc_truncate(str_wc, width, 1);
818 wcstombs(str, str_wc, SIZE(str));
819 #endif
820 } else {
821 memcpy(str, src, width);
822 str[width]='\0';
823 }
824 }
825
826 spaces = width - used;
827 spaces = ( spaces < 0 ? 0 : spaces );
828
829 return snprintf(dest, dest_size, "%*s%s%*s",
830 spaces / 2 + spaces % 2, "",
831 str_to_print,
832 spaces / 2, "" );
833 }
834
835 void
836 center(str, len, separate)
837 const char *str;
838 int len;
839 int separate;
840 {
841 char lineout[FMT_ST_CHARS];
842 center_str(str, lineout, SIZE(lineout), len);
843 fputs(lineout, stdout);
844 if (separate)
845 printf("%*s", separate, "");
846 }
847
848 void
849 usage()
850 {
851
852 fprintf(stderr, _("usage: cal [-13smjyV] [[[day] month] year]\n"));
853 exit(1);
854 }