master xplshn/aruu / cmd / posix / cal.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include <limits.h>
  4#include <stdint.h>
  5#include <stdio.h>
  6#include <stdlib.h>
  7#include <string.h>
  8#include <time.h>
  9#include <unistd.h>
 10
 11#include "util.h"
 12
 13enum { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };
 14enum caltype { JULIAN, GREGORIAN };
 15enum { TRANS_YEAR = 1752, TRANS_MONTH = SEP, TRANS_DAY = 2 };
 16
 17static struct tm *ltime;
 18
 19static int
 20isleap(size_t year, enum caltype cal)
 21{
 22  if (cal == GREGORIAN) {
 23    if (year % 400 == 0)
 24      return 1;
 25    if (year % 100 == 0)
 26      return 0;
 27    return (year % 4 == 0);
 28  } else { /* cal == Julian */
 29    return (year % 4 == 0);
 30  }
 31}
 32
 33static int
 34monthlength(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 */
 42static int
 43dayofweek(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
 58static void
 59printgrid(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 == (size_t)(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
 94static void
 95drawcal(size_t year, int month, size_t ncols, size_t nmons, int fday)
 96{
 97  char *smon[] = {
 98      "January",
 99      "February",
100      "March",
101      "April",
102      "May",
103      "June",
104      "July",
105      "August",
106      "September",
107      "October",
108      "November",
109      "December"
110  };
111  char *days[] = {
112      "Su",
113      "Mo",
114      "Tu",
115      "We",
116      "Th",
117      "Fr",
118      "Sa",
119  };
120  size_t m, n, col, cur_year, cur_month;
121  int    line, pad, dow;
122  char   month_year[sizeof("Su Mo Tu We Th Fr Sa")];
123
124  for (m = 0; m < nmons;) {
125    n = m;
126    for (col = 0; 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      snprintf(month_year, sizeof(month_year), "%s %zu", smon[cur_month], cur_year);
134      pad = sizeof(month_year) - 1 - strlen(month_year);
135      printf("%*s%s%*s   ", pad / 2 + pad % 2, "", month_year, pad / 2, "");
136    }
137    putchar('\n');
138    for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) {
139      for (dow = fday; dow < (fday + 7); ++dow)
140        printf("%s ", days[dow % 7]);
141      printf("  ");
142    }
143    putchar('\n');
144    for (line = 1; line <= 6; ++line) {
145      for (col = 0, m = n; m < nmons && col < ncols; ++col, ++m) {
146        cur_year  = year + m / 12;
147        cur_month = month + m % 12;
148        if (cur_month > 11) {
149          cur_month -= 12;
150          cur_year += 1;
151        }
152        printgrid(cur_year, cur_month, fday, line);
153        printf("  ");
154      }
155      putchar('\n');
156    }
157  }
158}
159
160static void
161usage(void)
162{
163#ifdef FEATURE_CAL_EXT
164  eprintf(
165      "usage: %s [-1 | -3 | -y | -n num] "
166      "[-s | -m | -f num] [-c num] [[month] year]\n",
167      argv0
168  );
169#else
170  eprintf("usage: %s [-y] [[month] year]\n", argv0);
171#endif
172}
173
174// ?man cal: display a calendar
175// ?man print a formatted calendar of the specified month or year
176int
177main(int argc, char *argv[])
178{
179  time_t now;
180  size_t year, ncols, nmons;
181  int    fday, month;
182
183  now   = time(NULL);
184  ltime = localtime(&now);
185  year  = ltime->tm_year + 1900;
186  month = ltime->tm_mon + 1;
187  fday  = 0;
188
189  if (!isatty(STDOUT_FILENO))
190    ltime = NULL; /* don't highlight today's date */
191
192#ifdef FEATURE_CAL_EXT
193  ncols = 3;
194  nmons = 0;
195#else
196  ncols = 1;
197  nmons = 1;
198#endif
199
200  ARGBEGIN
201  {
202#ifdef FEATURE_CAL_EXT
203    // ?man -1: specify option flag
204    case '1':
205      nmons = 1;
206      break;
207    // ?man -3: specify option flag
208    case '3':
209      nmons = 3;
210      if (--month == 0) {
211        month = 12;
212        year--;
213      }
214      break;
215    // ?man -c:num: print count or perform stdout action
216    case 'c':
217      ncols = estrtonum(
218          EARGF(usage()), 0, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX)
219      );
220      break;
221    // ?man -f:num: force the operation
222    case 'f':
223      fday = estrtonum(EARGF(usage()), 0, 6);
224      break;
225    // ?man -m: specify mode or limit
226    case 'm': /* Monday */
227      fday = 1;
228      break;
229    // ?man -n:num: print line numbers or counts
230    case 'n':
231      nmons = estrtonum(
232          EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX)
233      );
234      break;
235    // ?man -s: silent mode or print summary
236    case 's': /* Sunday */
237      fday = 0;
238      break;
239#endif
240    // ?man -y: specify option flag
241    case 'y':
242      month = 1;
243      nmons = 12;
244      break;
245    default:
246      usage();
247  }
248  ARGEND
249
250#ifdef FEATURE_CAL_EXT
251  if (nmons == 0) {
252    if (argc == 1) {
253      month = 1;
254      nmons = 12;
255    } else {
256      nmons = 1;
257    }
258  }
259#endif
260
261  switch (argc) {
262    case 2:
263      month = estrtonum(argv[0], 1, 12);
264      argv++;
265      /* fallthrough */
266    case 1:
267      year = estrtonum(argv[0], 0, INT_MAX);
268      break;
269    case 0:
270      break;
271    default:
272      usage();
273  }
274
275  drawcal(year, month - 1, ncols, nmons, fday);
276
277  return fshut(stdout, "<stdout>");
278}