2 * Copyright (c) 1989, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
5 * This code is derived from software contributed to Berkeley by
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
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.
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
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
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.
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
58 #include <sys/types.h>
69 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
73 #elif defined(HAVE_NCURSES_NCURSES_H)
74 #include <ncurses/ncurses.h>
77 #include <term.h> /* include after <curses.h> */
80 my_setupterm(const char *term, int fildes, int *errret) {
81 setupterm((char*)term, fildes, errret);
85 my_putstring(char *s) {
90 my_tgetstr(char *s, char *ss) {
91 const char* ret = tigetstr(ss);
92 if (!ret || ret==(char*)-1)
98 #elif defined(HAVE_LIBTERMCAP)
102 char termbuffer[4096];
104 char *strbuf = termbuffer;
107 my_setupterm(const char *term, int fildes, int *errret) {
108 *errret = tgetent(tcbuffer, term);
112 my_putstring(char *s) {
113 tputs (s, 1, putchar);
117 my_tgetstr(char *s, char *ss) {
118 const char* ret = tgetstr(s, &strbuf);
125 #else /* ! (HAVE_LIBTERMCAP || HAVE_LIBNCURSES || HAVE_LIBNCURSESW) */
128 my_putstring(char *s) {
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 */
140 #ifdef HAVE_LANGINFO_H
141 # include <langinfo.h>
143 # include <localeinfo.h> /* libc4 only */
146 #include "widechar.h"
148 #define SIZE(a) (sizeof(a)/sizeof((a)[0]))
150 /* allow compile-time define to over-ride default */
155 #if ( NUM_MONTHS != 1 && NUM_MONTHS !=3 )
156 #error NUM_MONTHS must be 1 or 3
159 #define THURSDAY 4 /* for reformation */
160 #define SATURDAY 6 /* 1 Jan 1 was a Saturday */
162 #define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */
163 #define NUMBER_MISSING_DAYS 11 /* 11 day correction */
165 #define MAXDAYS 42 /* slots in a month array */
166 #define SPACE -1 /* used in day array */
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},
173 #define SEP1752_OFS 4 /* sep1752[4] is a Sunday */
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,
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,
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
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 */
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];
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))
222 /* number of centuries since 1700, not inclusive */
223 #define centuries_since_1700(yr) \
224 ((yr) > 1700 ? (yr) / 100 - 17 : 0)
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)
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))
234 /* 0 => sunday, 1 => monday */
238 #define TODAY_FLAG 0x400 /* flag day for highlighting */
240 #define FMT_ST_LINES 8
241 #define FMT_ST_CHARS 300 /* 90 suffices in most locales */
244 char s[FMT_ST_LINES][FMT_ST_CHARS];
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 *);
260 void headers_init(void);
261 extern char *__progname;
264 main(int argc, char **argv) {
265 struct tm *local_time;
267 int ch, day, month, year, yflag;
269 int num_months = NUM_MONTHS;
272 if ((p = strrchr(progname, '/')) != NULL)
274 __progname = progname;
276 setlocale(LC_ALL, "");
277 bindtextdomain(PACKAGE, LOCALEDIR);
280 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
281 if ((term = getenv("TERM"))) {
283 my_setupterm(term, 1, &ret);
285 Senter = my_tgetstr("so","smso");
286 Sexit = my_tgetstr("se","rmso");
287 Slen = strlen(Senter) + strlen(Sexit);
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.
298 #if HAVE_DECL__NL_TIME_WEEK_1STDAY
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:
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
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
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;
324 while ((ch = getopt(argc, argv, "13mjsyV")) != -1)
327 num_months = 1; /* default */
333 weekstart = 0; /* default */
345 printf(_("%s from %s\n"),
346 progname, PACKAGE_STRING);
356 local_time = localtime(&now);
358 day = month = year = 0;
361 if ((day = atoi(*argv++)) < 1 || day > 31)
362 errx(1, _("illegal day value: use 1-%d"), 31);
365 if ((month = atoi(*argv++)) < 1 || month > 12)
366 errx(1, _("illegal month value: use 1-12"));
369 if ((year = atoi(*argv)) < 1 || year > 9999)
370 errx(1, _("illegal year value: use 1-9999"));
372 int dm = days_in_month[leap_year(year)][month];
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;
383 day = local_time->tm_yday + 1;
384 year = local_time->tm_year + 1900;
385 month = local_time->tm_mon + 1;
393 day = 0; /* don't highlight */
399 else if (num_months == 1)
400 monthly(day, month, year);
401 else if (num_months == 3)
402 monthly3(day, month, year);
406 void headers_init(void)
409 char *cur_dh = day_headings, *cur_j_dh = j_day_headings;
411 strcpy(day_headings,"");
412 strcpy(j_day_headings,"");
414 #ifdef HAVE_LANGINFO_H
415 # define weekday(wd) nl_langinfo(ABDAY_1+wd)
417 # define weekday(wd) _time_info->abbrev_wkday[wd]
420 for(i = 0 ; i < 7 ; i++ ) {
422 wd = (i + weekstart) % 7;
425 strcat(cur_dh++, " ");
426 space_left = sizeof(day_headings) - (cur_dh - day_headings);
429 cur_dh += center_str(weekday(wd), cur_dh, space_left, 2);
432 strcat(cur_j_dh++, " ");
433 space_left = sizeof(j_day_headings) - (cur_j_dh - j_day_headings);
436 cur_j_dh += center_str(weekday(wd), cur_j_dh, space_left, 3);
441 for (i = 0; i < 12; i++) {
442 #ifdef HAVE_LANGINFO_H
443 full_month[i] = nl_langinfo(MON_1+i);
445 full_month[i] = _time_info->full_month[i];
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;
456 day_array(day, month, year, days);
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.
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);
468 snprintf(out->s[1], FMT_ST_CHARS, "%s",
469 julian ? j_day_headings : day_headings);
470 for (row = 0; row < 6; row++) {
472 for (col = 0, p = lineout; col < 7; col++) {
473 int xd = days[row * 7 + col];
474 if (xd != SPACE && (xd & TODAY_FLAG))
476 p = ascii_day(p, xd);
479 trim_trailing_spaces(lineout);
480 snprintf(out->s[row+2], FMT_ST_CHARS, "%s", lineout);
482 Hrow = out->s[row+2];
487 monthly(int day, int month, int year) {
491 do_monthly(day, month, year, &out);
492 for (i = 0; i < FMT_ST_LINES; i++) {
493 my_putstring(out.s[i]);
499 monthly3(int day, int month, int year) {
500 char lineout[FMT_ST_CHARS];
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;
511 prev_year = year - 1;
513 prev_month = month - 1;
518 next_year = year + 1;
520 next_month = month + 1;
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);
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++) {
533 w1 = w2 = w3 = width;
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);
541 snprintf(lineout, SIZE(lineout), "%-*s %-*s %-*s\n",
546 my_putstring(lineout);
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];
556 snprintf(lineout, sizeof(lineout), "%d", year);
557 center(lineout, J_WEEK_LEN*2 + J_HEAD_SEP - 1, 0);
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, "",
569 for (row = 0; row < 6; row++) {
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, " ");
578 trim_trailing_spaces(lineout);
579 my_putstring(lineout);
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];
592 snprintf(lineout, sizeof(lineout), "%d", year);
593 center(lineout, WEEK_LEN*3 + HEAD_SEP*2 - 1, 0);
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++) {
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, " ");
615 trim_trailing_spaces(lineout);
616 my_putstring(lineout);
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.
631 day_array(int day, int month, int year, int *days) {
632 int julday, daynum, dw, dm;
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;
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;
652 days[dw] |= TODAY_FLAG;
659 * return the 1 based day number within the year
662 day_in_year(int day, int month, int year) {
665 leap = leap_year(year);
666 for (i = 1; i < month; i++)
667 day += days_in_month[leap][i];
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
679 day_in_week(int day, int month, int year) {
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);
692 ascii_day(char *p, int day) {
695 static char *aday[] = {
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",
705 int len = julian ? J_DAY_LEN : DAY_LEN;
709 if (day & TODAY_FLAG) {
711 p += sprintf(p, "%s", Senter);
715 if ((val = day / 100)) {
728 *p++ = day % 10 + '0';
734 p += sprintf(p, "%s", Sexit);
740 trim_trailing_spaces(s)
747 while (p > s && isspace(*--p))
755 /* replace non printable chars.
756 * return 1 if replacement made, 0 otherwise */
757 int wc_ensure_printable(wchar_t* wchars)
760 wchar_t* wc = wchars;
762 if (!iswprint((wint_t) *wc)) {
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)
777 while (*(wchars+wc)) {
778 cells = wcswidth(wchars, wc+1);
780 if (wc >= minchars) {
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.
797 center_str(const char* src, char* dest, size_t dest_size, int width)
800 wchar_t str_wc[FMT_ST_CHARS];
802 char str[FMT_ST_CHARS];
803 const char* str_to_print=src;
804 int used, spaces, wc_conversion=0, wc_enabled=0;
807 if (mbstowcs(str_wc, src, SIZE(str_wc)) > 0) {
808 str_wc[SIZE(str_wc)-1]=L'\0';
810 wc_conversion = wc_ensure_printable(str_wc);
811 used = wcswidth(str_wc, SIZE(str_wc));
817 if (wc_conversion || used > width) {
821 used = wc_truncate(str_wc, width, 1);
822 wcstombs(str, str_wc, SIZE(str));
825 memcpy(str, src, width);
830 spaces = width - used;
831 spaces = ( spaces < 0 ? 0 : spaces );
833 return snprintf(dest, dest_size, "%*s%s%*s",
834 spaces / 2 + spaces % 2, "",
840 center(str, len, separate)
845 char lineout[FMT_ST_CHARS];
846 center_str(str, lineout, SIZE(lineout), len);
847 fputs(lineout, stdout);
849 printf("%*s", separate, "");
856 fprintf(stderr, _("usage: cal [-13smjyV] [[[day] month] year]\n"));