Index | Thread | Search

From:
Walter Alejandro Iglesias <wai@roquesor.com>
Subject:
Re: cal: add option to highlight the current day
To:
Johannes Thyssen Tishman <jtt@openbsd.org>, tech@openbsd.org, landry@openbsd.org, deraadt@openbsd.org
Date:
Sun, 5 Jul 2026 12:38:13 +0200

Download raw body.

Thread
On Sat, Jul 04, 2026 at 07:25:20AM -0600, Theo de Raadt wrote:
> Calling tgetent() late is bizzare, and it made you do this:
> 
> -       if (pledge("stdio", NULL) == -1)
> +       if (pledge("stdio rpath tty", NULL) == -1)
>                 err(1, "pledge");
> 
> In a word: Nope.
> 
> 

I've had this patch for a while.  I didn't post it here because I
thought no one would be interested.  Now I've updated it with the latest
sources.

When you pipe the output to a file it does not send the highlight code.

It also corrects separation between rows of months with cal -y.


Index: cal.c
===================================================================
RCS file: /cvs/src/usr.bin/cal/cal.c,v
diff -u -p -u -p -r1.36 cal.c
--- cal.c	2 Jul 2026 20:40:53 -0000	1.36
+++ cal.c	5 Jul 2026 10:37:44 -0000
@@ -128,6 +128,8 @@ int julian;
 int mflag = 0;
 int wflag = 0;
 
+struct tm *local_time = NULL;
+
 void	ascii_day(char *, int);
 void	center(const char *, int, int);
 void	day_array(int, int, int *);
@@ -135,6 +137,7 @@ int	day_in_week(int, int, int);
 int	day_in_year(int, int, int);
 int	week(int, int, int);
 int	isoweek(int, int, int);
+int	is_today(int, int, int);
 void	j_yearly(int);
 void	monthly(int, int);
 void	trim_trailing_spaces(char *);
@@ -145,7 +148,6 @@ int	parsemonth(const char *);
 int
 main(int argc, char *argv[])
 {
-	struct tm *local_time;
 	time_t now;
 	int ch, month, year, yflag;
 	const char *errstr;
@@ -153,6 +155,9 @@ main(int argc, char *argv[])
 	if (pledge("stdio", NULL) == -1)
 		err(1, "pledge");
 
+	(void)time(&now);
+        local_time = localtime(&now);
+
 	yflag = year = 0;
 	while ((ch = getopt(argc, argv, "jmwy")) != -1)
 		switch(ch) {
@@ -202,8 +207,6 @@ main(int argc, char *argv[])
 			if (yflag)
 				errx(1, "specifying a month conflicts with -y");
 			month = parsemonth(*argv);
-			(void)time(&now);
-			local_time = localtime(&now);
 			year = local_time->tm_year + 1900;
 		} else {
 			year = strtonum(*argv, 1, 9999, &errstr);
@@ -212,8 +215,6 @@ main(int argc, char *argv[])
 		}
 		break;
 	case 0:
-		(void)time(&now);
-		local_time = localtime(&now);
 		year = local_time->tm_year + 1900;
 		if (!yflag)
 			month = local_time->tm_mon + 1;
@@ -295,32 +296,39 @@ isoweek(int day, int month, int year)
 		return n/7 + 1;
 }
 
+
 void
 monthly(int month, int year)
 {
-	int col, row, len, days[MAXDAYS], firstday;
-	char *p, lineout[30];
+	int col, today, row, len, days[MAXDAYS], firstday, day_width;
+	char day[J_DAY_LEN], lineout[30];
 
 	day_array(month, year, days);
 	(void)snprintf(lineout, sizeof(lineout), "%s %d",
 	    month_names[month - 1], year);
+
 	len = strlen(lineout);
 	(void)printf("%*s%s\n%s\n",
 	    ((julian ? J_WEEK_LEN : WEEK_LEN) - len) / 2, "",
 	    lineout, day_headings);
 	for (row = 0; row < 6; row++) {
 		firstday = SPACE;
-		for (col = 0, p = lineout; col < 7; col++,
-		    p += julian ? J_DAY_LEN : DAY_LEN) {
-			if (firstday == SPACE && days[row * 7 + col] != SPACE)
-				firstday = days[row * 7 + col];
-			ascii_day(p, days[row * 7 + col]);
+		for (col = 0; col < 7; col++) {
+			today = days[row * 7 + col];
+			day_width = (julian ? J_DAY_LEN : DAY_LEN);
+			if (firstday == SPACE && today != SPACE)
+				firstday = today;
+			ascii_day(day, today);
+			if (is_today(today, month, year))
+				printf("\033[7m");
+			day[day_width] = '\0';
+			printf("%s", day);
+			if (is_today(today, month, year))
+				printf("\033[0m");
+			printf(" ");
 		}
-		*p = '\0';
-		trim_trailing_spaces(lineout);
-		(void)printf("%-20s", lineout);
 		if (wflag && firstday != SPACE)
-			printf(" [%2d]", week(firstday, month, year));
+			printf("[%2d]", week(firstday, month, year));
 		printf("\n");
 	}
 }
@@ -328,7 +336,7 @@ monthly(int month, int year)
 void
 j_yearly(int year)
 {
-	int col, *dp, i, month, row, which_cal;
+	int col, *dp, i, hl_offset, month, row, which_cal;
 	int days[12][MAXDAYS];
 	char *p, lineout[80];
 
@@ -340,21 +348,36 @@ j_yearly(int year)
 	(void)memset(lineout, ' ', sizeof(lineout) - 1);
 	lineout[sizeof(lineout) - 1] = '\0';
 	for (month = 0; month < 12; month += 2) {
+		if (month >= 6 && month <= 10)
+			(void)printf("\n");
 		center(month_names[month], J_WEEK_LEN, J_HEAD_SEP);
 		center(month_names[month + 1], J_WEEK_LEN, 0);
 		(void)printf("\n%s%*s%s\n", day_headings,
 		    J_HEAD_SEP, "", day_headings);
 
 		for (row = 0; row < 6; row++) {
+			hl_offset = -1;
 			for (which_cal = 0; which_cal < 2; which_cal++) {
 				p = lineout + which_cal * (J_WEEK_LEN + 2);
 				dp = &days[month + which_cal][row * 7];
-				for (col = 0; col < 7; col++, p += J_DAY_LEN)
+				for (col = 0; col < 7; col++, p += J_DAY_LEN) {
+					if (is_today(*dp, month +
+					    which_cal + 1, year))
+						hl_offset = p - lineout;
 					ascii_day(p, *dp++);
+					p[J_DAY_LEN - 1] = ' ';
+				}
 			}
 			*p = '\0';
 			trim_trailing_spaces(lineout);
-			(void)printf("%s\n", lineout);
+			if (hl_offset != -1 &&
+			    hl_offset < (int)strlen(lineout)) {
+				(void)printf("%.*s", hl_offset, lineout);
+				(void)printf("\033[7m%.3s\033[0m",
+				    lineout + hl_offset);
+				(void)printf("%s\n", lineout + hl_offset + 3);
+			} else
+				(void)printf("%s\n", lineout);
 		}
 	}
 	(void)printf("\n");
@@ -366,6 +389,7 @@ yearly(int year)
 	int col, *dp, i, month, row, which_cal, week_len, wn, firstday;
 	int days[12][MAXDAYS];
 	char *p, lineout[81];
+	int hl_offset;
 
 	week_len = WEEK_LEN;
 	if (wflag)
@@ -378,6 +402,8 @@ yearly(int year)
 	(void)memset(lineout, ' ', sizeof(lineout) - 1);
 	lineout[sizeof(lineout) - 1] = '\0';
 	for (month = 0; month < 12; month += 3) {
+		if (month > 3)
+			(void)printf("\n");
 		center(month_names[month], week_len, HEAD_SEP);
 		center(month_names[month + 1], week_len, HEAD_SEP);
 		center(month_names[month + 2], week_len, 0);
@@ -386,6 +412,7 @@ yearly(int year)
 		    HEAD_SEP + (wflag ? WEEKNUMBER_LEN : 0), "", day_headings);
 
 		for (row = 0; row < 6; row++) {
+			hl_offset = -1;
 			for (which_cal = 0; which_cal < 3; which_cal++) {
 				p = lineout + which_cal * (week_len + 2);
 
@@ -394,7 +421,10 @@ yearly(int year)
 				for (col = 0; col < 7; col++, p += DAY_LEN) {
 					if (firstday == SPACE && *dp != SPACE)
 						firstday = *dp;
+					if (is_today(*dp, month + which_cal + 1, year))
+						hl_offset = p - lineout;
 					ascii_day(p, *dp++);
+					p[DAY_LEN - 1] = ' ';
 				}
 				if (wflag && firstday != SPACE) {
 					wn = week(firstday,
@@ -407,7 +437,14 @@ yearly(int year)
 			}
 			*p = '\0';
 			trim_trailing_spaces(lineout);
-			(void)printf("%s\n", lineout);
+			if (hl_offset != -1 &&
+			    hl_offset < (int)strlen(lineout)) {
+				(void)printf("%.*s", hl_offset, lineout);
+				(void)printf("\033[7m%.2s\033[0m", lineout +
+				    hl_offset);
+				(void)printf("%s\n", lineout + hl_offset + 2);
+			} else
+				(void)printf("%s\n", lineout);
 		}
 	}
 	(void)printf("\n");
@@ -472,45 +509,46 @@ day_in_week(int day, int month, int year
 		return (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7);
 	return (THURSDAY);
 }
-
 void
 ascii_day(char *p, int day)
 {
-	int display, val;
-	static const char *aday[] = {
-		"",
-		" 1", " 2", " 3", " 4", " 5", " 6", " 7",
-		" 8", " 9", "10", "11", "12", "13", "14",
-		"15", "16", "17", "18", "19", "20", "21",
-		"22", "23", "24", "25", "26", "27", "28",
-		"29", "30", "31",
-	};
-
-	if (day == SPACE) {
-		memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN);
-		return;
-	}
-	if (julian) {
-		val = day / 100;
-		if (val) {
-			day %= 100;
-			*p++ = val + '0';
-			display = 1;
-		} else {
-			*p++ = ' ';
-			display = 0;
-		}
-		val = day / 10;
-		if (val || display)
-			*p++ = val + '0';
-		else
-			*p++ = ' ';
-		*p++ = day % 10 + '0';
-	} else {
-		*p++ = aday[day][0];
-		*p++ = aday[day][1];
-	}
-	*p = ' ';
+        int display, val;
+        static const char *aday[] = {
+                "",
+                " 1", " 2", " 3", " 4", " 5", " 6", " 7",
+                " 8", " 9", "10", "11", "12", "13", "14",
+                "15", "16", "17", "18", "19", "20", "21",
+                "22", "23", "24", "25", "26", "27", "28",
+                "29", "30", "31",
+        };
+
+        if (day == SPACE) {
+                int text_width = (julian ? J_DAY_LEN : DAY_LEN) - 1;
+                memset(p, ' ', text_width);
+                p[text_width] = '\0';
+                return;
+        }
+        if (julian) {
+                val = day / 100;
+                if (val) {
+                        day %= 100;
+                        *p++ = val + '0';
+                        display = 1;
+                } else {
+                        *p++ = ' ';
+                        display = 0;
+                }
+                val = day / 10;
+                if (val || display)
+                        *p++ = val + '0';
+                else
+                        *p++ = ' ';
+                *p++ = day % 10 + '0';
+        } else {
+                *p++ = aday[day][0];
+                *p++ = aday[day][1];
+        }
+        *p = '\0';
 }
 
 void
@@ -560,4 +598,18 @@ parsemonth(const char *s)
 	if (v <= 0 || v > 12)
 		errx(1, "invalid month: use 1-12 or a name");
 	return (v);
+}
+
+int
+is_today(int day, int month, int year)
+{
+	if (!isatty(STDOUT_FILENO))
+		return (0);
+
+	if (day <= 0 || local_time == NULL)
+		return (0);
+
+	return (year == local_time->tm_year + 1900 &&
+	    month == local_time->tm_mon + 1 &&
+	    day == (julian ? local_time->tm_yday + 1 : local_time->tm_mday));
 }


-- 
Walter