Re: cal: add option to highlight the current day

Walter Alejandro Iglesias <[email protected]>
Newsgroups gmane.os.openbsd.tech
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.