1 /* $OpenBSD: cal.c,v 1.10 2003/06/10 22:20:45 deraadt 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 char copyright[] = 38 "@(#) Copyright (c) 1989, 1993, 1994\n\ 39 The Regents of the University of California. All rights reserved.\n"; 40 #endif /* not lint */ 41 42 #ifndef lint 43 #if 0 44 static char sccsid[] = "@(#)cal.c 8.4 (Berkeley) 4/2/94"; 45 #else 46 static char rcsid[] = "$OpenBSD: cal.c,v 1.10 2003/06/10 22:20:45 deraadt Exp $"; 47 #endif 48 #endif /* not lint */ 49 50 #include <sys/types.h> 51 52 #include <ctype.h> 53 #include <err.h> 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <time.h> 58 #include <tzfile.h> 59 #include <unistd.h> 60 61 #define THURSDAY 4 /* for reformation */ 62 #define SATURDAY 6 /* 1 Jan 1 was a Saturday */ 63 64 #define FIRST_MISSING_DAY 639799 /* 3 Sep 1752 */ 65 #define NUMBER_MISSING_DAYS 11 /* 11 day correction */ 66 67 #define MAXDAYS 42 /* max slots in a month array */ 68 #define SPACE -1 /* used in day array */ 69 70 static int days_in_month[2][13] = { 71 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, 72 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, 73 }; 74 75 int sep1752[MAXDAYS] = { 76 SPACE, SPACE, 1, 2, 14, 15, 16, 77 17, 18, 19, 20, 21, 22, 23, 78 24, 25, 26, 27, 28, 29, 30, 79 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 80 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 81 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 82 }, j_sep1752[MAXDAYS] = { 83 SPACE, SPACE, 245, 246, 258, 259, 260, 84 261, 262, 263, 264, 265, 266, 267, 85 268, 269, 270, 271, 272, 273, 274, 86 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 87 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 88 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 89 }, empty[MAXDAYS] = { 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 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 94 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 95 SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 96 }; 97 98 char *month_names[12] = { 99 "January", "February", "March", "April", "May", "June", 100 "July", "August", "September", "October", "November", "December", 101 }; 102 103 char *day_headings = "Su Mo Tu We Th Fr Sa"; 104 char *j_day_headings = " Su Mo Tu We Th Fr Sa"; 105 106 /* leap year -- account for gregorian reformation in 1752 */ 107 #define leap_year(yr) \ 108 ((yr) <= 1752 ? !((yr) % 4) : \ 109 (!((yr) % 4) && ((yr) % 100)) || !((yr) % 400)) 110 111 /* number of centuries since 1700, not inclusive */ 112 #define centuries_since_1700(yr) \ 113 ((yr) > 1700 ? (yr) / 100 - 17 : 0) 114 115 /* number of centuries since 1700 whose modulo of 400 is 0 */ 116 #define quad_centuries_since_1700(yr) \ 117 ((yr) > 1600 ? ((yr) - 1600) / 400 : 0) 118 119 /* number of leap years between year 1 and this year, not inclusive */ 120 #define leap_years_since_year_1(yr) \ 121 ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr)) 122 123 int julian; 124 125 void ascii_day(char *, int); 126 void center(char *, int, int); 127 void day_array(int, int, int *); 128 int day_in_week(int, int, int); 129 int day_in_year(int, int, int); 130 void j_yearly(int); 131 void monthly(int, int); 132 void trim_trailing_spaces(char *); 133 void usage(void); 134 void yearly(int); 135 136 int 137 main(int argc, char *argv[]) 138 { 139 struct tm *local_time; 140 time_t now; 141 int ch, month, year, yflag; 142 143 yflag = year = 0; 144 while ((ch = getopt(argc, argv, "jy")) != -1) 145 switch(ch) { 146 case 'j': 147 julian = 1; 148 break; 149 case 'y': 150 yflag = 1; 151 break; 152 case '?': 153 default: 154 usage(); 155 } 156 argc -= optind; 157 argv += optind; 158 159 month = 0; 160 switch(argc) { 161 case 2: 162 if ((month = atoi(*argv++)) < 1 || month > 12) 163 errx(1, "illegal month value: use 1-12"); 164 /* FALLTHROUGH */ 165 case 1: 166 if ((year = atoi(*argv)) < 1 || year > 9999) 167 errx(1, "illegal year value: use 1-9999"); 168 break; 169 case 0: 170 (void)time(&now); 171 local_time = localtime(&now); 172 year = local_time->tm_year + TM_YEAR_BASE; 173 if (!yflag) 174 month = local_time->tm_mon + 1; 175 break; 176 default: 177 usage(); 178 } 179 180 if (month) 181 monthly(month, year); 182 else if (julian) 183 j_yearly(year); 184 else 185 yearly(year); 186 exit(0); 187 } 188 189 #define DAY_LEN 3 /* 3 spaces per day */ 190 #define J_DAY_LEN 4 /* 4 spaces per day */ 191 #define WEEK_LEN 20 /* 7 * 3 - one space at the end */ 192 #define J_WEEK_LEN 27 /* 7 * 4 - one space at the end */ 193 #define HEAD_SEP 2 /* spaces between day headings */ 194 #define J_HEAD_SEP 2 195 196 void 197 monthly(int month, int year) 198 { 199 int col, row, len, days[MAXDAYS]; 200 char *p, lineout[30]; 201 202 day_array(month, year, days); 203 len = snprintf(lineout, sizeof lineout, "%s %d", 204 month_names[month - 1], year); 205 (void)printf("%*s%s\n%s\n", 206 ((julian ? J_WEEK_LEN : WEEK_LEN) - len) / 2, "", 207 lineout, julian ? j_day_headings : day_headings); 208 for (row = 0; row < 6; row++) { 209 for (col = 0, p = lineout; col < 7; col++, 210 p += julian ? J_DAY_LEN : DAY_LEN) 211 ascii_day(p, days[row * 7 + col]); 212 *p = '\0'; 213 trim_trailing_spaces(lineout); 214 (void)printf("%s\n", lineout); 215 } 216 } 217 218 void 219 j_yearly(int year) 220 { 221 int col, *dp, i, month, row, which_cal; 222 int days[12][MAXDAYS]; 223 char *p, lineout[80]; 224 225 (void)snprintf(lineout, sizeof lineout, "%d", year); 226 center(lineout, J_WEEK_LEN * 2 + J_HEAD_SEP, 0); 227 (void)printf("\n\n"); 228 for (i = 0; i < 12; i++) 229 day_array(i + 1, year, days[i]); 230 (void)memset(lineout, ' ', sizeof(lineout) - 1); 231 lineout[sizeof(lineout) - 1] = '\0'; 232 for (month = 0; month < 12; month += 2) { 233 center(month_names[month], J_WEEK_LEN, J_HEAD_SEP); 234 center(month_names[month + 1], J_WEEK_LEN, 0); 235 (void)printf("\n%s%*s%s\n", j_day_headings, J_HEAD_SEP, "", 236 j_day_headings); 237 for (row = 0; row < 6; row++) { 238 for (which_cal = 0; which_cal < 2; which_cal++) { 239 p = lineout + which_cal * (J_WEEK_LEN + 2); 240 dp = &days[month + which_cal][row * 7]; 241 for (col = 0; col < 7; col++, p += J_DAY_LEN) 242 ascii_day(p, *dp++); 243 } 244 *p = '\0'; 245 trim_trailing_spaces(lineout); 246 (void)printf("%s\n", lineout); 247 } 248 } 249 (void)printf("\n"); 250 } 251 252 void 253 yearly(int year) 254 { 255 int col, *dp, i, month, row, which_cal; 256 int days[12][MAXDAYS]; 257 char *p, lineout[80]; 258 259 (void)snprintf(lineout, sizeof lineout, "%d", year); 260 center(lineout, WEEK_LEN * 3 + HEAD_SEP * 2, 0); 261 (void)printf("\n\n"); 262 for (i = 0; i < 12; i++) 263 day_array(i + 1, year, days[i]); 264 (void)memset(lineout, ' ', sizeof(lineout) - 1); 265 lineout[sizeof(lineout) - 1] = '\0'; 266 for (month = 0; month < 12; month += 3) { 267 center(month_names[month], WEEK_LEN, HEAD_SEP); 268 center(month_names[month + 1], WEEK_LEN, HEAD_SEP); 269 center(month_names[month + 2], WEEK_LEN, 0); 270 (void)printf("\n%s%*s%s%*s%s\n", day_headings, HEAD_SEP, 271 "", day_headings, HEAD_SEP, "", day_headings); 272 for (row = 0; row < 6; row++) { 273 for (which_cal = 0; which_cal < 3; which_cal++) { 274 p = lineout + which_cal * (WEEK_LEN + 2); 275 dp = &days[month + which_cal][row * 7]; 276 for (col = 0; col < 7; col++, p += DAY_LEN) 277 ascii_day(p, *dp++); 278 } 279 *p = '\0'; 280 trim_trailing_spaces(lineout); 281 (void)printf("%s\n", lineout); 282 } 283 } 284 (void)printf("\n"); 285 } 286 287 /* 288 * day_array -- 289 * Fill in an array of 42 integers with a calendar. Assume for a moment 290 * that you took the (maximum) 6 rows in a calendar and stretched them 291 * out end to end. You would have 42 numbers or spaces. This routine 292 * builds that array for any month from Jan. 1 through Dec. 9999. 293 */ 294 void 295 day_array(int month, int year, int *days) 296 { 297 int day, dw, dm; 298 299 if (month == 9 && year == 1752) { 300 memmove(days, 301 julian ? j_sep1752 : sep1752, MAXDAYS * sizeof(int)); 302 return; 303 } 304 memmove(days, empty, MAXDAYS * sizeof(int)); 305 dm = days_in_month[leap_year(year)][month]; 306 dw = day_in_week(1, month, year); 307 day = julian ? day_in_year(1, month, year) : 1; 308 while (dm--) 309 days[dw++] = day++; 310 } 311 312 /* 313 * day_in_year -- 314 * return the 1 based day number within the year 315 */ 316 int 317 day_in_year(int day, int month, int year) 318 { 319 int i, leap; 320 321 leap = leap_year(year); 322 for (i = 1; i < month; i++) 323 day += days_in_month[leap][i]; 324 return (day); 325 } 326 327 /* 328 * day_in_week 329 * return the 0 based day number for any date from 1 Jan. 1 to 330 * 31 Dec. 9999. Assumes the Gregorian reformation eliminates 331 * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all 332 * missing days. 333 */ 334 int 335 day_in_week(int day, int month, int year) 336 { 337 long temp; 338 339 temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1) 340 + day_in_year(day, month, year); 341 if (temp < FIRST_MISSING_DAY) 342 return ((temp - 1 + SATURDAY) % 7); 343 if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS)) 344 return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7); 345 return (THURSDAY); 346 } 347 348 void 349 ascii_day(char *p, int day) 350 { 351 int display, val; 352 static char *aday[] = { 353 "", 354 " 1", " 2", " 3", " 4", " 5", " 6", " 7", 355 " 8", " 9", "10", "11", "12", "13", "14", 356 "15", "16", "17", "18", "19", "20", "21", 357 "22", "23", "24", "25", "26", "27", "28", 358 "29", "30", "31", 359 }; 360 361 if (day == SPACE) { 362 memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN); 363 return; 364 } 365 if (julian) { 366 val = day / 100; 367 if (val) { 368 day %= 100; 369 *p++ = val + '0'; 370 display = 1; 371 } else { 372 *p++ = ' '; 373 display = 0; 374 } 375 val = day / 10; 376 if (val || display) 377 *p++ = val + '0'; 378 else 379 *p++ = ' '; 380 *p++ = day % 10 + '0'; 381 } else { 382 *p++ = aday[day][0]; 383 *p++ = aday[day][1]; 384 } 385 *p = ' '; 386 } 387 388 void 389 trim_trailing_spaces(char *s) 390 { 391 char *p; 392 393 for (p = s; *p; ++p) 394 continue; 395 while (p > s && isspace(*--p)) 396 continue; 397 if (p > s) 398 ++p; 399 *p = '\0'; 400 } 401 402 void 403 center(char *str, int len, int separate) 404 { 405 406 len -= strlen(str); 407 (void)printf("%*s%s%*s", len / 2, "", str, len / 2 + len % 2, ""); 408 if (separate) 409 (void)printf("%*s", separate, ""); 410 } 411 412 void 413 usage(void) 414 { 415 416 (void)fprintf(stderr, "usage: cal [-jy] [[month] year]\n"); 417 exit(1); 418 } 419