git: 1ca7f6618536 - main - ncal(1): handle Sweden's transitional calendar (1700-1712)

From: Alan Somers <asomers_at_FreeBSD.org>
Date: Tue, 13 Oct 2026 00:30:31 UTC
The branch main has been updated by asomers:

URL: https://cgit.FreeBSD.org/src/commit/?id=1ca7f6618536febba5b500c050cddaf06bd2342a

commit 1ca7f6618536febba5b500c050cddaf06bd2342a
Author:     Juan Michelini <juan@juan.com.uy>
AuthorDate: 2026-10-09 14:28:25 +0000
Commit:     Alan Somers <asomers@FreeBSD.org>
CommitDate: 2026-10-13 00:30:07 +0000

    ncal(1): handle Sweden's transitional calendar (1700-1712)
    
    Sweden used a unique intermediate calendar during 1700-1712.  In 1700,
    Sweden omitted the Julian leap day (Feb 29, 1700) to begin a gradual
    transition to the Gregorian calendar.  The transition was abandoned
    mid-way due to the Great Northern War; to revert to the Julian calendar,
    an extra day "Feb 30, 1712" was added.  Sweden finally adopted the
    Gregorian calendar in 1753 (already handled correctly by ncal).
    
    ncal previously displayed Feb 1700 as a 29-day month and Feb 1712 as a
    29-day month, both incorrect for the Swedish (-s SE) calendar mode.
    
    Add flag_sweden, which is set when the SE country code is selected (via
    -s SE or the locale) and cleared by -J, since -J selects the plain
    Julian calendar.  When active, sndaysr() and sdater() apply the one-day
    offset that characterised the Swedish transitional calendar:
    
      - Feb 1700: displayed with 28 days (no leap day)
      - 1700-03-01 to 1712-02-30: dates run one day ahead of Julian, so
        weekdays are correctly anchored to the actual solar day
      - Feb 1712: displayed with 30 days (the unique "Feb 30" day)
      - 1712-03-01 onward: back to Julian until the 1753 Gregorian switch
    
    Add ATF tests covering the 3-month and Julian-day views around both
    boundaries and the -J override.
    
    Reported-by:    John Costello <https://joxn.org>
    Signed-off-by:  Juan Michelini <juan@juan.com.uy>
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
    Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
    Reviewed by:    asomers, js
    MFC after:      2 weeks
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/2353
---
 usr.bin/ncal/ncal.c                               | 24 +++++++++++++++
 usr.bin/ncal/tests/Makefile                       |  5 ++++
 usr.bin/ncal/tests/ncal_test.sh                   | 36 +++++++++++++++++++++++
 usr.bin/ncal/tests/regress.se-J-170002-nhl.out    |  8 +++++
 usr.bin/ncal/tests/regress.se-r-3-1700-nhl.out    |  8 +++++
 usr.bin/ncal/tests/regress.se-r-3-1712-nhl.out    |  8 +++++
 usr.bin/ncal/tests/regress.se-r-jd-170003-nhl.out |  8 +++++
 usr.bin/ncal/tests/regress.se-r-jd-171202-nhl.out |  8 +++++
 8 files changed, 105 insertions(+)

diff --git a/usr.bin/ncal/ncal.c b/usr.bin/ncal/ncal.c
index dc50dd60bf0b..e4f2b96524a2 100644
--- a/usr.bin/ncal/ncal.c
+++ b/usr.bin/ncal/ncal.c
@@ -164,6 +164,7 @@ static int nswitch;		/* user defined switch date */
 static int nswitchb;		/* switch date for backward compatibility */
 static int highlightdate;
 static bool flag_monday;	/* user wants week starts on Monday */
+static bool flag_sweden;	/* Sweden, 1700-1712 */
 
 static char	*center(char *s, char *t, int w);
 static wchar_t *wcenter(wchar_t *s, wchar_t *t, int w);
@@ -219,6 +220,7 @@ main(int argc, char *argv[])
 	flag_highlight = isatty(STDOUT_FILENO);
 	flag_weeks = 0;
 	flag_monday = false;
+	flag_sweden = false;
 
 	/*
 	 * Use locale to determine the country code,
@@ -243,6 +245,7 @@ main(int argc, char *argv[])
 	} else {
 		nswitch = ndaysj(&p->dt);
 		dftswitch = p;
+		flag_sweden = (strcmp(p->cc, "SE") == 0);
 	}
 
 
@@ -285,6 +288,7 @@ main(int argc, char *argv[])
 				usage();
 			nswitch = ndaysj(&never);
 			flag_julian_cal = 1;
+			flag_sweden = false;
 			break;
 		case 'C':
 			flag_backward = 1;
@@ -342,6 +346,7 @@ main(int argc, char *argv[])
 				errx(EX_USAGE,
 				    "%s: invalid country code", optarg);
 			nswitch = ndaysj(&(p->dt));
+			flag_sweden = (strcmp(p->cc, "SE") == 0);
 			break;
 		case 'w':
 			if (flag_backward)
@@ -1015,6 +1020,11 @@ static int
 sndaysr(struct date *d)
 {
 
+	/* Sweden skipped Feb 29, 1700 and added Feb 30, 1712. */
+	if (flag_sweden && d->y * 12 + d->m >= 1700 * 12 + 3 &&
+	    d->y * 12 + d->m < 1712 * 12 + 3)
+		return (ndaysj(d) - 1);
+
 	if (nswitch != 0)
 		if (nswitch < ndaysj(d))
 			return (ndaysg(d));
@@ -1043,6 +1053,20 @@ static struct date *
 sdater(int nd, struct date *d)
 {
 
+	/* Inverse of the Sweden case in sndaysr(). */
+	if (flag_sweden) {
+		date start = { 1700, 2, 29 }, end = { 1712, 3, 1 };
+
+		if (nd >= ndaysj(&start) && nd < ndaysj(&end)) {
+			jdate(nd + 1, d);
+			if (d->y == 1712 && d->m == 3) {
+				d->m = 2;
+				d->d = 30;
+			}
+			return (d);
+		}
+	}
+
 	if (nswitch < nd)
 		return (gdate(nd, d));
 	else
diff --git a/usr.bin/ncal/tests/Makefile b/usr.bin/ncal/tests/Makefile
index c270e28b12f2..8e4c38d00317 100644
--- a/usr.bin/ncal/tests/Makefile
+++ b/usr.bin/ncal/tests/Makefile
@@ -91,5 +91,10 @@ ${PACKAGE}FILES+=		regress.s-r-B-nhl.out
 ${PACKAGE}FILES+=		regress.s-r-gmgy-nhl.out
 ${PACKAGE}FILES+=		regress.s-r-m-nhl.out
 ${PACKAGE}FILES+=		regress.s-r-mgy-nhl.out
+${PACKAGE}FILES+=		regress.se-J-170002-nhl.out
+${PACKAGE}FILES+=		regress.se-r-3-1700-nhl.out
+${PACKAGE}FILES+=		regress.se-r-3-1712-nhl.out
+${PACKAGE}FILES+=		regress.se-r-jd-170003-nhl.out
+${PACKAGE}FILES+=		regress.se-r-jd-171202-nhl.out
 
 .include <bsd.test.mk>
diff --git a/usr.bin/ncal/tests/ncal_test.sh b/usr.bin/ncal/tests/ncal_test.sh
index 51bc99a888cc..bd49fd061957 100644
--- a/usr.bin/ncal/tests/ncal_test.sh
+++ b/usr.bin/ncal/tests/ncal_test.sh
@@ -136,6 +136,39 @@ ncal_options_body()
 	ncal_out s-r-gmgy-nhl -N -d 2008.03 1 2007
 }
 
+atf_test_case sweden
+sweden_head()
+{
+	atf_set "descr" "Sweden's 1700-1712 transitional calendar"
+}
+sweden_body()
+{
+	# February 1700 has no leap day and February 1712 has 30 days.
+	ncal_out se-r-3-1700-nhl -N -h -3 -s SE -d 1700.02
+	ncal_out se-r-3-1712-nhl -N -h -3 -s SE -d 1712.02
+}
+
+atf_test_case sweden_jd
+sweden_jd_head()
+{
+	atf_set "descr" "Sweden's transitional calendar in Julian-day format"
+}
+sweden_jd_body()
+{
+	ncal_out se-r-jd-170003-nhl -N -h -j -s SE 3 1700
+	ncal_out se-r-jd-171202-nhl -N -h -j -s SE 2 1712
+}
+
+atf_test_case sweden_julian
+sweden_julian_head()
+{
+	atf_set "descr" "-J overrides the Swedish transitional calendar"
+}
+sweden_julian_body()
+{
+	ncal_out se-J-170002-nhl -N -h -s SE -J 2 1700
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case year_md
@@ -145,4 +178,7 @@ atf_init_test_cases()
 	atf_add_test_case invalid_args
 	atf_add_test_case cal_options
 	atf_add_test_case ncal_options
+	atf_add_test_case sweden
+	atf_add_test_case sweden_jd
+	atf_add_test_case sweden_julian
 }
diff --git a/usr.bin/ncal/tests/regress.se-J-170002-nhl.out b/usr.bin/ncal/tests/regress.se-J-170002-nhl.out
new file mode 100644
index 000000000000..0a1b0248da5b
--- /dev/null
+++ b/usr.bin/ncal/tests/regress.se-J-170002-nhl.out
@@ -0,0 +1,8 @@
+    February 1700     
+Mo     5 12 19 26   
+Tu     6 13 20 27   
+We     7 14 21 28   
+Th  1  8 15 22 29   
+Fr  2  9 16 23      
+Sa  3 10 17 24      
+Su  4 11 18 25      
diff --git a/usr.bin/ncal/tests/regress.se-r-3-1700-nhl.out b/usr.bin/ncal/tests/regress.se-r-3-1700-nhl.out
new file mode 100644
index 000000000000..ca2998159319
--- /dev/null
+++ b/usr.bin/ncal/tests/regress.se-r-3-1700-nhl.out
@@ -0,0 +1,8 @@
+    January 1700      February 1700     March 1700        
+Mo  1  8 15 22 29        5 12 19 26        5 12 19 26   
+Tu  2  9 16 23 30        6 13 20 27        6 13 20 27   
+We  3 10 17 24 31        7 14 21 28        7 14 21 28   
+Th  4 11 18 25        1  8 15 22        1  8 15 22 29   
+Fr  5 12 19 26        2  9 16 23        2  9 16 23 30   
+Sa  6 13 20 27        3 10 17 24        3 10 17 24 31   
+Su  7 14 21 28        4 11 18 25        4 11 18 25      
diff --git a/usr.bin/ncal/tests/regress.se-r-3-1712-nhl.out b/usr.bin/ncal/tests/regress.se-r-3-1712-nhl.out
new file mode 100644
index 000000000000..ec508304fef8
--- /dev/null
+++ b/usr.bin/ncal/tests/regress.se-r-3-1712-nhl.out
@@ -0,0 +1,8 @@
+    January 1712      February 1712     March 1712        
+Mo  1  8 15 22 29        5 12 19 26        3 10 17 24 31
+Tu  2  9 16 23 30        6 13 20 27        4 11 18 25   
+We  3 10 17 24 31        7 14 21 28        5 12 19 26   
+Th  4 11 18 25        1  8 15 22 29        6 13 20 27   
+Fr  5 12 19 26        2  9 16 23 30        7 14 21 28   
+Sa  6 13 20 27        3 10 17 24        1  8 15 22 29   
+Su  7 14 21 28        4 11 18 25        2  9 16 23 30   
diff --git a/usr.bin/ncal/tests/regress.se-r-jd-170003-nhl.out b/usr.bin/ncal/tests/regress.se-r-jd-170003-nhl.out
new file mode 100644
index 000000000000..3989c4a49823
--- /dev/null
+++ b/usr.bin/ncal/tests/regress.se-r-jd-170003-nhl.out
@@ -0,0 +1,8 @@
+    March 1700              
+Mo      64  71  78  85    
+Tu      65  72  79  86    
+We      66  73  80  87    
+Th  60  67  74  81  88    
+Fr  61  68  75  82  89    
+Sa  62  69  76  83  90    
+Su  63  70  77  84        
diff --git a/usr.bin/ncal/tests/regress.se-r-jd-171202-nhl.out b/usr.bin/ncal/tests/regress.se-r-jd-171202-nhl.out
new file mode 100644
index 000000000000..1efdfa24c994
--- /dev/null
+++ b/usr.bin/ncal/tests/regress.se-r-jd-171202-nhl.out
@@ -0,0 +1,8 @@
+    February 1712           
+Mo      36  43  50  57    
+Tu      37  44  51  58    
+We      38  45  52  59    
+Th  32  39  46  53  60    
+Fr  33  40  47  54  61    
+Sa  34  41  48  55        
+Su  35  42  49  56