sbase

suckless unix tools
git clone git://git.2f30.org/sbase
Log | Files | Refs | README | LICENSE

cal.c (5009B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <limits.h>
      3 #include <stdint.h>
      4 #include <stdio.h>
      5 #include <stdlib.h>
      6 #include <string.h>
      7 #include <time.h>
      8 #include <unistd.h>
      9 
     10 #include "util.h"
     11 
     12 enum { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
     13 enum caltype { JULIAN, GREGORIAN };
     14 enum { TRANS_YEAR = 1752, TRANS_MONTH = SEP, TRANS_DAY = 2 };
     15 
     16 static struct tm *ltime;
     17 
     18 static int
     19 isleap(size_t year, enum caltype cal)
     20 {
     21 	if (cal == GREGORIAN) {
     22 		if (year % 400 == 0)
     23 			return 1;
     24 		if (year % 100 == 0)
     25 			return 0;
     26 		return (year % 4 == 0);
     27 	}
     28 	else { /* cal == Julian */
     29 		return (year % 4 == 0);
     30 	}
     31 }
     32 
     33 static int
     34 monthlength(size_t year, int month, enum caltype cal)
     35 {
     36 	int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
     37 
     38 	return (month == FEB && isleap(year, cal)) ? 29 : mdays[month];
     39 }
     40 
     41 /* From http://www.tondering.dk/claus/cal/chrweek.php#calcdow */
     42 static int
     43 dayofweek(size_t year, int month, int dom, enum caltype cal)
     44 {
     45 	size_t y;
     46 	int m, a;
     47 
     48 	a = (13 - month) / 12;
     49 	y = year - a;
     50 	m = month + 12 * a - 1;
     51 
     52 	if (cal == GREGORIAN)
     53 		return (dom + y + y / 4 - y / 100 + y / 400 + (31 * m) / 12) % 7;
     54 	else  /* cal == Julian */
     55 		return (5 + dom + y + y / 4 + (31 * m) / 12) % 7;
     56 }
     57 
     58 static void
     59 printgrid(size_t year, int month, int fday, int line)
     60 {
     61 	enum caltype cal;
     62 	int offset, dom, d = 0, trans; /* are we in the transition from Julian to Gregorian? */
     63 	int today = 0;
     64 
     65 	cal = (year < TRANS_YEAR || (year == TRANS_YEAR && month <= TRANS_MONTH)) ? JULIAN : GREGORIAN;
     66 	trans = (year == TRANS_YEAR && month == TRANS_MONTH);
     67 	offset = dayofweek(year, month, 1, cal) - fday;
     68 
     69 	if (offset < 0)
     70 		offset += 7;
     71 	if (line == 1) {
     72 		for (; d < offset; ++d)
     73 			printf("   ");
     74 		dom = 1;
     75 	} else {
     76 		dom = 8 - offset + (line - 2) * 7;
     77 		if (trans && !(line == 2 && fday == 3))
     78 			dom += 11;
     79 	}
     80 	if (ltime && year == ltime->tm_year + 1900 && month == ltime->tm_mon)
     81 		today = ltime->tm_mday;
     82 	for (; d < 7 && dom <= monthlength(year, month, cal); ++d, ++dom) {
     83 		if (dom == today)
     84 			printf("\x1b[7m%2d\x1b[0m ", dom); /* highlight today's date */
     85 		else
     86 			printf("%2d ", dom);
     87 		if (trans && dom == TRANS_DAY)
     88 			dom += 11;
     89 	}
     90 	for (; d < 7; ++d)
     91 		printf("   ");
     92 }
     93 
     94 static void
     95 drawcal(size_t year, int month, size_t ncols, size_t nmons, int fday)
     96 {
     97 	char *smon[] = { "January", "February", "March", "April",
     98 	                 "May", "June", "July", "August",
     99 	                 "September", "October", "November", "December" };
    100 	char *days[] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", };
    101 	size_t m, n, col, cur_year, cur_month, dow;
    102 	int line, pad;
    103 	char month_year[sizeof("Su Mo Tu We Th Fr Sa")];
    104 
    105 	for (m = 0; m < nmons; ) {
    106 		n = m;
    107 		for (col = 0; m < nmons && col < ncols; ++col, ++m) {
    108 			cur_year = year + m / 12;
    109 			cur_month = month + m % 12;
    110 			if (cur_month > 11) {
    111 				cur_month -= 12;
    112 				cur_year += 1;
    113 			}
    114 			snprintf(month_year, sizeof(month_year), "%s %zu", smon[cur_month], cur_year);
    115 			pad = sizeof(month_year) - 1 - strlen(month_year);
    116 			printf("%*s%s%*s   ", pad / 2 + pad % 2, "", month_year, pad / 2, "");
    117 		}
    118 		putchar('\n');
    119 		for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) {
    120 			for (dow = fday; dow < (fday + 7); ++dow)
    121 				printf("%s ", days[dow % 7]);
    122 			printf("  ");
    123 		}
    124 		putchar('\n');
    125 		for (line = 1; line <= 6; ++line) {
    126 			for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) {
    127 				cur_year = year + m / 12;
    128 				cur_month = month + m % 12;
    129 				if (cur_month > 11) {
    130 					cur_month -= 12;
    131 					cur_year += 1;
    132 				}
    133 				printgrid(cur_year, cur_month, fday, line);
    134 				printf("  ");
    135 			}
    136 			putchar('\n');
    137 		}
    138 	}
    139 }
    140 
    141 static void
    142 usage(void)
    143 {
    144 	eprintf("usage: %s [-1 | -3 | -y | -n num] "
    145 	        "[-s | -m | -f num] [-c num] [[month] year]\n", argv0);
    146 }
    147 
    148 int
    149 main(int argc, char *argv[])
    150 {
    151 	time_t now;
    152 	size_t year, ncols, nmons;
    153 	int fday, month;
    154 
    155 	now   = time(NULL);
    156 	ltime = localtime(&now);
    157 	year  = ltime->tm_year + 1900;
    158 	month = ltime->tm_mon + 1;
    159 	fday  = 0;
    160 
    161 	if (!isatty(STDOUT_FILENO))
    162 		ltime = NULL; /* don't highlight today's date */
    163 
    164 	ncols = 3;
    165 	nmons = 0;
    166 
    167 	ARGBEGIN {
    168 	case '1':
    169 		nmons = 1;
    170 		break;
    171 	case '3':
    172 		nmons = 3;
    173 		if (--month == 0) {
    174 			month = 12;
    175 			year--;
    176 		}
    177 		break;
    178 	case 'c':
    179 		ncols = estrtonum(EARGF(usage()), 0, MIN(SIZE_MAX, LLONG_MAX));
    180 		break;
    181 	case 'f':
    182 		fday = estrtonum(EARGF(usage()), 0, 6);
    183 		break;
    184 	case 'm': /* Monday */
    185 		fday = 1;
    186 		break;
    187 	case 'n':
    188 		nmons = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX));
    189 		break;
    190 	case 's': /* Sunday */
    191 		fday = 0;
    192 		break;
    193 	case 'y':
    194 		month = 1;
    195 		nmons = 12;
    196 		break;
    197 	default:
    198 		usage();
    199 	} ARGEND
    200 
    201 	if (nmons == 0) {
    202 		if (argc == 1) {
    203 			month = 1;
    204 			nmons = 12;
    205 		} else {
    206 			nmons = 1;
    207 		}
    208 	}
    209 
    210 	switch (argc) {
    211 	case 2:
    212 		month = estrtonum(argv[0], 1, 12);
    213 		argv++;
    214 	case 1: /* fallthrough */
    215 		year = estrtonum(argv[0], 0, INT_MAX);
    216 		break;
    217 	case 0:
    218 		break;
    219 	default:
    220 		usage();
    221 	}
    222 
    223 	drawcal(year, month - 1, ncols, nmons, fday);
    224 
    225 	return fshut(stdout, "<stdout>");
    226 }