]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - misc-utils/cal.c
findmnt: keep it easy for static analyzers
[thirdparty/util-linux.git] / misc-utils / cal.c
index a854eaf9d073382d277bce99ea608746eeb0d064..1d4d241345659969f1b5df4e377befe5125dd07b 100644 (file)
 #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 */
@@ -257,6 +261,24 @@ static void center(const char *str, size_t len, int separate);
 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;
@@ -267,7 +289,6 @@ int main(int argc, char **argv)
        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,
@@ -311,7 +332,7 @@ int main(int argc, char **argv)
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
-       atexit(close_stdout);
+       close_stdout_atexit();
 
        term = getenv("TERM");
        if (term) {
@@ -363,12 +384,11 @@ int main(int argc, char **argv)
 
                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 */
@@ -415,8 +435,7 @@ int main(int argc, char **argv)
                        ctl.reform_year = ISO;
                        break;
                case 'V':
-                       printf(UTIL_LINUX_VERSION);
-                       return EXIT_SUCCESS;
+                       print_version(EXIT_SUCCESS);
                case 'h':
                        usage();
                default:
@@ -441,12 +460,12 @@ int main(int argc, char **argv)
                        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);
 
@@ -486,7 +505,8 @@ int main(int argc, char **argv)
                }
                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:
@@ -529,26 +549,57 @@ int main(int argc, char **argv)
 
        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
@@ -574,7 +625,7 @@ static void init_monthnames(struct cal_control *ctl)
                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)
@@ -585,7 +636,7 @@ 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)
@@ -705,19 +756,19 @@ static void cal_output_header(struct cal_month *month, const struct cal_control
 
        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);
                }
        }
@@ -725,14 +776,14 @@ static void cal_output_header(struct cal_month *month, const struct cal_control
        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);
                }
        }
@@ -763,12 +814,13 @@ static void cal_output_months(struct cal_month *month, const struct cal_control
                                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
@@ -780,44 +832,45 @@ static void cal_output_months(struct cal_month *month, const struct cal_control
                             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;
        }
 
@@ -861,14 +914,11 @@ static void yearly(const struct cal_control *ctl)
                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");
 }
 
 /*
@@ -997,10 +1047,13 @@ static int week_to_day(const struct cal_control *ctl)
        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;