#include "strutils.h"
#include "optutils.h"
#include "timeutils.h"
+#include "ttyutils.h"
+
+#define DOY_MONTH_WIDTH 27 /* -j month width */
+#define DOM_MONTH_WIDTH 20 /* month width */
static int has_term = 0;
static const char *Senter = "", *Sexit = ""; /* enter and exit standout mode */
static int parse_reform_year(const char *reform_year);
static void __attribute__((__noreturn__)) usage(void);
+#ifdef TEST_CAL
+static time_t cal_time(time_t *t)
+{
+ char *str = getenv("CAL_TEST_TIME");
+
+ if (str) {
+ uint64_t x = strtou64_or_err(str, "failed to parse CAL_TEST_TIME");
+
+ *t = x;
+ return *t;
+ }
+
+ return time(t);
+}
+#else
+# define cal_time(t) time(t)
+#endif
+
int main(int argc, char **argv)
{
struct tm *local_time;
static struct cal_control ctl = {
.reform_year = DEFAULT_REFORM_YEAR,
.weekstart = SUNDAY,
- .num_months = 1, /* default is "cal -1" */
.span_months = 0,
.colormode = UL_COLORMODE_UNDEF,
.weektype = WEEK_NUM_DISABLED,
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
- atexit(close_stdout);
+ close_stdout_atexit();
term = getenv("TERM");
if (term) {
switch(ch) {
case '1':
- /* default */
+ ctl.num_months = 1;
break;
case '3':
ctl.num_months = 3;
ctl.span_months = 1;
- ctl.months_in_row = 3;
break;
case 's':
ctl.weekstart = SUNDAY; /* default */
ctl.reform_year = ISO;
break;
case 'V':
- printf(UTIL_LINUX_VERSION);
- return EXIT_SUCCESS;
+ print_version(EXIT_SUCCESS);
case 'h':
usage();
default:
now = (time_t) (x / 1000000);
/* cal <monthname> */
else if ((ctl.req.month = monthname_to_number(&ctl, *argv)) > 0)
- time(&now); /* this year */
+ cal_time(&now); /* this year */
else
errx(EXIT_FAILURE, _("failed to parse timestamp or unknown month name: %s"), *argv);
argc = 0;
} else
- time(&now);
+ cal_time(&now);
local_time = localtime(&now);
}
if (!ctl.req.month && !ctl.req.week) {
ctl.req.month = local_time->tm_mon + 1;
- yflag = 1;
+ if (!ctl.num_months)
+ yflag = 1;
}
break;
case 0:
headers_init(&ctl);
- if (!colors_init(ctl.colormode, "cal")) {
- ctl.req.day = 0;
- ctl.weektype &= ~WEEK_NUM_MASK;
+ if (colors_init(ctl.colormode, "cal") == 0) {
+ /*
+ * If standout mode available (Senter and Sexit are set) and
+ * user or terminal-colors.d do not disable colors than
+ * ignore colors_init().
+ */
+ if (*Senter && *Sexit && colors_mode() != UL_COLORMODE_NEVER) {
+ /* let use standout mode */
+ ;
+ } else {
+ /* disable */
+ Senter = ""; Sexit = "";
+ ctl.req.day = 0;
+ ctl.weektype &= ~WEEK_NUM_MASK;
+ }
}
if (yflag || Yflag) {
ctl.gutter_width = 3;
- ctl.num_months = MONTHS_IN_YEAR;
+ if (!ctl.num_months)
+ ctl.num_months = MONTHS_IN_YEAR;
if (yflag) {
ctl.req.start_month = 1; /* start from Jan */
ctl.header_year = 1; /* print year number */
}
}
- if (ctl.num_months > 1 && ctl.months_in_row == 0)
- ctl.months_in_row = ctl.julian ? MONTHS_IN_YEAR_ROW - 1 :
- MONTHS_IN_YEAR_ROW;
- else if (!ctl.months_in_row)
+ if (ctl.num_months > 1 && ctl.months_in_row == 0) {
+ ctl.months_in_row = MONTHS_IN_YEAR_ROW; /* default */
+
+ if (isatty(STDOUT_FILENO)) {
+ int w, mw, extra, new_n;
+
+ w = get_terminal_width(80);
+ mw = ctl.julian ? DOY_MONTH_WIDTH : DOM_MONTH_WIDTH;
+
+ if (w < mw)
+ w = mw;
+
+ extra = ((w / mw) - 1) * ctl.gutter_width;
+ new_n = (w - extra) / mw;
+
+ if (new_n < MONTHS_IN_YEAR_ROW)
+ ctl.months_in_row = new_n > 0 ? new_n : 1;
+ }
+ } else if (!ctl.months_in_row)
ctl.months_in_row = 1;
+ if (!ctl.num_months)
+ ctl.num_months = 1; /* display at least one month */
+
if (yflag || Yflag)
yearly(&ctl);
else
return; /* already initialized */
for (i = 0; i < MONTHS_IN_YEAR; i++)
- ctl->full_month[i] = nl_langinfo(MON_1 + i);
+ ctl->full_month[i] = nl_langinfo(ALTMON_1 + i);
}
static void init_abbr_monthnames(struct cal_control *ctl)
return; /* already initialized */
for (i = 0; i < MONTHS_IN_YEAR; i++)
- ctl->abbr_month[i] = nl_langinfo(ABMON_1 + i);
+ ctl->abbr_month[i] = nl_langinfo(_NL_ABALTMON_1 + i);
}
static int monthname_to_number(struct cal_control *ctl, const char *name)
if (ctl->header_hint || ctl->header_year) {
for (i = month; i; i = i->next) {
- sprintf(out, _("%s"), ctl->full_month[i->month - 1]);
+ snprintf(out, sizeof(out), "%s", ctl->full_month[i->month - 1]);
center(out, ctl->week_width - 1, i->next == NULL ? 0 : ctl->gutter_width);
}
if (!ctl->header_year) {
my_putstring("\n");
for (i = month; i; i = i->next) {
- sprintf(out, _("%04d"), i->year);
+ snprintf(out, sizeof(out), "%04d", i->year);
center(out, ctl->week_width - 1, i->next == NULL ? 0 : ctl->gutter_width);
}
}
} else {
for (i = month; i; i = i->next) {
- sprintf(out, _("%s %04d"), ctl->full_month[i->month - 1], i->year);
+ snprintf(out, sizeof(out), "%s %04d", ctl->full_month[i->month - 1], i->year);
center(out, ctl->week_width - 1, i->next == NULL ? 0 : ctl->gutter_width);
}
}
for (i = month; i; i = i->next) {
if (ctl->weektype) {
if (ctl->julian)
- sprintf(out, "%*s%s", (int)ctl->day_width - 1, "", day_headings);
+ snprintf(out, sizeof(out), "%*s%s", (int)ctl->day_width - 1, "", day_headings);
else
- sprintf(out, "%*s%s", (int)ctl->day_width, "", day_headings);
+ snprintf(out, sizeof(out), "%*s%s", (int)ctl->day_width, "", day_headings);
my_putstring(out);
} else
my_putstring(day_headings);
if (i->next != NULL) {
- sprintf(out, "%*s", ctl->gutter_width, "");
+ snprintf(out, sizeof(out), "%*s", ctl->gutter_width, "");
my_putstring(out);
}
}
if (0 < i->weeks[week_line]) {
if ((ctl->weektype & WEEK_NUM_MASK) ==
i->weeks[week_line])
- sprintf(out, "%s%2d%s", Senter, i->weeks[week_line],
- Sexit);
+ snprintf(out, sizeof(out), "%s%2d%s",
+ Senter, i->weeks[week_line],
+ Sexit);
else
- sprintf(out, "%2d", i->weeks[week_line]);
+ snprintf(out, sizeof(out), "%2d", i->weeks[week_line]);
} else
- sprintf(out, "%2s", "");
+ snprintf(out, sizeof(out), "%2s", "");
my_putstring(out);
skip = ctl->day_width;
} else
d < DAYS_IN_WEEK * week_line + DAYS_IN_WEEK; d++) {
if (0 < i->days[d]) {
if (reqday == i->days[d])
- sprintf(out, "%*s%s%*d%s", skip - (ctl->julian ? 3 : 2),
+ snprintf(out, sizeof(out), "%*s%s%*d%s",
+ skip - (ctl->julian ? 3 : 2),
"", Senter, (ctl->julian ? 3 : 2),
i->days[d], Sexit);
else
- sprintf(out, "%*d", skip, i->days[d]);
+ snprintf(out, sizeof(out), "%*d", skip, i->days[d]);
} else
- sprintf(out, "%*s", skip, "");
+ snprintf(out, sizeof(out), "%*s", skip, "");
my_putstring(out);
if (skip < (int)ctl->day_width)
skip++;
}
if (i->next != NULL) {
- sprintf(out, "%*s", ctl->gutter_width, "");
+ snprintf(out, sizeof(out), "%*s", ctl->gutter_width, "");
my_putstring(out);
}
}
- if (i == NULL) {
- int extra = ctl->num_months > 3 ? 0 : 1;
- sprintf(out, "%*s\n", ctl->gutter_width - extra, "");
- my_putstring(out);
- }
+ if (i == NULL)
+ my_putstring("\n");
}
}
static void monthly(const struct cal_control *ctl)
{
struct cal_month m1,m2,m3, *m;
- int i, rows, new_month, month = ctl->req.start_month ? ctl->req.start_month : ctl->req.month;
+ int i, rows, month = ctl->req.start_month ? ctl->req.start_month : ctl->req.month;
int32_t year = ctl->req.year;
/* cal -3, cal -Y --span, etc. */
if (ctl->span_months) {
- new_month = month - ctl->num_months / 2;
+ int new_month = month - ctl->num_months / 2;
if (new_month < 1) {
- month = new_month + MONTHS_IN_YEAR;
- year--;
- }
- else
+ new_month *= -1;
+ year -= (new_month / MONTHS_IN_YEAR) + 1;
+
+ if (new_month > MONTHS_IN_YEAR)
+ new_month %= MONTHS_IN_YEAR;
+ month = MONTHS_IN_YEAR - new_month;
+ } else
month = new_month;
}
year_width--;
if (ctl->header_year) {
- sprintf(out, "%04d", ctl->req.year);
+ snprintf(out, sizeof(out), "%04d", ctl->req.year);
center(out, year_width, 0);
my_putstring("\n\n");
}
monthly(ctl);
-
- /* Is empty line at the end year output really needed? */
- my_putstring("\n");
}
/*
wday = day_in_week(ctl, 1, JANUARY, ctl->req.year);
yday = ctl->req.week * DAYS_IN_WEEK - wday;
+ if (ctl->req.year == ctl->reform_year && yday >= YDAY_AFTER_MISSING)
+ yday += NUMBER_MISSING_DAYS;
+
if (ctl->weektype & WEEK_NUM_ISO)
yday -= (wday >= FRIDAY ? -2 : 5);
else
- yday -= (wday == SUNDAY ? 6 : -1); /* WEEK_NUM_US */
+ yday -= 6; /* WEEK_NUM_US */
if (yday <= 0)
return 1;