]> git.ipfire.org Git - thirdparty/util-linux.git/blob - misc-utils/cal.c
textual: use UTIL_LINUX_VERSION everywhere
[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 <getopt.h>
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <string.h>
65 #include <time.h>
66 #include <unistd.h>
67 #include <errno.h>
68
69 #include "c.h"
70 #include "closestream.h"
71 #include "nls.h"
72 #include "mbsalign.h"
73 #include "strutils.h"
74
75 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
76
77 #ifdef HAVE_NCURSES_H
78 #include <ncurses.h>
79 #elif defined(HAVE_NCURSES_NCURSES_H)
80 #include <ncurses/ncurses.h>
81 #endif
82
83 #include <term.h> /* include after <curses.h> */
84
85 static void
86 my_setupterm(const char *term, int fildes, int *errret) {
87 setupterm((char*)term, fildes, errret);
88 }
89
90 static void
91 my_putstring(char *s) {
92 putp(s);
93 }
94
95 static const char *
96 my_tgetstr(char *s __attribute__ ((__unused__)), char *ss) {
97 const char* ret = tigetstr(ss);
98 if (!ret || ret==(char*)-1)
99 return "";
100 else
101 return ret;
102 }
103
104 #elif defined(HAVE_LIBTERMCAP)
105
106 #include <termcap.h>
107
108 char termbuffer[4096];
109 char tcbuffer[4096];
110 char *strbuf = termbuffer;
111
112 static void
113 my_setupterm(const char *term, int fildes, int *errret) {
114 *errret = tgetent(tcbuffer, term);
115 }
116
117 static void
118 my_putstring(char *s) {
119 tputs (s, 1, putchar);
120 }
121
122 static const char *
123 my_tgetstr(char *s, char *ss __attribute__ ((__unused__))) {
124 const char* ret = tgetstr(s, &strbuf);
125 if (!ret)
126 return "";
127 else
128 return ret;
129 }
130
131 #else /* ! (HAVE_LIBTERMCAP || HAVE_LIBNCURSES || HAVE_LIBNCURSESW) */
132
133 static void
134 my_putstring(char *s) {
135 fputs(s, stdout);
136 }
137
138 #endif
139
140
141 const char *term="";
142 const char *Senter="", *Sexit="";/* enter and exit standout mode */
143 int Slen; /* strlen of Senter+Sexit */
144 char *Hrow; /* pointer to highlighted row in month */
145
146 #include "widechar.h"
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, size_t width);
247 void center(const char *, size_t, 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 static void __attribute__ ((__noreturn__)) usage(FILE * out);
258 void headers_init(void);
259
260 int
261 main(int argc, char **argv) {
262 struct tm *local_time;
263 time_t now;
264 int ch, day, month, year, yflag;
265 int num_months = NUM_MONTHS;
266
267 static const struct option longopts[] = {
268 {"one", no_argument, NULL, '1'},
269 {"three", no_argument, NULL, '3'},
270 {"sunday", no_argument, NULL, 's'},
271 {"monday", no_argument, NULL, 'm'},
272 {"julian", no_argument, NULL, 'j'},
273 {"year", no_argument, NULL, 'y'},
274 {"version", no_argument, NULL, 'V'},
275 {"help", no_argument, NULL, 'h'},
276 {NULL, 0, NULL, 0}
277 };
278
279 setlocale(LC_ALL, "");
280 bindtextdomain(PACKAGE, LOCALEDIR);
281 textdomain(PACKAGE);
282 atexit(close_stdout);
283
284 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
285 if ((term = getenv("TERM"))) {
286 int ret;
287 my_setupterm(term, 1, &ret);
288 if (ret > 0) {
289 Senter = my_tgetstr("so","smso");
290 Sexit = my_tgetstr("se","rmso");
291 Slen = strlen(Senter) + strlen(Sexit);
292 }
293 }
294 #endif
295
296 /*
297 * The traditional Unix cal utility starts the week at Sunday,
298 * while ISO 8601 starts at Monday. We read the start day from
299 * the locale database, which can be overridden with the
300 * -s (Sunday) or -m (Monday) options.
301 */
302 #if HAVE_DECL__NL_TIME_WEEK_1STDAY
303 /*
304 * You need to use 2 locale variables to get the first day of the week.
305 * This is needed to support first_weekday=2 and first_workday=1 for
306 * the rare case where working days span across 2 weeks.
307 * This shell script shows the combinations and calculations involved:
308 *
309 * for LANG in en_US ru_RU fr_FR csb_PL POSIX; do
310 * printf "%s:\t%s + %s -1 = " $LANG $(locale week-1stday first_weekday)
311 * date -d"$(locale week-1stday) +$(($(locale first_weekday)-1))day" +%w
312 * done
313 *
314 * en_US: 19971130 + 1 -1 = 0 #0 = sunday
315 * ru_RU: 19971130 + 2 -1 = 1
316 * fr_FR: 19971201 + 1 -1 = 1
317 * csb_PL: 19971201 + 2 -1 = 2
318 * POSIX: 19971201 + 7 -1 = 0
319 */
320 {
321 int wfd;
322 union { unsigned int word; char *string; } val;
323 val.string = nl_langinfo(_NL_TIME_WEEK_1STDAY);
324
325 wfd = val.word;
326 wfd = day_in_week(wfd % 100, (wfd / 100) % 100, wfd / (100 * 100));
327 weekstart = (wfd + *nl_langinfo(_NL_TIME_FIRST_WEEKDAY) - 1) % 7;
328 }
329 #endif
330
331 yflag = 0;
332 while ((ch = getopt_long(argc, argv, "13mjsyVh", longopts, NULL)) != -1)
333 switch(ch) {
334 case '1':
335 num_months = 1; /* default */
336 break;
337 case '3':
338 num_months = 3;
339 break;
340 case 's':
341 weekstart = 0; /* default */
342 break;
343 case 'm':
344 weekstart = 1;
345 break;
346 case 'j':
347 julian = 1;
348 break;
349 case 'y':
350 yflag = 1;
351 break;
352 case 'V':
353 printf(UTIL_LINUX_VERSION);
354 return EXIT_SUCCESS;
355 case 'h':
356 usage(stdout);
357 case '?':
358 default:
359 usage(stderr);
360 }
361 argc -= optind;
362 argv += optind;
363
364 time(&now);
365 local_time = localtime(&now);
366
367 day = month = year = 0;
368 switch(argc) {
369 case 3:
370 day = strtos32_or_err(*argv++, _("illegal day value"));
371 if (day < 1 || 31 < day)
372 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), 31);
373 /* FALLTHROUGH */
374 case 2:
375 month = strtos32_or_err(*argv++, _("illegal month value: use 1-12"));
376 if (month < 1 || 12 < month)
377 errx(EXIT_FAILURE, _("illegal month value: use 1-12"));
378 /* FALLTHROUGH */
379 case 1:
380 year = strtos32_or_err(*argv++, _("illegal year value: use 1-9999"));
381 if (year < 1 || 9999 < year)
382 errx(EXIT_FAILURE, _("illegal year value: use 1-9999"));
383 if (day) {
384 int dm = days_in_month[leap_year(year)][month];
385 if (day > dm)
386 errx(EXIT_FAILURE, _("illegal day value: use 1-%d"), dm);
387 day = day_in_year(day, month, year);
388 } else if ((local_time->tm_year + 1900) == year) {
389 day = local_time->tm_yday + 1;
390 }
391 if (!month)
392 yflag=1;
393 break;
394 case 0:
395 day = local_time->tm_yday + 1;
396 year = local_time->tm_year + 1900;
397 month = local_time->tm_mon + 1;
398 break;
399 default:
400 usage(stderr);
401 }
402 headers_init();
403
404 if (!isatty(1))
405 day = 0; /* don't highlight */
406
407 if (yflag && julian)
408 j_yearly(day, year);
409 else if (yflag)
410 yearly(day, year);
411 else if (num_months == 1)
412 monthly(day, month, year);
413 else if (num_months == 3)
414 monthly3(day, month, year);
415
416 return EXIT_SUCCESS;
417 }
418
419 void headers_init(void)
420 {
421 int i, wd;
422 char *cur_dh = day_headings, *cur_j_dh = j_day_headings;
423
424 strcpy(day_headings, "");
425 strcpy(j_day_headings, "");
426
427 for (i = 0; i < 7; i++) {
428 ssize_t space_left;
429 wd = (i + weekstart) % 7;
430
431 if (i)
432 strcat(cur_dh++, " ");
433 space_left =
434 sizeof(day_headings) - (cur_dh - day_headings);
435 if (space_left <= 2)
436 break;
437 cur_dh +=
438 center_str(nl_langinfo(ABDAY_1 + wd), cur_dh,
439 space_left, 2);
440
441 if (i)
442 strcat(cur_j_dh++, " ");
443 space_left =
444 sizeof(j_day_headings) - (cur_j_dh - j_day_headings);
445 if (space_left <= 3)
446 break;
447 cur_j_dh +=
448 center_str(nl_langinfo(ABDAY_1 + wd), cur_j_dh,
449 space_left, 3);
450 }
451
452 for (i = 0; i < 12; i++)
453 full_month[i] = nl_langinfo(MON_1 + i);
454 }
455
456 void
457 do_monthly(int day, int month, int year, struct fmt_st *out) {
458 int col, row, days[MAXDAYS];
459 char *p, lineout[FMT_ST_CHARS];
460 int width = (julian ? J_WEEK_LEN : WEEK_LEN) - 1;
461
462 day_array(day, month, year, days);
463
464 /*
465 * %s is the month name, %d the year number.
466 * you can change the order and/or add something here; eg for
467 * Basque the translation should be: "%2$dko %1$s", and
468 * the Vietnamese should be "%s na(m %d", etc.
469 */
470 snprintf(lineout, sizeof(lineout), _("%s %d"),
471 full_month[month - 1], year);
472 center_str(lineout, out->s[0], ARRAY_SIZE(out->s[0]), width);
473
474 snprintf(out->s[1], FMT_ST_CHARS, "%s",
475 julian ? j_day_headings : day_headings);
476 for (row = 0; row < 6; row++) {
477 int has_hl = 0;
478 for (col = 0, p = lineout; col < 7; col++) {
479 int xd = days[row * 7 + col];
480 if (xd != SPACE && (xd & TODAY_FLAG))
481 has_hl = 1;
482 p = ascii_day(p, xd);
483 }
484 *p = '\0';
485 trim_trailing_spaces(lineout);
486 snprintf(out->s[row+2], FMT_ST_CHARS, "%s", lineout);
487 if (has_hl)
488 Hrow = out->s[row+2];
489 }
490 }
491
492 void
493 monthly(int day, int month, int year) {
494 int i;
495 struct fmt_st out;
496
497 do_monthly(day, month, year, &out);
498 for (i = 0; i < FMT_ST_LINES; i++) {
499 my_putstring(out.s[i]);
500 my_putstring("\n");
501 }
502 }
503
504 void
505 monthly3(int day, int month, int year) {
506 char lineout[FMT_ST_CHARS];
507 int i;
508 int width;
509 struct fmt_st out_prev;
510 struct fmt_st out_curm;
511 struct fmt_st out_next;
512 int prev_month, prev_year;
513 int next_month, next_year;
514
515 if (month == 1) {
516 prev_month = 12;
517 prev_year = year - 1;
518 } else {
519 prev_month = month - 1;
520 prev_year = year;
521 }
522 if (month == 12) {
523 next_month = 1;
524 next_year = year + 1;
525 } else {
526 next_month = month + 1;
527 next_year = year;
528 }
529
530 do_monthly(day, prev_month, prev_year, &out_prev);
531 do_monthly(day, month, year, &out_curm);
532 do_monthly(day, next_month, next_year, &out_next);
533
534 width = (julian ? J_WEEK_LEN : WEEK_LEN) -1;
535 for (i = 0; i < 2; i++) {
536 snprintf(lineout, sizeof(lineout),
537 "%s %s %s\n", out_prev.s[i], out_curm.s[i], out_next.s[i]);
538 my_putstring(lineout);
539 }
540 for (i = 2; i < FMT_ST_LINES; i++) {
541 int w1, w2, w3;
542 w1 = w2 = w3 = width;
543
544 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW) || defined(HAVE_LIBTERMCAP)
545 /* adjust width to allow for non printable characters */
546 w1 += (out_prev.s[i] == Hrow ? Slen : 0);
547 w2 += (out_curm.s[i] == Hrow ? Slen : 0);
548 w3 += (out_next.s[i] == Hrow ? Slen : 0);
549 #endif
550 snprintf(lineout, sizeof(lineout), "%-*s %-*s %-*s\n",
551 w1, out_prev.s[i],
552 w2, out_curm.s[i],
553 w3, out_next.s[i]);
554
555 my_putstring(lineout);
556 }
557 }
558
559 void
560 j_yearly(int day, int year) {
561 int col, *dp, i, month, row, which_cal;
562 int days[12][MAXDAYS];
563 char *p, lineout[80];
564
565 snprintf(lineout, sizeof(lineout), "%d", year);
566 center(lineout, J_WEEK_LEN*2 + J_HEAD_SEP - 1, 0);
567 my_putstring("\n\n");
568
569 for (i = 0; i < 12; i++)
570 day_array(day, i + 1, year, days[i]);
571 memset(lineout, ' ', sizeof(lineout) - 1);
572 lineout[sizeof(lineout) - 1] = '\0';
573 for (month = 0; month < 12; month += 2) {
574 center(full_month[month], J_WEEK_LEN-1, J_HEAD_SEP+1);
575 center(full_month[month + 1], J_WEEK_LEN-1, 0);
576 snprintf(lineout, sizeof(lineout),
577 "\n%s%*s %s\n", j_day_headings, J_HEAD_SEP, "",
578 j_day_headings);
579 my_putstring(lineout);
580 for (row = 0; row < 6; row++) {
581 p = lineout;
582 for (which_cal = 0; which_cal < 2; which_cal++) {
583 dp = &days[month + which_cal][row * 7];
584 for (col = 0; col < 7; col++)
585 p = ascii_day(p, *dp++);
586 p += sprintf(p, " ");
587 }
588 *p = '\0';
589 trim_trailing_spaces(lineout);
590 my_putstring(lineout);
591 my_putstring("\n");
592 }
593 }
594 my_putstring("\n");
595 }
596
597 void
598 yearly(int day, int year) {
599 int col, *dp, i, month, row, which_cal;
600 int days[12][MAXDAYS];
601 char *p, lineout[100];
602
603 snprintf(lineout, sizeof(lineout), "%d", year);
604 center(lineout, WEEK_LEN*3 + HEAD_SEP*2 - 1, 0);
605 my_putstring("\n\n");
606
607 for (i = 0; i < 12; i++)
608 day_array(day, i + 1, year, days[i]);
609 memset(lineout, ' ', sizeof(lineout) - 1);
610 lineout[sizeof(lineout) - 1] = '\0';
611 for (month = 0; month < 12; month += 3) {
612 center(full_month[month], WEEK_LEN-1, HEAD_SEP+1);
613 center(full_month[month + 1], WEEK_LEN-1, HEAD_SEP+1);
614 center(full_month[month + 2], WEEK_LEN-1, 0);
615 snprintf(lineout, sizeof(lineout),
616 "\n%s%*s %s%*s %s\n", day_headings, HEAD_SEP,
617 "", day_headings, HEAD_SEP, "", day_headings);
618 my_putstring(lineout);
619 for (row = 0; row < 6; row++) {
620 p = lineout;
621 for (which_cal = 0; which_cal < 3; which_cal++) {
622 dp = &days[month + which_cal][row * 7];
623 for (col = 0; col < 7; col++)
624 p = ascii_day(p, *dp++);
625 p += sprintf(p, " ");
626 }
627 *p = '\0';
628 trim_trailing_spaces(lineout);
629 my_putstring(lineout);
630 my_putstring("\n");
631 }
632 }
633 my_putstring("\n");
634 }
635
636 /*
637 * day_array --
638 * Fill in an array of 42 integers with a calendar. Assume for a moment
639 * that you took the (maximum) 6 rows in a calendar and stretched them
640 * out end to end. You would have 42 numbers or spaces. This routine
641 * builds that array for any month from Jan. 1 through Dec. 9999.
642 */
643 void
644 day_array(int day, int month, int year, int *days) {
645 int julday, daynum, dw, dm;
646 int *d_sep1752;
647
648 if (month == 9 && year == 1752) {
649 int sep1752_ofs = (weekstart + SEP1752_OFS) % 7;
650 d_sep1752 = julian ? j_sep1752 : sep1752;
651 memcpy(days, d_sep1752 + sep1752_ofs, MAXDAYS * sizeof(int));
652 for (dm=0; dm<MAXDAYS; dm++)
653 if (j_sep1752[dm + sep1752_ofs] == day)
654 days[dm] |= TODAY_FLAG;
655 return;
656 }
657 memcpy(days, empty, MAXDAYS * sizeof(int));
658 dm = days_in_month[leap_year(year)][month];
659 dw = (day_in_week(1, month, year) - weekstart + 7) % 7;
660 julday = day_in_year(1, month, year);
661 daynum = julian ? julday : 1;
662 while (dm--) {
663 days[dw] = daynum++;
664 if (julday++ == day)
665 days[dw] |= TODAY_FLAG;
666 dw++;
667 }
668 }
669
670 /*
671 * day_in_year --
672 * return the 1 based day number within the year
673 */
674 int
675 day_in_year(int day, int month, int year) {
676 int i, leap;
677
678 leap = leap_year(year);
679 for (i = 1; i < month; i++)
680 day += days_in_month[leap][i];
681 return day;
682 }
683
684 /*
685 * day_in_week
686 * return the 0 based day number for any date from 1 Jan. 1 to
687 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates
688 * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all
689 * missing days.
690 */
691 int
692 day_in_week(int day, int month, int year) {
693 long temp;
694
695 temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1)
696 + day_in_year(day, month, year);
697 if (temp < FIRST_MISSING_DAY)
698 return ((temp - 1 + SATURDAY) % 7);
699 if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS))
700 return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
701 return (THURSDAY);
702 }
703
704 char *
705 ascii_day(char *p, int day) {
706 int display, val;
707 int highlight = 0;
708 static char *aday[] = {
709 "",
710 " 1", " 2", " 3", " 4", " 5", " 6", " 7",
711 " 8", " 9", "10", "11", "12", "13", "14",
712 "15", "16", "17", "18", "19", "20", "21",
713 "22", "23", "24", "25", "26", "27", "28",
714 "29", "30", "31",
715 };
716
717 if (day == SPACE) {
718 int len = julian ? J_DAY_LEN : DAY_LEN;
719 memset(p, ' ', len);
720 return p+len;
721 }
722 if (day & TODAY_FLAG) {
723 day &= ~TODAY_FLAG;
724 p += sprintf(p, "%s", Senter);
725 highlight = 1;
726 }
727 if (julian) {
728 if ((val = day / 100)) {
729 day %= 100;
730 *p++ = val + '0';
731 display = 1;
732 } else {
733 *p++ = ' ';
734 display = 0;
735 }
736 val = day / 10;
737 if (val || display)
738 *p++ = val + '0';
739 else
740 *p++ = ' ';
741 *p++ = day % 10 + '0';
742 } else {
743 *p++ = aday[day][0];
744 *p++ = aday[day][1];
745 }
746 if (highlight)
747 p += sprintf(p, "%s", Sexit);
748 *p++ = ' ';
749 return p;
750 }
751
752 void
753 trim_trailing_spaces(char *s)
754 {
755 char *p;
756
757 for (p = s; *p; ++p)
758 continue;
759 while (p > s && isspace(*--p))
760 continue;
761 if (p > s)
762 ++p;
763 *p = '\0';
764 }
765
766 /*
767 * Center string, handling multibyte characters appropriately.
768 * In addition if the string is too large for the width it's truncated.
769 * The number of trailing spaces may be 1 less than the number of leading spaces.
770 */
771 int
772 center_str(const char* src, char* dest, size_t dest_size, size_t width)
773 {
774 return mbsalign(src, dest, dest_size, &width,
775 MBS_ALIGN_CENTER, MBA_UNIBYTE_FALLBACK);
776 }
777
778 void
779 center(const char *str, size_t len, int separate)
780 {
781 char lineout[FMT_ST_CHARS];
782
783 center_str(str, lineout, ARRAY_SIZE(lineout), len);
784 my_putstring(lineout);
785
786 if (separate) {
787 snprintf(lineout, sizeof(lineout), "%*s", separate, "");
788 my_putstring(lineout);
789 }
790 }
791
792 static void __attribute__ ((__noreturn__)) usage(FILE * out)
793 {
794 fputs(_("\nUsage:\n"), out);
795 fprintf(out,
796 _(" %s [options] [[[day] month] year]\n"),
797 program_invocation_short_name);
798
799 fputs(_("\nOptions:\n"), out);
800 fputs(_(" -1, --one show only current month (default)\n"
801 " -3, --three show previous, current and next month\n"
802 " -s, --sunday Sunday as first day of week\n"
803 " -m, --monday Monday as first day of week\n"
804 " -j, --julian output Julian dates\n"
805 " -y, --year show whole current year\n"
806 " -V, --version display version information and exit\n"
807 " -h, --help display this help text and exit\n\n"), out);
808
809 exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
810 }