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