~sircmpwn/ctools

Implement cal v1 PROPOSED

Lo: 1
 Implement cal

 3 files changed, 325 insertions(+), 0 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/ctools/patches/9829/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH] Implement cal Export this patch

Thanks,
Lo
---
 doc/cal.1.scd |  37 +++++++
 meson.build   |   1 +
 src/cal.c     | 287 ++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 325 insertions(+)
 create mode 100644 doc/cal.1.scd
 create mode 100644 src/cal.c

diff --git a/doc/cal.1.scd b/doc/cal.1.scd
new file mode 100644
index 0000000..242f983
--- /dev/null
+++ b/doc/cal.1.scd
@@ -0,0 +1,37 @@
cal(1) "ctools"

# NAME

cal - print a calendar

# SYNOPSIS

*cal* [[_month_] _year_]

# DESCRIPTION

*cal* will print a calendar to _stdout_ using the Julian calendar for dates 
from January 1, 1 thourgh September 2,1752 and the Gregorian calendar for 
dates from September 14, 1752 throigh December 31, 9999. 
If no operand is given, *cal* will print one-month calendar for the current 
month. 
If only the _year_ operand is given, *cal* will print a calendar for the 12 
months of the given year. 
If both _month_ and _year_ are specified, *cal* will print a calendar for the 
given month and year.

# OPTIONS

*month*
	Specify the month to be displayed, represented as a decimal integer 
	from 1 (January) to 12 (December).
*year*
	Specify the year for which the calendar is displayed, represented as a 
	decimal integer from 1 to 9999.

# DISCLAIMER

This command is part of ctools and is compatible with POSIX-1.2017, and may
optionally support XSI extensions. This man page is not intended to be a
complete reference, and where it disagrees with the specification, the
specification takes precedence.
diff --git a/meson.build b/meson.build
index fbf4840..6195c1e 100644
--- a/meson.build
+++ b/meson.build
@@ -12,6 +12,7 @@ project(

oneshots = [
	'basename',
	'cal',
	'cat',
	'chgrp',
	'chmod',
diff --git a/src/cal.c b/src/cal.c
new file mode 100644
index 0000000..4f21db7
--- /dev/null
+++ b/src/cal.c
@@ -0,0 +1,287 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>

enum config {
	INNER_SPACE = 1,
	COL_ROW_SPACES = 4,
	MONTH_WIDTH = 2 * 7 + 6 * INNER_SPACE + 1
};
const char *days_of_the_week[7] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"};

const int days_in_months[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

const int first_week_1752[7] = {-1, -1, 1, 2, 14, 15, 16};

const char *months[12] = {"January",
		"February",
		"March",
		"April",
		"May",
		"June",
		"July",
		"August",
		"September",
		"October",
		"November",
		"December"};

int
n_digits(int n) {
	int n_digits = 0;
	while (n != 0) {
		n /= 10;
		++n_digits;
	}
	return n_digits;
}

/* check if February is a leap year*/
int
days(int year, int month) {
	if (month == 1) {
		if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
			return 29;
		}
	}
	return days_in_months[month];
}

/*using Zeller's congruence */
int
first_day_of_month(int year, int month) {
	int h;

	int y = month <= 1 ? year - 1 : year;
	int m = month > 1 ? month + 1 : month + 13;
	if (year < 1752 || (year == 1752 && month < 9)) {
		h = 1 + 13 * (m + 1) / 5 + y + y / 4 + 5;
	} else {
		h = 1 + (13 * (m + 1)) / 5 + y + y / 4 - y / 100 + y / 400;
	}

	h = h % 7;

	/*Zeller's formula starts from h0 = Saturday*/
	return h - 1 >= 0 ? h - 1 : 6;
}

struct month_str {
	char month_year_header[MONTH_WIDTH];
	char month_header[MONTH_WIDTH];
	char year[5];
	int days_numbers[6][7];
};

/*Fills dest with spaces and then center src*/
void
center_print(char *dest, const char *src, size_t dest_size) {
	memset(dest, ' ', dest_size);
	dest[dest_size - 1] = '\0';
	int center = strlen(dest) / 2;
	size_t src_len = strlen(src);
	int start_index = center - src_len / 2;
	strncpy(&dest[start_index], src, src_len);
}

struct month_str
create_month_str(int year, int month) {
	struct month_str m;

	size_t year_digits = n_digits(year);
	size_t l = strlen(months[month]) + year_digits + 1;
	char s[l + 1];
	sprintf(s, "%s %d", months[month], year);
	s[l] = '\0';
	center_print(m.month_year_header, s, MONTH_WIDTH);

	char y[year_digits + 1];
	sprintf(y, "%d", year);
	y[year_digits] = '\0';
	center_print(m.year, y, year_digits + 1);

	center_print(m.month_header, months[month], MONTH_WIDTH);

	/* September 1752   
	*  Su Mo Tu We Th Fr Sa
	*         1  2 14 15 16
	*  17 18 19 20 21 22 23
	*  24 25 26 27 28 29 30
 	*/
	if (year == 1752 && month == 8) {
		memcpy(&m.days_numbers[0], first_week_1752, sizeof(first_week_1752));
		int k = 17;
		for (int i = 7; i < 21; i++) {
			m.days_numbers[i / 7][i % 7] = k++;
		}
		for (int i = 21; i < 6 * 7; i++) {
			m.days_numbers[i / 7][i % 7] = -1;
		}
	} else {

		int first_day = first_day_of_month(year, month);
		int days_in_month = days(year, month);

		for (int i = 0; i < first_day; i++) {
			m.days_numbers[0][i] = -1;
		}
		int f = first_day + days_in_month;
		int k = 1;
		for (int i = first_day; i < f; i++) {
			m.days_numbers[i / 7][i % 7] = k++;
		}

		for (int i = f; i < 6 * 7; i++) {
			m.days_numbers[i / 7][i % 7] = -1;
		}
	}
	return m;
}

void
print_day(int d, int col_index) {
	/*If d == -1, then the we should print 2 + INNER_SPACE spaces. 
	 * in case the current position is at the left (col_index ==0) 
	 * then we print only 2 spaces*/
	if (d == -1) {
		printf("%*s", col_index == 0 ? 2 : 2 + INNER_SPACE, "");
	} else {
		if (col_index != 0) {
			printf("%*s", INNER_SPACE, "");
			printf("%2d", d);
		} else {
			printf("%2d", d);
		}
	}
}

void
print_cal(int year, int month) {
	int n_rows = 4;
	int n_cols = 3;
	/*TODO: find a POSIX way to get the width of the terminal for printing
	 * and avoid setting fixed values for n_rows and n_cols
	 * */
	/*if month is specified, print a single month
	 * */
	if (month != -1) {
		struct month_str m = create_month_str(year, month);
		printf("%s\n", m.month_year_header);

		for (int i = 0; i < 7; i++) {
			printf("%s", days_of_the_week[i]);
			if (i <= 6) {
				printf("%*s", INNER_SPACE, "");
			}
		}
		printf("\n");

		for (int i = 0; i < 6; i++) {
			for (int j = 0; j < 7; j++) {
				print_day(m.days_numbers[i][j], j);
			}
			printf("\n");
		}
		printf("\n");
	} else {
		struct month_str months_arr[12];

		for (int i = 0; i < 12; i++) {
			months_arr[i] = create_month_str(year, i);
		}

		const int total_line_width =
				n_cols * MONTH_WIDTH + (n_cols - 1) * COL_ROW_SPACES + 1;
		char header[total_line_width];
		center_print(header, months_arr[0].year, total_line_width);
		/*year header centered on the first line*/
		printf("%s\n", header);
		printf("\n");
		for (int r = 0; r < n_rows; r++) {
			/* January, February,... */
			for (int c = 0, p = r * n_cols; c < n_cols; c++, p++) {
				printf("%s", months_arr[p].month_header);
				printf("%*s", COL_ROW_SPACES, "");
			}
			printf("\n");
			/* Su Mo Tu We ...*/
			for (int c = 0, p = r * n_cols; c < n_cols; c++, p++) {
				for (int i = 0; i < 7; i++) {
					printf("%s", days_of_the_week[i]);
					if (i <= 5) {
						printf("%*s", INNER_SPACE, "");
					}
				}
				printf("%*s", COL_ROW_SPACES, "");
			}
			printf("\n");

			/* single days of each month */
			for (int i = 0; i < 6; i++) {
				for (int c = 0, p = r * n_cols; c < n_cols; c++, p++) {
					for (int j = 0; j < 7; j++) {
						print_day(months_arr[p].days_numbers[i][j], j);
					}
					printf("%*s", COL_ROW_SPACES, "");
				}
				printf("\n");
			}
		}
	}
}

static void
usage(void) {
	fprintf(stderr, "usage: cal [[month] year]\n");
}

int
main(int argc, char *argv[]) {
	if (argc > 3) {
		usage();
		return 1;
	}

	int month = -1;
	int year = -1;
	if (argc == 1) {

		/* If no argument is passed consider current date */
		time_t now = time(0);
		struct tm *ct;
		ct = localtime(&now);
		month = ct->tm_mon;		   /* tm_mon in [0,11] as for the other inputs*/
		year = ct->tm_year + 1900; /* years since 1900*/
	} else if (argc == 2) {
		/* only year specified */
		char *endptr;
		errno = 0;
		year = strtol(argv[1], &endptr, 10);
		if (endptr[0] != '\0' || year > 9999 || year <= 0 || errno != 0) {
			fprintf(stderr, "Illegal year value: '%s'\n", argv[1]);
			return 1;
		}
	} else if (argc == 3) {
		errno = 0;
		char *endptr;
		month = strtol(argv[1], &endptr, 10);
		if (errno != 0 || endptr[0] != '\0' || month > 12 || month <= 0) {
			fprintf(stderr, "Illegal month value: '%s'\n", argv[2]);
			return 1;
		} else {
			month = month - 1; /* input month [1-12] we want [0-11]*/
		}

		year = strtol(argv[2], &endptr, 10);
		if (errno != 0 || endptr[0] != '\0' || year > 9999 || year <= 0) {
			fprintf(stderr, "Illegal year value: '%s'\n", argv[1]);
			return 1;
		}
	}
	/*month in [0-11] */
	print_cal(year, month);
	return 0;
}
-- 
2.25.1
Sorry for the delay in reviewing these! These have been sitting on my
plate for a while.

On Wed Feb 19, 2020 at 1:51 PM, Lo wrote: