1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 1997 Wolfgang Helbig 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/cdefs.h> 30 #include <calendar.h> 31 #include <ctype.h> 32 #include <err.h> 33 #include <langinfo.h> 34 #include <libgen.h> 35 #include <locale.h> 36 #include <stdio.h> 37 #include <stdlib.h> 38 #include <string.h> 39 #include <stdbool.h> 40 #include <sysexits.h> 41 #include <time.h> 42 #include <unistd.h> 43 #include <wchar.h> 44 #include <wctype.h> 45 #include <term.h> 46 #undef lines /* term.h defines this */ 47 48 /* Width of one month with backward compatibility and in regular mode*/ 49 #define MONTH_WIDTH_B_J 27 50 #define MONTH_WIDTH_B 20 51 52 #define MONTH_WIDTH_R_J 24 53 #define MONTH_WIDTH_R 18 54 55 #define MAX_WIDTH 64 56 57 typedef struct date date; 58 59 struct monthlines { 60 wchar_t name[MAX_WIDTH + 1]; 61 char lines[7][MAX_WIDTH + 1]; 62 char weeks[MAX_WIDTH + 1]; 63 unsigned int extralen[7]; 64 }; 65 66 struct weekdays { 67 wchar_t names[7][4]; 68 }; 69 70 /* The switches from Julian to Gregorian in some countries */ 71 static struct djswitch { 72 const char *cc; /* Country code according to ISO 3166 */ 73 const char *nm; /* Name of country */ 74 date dt; /* Last day of Julian calendar */ 75 } switches[] = { 76 {"AL", "Albania", {1912, 11, 30}}, 77 {"AT", "Austria", {1583, 10, 5}}, 78 {"AU", "Australia", {1752, 9, 2}}, 79 {"BE", "Belgium", {1582, 12, 14}}, 80 {"BG", "Bulgaria", {1916, 3, 31}}, 81 {"CA", "Canada", {1752, 9, 2}}, 82 {"CH", "Switzerland", {1655, 2, 28}}, 83 {"CN", "China", {1911, 12, 18}}, 84 {"CZ", "Czech Republic",{1584, 1, 6}}, 85 {"DE", "Germany", {1700, 2, 18}}, 86 {"DK", "Denmark", {1700, 2, 18}}, 87 {"ES", "Spain", {1582, 10, 4}}, 88 {"FI", "Finland", {1753, 2, 17}}, 89 {"FR", "France", {1582, 12, 9}}, 90 {"GB", "United Kingdom",{1752, 9, 2}}, 91 {"GR", "Greece", {1924, 3, 9}}, 92 {"HU", "Hungary", {1587, 10, 21}}, 93 {"IS", "Iceland", {1700, 11, 16}}, 94 {"IT", "Italy", {1582, 10, 4}}, 95 {"JP", "Japan", {1918, 12, 18}}, 96 {"LT", "Lithuania", {1918, 2, 1}}, 97 {"LU", "Luxembourg", {1582, 12, 14}}, 98 {"LV", "Latvia", {1918, 2, 1}}, 99 {"NL", "Netherlands", {1582, 12, 14}}, 100 {"NO", "Norway", {1700, 2, 18}}, 101 {"PL", "Poland", {1582, 10, 4}}, 102 {"PT", "Portugal", {1582, 10, 4}}, 103 {"RO", "Romania", {1919, 3, 31}}, 104 {"RU", "Russia", {1918, 1, 31}}, 105 {"SI", "Slovenia", {1919, 3, 4}}, 106 {"SE", "Sweden", {1753, 2, 17}}, 107 {"TR", "Turkey", {1926, 12, 18}}, 108 {"US", "United States", {1752, 9, 2}}, 109 {"YU", "Yugoslavia", {1919, 3, 4}} 110 }; 111 112 static struct djswitch *dftswitch = 113 switches + sizeof(switches) / sizeof(struct djswitch) - 2; 114 /* default switch (should be "US") */ 115 116 /* Table used to print day of month and week numbers */ 117 static char daystr[] = " 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15" 118 " 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31" 119 " 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47" 120 " 48 49 50 51 52 53"; 121 122 /* Table used to print day of year and week numbers */ 123 static char jdaystr[] = " 1 2 3 4 5 6 7 8 9" 124 " 10 11 12 13 14 15 16 17 18 19" 125 " 20 21 22 23 24 25 26 27 28 29" 126 " 30 31 32 33 34 35 36 37 38 39" 127 " 40 41 42 43 44 45 46 47 48 49" 128 " 50 51 52 53 54 55 56 57 58 59" 129 " 60 61 62 63 64 65 66 67 68 69" 130 " 70 71 72 73 74 75 76 77 78 79" 131 " 80 81 82 83 84 85 86 87 88 89" 132 " 90 91 92 93 94 95 96 97 98 99" 133 " 100 101 102 103 104 105 106 107 108 109" 134 " 110 111 112 113 114 115 116 117 118 119" 135 " 120 121 122 123 124 125 126 127 128 129" 136 " 130 131 132 133 134 135 136 137 138 139" 137 " 140 141 142 143 144 145 146 147 148 149" 138 " 150 151 152 153 154 155 156 157 158 159" 139 " 160 161 162 163 164 165 166 167 168 169" 140 " 170 171 172 173 174 175 176 177 178 179" 141 " 180 181 182 183 184 185 186 187 188 189" 142 " 190 191 192 193 194 195 196 197 198 199" 143 " 200 201 202 203 204 205 206 207 208 209" 144 " 210 211 212 213 214 215 216 217 218 219" 145 " 220 221 222 223 224 225 226 227 228 229" 146 " 230 231 232 233 234 235 236 237 238 239" 147 " 240 241 242 243 244 245 246 247 248 249" 148 " 250 251 252 253 254 255 256 257 258 259" 149 " 260 261 262 263 264 265 266 267 268 269" 150 " 270 271 272 273 274 275 276 277 278 279" 151 " 280 281 282 283 284 285 286 287 288 289" 152 " 290 291 292 293 294 295 296 297 298 299" 153 " 300 301 302 303 304 305 306 307 308 309" 154 " 310 311 312 313 314 315 316 317 318 319" 155 " 320 321 322 323 324 325 326 327 328 329" 156 " 330 331 332 333 334 335 336 337 338 339" 157 " 340 341 342 343 344 345 346 347 348 349" 158 " 350 351 352 353 354 355 356 357 358 359" 159 " 360 361 362 363 364 365 366"; 160 161 static int flag_nohighlight; /* user doesn't want a highlighted today */ 162 static int flag_weeks; /* user wants number of week */ 163 static int nswitch; /* user defined switch date */ 164 static int nswitchb; /* switch date for backward compatibility */ 165 static int highlightdate; 166 static bool flag_monday; /* user wants week starts on Monday */ 167 168 static char *center(char *s, char *t, int w); 169 static wchar_t *wcenter(wchar_t *s, wchar_t *t, int w); 170 static int firstday(int y, int m); 171 static void highlight(char *dst, char *src, int len, int *extraletters); 172 static void mkmonthr(int year, int month, int jd_flag, 173 struct monthlines * monthl); 174 static void mkmonthb(int year, int month, int jd_flag, 175 struct monthlines * monthl); 176 static void mkweekdays(struct weekdays * wds); 177 static void monthranger(int year, int m, int jd_flag, 178 int before, int after); 179 static void monthrangeb(int year, int m, int jd_flag, 180 int before, int after); 181 static int parsemonth(const char *s, int *m, int *y); 182 static void printcc(void); 183 static void printeaster(int year, int julian, int orthodox); 184 static date *sdater(int ndays, struct date * d); 185 static date *sdateb(int ndays, struct date * d); 186 static int sndaysr(struct date * d); 187 static int sndaysb(struct date * d); 188 static void usage(void); 189 190 int 191 main(int argc, char *argv[]) 192 { 193 struct djswitch *p, *q; /* to search user defined switch date */ 194 date never = {10000, 1, 1}; /* outside valid range of dates */ 195 date ukswitch = {1752, 9, 2};/* switch date for Great Britain */ 196 date dt; 197 int ch; /* holds the option character */ 198 int m = 0; /* month */ 199 int y = 0; /* year */ 200 int flag_backward = 0; /* user called cal--backward compat. */ 201 int flag_wholeyear = 0; /* user wants the whole year */ 202 int flag_julian_cal = 0; /* user wants Julian Calendar */ 203 int flag_julian_day = 0; /* user wants the Julian day numbers */ 204 int flag_orthodox = 0; /* user wants Orthodox easter */ 205 int flag_easter = 0; /* user wants easter date */ 206 int flag_3months = 0; /* user wants 3 month display (-3) */ 207 int flag_after = 0; /* user wants to see months after */ 208 int flag_before = 0; /* user wants to see months before */ 209 int flag_specifiedmonth = 0;/* user wants to see this month (-m) */ 210 int flag_givenmonth = 0; /* user has specified month [n] */ 211 int flag_givenyear = 0; /* user has specified year [n] */ 212 char *cp; /* character pointer */ 213 char *flag_today = NULL; /* debug: use date as being today */ 214 char *flag_month = NULL; /* requested month as string */ 215 char *flag_highlightdate = NULL; /* debug: date to highlight */ 216 int before, after; 217 const char *locale; /* locale to get country code */ 218 219 flag_nohighlight = 0; 220 flag_weeks = 0; 221 flag_monday = false; 222 223 /* 224 * Use locale to determine the country code, 225 * and use the country code to determine the default 226 * switchdate and date format from the switches table. 227 */ 228 if (setlocale(LC_ALL, "") == NULL) 229 warn("setlocale"); 230 locale = setlocale(LC_TIME, NULL); 231 if (locale == NULL || 232 strcmp(locale, "C") == 0 || 233 strcmp(locale, "POSIX") == 0 || 234 strcmp(locale, "ASCII") == 0 || 235 strcmp(locale, "US-ASCII") == 0) 236 locale = "_US"; 237 q = switches + sizeof(switches) / sizeof(struct djswitch); 238 for (p = switches; p != q; p++) 239 if ((cp = strstr(locale, p->cc)) != NULL && *(cp - 1) == '_') 240 break; 241 if (p == q) { 242 nswitch = ndaysj(&dftswitch->dt); 243 } else { 244 nswitch = ndaysj(&p->dt); 245 dftswitch = p; 246 } 247 248 249 /* 250 * Get the filename portion of argv[0] and set flag_backward if 251 * this program is called "cal". 252 */ 253 if (strncmp(basename(argv[0]), "cal", strlen("cal")) == 0) 254 flag_backward = 1; 255 256 /* Set the switch date to United Kingdom if backwards compatible */ 257 if (flag_backward) 258 nswitchb = ndaysj(&ukswitch); 259 260 before = after = -1; 261 262 while ((ch = getopt(argc, argv, "3A:B:Cd:eH:hjJm:Nops:wyM")) != -1) 263 switch (ch) { 264 case '3': 265 flag_3months = 1; 266 break; 267 case 'A': 268 if (flag_after > 0) 269 errx(EX_USAGE, "Double -A specified"); 270 flag_after = strtol(optarg, NULL, 10); 271 if (flag_after <= 0) 272 errx(EX_USAGE, 273 "Argument to -A must be positive"); 274 break; 275 case 'B': 276 if (flag_before > 0) 277 errx(EX_USAGE, "Double -B specified"); 278 flag_before = strtol(optarg, NULL, 10); 279 if (flag_before <= 0) 280 errx(EX_USAGE, 281 "Argument to -B must be positive"); 282 break; 283 case 'J': 284 if (flag_backward) 285 usage(); 286 nswitch = ndaysj(&never); 287 flag_julian_cal = 1; 288 break; 289 case 'C': 290 flag_backward = 1; 291 break; 292 case 'N': 293 flag_backward = 0; 294 break; 295 case 'd': 296 flag_today = optarg; 297 break; 298 case 'H': 299 flag_highlightdate = optarg; 300 break; 301 case 'h': 302 flag_nohighlight = 1; 303 break; 304 case 'e': 305 if (flag_backward) 306 usage(); 307 flag_easter = 1; 308 break; 309 case 'j': 310 flag_julian_day = 1; 311 break; 312 case 'M': 313 flag_monday = true; 314 break; 315 case 'm': 316 if (flag_specifiedmonth) 317 errx(EX_USAGE, "Double -m specified"); 318 flag_month = optarg; 319 flag_specifiedmonth = 1; 320 break; 321 case 'o': 322 if (flag_backward) 323 usage(); 324 flag_orthodox = 1; 325 flag_easter = 1; 326 break; 327 case 'p': 328 if (flag_backward) 329 usage(); 330 printcc(); 331 return (0); 332 break; 333 case 's': 334 if (flag_backward) 335 usage(); 336 q = switches + 337 sizeof(switches) / sizeof(struct djswitch); 338 for (p = switches; 339 p != q && strcmp(p->cc, optarg) != 0; p++) 340 ; 341 if (p == q) 342 errx(EX_USAGE, 343 "%s: invalid country code", optarg); 344 nswitch = ndaysj(&(p->dt)); 345 break; 346 case 'w': 347 if (flag_backward) 348 usage(); 349 flag_weeks = 1; 350 break; 351 case 'y': 352 flag_wholeyear = 1; 353 break; 354 default: 355 usage(); 356 } 357 358 argc -= optind; 359 argv += optind; 360 361 switch (argc) { 362 case 2: 363 if (flag_easter) 364 usage(); 365 flag_month = *argv++; 366 flag_givenmonth = 1; 367 m = strtol(flag_month, NULL, 10); 368 /* FALLTHROUGH */ 369 case 1: 370 y = atoi(*argv); 371 if (y < 1 || y > 9999) 372 errx(EX_USAGE, "year `%s' not in range 1..9999", *argv); 373 argv++; 374 flag_givenyear = 1; 375 break; 376 case 0: 377 if (flag_today != NULL) { 378 y = strtol(flag_today, NULL, 10); 379 m = strtol(flag_today + 5, NULL, 10); 380 } else { 381 time_t t; 382 struct tm *tm; 383 384 t = time(NULL); 385 tm = localtime(&t); 386 y = tm->tm_year + 1900; 387 m = tm->tm_mon + 1; 388 } 389 break; 390 default: 391 usage(); 392 } 393 394 if (flag_month != NULL) { 395 if (parsemonth(flag_month, &m, &y)) { 396 errx(EX_USAGE, 397 "%s is neither a month number (1..12) nor a name", 398 flag_month); 399 } 400 } 401 402 /* 403 * What is not supported: 404 * -3 with -A or -B 405 * -3 displays 3 months, -A and -B change that behaviour. 406 * -3 with -y 407 * -3 displays 3 months, -y says display a whole year. 408 * -3 with a given year but no given month or without -m 409 * -3 displays 3 months, no month specified doesn't make clear 410 * which three months. 411 * -m with a given month 412 * conflicting arguments, both specify the same field. 413 * -y with -m 414 * -y displays the whole year, -m displays a single month. 415 * -y with a given month 416 * -y displays the whole year, the given month displays a single 417 * month. 418 * -y with -A or -B 419 * -y displays the whole year, -A and -B display extra months. 420 */ 421 422 /* -3 together with -A or -B. */ 423 if (flag_3months && (flag_after || flag_before)) 424 errx(EX_USAGE, "-3 together with -A and -B is not supported."); 425 /* -3 together with -y. */ 426 if (flag_3months && flag_wholeyear) 427 errx(EX_USAGE, "-3 together with -y is not supported."); 428 /* -3 together with givenyear but no givenmonth. */ 429 if (flag_3months && flag_givenyear && 430 !(flag_givenmonth || flag_specifiedmonth)) 431 errx(EX_USAGE, 432 "-3 together with a given year but no given month is " 433 "not supported."); 434 /* -m together with xx xxxx. */ 435 if (flag_specifiedmonth && flag_givenmonth) 436 errx(EX_USAGE, 437 "-m together with a given month is not supported."); 438 /* -y together with -m. */ 439 if (flag_wholeyear && flag_specifiedmonth) 440 errx(EX_USAGE, "-y together with -m is not supported."); 441 /* -y together with xx xxxx. */ 442 if (flag_wholeyear && flag_givenmonth) 443 errx(EX_USAGE, "-y together a given month is not supported."); 444 /* -y together with -A or -B. */ 445 if (flag_wholeyear && (flag_before > 0 || flag_after > 0)) 446 errx(EX_USAGE, "-y together a -A or -B is not supported."); 447 /* The rest should be fine. */ 448 449 /* Select the period to display, in order of increasing priority .*/ 450 if (flag_wholeyear || 451 (flag_givenyear && !(flag_givenmonth || flag_specifiedmonth))) { 452 m = 1; 453 before = 0; 454 after = 11; 455 } 456 if (flag_givenyear && flag_givenmonth) { 457 before = 0; 458 after = 0; 459 } 460 if (flag_specifiedmonth) { 461 before = 0; 462 after = 0; 463 } 464 if (flag_before) { 465 before = flag_before; 466 } 467 if (flag_after) { 468 after = flag_after; 469 } 470 if (flag_3months) { 471 before = 1; 472 after = 1; 473 } 474 if (after == -1) 475 after = 0; 476 if (before == -1) 477 before = 0; 478 479 /* Highlight a specified day or today .*/ 480 if (flag_highlightdate != NULL) { 481 dt.y = strtol(flag_highlightdate, NULL, 10); 482 dt.m = strtol(flag_highlightdate + 5, NULL, 10); 483 dt.d = strtol(flag_highlightdate + 8, NULL, 10); 484 } else { 485 time_t t; 486 struct tm *tm1; 487 488 t = time(NULL); 489 tm1 = localtime(&t); 490 dt.y = tm1->tm_year + 1900; 491 dt.m = tm1->tm_mon + 1; 492 dt.d = tm1->tm_mday; 493 } 494 highlightdate = sndaysb(&dt); 495 496 /* And now we finally start to calculate and output calendars. */ 497 if (flag_easter) 498 printeaster(y, flag_julian_cal, flag_orthodox); 499 else 500 if (flag_backward) 501 monthrangeb(y, m, flag_julian_day, before, after); 502 else 503 monthranger(y, m, flag_julian_day, before, after); 504 if (ferror(stdout) != 0 || fflush(stdout) != 0) 505 err(1, "stdout"); 506 return (0); 507 } 508 509 static void 510 usage(void) 511 { 512 513 fputs( 514 "Usage: cal [general options] [-hjy] [[month] year]\n" 515 " cal [general options] [-hj] [-m month] [year]\n" 516 " ncal [general options] [-hJjpwy] [-s country_code] [[month] year]\n" 517 " ncal [general options] [-hJeo] [year]\n" 518 "General options: [-NCM3] [-A months] [-B months]\n" 519 "For debug the highlighting: [-H yyyy-mm-dd] [-d yyyy-mm]\n", 520 stderr); 521 exit(EX_USAGE); 522 } 523 524 /* Print the assumed switches for all countries. */ 525 static void 526 printcc(void) 527 { 528 struct djswitch *p; 529 int n; /* number of lines to print */ 530 int m; /* offset from left to right table entry on the same line */ 531 532 #define FSTR "%c%s %-15s%4d-%02d-%02d" 533 #define DFLT(p) ((p) == dftswitch ? '*' : ' ') 534 #define FSTRARG(p) DFLT(p), (p)->cc, (p)->nm, (p)->dt.y, (p)->dt.m, (p)->dt.d 535 536 n = sizeof(switches) / sizeof(struct djswitch); 537 m = (n + 1) / 2; 538 n /= 2; 539 for (p = switches; p != switches + n; p++) 540 printf(FSTR" "FSTR"\n", FSTRARG(p), FSTRARG(p+m)); 541 if (m != n) 542 printf(FSTR"\n", FSTRARG(p)); 543 } 544 545 /* Print the date of easter sunday. */ 546 static void 547 printeaster(int y, int julian, int orthodox) 548 { 549 date dt; 550 struct tm tm; 551 char buf[MAX_WIDTH]; 552 static int d_first = -1; 553 554 if (d_first < 0) 555 d_first = (*nl_langinfo(D_MD_ORDER) == 'd'); 556 /* force orthodox easter for years before 1583 */ 557 if (y < 1583) 558 orthodox = 1; 559 560 if (orthodox) 561 if (julian) 562 easteroj(y, &dt); 563 else 564 easterog(y, &dt); 565 else 566 easterg(y, &dt); 567 568 memset(&tm, 0, sizeof(tm)); 569 tm.tm_year = dt.y - 1900; 570 tm.tm_mon = dt.m - 1; 571 tm.tm_mday = dt.d; 572 strftime(buf, sizeof(buf), d_first ? "%e %B %Y" : "%B %e %Y", &tm); 573 printf("%s\n", buf); 574 } 575 576 #define MW(mw, me) ((mw) + me) 577 #define DECREASEMONTH(m, y) \ 578 if (--m == 0) { \ 579 m = 12; \ 580 y--; \ 581 } 582 #define INCREASEMONTH(m, y) \ 583 if (++(m) == 13) { \ 584 (m) = 1; \ 585 (y)++; \ 586 } 587 #define M2Y(m) ((m) / 12) 588 #define M2M(m) (1 + (m) % 12) 589 590 /* Print all months for the period in the range [ before .. y-m .. after ]. */ 591 static void 592 monthrangeb(int y, int m, int jd_flag, int before, int after) 593 { 594 struct monthlines year[12]; 595 struct weekdays wds; 596 char s[MAX_WIDTH], t[MAX_WIDTH]; 597 wchar_t ws[MAX_WIDTH], ws1[MAX_WIDTH]; 598 const char *wdss; 599 int i, j; 600 int mpl; 601 int mw; 602 int m1, m2; 603 int printyearheader; 604 int prevyear = -1; 605 606 mpl = jd_flag ? 2 : 3; 607 mw = jd_flag ? MONTH_WIDTH_B_J : MONTH_WIDTH_B; 608 wdss = (mpl == 2) ? " " : ""; 609 610 while (before != 0) { 611 DECREASEMONTH(m, y); 612 before--; 613 after++; 614 } 615 m1 = y * 12 + m - 1; 616 m2 = m1 + after; 617 618 mkweekdays(&wds); 619 620 /* 621 * The year header is printed when there are more than 'mpl' months 622 * and if the first month is a multitude of 'mpl'. 623 * If not, it will print the year behind every month. 624 */ 625 printyearheader = (after >= mpl - 1) && (M2M(m1) - 1) % mpl == 0; 626 627 m = m1; 628 while (m <= m2) { 629 int count = 0; 630 for (i = 0; i != mpl && m + i <= m2; i++) { 631 mkmonthb(M2Y(m + i), M2M(m + i) - 1, jd_flag, year + i); 632 count++; 633 } 634 635 /* Empty line between two rows of months */ 636 if (m != m1) 637 printf("\n"); 638 639 /* Year at the top. */ 640 if (printyearheader && M2Y(m) != prevyear) { 641 sprintf(s, "%d", M2Y(m)); 642 printf("%s\n", center(t, s, mpl * mw)); 643 prevyear = M2Y(m); 644 } 645 646 /* Month names. */ 647 for (i = 0; i < count; i++) 648 if (printyearheader) 649 wprintf(L"%-*ls ", 650 mw, wcenter(ws, year[i].name, mw)); 651 else { 652 swprintf(ws, sizeof(ws)/sizeof(ws[0]), 653 L"%-ls %d", year[i].name, M2Y(m + i)); 654 wprintf(L"%-*ls ", mw, wcenter(ws1, ws, mw)); 655 } 656 printf("\n"); 657 658 /* Day of the week names. */ 659 for (i = 0; i < count; i++) { 660 wprintf(L"%s%ls%s%ls%s%ls%s%ls%s%ls%s%ls%s%ls ", 661 wdss, wds.names[flag_monday ? 0 : 6], 662 wdss, wds.names[flag_monday ? 1 : 0], 663 wdss, wds.names[flag_monday ? 2 : 1], 664 wdss, wds.names[flag_monday ? 3 : 2], 665 wdss, wds.names[flag_monday ? 4 : 3], 666 wdss, wds.names[flag_monday ? 5 : 4], 667 wdss, wds.names[flag_monday ? 6 : 5]); 668 } 669 printf("\n"); 670 671 /* And the days of the month. */ 672 for (i = 0; i != 6; i++) { 673 for (j = 0; j < count; j++) 674 printf("%-*s ", 675 MW(mw, year[j].extralen[i]), 676 year[j].lines[i]+1); 677 printf("\n"); 678 } 679 680 m += mpl; 681 } 682 } 683 684 static void 685 monthranger(int y, int m, int jd_flag, int before, int after) 686 { 687 struct monthlines year[12]; 688 struct weekdays wds; 689 char s[MAX_WIDTH], t[MAX_WIDTH]; 690 int i, j; 691 int mpl; 692 int mw; 693 int m1, m2; 694 int prevyear = -1; 695 int printyearheader; 696 697 mpl = jd_flag ? 3 : 4; 698 mw = jd_flag ? MONTH_WIDTH_R_J : MONTH_WIDTH_R; 699 700 while (before != 0) { 701 DECREASEMONTH(m, y); 702 before--; 703 after++; 704 } 705 m1 = y * 12 + m - 1; 706 m2 = m1 + after; 707 708 mkweekdays(&wds); 709 710 /* 711 * The year header is printed when there are more than 'mpl' months 712 * and if the first month is a multitude of 'mpl'. 713 * If not, it will print the year behind every month. 714 */ 715 printyearheader = (after >= mpl - 1) && (M2M(m1) - 1) % mpl == 0; 716 717 m = m1; 718 while (m <= m2) { 719 int count = 0; 720 for (i = 0; i != mpl && m + i <= m2; i++) { 721 mkmonthr(M2Y(m + i), M2M(m + i) - 1, jd_flag, year + i); 722 count++; 723 } 724 725 /* Empty line between two rows of months. */ 726 if (m != m1) 727 printf("\n"); 728 729 /* Year at the top. */ 730 if (printyearheader && M2Y(m) != prevyear) { 731 sprintf(s, "%d", M2Y(m)); 732 printf("%s\n", center(t, s, mpl * mw)); 733 prevyear = M2Y(m); 734 } 735 736 /* Month names. */ 737 wprintf(L" "); 738 for (i = 0; i < count; i++) 739 if (printyearheader) 740 wprintf(L"%-*ls", mw, year[i].name); 741 else 742 wprintf(L"%-ls %-*d", year[i].name, 743 mw - wcslen(year[i].name) - 1, M2Y(m + i)); 744 printf("\n"); 745 746 /* And the days of the month. */ 747 for (i = 0; i != 7; i++) { 748 /* Week day */ 749 wprintf(L"%.2ls", wds.names[i]); 750 751 /* Full months */ 752 for (j = 0; j < count; j++) 753 printf("%-*s", 754 MW(mw, year[j].extralen[i]), 755 year[j].lines[i]); 756 printf("\n"); 757 } 758 759 /* Week numbers. */ 760 if (flag_weeks) { 761 printf(" "); 762 for (i = 0; i < count; i++) 763 printf("%-*s", mw, year[i].weeks); 764 printf("\n"); 765 } 766 767 m += mpl; 768 } 769 return; 770 } 771 772 static void 773 mkmonthr(int y, int m, int jd_flag, struct monthlines *mlines) 774 { 775 776 struct tm tm; /* for strftime printing local names of 777 * months */ 778 date dt; /* handy date */ 779 int dw; /* width of numbers */ 780 int first; /* first day of month */ 781 int firstm; /* first day of first week of month */ 782 int i, j, k, l; /* just indices */ 783 int last; /* the first day of next month */ 784 int jan1 = 0; /* the first day of this year */ 785 char *ds; /* pointer to day strings (daystr or 786 * jdaystr) */ 787 788 /* Set name of month. */ 789 memset(&tm, 0, sizeof(tm)); 790 tm.tm_mon = m; 791 wcsftime(mlines->name, sizeof(mlines->name) / sizeof(mlines->name[0]), 792 L"%OB", &tm); 793 mlines->name[0] = towupper(mlines->name[0]); 794 795 /* 796 * Set first and last to the day number of the first day of this 797 * month and the first day of next month respectively. Set jan1 to 798 * the day number of the first day of this year. 799 */ 800 first = firstday(y, m + 1); 801 if (m == 11) 802 last = firstday(y + 1, 1); 803 else 804 last = firstday(y, m + 2); 805 806 if (jd_flag) 807 jan1 = firstday(y, 1); 808 809 /* 810 * Set firstm to the day number of monday of the first week of 811 * this month. (This might be in the last month) 812 */ 813 firstm = first - weekday(first); 814 815 /* Set ds (daystring) and dw (daywidth) according to the jd_flag. */ 816 if (jd_flag) { 817 ds = jdaystr; 818 dw = 4; 819 } else { 820 ds = daystr; 821 dw = 3; 822 } 823 824 /* 825 * Fill the lines with day of month or day of year (julian day) 826 * line index: i, each line is one weekday. column index: j, each 827 * column is one day number. print column index: k. 828 */ 829 for (i = 0; i != 7; i++) { 830 l = 0; 831 for (j = firstm + i, k = 0; j < last; j += 7, k += dw) { 832 if (j >= first) { 833 if (jd_flag) 834 dt.d = j - jan1 + 1; 835 else 836 sdater(j, &dt); 837 if (j == highlightdate && !flag_nohighlight 838 && isatty(STDOUT_FILENO)) 839 highlight(mlines->lines[i] + k, 840 ds + dt.d * dw, dw, &l); 841 else 842 memcpy(mlines->lines[i] + k + l, 843 ds + dt.d * dw, dw); 844 } else 845 memcpy(mlines->lines[i] + k + l, " ", dw); 846 } 847 mlines->lines[i][k + l] = '\0'; 848 mlines->extralen[i] = l; 849 } 850 851 /* fill the weeknumbers. */ 852 if (flag_weeks) { 853 for (j = firstm, k = 0; j < last; k += dw, j += 7) 854 if (j <= nswitch) 855 memset(mlines->weeks + k, ' ', dw); 856 else 857 memcpy(mlines->weeks + k, 858 ds + week(j, &i)*dw, dw); 859 mlines->weeks[k] = '\0'; 860 } 861 } 862 863 static void 864 mkmonthb(int y, int m, int jd_flag, struct monthlines *mlines) 865 { 866 867 struct tm tm; /* for strftime printing local names of 868 * months */ 869 date dt; /* handy date */ 870 int dw; /* width of numbers */ 871 int first; /* first day of month */ 872 int firstsm; /* sunday or monday of first week of month */ 873 int i, j, k, l; /* just indices */ 874 int jan1 = 0; /* the first day of this year */ 875 int last; /* the first day of next month */ 876 char *ds; /* pointer to day strings (daystr or 877 * jdaystr) */ 878 879 /* Set ds (daystring) and dw (daywidth) according to the jd_flag */ 880 if (jd_flag) { 881 ds = jdaystr; 882 dw = 4; 883 } else { 884 ds = daystr; 885 dw = 3; 886 } 887 888 /* Set name of month centered. */ 889 memset(&tm, 0, sizeof(tm)); 890 tm.tm_mon = m; 891 wcsftime(mlines->name, sizeof(mlines->name) / sizeof(mlines->name[0]), 892 L"%OB", &tm); 893 mlines->name[0] = towupper(mlines->name[0]); 894 895 /* 896 * Set first and last to the day number of the first day of this 897 * month and the first day of next month respectively. Set jan1 to 898 * the day number of Jan 1st of this year. 899 */ 900 dt.y = y; 901 dt.m = m + 1; 902 dt.d = 1; 903 first = sndaysb(&dt); 904 if (m == 11) { 905 dt.y = y + 1; 906 dt.m = 1; 907 dt.d = 1; 908 } else { 909 dt.y = y; 910 dt.m = m + 2; 911 dt.d = 1; 912 } 913 last = sndaysb(&dt); 914 915 if (jd_flag) { 916 dt.y = y; 917 dt.m = 1; 918 dt.d = 1; 919 jan1 = sndaysb(&dt); 920 } 921 922 /* 923 * Set firstsm to the day number of sunday or monday of the first week 924 * of this month. (This might be in the last month) 925 */ 926 if (flag_monday) 927 firstsm = first - weekday(first); 928 else 929 firstsm = first - (weekday(first) + 1) % 7; 930 931 /* 932 * Fill the lines with day of month or day of year (Julian day) 933 * line index: i, each line is one week. column index: j, each 934 * column is one day number. print column index: k. 935 */ 936 for (i = 0; i != 6; i++) { 937 l = 0; 938 for (j = firstsm + 7 * i, k = 0; j < last && k != dw * 7; 939 j++, k += dw) { 940 if (j >= first) { 941 if (jd_flag) 942 dt.d = j - jan1 + 1; 943 else 944 sdateb(j, &dt); 945 if (j == highlightdate && !flag_nohighlight 946 && isatty(STDOUT_FILENO)) 947 highlight(mlines->lines[i] + k, 948 ds + dt.d * dw, dw, &l); 949 else 950 memcpy(mlines->lines[i] + k + l, 951 ds + dt.d * dw, dw); 952 } else 953 memcpy(mlines->lines[i] + k + l, " ", dw); 954 } 955 if (k == 0) 956 mlines->lines[i][1] = '\0'; 957 else 958 mlines->lines[i][k + l] = '\0'; 959 mlines->extralen[i] = l; 960 } 961 } 962 963 /* Put the local names of weekdays into the wds. */ 964 static void 965 mkweekdays(struct weekdays *wds) 966 { 967 int i, len, width = 0; 968 struct tm tm; 969 wchar_t buf[20]; 970 971 memset(&tm, 0, sizeof(tm)); 972 973 for (i = 0; i != 7; i++) { 974 tm.tm_wday = (i+1) % 7; 975 wcsftime(buf, sizeof(buf)/sizeof(buf[0]), L"%a", &tm); 976 for (len = 2; len > 0; --len) { 977 if ((width = wcswidth(buf, len)) <= 2) 978 break; 979 } 980 wmemset(wds->names[i], L'\0', 4); 981 if (width == 1) 982 wds->names[i][0] = L' '; 983 wcsncat(wds->names[i], buf, len); 984 wcsncat(wds->names[i], L" ", 1); 985 } 986 } 987 988 /* 989 * Compute the day number of the first existing date after the first day in 990 * month. (the first day in month and even the month might not exist!) 991 */ 992 static int 993 firstday(int y, int m) 994 { 995 date dt; 996 int nd; 997 998 dt.y = y; 999 dt.m = m; 1000 dt.d = 1; 1001 nd = sndaysr(&dt); 1002 for (;;) { 1003 sdater(nd, &dt); 1004 if ((dt.m >= m && dt.y == y) || dt.y > y) 1005 return (nd); 1006 else 1007 nd++; 1008 } 1009 /* NEVER REACHED */ 1010 } 1011 1012 /* 1013 * Compute the number of days from date, obey the local switch from 1014 * Julian to Gregorian if specified by the user. 1015 */ 1016 static int 1017 sndaysr(struct date *d) 1018 { 1019 1020 if (nswitch != 0) 1021 if (nswitch < ndaysj(d)) 1022 return (ndaysg(d)); 1023 else 1024 return (ndaysj(d)); 1025 else 1026 return ndaysg(d); 1027 } 1028 1029 /* 1030 * Compute the number of days from date, obey the switch from 1031 * Julian to Gregorian as used by UK and her colonies. 1032 */ 1033 static int 1034 sndaysb(struct date *d) 1035 { 1036 1037 if (nswitchb < ndaysj(d)) 1038 return (ndaysg(d)); 1039 else 1040 return (ndaysj(d)); 1041 } 1042 1043 /* Inverse of sndays. */ 1044 static struct date * 1045 sdater(int nd, struct date *d) 1046 { 1047 1048 if (nswitch < nd) 1049 return (gdate(nd, d)); 1050 else 1051 return (jdate(nd, d)); 1052 } 1053 1054 /* Inverse of sndaysb. */ 1055 static struct date * 1056 sdateb(int nd, struct date *d) 1057 { 1058 1059 if (nswitchb < nd) 1060 return (gdate(nd, d)); 1061 else 1062 return (jdate(nd, d)); 1063 } 1064 1065 /* Center string t in string s of length w by putting enough leading blanks. */ 1066 static char * 1067 center(char *s, char *t, int w) 1068 { 1069 char blanks[MAX_WIDTH]; 1070 1071 memset(blanks, ' ', sizeof(blanks)); 1072 sprintf(s, "%.*s%s", (int)(w - strlen(t)) / 2, blanks, t); 1073 return (s); 1074 } 1075 1076 /* Center string t in string s of length w by putting enough leading blanks. */ 1077 static wchar_t * 1078 wcenter(wchar_t *s, wchar_t *t, int w) 1079 { 1080 char blanks[MAX_WIDTH]; 1081 1082 memset(blanks, ' ', sizeof(blanks)); 1083 swprintf(s, MAX_WIDTH, L"%.*s%ls", (int)(w - wcslen(t)) / 2, blanks, t); 1084 return (s); 1085 } 1086 1087 static int 1088 parsemonth(const char *s, int *m, int *y) 1089 { 1090 int nm, ny; 1091 char *cp; 1092 struct tm tm; 1093 1094 nm = (int)strtol(s, &cp, 10); 1095 if (cp != s) { 1096 ny = *y; 1097 if (*cp == '\0') { 1098 ; /* no special action */ 1099 } else if (*cp == 'f' || *cp == 'F') { 1100 if (nm <= *m) 1101 ny++; 1102 } else if (*cp == 'p' || *cp == 'P') { 1103 if (nm >= *m) 1104 ny--; 1105 } else 1106 return (1); 1107 if (nm < 1 || nm > 12) 1108 return 1; 1109 *m = nm; 1110 *y = ny; 1111 return (0); 1112 } 1113 if (strptime(s, "%B", &tm) != NULL || strptime(s, "%b", &tm) != NULL) { 1114 *m = tm.tm_mon + 1; 1115 return (0); 1116 } 1117 return (1); 1118 } 1119 1120 static void 1121 highlight(char *dst, char *src, int len, int *extralen) 1122 { 1123 static int first = 1; 1124 static const char *term_so, *term_se; 1125 1126 if (first) { 1127 static char cbuf[512]; 1128 char tbuf[1024], *b; 1129 1130 term_se = term_so = NULL; 1131 1132 /* On how to highlight on this type of terminal (if any). */ 1133 if (isatty(STDOUT_FILENO) && tgetent(tbuf, NULL) == 1) { 1134 b = cbuf; 1135 term_so = tgetstr("so", &b); 1136 term_se = tgetstr("se", &b); 1137 } 1138 1139 first = 0; 1140 } 1141 1142 /* 1143 * This check is not necessary, should have been handled before calling 1144 * this function. 1145 */ 1146 if (flag_nohighlight) { 1147 memcpy(dst, src, len); 1148 return; 1149 } 1150 1151 /* 1152 * If it is a real terminal, use the data from the termcap database. 1153 */ 1154 if (term_so != NULL && term_se != NULL) { 1155 /* separator. */ 1156 dst[0] = ' '; 1157 dst++; 1158 /* highlight on. */ 1159 memcpy(dst, term_so, strlen(term_so)); 1160 dst += strlen(term_so); 1161 /* the actual text. (minus leading space) */ 1162 len--; 1163 src++; 1164 memcpy(dst, src, len); 1165 dst += len; 1166 /* highlight off. */ 1167 memcpy(dst, term_se, strlen(term_se)); 1168 *extralen = strlen(term_so) + strlen(term_se); 1169 return; 1170 } 1171 1172 /* 1173 * Otherwise, print a _, backspace and the letter. 1174 */ 1175 *extralen = 0; 1176 /* skip leading space. */ 1177 src++; 1178 len--; 1179 /* separator. */ 1180 dst[0] = ' '; 1181 dst++; 1182 while (len > 0) { 1183 /* _ and backspace. */ 1184 memcpy(dst, "_\010", 2); 1185 dst += 2; 1186 *extralen += 2; 1187 /* the character. */ 1188 *dst++ = *src++; 1189 len--; 1190 } 1191 return; 1192 } 1193