From dc435b7870d784cd46753e5fcaae529a669f92f3 Mon Sep 17 00:00:00 2001
From: Quantum <quantum2048@gmail.com>
Date: Sat, 12 Feb 2022 16:58:32 -0500
Subject: [PATCH] Implement handling of complementary days

---
 src/Calendar.scss | 18 ++++++++++++++++++
 src/Calendar.tsx  | 28 ++++++++++++++++++++++++----
 src/dates.test.ts | 21 ++++++++++++++++++++-
 src/dates.ts      |  4 ++++
 4 files changed, 66 insertions(+), 5 deletions(-)

diff --git a/src/Calendar.scss b/src/Calendar.scss
index db70794..fd95545 100644
--- a/src/Calendar.scss
+++ b/src/Calendar.scss
@@ -64,6 +64,24 @@
     }
 }
 
+@include media-breakpoint-up(lg) {
+    .ComplementaryDays {
+        @include make-row();
+
+        > * {
+            @include make-col-ready();
+        }
+    }
+
+    .ComplementaryDay {
+        @include make-col($columns: 2);
+    }
+
+    .ComplementaryDays-splitter {
+        width: 100%;
+    }
+}
+
 .Day, .DecadeName {
     margin: 0.5em;
     @include make-col($columns: 10);
diff --git a/src/Calendar.tsx b/src/Calendar.tsx
index 539a636..3e20cf5 100644
--- a/src/Calendar.tsx
+++ b/src/Calendar.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import './Calendar.scss';
-import {Day, decadeNames, frJDN, jdnGregorian, jdnLongCount, Month, monthName} from './dates';
+import {dateName, Day, decadeNames, frIsLeap, frJDN, jdnGregorian, jdnLongCount, Month, monthName} from './dates';
 
 type MonthProps = {
     year: number;
@@ -53,9 +53,26 @@ function NormalMonth({year, month, todayJDN}: MonthProps & { todayJDN: number })
     </div>;
 }
 
+function ComplementaryDay({year, month, day, todayJDN}: DateProps & { todayJDN: number }): JSX.Element {
+    const jdn = frJDN(year, month, day);
+    return <div className={`Day ComplementaryDay ${jdn === todayJDN ? 'Day-today' : ''}`}>
+        <div className="Day-name">{dateName(month, day)}</div>
+        <DayDetail jdn={jdn}/>
+    </div>;
+}
+
+function ComplementaryDays({year, todayJDN}: {year: number, todayJDN: number}): JSX.Element {
+    return <div className="ComplementaryDays">{
+        Array.from(Array(frIsLeap(year) ? 6 : 5).keys()).map(i => <>
+            <ComplementaryDay year={year} month={13} day={i + 1 as Day} todayJDN={todayJDN}/>
+            {i % 2 === 1 && <div className="ComplementaryDays-splitter"/>}
+        </>)
+    }</div>;
+}
+
 export class Calendar extends React.Component<CalendarProps, CalendarState> {
     private goToNormalized(year: number, month: number) {
-        if (month < 0) {
+        if (month < 1) {
             --year;
             month += 13;
         }
@@ -95,7 +112,9 @@ export class Calendar extends React.Component<CalendarProps, CalendarState> {
                             onClick={this.prevMonth.bind(this)}>‹
                     </button>
                 </div>
-                <div className="Calendar-month-name">{monthName(this.props.month)} {this.props.year}</div>
+                <div className="Calendar-month-name">
+                    {this.props.month < 13 && monthName(this.props.month)} {this.props.year}
+                </div>
                 <div className="Calendar-next">
                     <button type="button" className="btn btn-secondary" title="Next month"
                             onClick={this.nextMonth.bind(this)}>›
@@ -105,7 +124,8 @@ export class Calendar extends React.Component<CalendarProps, CalendarState> {
                     </button>
                 </div>
             </div>
-            <NormalMonth year={this.props.year} month={this.props.month} todayJDN={this.props.todayJDN}/>
+            {this.props.month < 13 && <NormalMonth year={this.props.year} month={this.props.month} todayJDN={this.props.todayJDN}/>}
+            {this.props.month === 13 && <ComplementaryDays year={this.props.year} todayJDN={this.props.todayJDN}/>}
         </div>;
     }
 }
diff --git a/src/dates.test.ts b/src/dates.test.ts
index c517f70..b860b37 100644
--- a/src/dates.test.ts
+++ b/src/dates.test.ts
@@ -1,4 +1,4 @@
-import {dateName, frJDN, gregorianJDN, jdnFrench, jdnGregorian, jdnLongCount, monthName} from './dates';
+import {dateName, frIsLeap, frJDN, gregorianJDN, jdnFrench, jdnGregorian, jdnLongCount, monthName} from './dates';
 
 describe('gregorianJDN', () => {
     it('works', () => {
@@ -30,6 +30,25 @@ describe('frJDN', () => {
     });
 });
 
+describe('frIsLeap', () => {
+    it('works for sample dates', () => {
+        expect(frIsLeap(1)).toBeFalsy();
+        expect(frIsLeap(8)).toBeFalsy();
+        expect(frIsLeap(3)).toBeTruthy();
+        expect(frIsLeap(7)).toBeTruthy();
+        expect(frIsLeap(11)).toBeTruthy();
+    });
+
+    it('works in years starting/ending near midnight', () => {
+        expect(frIsLeap(110)).toBeTruthy();
+        expect(frIsLeap(205)).toBeTruthy();
+        expect(frIsLeap(2489)).toBeFalsy();
+        expect(frIsLeap(111)).toBeFalsy();
+        expect(frIsLeap(206)).toBeFalsy();
+        expect(frIsLeap(2490)).toBeTruthy();
+    });
+});
+
 describe('jdnFrench', () => {
     it('works for sample dates', () => {
         expect(jdnFrench(2375840)).toEqual({year: 1, month: 1, day: 1});
diff --git a/src/dates.ts b/src/dates.ts
index 260cbec..57ca880 100644
--- a/src/dates.ts
+++ b/src/dates.ts
@@ -87,6 +87,10 @@ export function frJDN(year: number, month: Month, day: Day): number {
     return startJD + 365 * dy + leaps[dy] + dd;
 }
 
+export function frIsLeap(year: number): boolean {
+    return !!data.leap[year - startYear];
+}
+
 export function jdnGregorian(jdn: number): Date {
     const e = 4 * (jdn + 1401 + Math.floor(Math.floor((4 * jdn + 274277) / 146097) * 3 / 4) - 38) + 3;
     const h = 5 * Math.floor((e % 1461 + 1461) % 1461 / 4) + 2;