1 /* $OpenBSD: cal.c,v 1.24 2009/01/01 21:07:17 otto 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.24 2009/01/01 21:07:17 otto 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 int isoweek(int, int, int); 150 void j_yearly(int); 151 void monthly(int, int); 152 void trim_trailing_spaces(char *); 153 void usage(void); 154 void yearly(int); 155 int parsemonth(const char *); 156 157 int 158 main(int argc, char *argv[]) 159 { 160 struct tm *local_time; 161 time_t now; 162 int ch, month, year, yflag; 163 const char *errstr; 164 165 yflag = year = 0; 166 while ((ch = getopt(argc, argv, "jmwy")) != -1) 167 switch(ch) { 168 case 'j': 169 julian = 1; 170 break; 171 case 'm': 172 mflag = 1; 173 break; 174 case 'w': 175 wflag = 1; 176 break; 177 case 'y': 178 yflag = 1; 179 break; 180 case '?': 181 default: 182 usage(); 183 } 184 argc -= optind; 185 argv += optind; 186 187 if (julian && wflag) 188 usage(); 189 190 day_headings = DAY_HEADINGS_S; 191 sep1752 = sep1752s; 192 if (mflag && julian) { 193 sep1752 = sep1752jm; 194 day_headings = DAY_HEADINGS_JM; 195 } else if (mflag) { 196 sep1752 = sep1752m; 197 day_headings = DAY_HEADINGS_M; 198 } else if (julian) { 199 sep1752 = sep1752js; 200 day_headings = DAY_HEADINGS_JS; 201 } 202 203 month = 0; 204 switch(argc) { 205 case 2: 206 month = parsemonth(*argv++); 207 if (!month) 208 errx(1, "Unable to parse month"); 209 /* FALLTHROUGH */ 210 case 1: 211 if (argc == 1 && !isdigit(*argv[0])) { 212 month = parsemonth(*argv); 213 if (!month) 214 errx(1, "illegal year value: use 1-9999"); 215 (void)time(&now); 216 local_time = localtime(&now); 217 year = local_time->tm_year + TM_YEAR_BASE; 218 } else { 219 year = strtonum(*argv, 1, 9999, &errstr); 220 if (errstr) 221 errx(1, "illegal year value: use 1-9999"); 222 } 223 break; 224 case 0: 225 (void)time(&now); 226 local_time = localtime(&now); 227 year = local_time->tm_year + TM_YEAR_BASE; 228 if (!yflag) 229 month = local_time->tm_mon + 1; 230 break; 231 default: 232 usage(); 233 } 234 235 if (month) 236 monthly(month, year); 237 else if (julian) 238 j_yearly(year); 239 else 240 yearly(year); 241 exit(0); 242 } 243 244 #define DAY_LEN 3 /* 3 spaces per day */ 245 #define J_DAY_LEN 4 /* 4 spaces per day */ 246 #define WEEK_LEN 20 /* 7 * 3 - one space at the end */ 247 #define WEEKNUMBER_LEN 5 /* 5 spaces per week number */ 248 #define J_WEEK_LEN 27 /* 7 * 4 - one space at the end */ 249 #define HEAD_SEP 2 /* spaces between day headings */ 250 #define J_HEAD_SEP 2 251 252 int 253 week(int day, int month, int year) 254 { 255 int yearday; 256 int firstweekday; 257 int weekday; 258 int firstday; 259 int firstsunday; 260 int shift; 261 262 if (mflag) 263 return isoweek(day, month, year); 264 265 yearday = day_in_year(day, month, year); 266 firstweekday = day_in_week(1, 1, year) + 1; 267 weekday = day_in_week(day, month, year) + 1; 268 firstday = day_in_year(1, 1, year); 269 firstsunday = firstday + (8 - firstweekday); 270 271 shift = 1; 272 if (yearday < firstsunday) 273 return (1); 274 if (firstweekday > THURSDAY - 1) 275 shift = 2; 276 return ((((yearday + 1) - (weekday - 1)) / 7) + shift); 277 } 278 279 int 280 isoweek(int day, int month, int year) 281 { 282 /* http://www.tondering.dk/claus/cal/node8.html */ 283 int a, b, c, s, e, f, g, d, n; 284 285 a = month <= 2 ? year - 1 : year; 286 b = a/4 - a/100 + a/400; 287 c = (a-1)/4 - (a-1)/100 + (a-1)/400; 288 s = b - c; 289 if (month <= 2) { 290 e = 0; 291 f = day - 1 + 31 * (month-1); 292 } else { 293 e = s + 1; 294 f = day + ((153 * (month-3) + 2) / 5) + 58 + s; 295 } 296 g = (a + b) % 7; 297 d = (f + g - e) % 7; 298 n = f + 3 - d; 299 300 if (n < 0) 301 return 53 - (g - s) / 5; 302 else if (n > 364 + s) 303 return 1; 304 else 305 return n/7 + 1; 306 } 307 308 void 309 monthly(int month, int year) 310 { 311 int col, row, len, days[MAXDAYS], firstday; 312 char *p, lineout[30]; 313 314 day_array(month, year, days); 315 (void)snprintf(lineout, sizeof(lineout), "%s %d", 316 month_names[month - 1], year); 317 len = strlen(lineout); 318 (void)printf("%*s%s\n%s\n", 319 ((julian ? J_WEEK_LEN : WEEK_LEN) - len) / 2, "", 320 lineout, day_headings); 321 for (row = 0; row < 6; row++) { 322 firstday = SPACE; 323 for (col = 0, p = lineout; col < 7; col++, 324 p += julian ? J_DAY_LEN : DAY_LEN) { 325 if (firstday == SPACE && days[row * 7 + col] != SPACE) 326 firstday = days[row * 7 + col]; 327 ascii_day(p, days[row * 7 + col]); 328 } 329 *p = '\0'; 330 trim_trailing_spaces(lineout); 331 (void)printf("%-20s", lineout); 332 if (wflag && firstday != SPACE) 333 printf(" [%2d]", week(firstday, month, year)); 334 printf("\n"); 335 } 336 } 337 338 void 339 j_yearly(int year) 340 { 341 int col, *dp, i, month, row, which_cal; 342 int days[12][MAXDAYS]; 343 char *p, lineout[80]; 344 345 (void)snprintf(lineout, sizeof(lineout), "%d", year); 346 center(lineout, J_WEEK_LEN * 2 + J_HEAD_SEP, 0); 347 (void)printf("\n\n"); 348 for (i = 0; i < 12; i++) 349 day_array(i + 1, year, days[i]); 350 (void)memset(lineout, ' ', sizeof(lineout) - 1); 351 lineout[sizeof(lineout) - 1] = '\0'; 352 for (month = 0; month < 12; month += 2) { 353 center(month_names[month], J_WEEK_LEN, J_HEAD_SEP); 354 center(month_names[month + 1], J_WEEK_LEN, 0); 355 (void)printf("\n%s%*s%s\n", day_headings, 356 J_HEAD_SEP, "", day_headings); 357 358 for (row = 0; row < 6; row++) { 359 for (which_cal = 0; which_cal < 2; which_cal++) { 360 p = lineout + which_cal * (J_WEEK_LEN + 2); 361 dp = &days[month + which_cal][row * 7]; 362 for (col = 0; col < 7; col++, p += J_DAY_LEN) 363 ascii_day(p, *dp++); 364 } 365 *p = '\0'; 366 trim_trailing_spaces(lineout); 367 (void)printf("%s\n", lineout); 368 } 369 } 370 (void)printf("\n"); 371 } 372 373 void 374 yearly(int year) 375 { 376 int col, *dp, i, month, row, which_cal, week_len, wn, firstday; 377 int days[12][MAXDAYS]; 378 char *p, lineout[81]; 379 380 week_len = WEEK_LEN; 381 if (wflag) 382 week_len += WEEKNUMBER_LEN; 383 (void)snprintf(lineout, sizeof(lineout), "%d", year); 384 center(lineout, week_len * 3 + HEAD_SEP * 2, 0); 385 (void)printf("\n\n"); 386 for (i = 0; i < 12; i++) 387 day_array(i + 1, year, days[i]); 388 (void)memset(lineout, ' ', sizeof(lineout) - 1); 389 lineout[sizeof(lineout) - 1] = '\0'; 390 for (month = 0; month < 12; month += 3) { 391 center(month_names[month], week_len, HEAD_SEP); 392 center(month_names[month + 1], week_len, HEAD_SEP); 393 center(month_names[month + 2], week_len, 0); 394 (void)printf("\n%s%*s%s%*s%s\n", day_headings, 395 HEAD_SEP + (wflag ? WEEKNUMBER_LEN : 0), "", day_headings, 396 HEAD_SEP + (wflag ? WEEKNUMBER_LEN : 0), "", day_headings); 397 398 for (row = 0; row < 6; row++) { 399 for (which_cal = 0; which_cal < 3; which_cal++) { 400 p = lineout + which_cal * (week_len + 2); 401 402 dp = &days[month + which_cal][row * 7]; 403 firstday = SPACE; 404 for (col = 0; col < 7; col++, p += DAY_LEN) { 405 if (firstday == SPACE && *dp != SPACE) 406 firstday = *dp; 407 ascii_day(p, *dp++); 408 } 409 if (wflag && firstday != SPACE) { 410 wn = week(firstday, 411 month + which_cal + 1, year); 412 (void)snprintf(p, 5, "[%2d]", wn); 413 p += strlen(p); 414 *p = ' '; 415 } else 416 memset(p, ' ', 4); 417 } 418 *p = '\0'; 419 trim_trailing_spaces(lineout); 420 (void)printf("%s\n", lineout); 421 } 422 } 423 (void)printf("\n"); 424 } 425 426 /* 427 * day_array -- 428 * Fill in an array of 42 integers with a calendar. Assume for a moment 429 * that you took the (maximum) 6 rows in a calendar and stretched them 430 * out end to end. You would have 42 numbers or spaces. This routine 431 * builds that array for any month from Jan. 1 through Dec. 9999. 432 */ 433 void 434 day_array(int month, int year, int *days) 435 { 436 int day, dw, dm; 437 438 if (month == 9 && year == 1752) { 439 memmove(days, sep1752, MAXDAYS * sizeof(int)); 440 return; 441 } 442 memmove(days, empty, MAXDAYS * sizeof(int)); 443 dm = days_in_month[leap_year(year)][month]; 444 dw = day_in_week(mflag?0:1, month, year); 445 day = julian ? day_in_year(1, month, year) : 1; 446 while (dm--) 447 days[dw++] = day++; 448 } 449 450 /* 451 * day_in_year -- 452 * return the 1 based day number within the year 453 */ 454 int 455 day_in_year(int day, int month, int year) 456 { 457 int i, leap; 458 459 leap = leap_year(year); 460 for (i = 1; i < month; i++) 461 day += days_in_month[leap][i]; 462 return (day); 463 } 464 465 /* 466 * day_in_week 467 * return the 0 based day number for any date from 1 Jan. 1 to 468 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates 469 * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all 470 * missing days. 471 */ 472 int 473 day_in_week(int day, int month, int year) 474 { 475 long temp; 476 477 temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1) 478 + day_in_year(day, month, year); 479 if (temp < FIRST_MISSING_DAY) 480 return ((temp - 1 + SATURDAY) % 7); 481 if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS)) 482 return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7); 483 return (THURSDAY); 484 } 485 486 void 487 ascii_day(char *p, int day) 488 { 489 int display, val; 490 static const char *aday[] = { 491 "", 492 " 1", " 2", " 3", " 4", " 5", " 6", " 7", 493 " 8", " 9", "10", "11", "12", "13", "14", 494 "15", "16", "17", "18", "19", "20", "21", 495 "22", "23", "24", "25", "26", "27", "28", 496 "29", "30", "31", 497 }; 498 499 if (day == SPACE) { 500 memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN); 501 return; 502 } 503 if (julian) { 504 val = day / 100; 505 if (val) { 506 day %= 100; 507 *p++ = val + '0'; 508 display = 1; 509 } else { 510 *p++ = ' '; 511 display = 0; 512 } 513 val = day / 10; 514 if (val || display) 515 *p++ = val + '0'; 516 else 517 *p++ = ' '; 518 *p++ = day % 10 + '0'; 519 } else { 520 *p++ = aday[day][0]; 521 *p++ = aday[day][1]; 522 } 523 *p = ' '; 524 } 525 526 void 527 trim_trailing_spaces(char *s) 528 { 529 char *p; 530 531 for (p = s; *p; ++p) 532 continue; 533 while (p > s && isspace(*--p)) 534 continue; 535 if (p > s) 536 ++p; 537 *p = '\0'; 538 } 539 540 void 541 center(const char *str, int len, int separate) 542 { 543 544 len -= strlen(str); 545 (void)printf("%*s%s%*s", len / 2, "", str, 546 len / 2 + len % 2 + separate, ""); 547 } 548 549 void 550 usage(void) 551 { 552 553 (void)fprintf(stderr, "usage: cal [-jmwy] [month] [year]\n"); 554 exit(1); 555 } 556 557 int 558 parsemonth(const char *s) 559 { 560 struct tm tm; 561 char *cp; 562 int v; 563 564 v = (int)strtol(s, &cp, 10); 565 if (*cp != '\0') { /* s wasn't purely numeric */ 566 v = 0; 567 if ((cp = strptime(s, "%b", &tm)) != NULL && *cp == '\0') 568 v = tm.tm_mon + 1; 569 } 570 if (v <= 0 || v > 12) 571 errx(1, "invalid month: use 1-12 or a name"); 572 return (v); 573 } 574