1 /* $OpenBSD: cal.c,v 1.23 2008/04/18 14:41:04 pyr Exp $ */ 2 /* $NetBSD: cal.c,v 1.6 1995/03/26 03:10:24 glass Exp $ */ 3 4 /* 5 * Copyright (c) 1989, 1993, 1994 6 * The Regents of the University of California. All rights reserved. 7 * 8 * This code is derived from software contributed to Berkeley by 9 * Kim Letkeman. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #ifndef lint 37 static const char copyright[] = 38 "@(#) Copyright (c) 1989, 1993, 1994\n\ 39 The Regents of the University of California. All rights reserved.\n"; 40 #if 0 41 static char sccsid[] = "@(#)cal.c 8.4 (Berkeley) 4/2/94"; 42 #else 43 static const char rcsid[] = "$OpenBSD: cal.c,v 1.23 2008/04/18 14:41:04 pyr Exp $"; 44 #endif 45 #endif /* not lint */ 46 47 #include <sys/types.h> 48 49 #include <ctype.h> 50 #include <err.h> 51 #include <stdio.h> 52 #include <stdlib.h> 53 #include <string.h> 54 #include <time.h> 55 #include <tzfile.h> 56 #include <unistd.h> 57 58 #define THURSDAY 4 /* for reformation */ 59 #define SATURDAY 6 /* 1 Jan 1 was a Saturday */ 60 61 #define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */ 62 #define NUMBER_MISSING_DAYS 11 /* 11 day correction */ 63 64 #define MAXDAYS 42 /* max slots in a month array */ 65 #define SPACE -1 /* used in day array */ 66 67 static const int days_in_month[2][13] = { 68 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, 69 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, 70 }; 71 72 const int sep1752s[MAXDAYS] = { 73 SPACE, SPACE, 1, 2, 14, 15, 16, 74 17, 18, 19, 20, 21, 22, 23, 75 24, 25, 26, 27, 28, 29, 30, 76 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 77 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 78 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 79 }, sep1752m[MAXDAYS] = { 80 SPACE, 1, 2, 14, 15, 16, 17, 81 18, 19, 20, 21, 22, 23, 24, 82 25, 26, 27, 28, 29, 30, SPACE, 83 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 84 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 85 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 86 }, sep1752js[MAXDAYS] = { 87 SPACE, SPACE, 245, 246, 258, 259, 260, 88 261, 262, 263, 264, 265, 266, 267, 89 268, 269, 270, 271, 272, 273, 274, 90 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 91 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 92 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 93 }, sep1752jm[MAXDAYS] = { 94 SPACE, 245, 246, 258, 259, 260, 261, 95 262, 263, 264, 265, 266, 267, 268, 96 269, 270, 271, 272, 273, 274, SPACE, 97 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 98 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 99 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 100 }, empty[MAXDAYS] = { 101 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 102 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 103 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 104 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 105 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 106 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 107 }; 108 109 const char *month_names[12] = { 110 "January", "February", "March", "April", "May", "June", 111 "July", "August", "September", "October", "November", "December", 112 }; 113 114 #define DAY_HEADINGS_S "Su Mo Tu We Th Fr Sa" 115 #define DAY_HEADINGS_M "Mo Tu We Th Fr Sa Su" 116 #define DAY_HEADINGS_JS " Su Mo Tu We Th Fr Sa" 117 #define DAY_HEADINGS_JM " Mo Tu We Th Fr Sa Su" 118 119 const int *sep1752 = NULL; 120 const char *day_headings = NULL; 121 122 /* leap year -- account for gregorian reformation in 1752 */ 123 #define leap_year(yr) \ 124 ((yr) <= 1752 ? !((yr) % 4) : \ 125 (!((yr) % 4) && ((yr) % 100)) || !((yr) % 400)) 126 127 /* number of centuries since 1700, not inclusive */ 128 #define centuries_since_1700(yr) \ 129 ((yr) > 1700 ? (yr) / 100 - 17 : 0) 130 131 /* number of centuries since 1700 whose modulo of 400 is 0 */ 132 #define quad_centuries_since_1700(yr) \ 133 ((yr) > 1600 ? ((yr) - 1600) / 400 : 0) 134 135 /* number of leap years between year 1 and this year, not inclusive */ 136 #define leap_years_since_year_1(yr) \ 137 ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr)) 138 139 int julian; 140 int mflag = 0; 141 int wflag = 0; 142 143 void ascii_day(char *, int); 144 void center(const char *, int, int); 145 void day_array(int, int, int *); 146 int day_in_week(int, int, int); 147 int day_in_year(int, int, int); 148 int week(int, int, int); 149 void j_yearly(int); 150 void monthly(int, int); 151 void trim_trailing_spaces(char *); 152 void usage(void); 153 void yearly(int); 154 int parsemonth(const char *); 155 156 int 157 main(int argc, char *argv[]) 158 { 159 struct tm *local_time; 160 time_t now; 161 int ch, month, year, yflag; 162 const char *errstr; 163 164 yflag = year = 0; 165 while ((ch = getopt(argc, argv, "jmwy")) != -1) 166 switch(ch) { 167 case 'j': 168 julian = 1; 169 break; 170 case 'm': 171 mflag = 1; 172 break; 173 case 'w': 174 wflag = 1; 175 break; 176 case 'y': 177 yflag = 1; 178 break; 179 case '?': 180 default: 181 usage(); 182 } 183 argc -= optind; 184 argv += optind; 185 186 if (julian && wflag) 187 usage(); 188 189 day_headings = DAY_HEADINGS_S; 190 sep1752 = sep1752s; 191 if (mflag && julian) { 192 sep1752 = sep1752jm; 193 day_headings = DAY_HEADINGS_JM; 194 } else if (mflag) { 195 sep1752 = sep1752m; 196 day_headings = DAY_HEADINGS_M; 197 } else if (julian) { 198 sep1752 = sep1752js; 199 day_headings = DAY_HEADINGS_JS; 200 } 201 202 month = 0; 203 switch(argc) { 204 case 2: 205 month = parsemonth(*argv++); 206 if (!month) 207 errx(1, "Unable to parse month"); 208 /* FALLTHROUGH */ 209 case 1: 210 if (argc == 1 && !isdigit(*argv[0])) { 211 month = parsemonth(*argv); 212 if (!month) 213 errx(1, "illegal year value: use 1-9999"); 214 (void)time(&now); 215 local_time = localtime(&now); 216 year = local_time->tm_year + TM_YEAR_BASE; 217 } else { 218 year = strtonum(*argv, 1, 9999, &errstr); 219 if (errstr) 220 errx(1, "illegal year value: use 1-9999"); 221 } 222 break; 223 case 0: 224 (void)time(&now); 225 local_time = localtime(&now); 226 year = local_time->tm_year + TM_YEAR_BASE; 227 if (!yflag) 228 month = local_time->tm_mon + 1; 229 break; 230 default: 231 usage(); 232 } 233 234 if (month) 235 monthly(month, year); 236 else if (julian) 237 j_yearly(year); 238 else 239 yearly(year); 240 exit(0); 241 } 242 243 #define DAY_LEN 3 /* 3 spaces per day */ 244 #define J_DAY_LEN 4 /* 4 spaces per day */ 245 #define WEEK_LEN 20 /* 7 * 3 - one space at the end */ 246 #define WEEKNUMBER_LEN 5 /* 5 spaces per week number */ 247 #define J_WEEK_LEN 27 /* 7 * 4 - one space at the end */ 248 #define HEAD_SEP 2 /* spaces between day headings */ 249 #define J_HEAD_SEP 2 250 251 int 252 week(int day, int month, int year) 253 { 254 int leap; 255 int prevleap; 256 int yearday; 257 int firstweekday; 258 int weekday; 259 int firstday; 260 int firstsunday; 261 int shift; 262 263 yearday = day_in_year(day, month, year); 264 firstweekday = day_in_week(1, 1, year) + 1; 265 weekday = day_in_week(day, month, year) + 1; 266 leap = leap_year(year); 267 prevleap = leap_year(year - 1); 268 firstday = firstsunday = day_in_year(1, 1, year); 269 firstsunday = firstday + (8 - firstweekday); 270 271 if (!mflag) 272 goto sunbased; 273 274 if (yearday <= (8 - firstweekday) && firstweekday > 4) { 275 if (firstweekday == 5 || (firstweekday == 6 && prevleap)) 276 return (53); 277 return (52); 278 } 279 280 if (((leap ? 366 : 365) - yearday) < (4 - weekday)) 281 return (1); 282 283 return (((yearday + (7 - weekday) + (firstweekday - 1)) / 7) 284 - (firstweekday > 4)); 285 286 sunbased: 287 shift = 1; 288 if (yearday < firstsunday) 289 return (1); 290 if (firstweekday > THURSDAY - 1) 291 shift = 2; 292 return ((((yearday + 1) - (weekday - 1)) / 7) + shift); 293 } 294 295 void 296 monthly(int month, int year) 297 { 298 int col, row, len, days[MAXDAYS], firstday; 299 char *p, lineout[30]; 300 301 day_array(month, year, days); 302 (void)snprintf(lineout, sizeof(lineout), "%s %d", 303 month_names[month - 1], year); 304 len = strlen(lineout); 305 (void)printf("%*s%s\n%s\n", 306 ((julian ? J_WEEK_LEN : WEEK_LEN) - len) / 2, "", 307 lineout, day_headings); 308 for (row = 0; row < 6; row++) { 309 firstday = SPACE; 310 for (col = 0, p = lineout; col < 7; col++, 311 p += julian ? J_DAY_LEN : DAY_LEN) { 312 if (firstday == SPACE && days[row * 7 + col] != SPACE) 313 firstday = days[row * 7 + col]; 314 ascii_day(p, days[row * 7 + col]); 315 } 316 *p = '\0'; 317 trim_trailing_spaces(lineout); 318 (void)printf("%-20s", lineout); 319 if (wflag && firstday != SPACE) 320 printf(" [%2d]", week(firstday - mflag, month, year)); 321 printf("\n"); 322 } 323 } 324 325 void 326 j_yearly(int year) 327 { 328 int col, *dp, i, month, row, which_cal; 329 int days[12][MAXDAYS]; 330 char *p, lineout[80]; 331 332 (void)snprintf(lineout, sizeof(lineout), "%d", year); 333 center(lineout, J_WEEK_LEN * 2 + J_HEAD_SEP, 0); 334 (void)printf("\n\n"); 335 for (i = 0; i < 12; i++) 336 day_array(i + 1, year, days[i]); 337 (void)memset(lineout, ' ', sizeof(lineout) - 1); 338 lineout[sizeof(lineout) - 1] = '\0'; 339 for (month = 0; month < 12; month += 2) { 340 center(month_names[month], J_WEEK_LEN, J_HEAD_SEP); 341 center(month_names[month + 1], J_WEEK_LEN, 0); 342 (void)printf("\n%s%*s%s\n", day_headings, 343 J_HEAD_SEP, "", day_headings); 344 345 for (row = 0; row < 6; row++) { 346 for (which_cal = 0; which_cal < 2; which_cal++) { 347 p = lineout + which_cal * (J_WEEK_LEN + 2); 348 dp = &days[month + which_cal][row * 7]; 349 for (col = 0; col < 7; col++, p += J_DAY_LEN) 350 ascii_day(p, *dp++); 351 } 352 *p = '\0'; 353 trim_trailing_spaces(lineout); 354 (void)printf("%s\n", lineout); 355 } 356 } 357 (void)printf("\n"); 358 } 359 360 void 361 yearly(int year) 362 { 363 int col, *dp, i, month, row, which_cal, week_len, wn, firstday; 364 int days[12][MAXDAYS]; 365 char *p, lineout[81]; 366 367 week_len = WEEK_LEN; 368 if (wflag) 369 week_len += WEEKNUMBER_LEN; 370 (void)snprintf(lineout, sizeof(lineout), "%d", year); 371 center(lineout, week_len * 3 + HEAD_SEP * 2, 0); 372 (void)printf("\n\n"); 373 for (i = 0; i < 12; i++) 374 day_array(i + 1, year, days[i]); 375 (void)memset(lineout, ' ', sizeof(lineout) - 1); 376 lineout[sizeof(lineout) - 1] = '\0'; 377 for (month = 0; month < 12; month += 3) { 378 center(month_names[month], week_len, HEAD_SEP); 379 center(month_names[month + 1], week_len, HEAD_SEP); 380 center(month_names[month + 2], week_len, 0); 381 (void)printf("\n%s%*s%s%*s%s\n", day_headings, 382 HEAD_SEP + (wflag ? WEEKNUMBER_LEN : 0), "", day_headings, 383 HEAD_SEP + (wflag ? WEEKNUMBER_LEN : 0), "", day_headings); 384 385 for (row = 0; row < 6; row++) { 386 for (which_cal = 0; which_cal < 3; which_cal++) { 387 p = lineout + which_cal * (week_len + 2); 388 389 dp = &days[month + which_cal][row * 7]; 390 firstday = SPACE; 391 for (col = 0; col < 7; col++, p += DAY_LEN) { 392 if (firstday == SPACE && *dp != SPACE) 393 firstday = *dp; 394 ascii_day(p, *dp++); 395 } 396 if (wflag && firstday != SPACE) { 397 wn = week(firstday - mflag, 398 month + which_cal + 1, year); 399 (void)snprintf(p, 5, "[%2d]", wn); 400 p += strlen(p); 401 *p = ' '; 402 } else 403 memset(p, ' ', 4); 404 } 405 *p = '\0'; 406 trim_trailing_spaces(lineout); 407 (void)printf("%s\n", lineout); 408 } 409 } 410 (void)printf("\n"); 411 } 412 413 /* 414 * day_array -- 415 * Fill in an array of 42 integers with a calendar. Assume for a moment 416 * that you took the (maximum) 6 rows in a calendar and stretched them 417 * out end to end. You would have 42 numbers or spaces. This routine 418 * builds that array for any month from Jan. 1 through Dec. 9999. 419 */ 420 void 421 day_array(int month, int year, int *days) 422 { 423 int day, dw, dm; 424 425 if (month == 9 && year == 1752) { 426 memmove(days, sep1752, MAXDAYS * sizeof(int)); 427 return; 428 } 429 memmove(days, empty, MAXDAYS * sizeof(int)); 430 dm = days_in_month[leap_year(year)][month]; 431 dw = day_in_week(mflag?0:1, month, year); 432 day = julian ? day_in_year(1, month, year) : 1; 433 while (dm--) 434 days[dw++] = day++; 435 } 436 437 /* 438 * day_in_year -- 439 * return the 1 based day number within the year 440 */ 441 int 442 day_in_year(int day, int month, int year) 443 { 444 int i, leap; 445 446 leap = leap_year(year); 447 for (i = 1; i < month; i++) 448 day += days_in_month[leap][i]; 449 return (day); 450 } 451 452 /* 453 * day_in_week 454 * return the 0 based day number for any date from 1 Jan. 1 to 455 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates 456 * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all 457 * missing days. 458 */ 459 int 460 day_in_week(int day, int month, int year) 461 { 462 long temp; 463 464 temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1) 465 + day_in_year(day, month, year); 466 if (temp < FIRST_MISSING_DAY) 467 return ((temp - 1 + SATURDAY) % 7); 468 if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS)) 469 return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7); 470 return (THURSDAY); 471 } 472 473 void 474 ascii_day(char *p, int day) 475 { 476 int display, val; 477 static const char *aday[] = { 478 "", 479 " 1", " 2", " 3", " 4", " 5", " 6", " 7", 480 " 8", " 9", "10", "11", "12", "13", "14", 481 "15", "16", "17", "18", "19", "20", "21", 482 "22", "23", "24", "25", "26", "27", "28", 483 "29", "30", "31", 484 }; 485 486 if (day == SPACE) { 487 memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN); 488 return; 489 } 490 if (julian) { 491 val = day / 100; 492 if (val) { 493 day %= 100; 494 *p++ = val + '0'; 495 display = 1; 496 } else { 497 *p++ = ' '; 498 display = 0; 499 } 500 val = day / 10; 501 if (val || display) 502 *p++ = val + '0'; 503 else 504 *p++ = ' '; 505 *p++ = day % 10 + '0'; 506 } else { 507 *p++ = aday[day][0]; 508 *p++ = aday[day][1]; 509 } 510 *p = ' '; 511 } 512 513 void 514 trim_trailing_spaces(char *s) 515 { 516 char *p; 517 518 for (p = s; *p; ++p) 519 continue; 520 while (p > s && isspace(*--p)) 521 continue; 522 if (p > s) 523 ++p; 524 *p = '\0'; 525 } 526 527 void 528 center(const char *str, int len, int separate) 529 { 530 531 len -= strlen(str); 532 (void)printf("%*s%s%*s", len / 2, "", str, 533 len / 2 + len % 2 + separate, ""); 534 } 535 536 void 537 usage(void) 538 { 539 540 (void)fprintf(stderr, "usage: cal [-jmwy] [month] [year]\n"); 541 exit(1); 542 } 543 544 int 545 parsemonth(const char *s) 546 { 547 struct tm tm; 548 char *cp; 549 int v; 550 551 v = (int)strtol(s, &cp, 10); 552 if (*cp != '\0') { /* s wasn't purely numeric */ 553 v = 0; 554 if ((cp = strptime(s, "%b", &tm)) != NULL && *cp == '\0') 555 v = tm.tm_mon + 1; 556 } 557 if (v <= 0 || v > 12) 558 errx(1, "invalid month: use 1-12 or a name"); 559 return (v); 560 } 561