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