Skip to content

Commit 41a1a0f

Browse files
stdlib: Add calendar LUT generation script
1 parent d8f7da0 commit 41a1a0f

File tree

2 files changed

+278
-1
lines changed

2 files changed

+278
-1
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ build/wenyan-linux
44
build/wenyan-macos
55
build/wenyan-win.exe
66
temp
7-
dist
7+
dist
8+
/tools/calendar.html

tools/make_calendar.js

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
// This script generates lookup tables for the calendar library.
2+
// Generated files:
3+
// lib/曆表.wy
4+
// The LUTs used by the standard calendar library.
5+
// tools/calendar.html
6+
// A table showing the first day of each month for checking.
7+
8+
try {
9+
process.chdir("./tools");
10+
} catch (e) { } //make sure we're in tools directory
11+
12+
// totalDay of 1st of month #n = ceil(n * 25101 / 850 - 1) + monthFix(n)
13+
// monthFix is tabulated below
14+
15+
// start index of the month start correction table
16+
// update this value if the table is extended further into the past
17+
const MONTH_FIX_START = 0;
18+
19+
// month start correction table
20+
// lists correction to the totalDay of 1st of each month
21+
const MONTH_FIX_TABLE = [];
22+
23+
24+
// totalMonth of leap month #n = ceil(n * 3157 / 94 - 27) + leapFix(n)
25+
// leapFix is tabulated below
26+
27+
// start index of the leap month correction table
28+
// update this value if the table is extended further into the past
29+
const LEAP_FIX_START = 0;
30+
31+
// leap month correction table
32+
// lists correction to the totalMonth of each leap month
33+
const LEAP_FIX_TABLE = [];
34+
35+
36+
const WENYAN_HEAD = `注曰「「此文程式之作文也。勿施以修訂。」」
37+
注曰「「程式書於文 tools/make_calendar.js 」」
38+
39+
`;
40+
41+
const getNumWord = function (n) {
42+
const NUM_CHARS = "〇一二三四五六七八九十";
43+
if (n < 0) {
44+
return "負" + getNumWord(-n);
45+
}
46+
let str = "";
47+
do {
48+
str = NUM_CHARS.charAt(n % 10) + str;
49+
n = Math.trunc(n / 10);
50+
} while (n > 0);
51+
return str;
52+
};
53+
54+
const generateWenyan = function () {
55+
const ELEMS_PER_LINE = 20;
56+
let wy = WENYAN_HEAD;
57+
wy += `今有一數。曰${getNumWord(MONTH_FIX_START)}。名之曰「始曆月」。\n`;
58+
wy += "今有一列。名之曰「曆月表」。\n";
59+
wy += `今有一數。曰${getNumWord(LEAP_FIX_START)}。名之曰「始閏月」。\n`;
60+
wy += "今有一列。名之曰「閏月表」。\n";
61+
wy += "\n充「曆月表」。\n";
62+
for (let i = 0; i < MONTH_FIX_TABLE.length; ++i) {
63+
if (i === 0 || (i + MONTH_FIX_START) % ELEMS_PER_LINE === 0) {
64+
wy += "\t";
65+
}
66+
wy += `以${getNumWord(MONTH_FIX_TABLE[i])}`;
67+
if (i + 1 === MONTH_FIX_TABLE.length || (i + 1 + MONTH_FIX_START) % ELEMS_PER_LINE === 0) {
68+
wy += "。\n";
69+
}
70+
}
71+
wy += "\n充「閏月表」。\n";
72+
for (let i = 0; i < LEAP_FIX_TABLE.length; ++i) {
73+
if (i === 0 || (i + LEAP_FIX_START) % ELEMS_PER_LINE === 0) {
74+
wy += "\t";
75+
}
76+
wy += `以${getNumWord(LEAP_FIX_TABLE[i])}`;
77+
if (i + 1 === LEAP_FIX_TABLE.length || (i + 1 + LEAP_FIX_START) % ELEMS_PER_LINE === 0) {
78+
wy += "。\n";
79+
}
80+
}
81+
return wy;
82+
};
83+
84+
85+
const HTML_HEAD = `<!-- DOCTYPE html -->
86+
<!-- Generated by make_calendar.js -->
87+
<html>
88+
<head>
89+
<meta charset="UTF-8">
90+
<title>Calendar</title>
91+
<style>
92+
table, th, td {
93+
border: solid 1px black;
94+
border-collapse: collapse;
95+
}
96+
th, td {
97+
padding: 0.25em;
98+
text-align: center;
99+
}
100+
th {
101+
background-color: #ccc;
102+
}
103+
.even {
104+
background-color: #eee;
105+
}
106+
.odd {
107+
background-color: #fff;
108+
}
109+
.leap {
110+
background-color: #bdf;
111+
}
112+
</style>
113+
</head>
114+
<body>
115+
<table>
116+
`;
117+
118+
const TABLE_HEAD = ` <tr>
119+
<th>年</th>
120+
<th>正月</th>
121+
<th>二月</th>
122+
<th>三月</th>
123+
<th>四月</th>
124+
<th>五月</th>
125+
<th>六月</th>
126+
<th>七月</th>
127+
<th>八月</th>
128+
<th>九月</th>
129+
<th>十月</th>
130+
<th>十一月</th>
131+
<th>十二月</th>
132+
</tr>
133+
`;
134+
135+
const HTML_TAIL = ` </table>
136+
</body>
137+
</html>
138+
`;
139+
140+
const CE_YEAR_OFFSET = 2697;
141+
142+
const getSexagenaryWord = function (n) {
143+
return "甲乙丙丁戊己庚辛壬癸".charAt((n - 1) % 10) + "子丑寅卯辰巳午未申酉戌亥".charAt((n - 1) % 12);
144+
};
145+
146+
const getMonthBeginDay = function (totalMonth) {
147+
const index = totalMonth - MONTH_FIX_START;
148+
if (index < 0) {
149+
return -Infinity;
150+
} else if (index >= MONTH_FIX_TABLE.length) {
151+
return Infinity;
152+
} else {
153+
return Math.ceil(totalMonth * 25101 / 850 - 1) + MONTH_FIX_TABLE[index];
154+
}
155+
};
156+
157+
const getLeapMonth = function (totalLeap) {
158+
const index = totalLeap - LEAP_FIX_START;
159+
if (index < 0) {
160+
return -Infinity;
161+
} else if (index >= LEAP_FIX_TABLE.length) {
162+
return Infinity;
163+
} else {
164+
return Math.ceil(totalLeap * 3157 / 94 - 27) + LEAP_FIX_TABLE[index];
165+
}
166+
};
167+
168+
const getYearMonth = function (totalMonth) {
169+
let totalLeap = Math.round((totalMonth + 27) * 94 / 3157);
170+
const leapMonth = getLeapMonth(totalLeap);
171+
if (totalMonth < leapMonth) {
172+
--totalLeap;
173+
}
174+
const year = Math.floor((totalMonth - totalLeap) / 12);
175+
const month = (totalMonth - totalLeap) % 12 + 1;
176+
const isLeapMonth = totalMonth === leapMonth;
177+
return { year: year, month: month, isLeapMonth: isLeapMonth };
178+
};
179+
180+
const getGregorianDay = function (totalDay) {
181+
const d = new Date((totalDay - 1704558) * 8.64e+7);
182+
return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1, day: d.getUTCDate() };
183+
};
184+
185+
const printYear = function (totalYear) {
186+
return `${totalYear - CE_YEAR_OFFSET}<br>${getSexagenaryWord(totalYear)}`;
187+
};
188+
189+
const printMonth = function (totalMonth) {
190+
const fillZero = function (n, digits) {
191+
const s = n.toString();
192+
if (s.length >= digits) {
193+
return s;
194+
} else {
195+
return new Array(digits - s.length).fill("0") + s;
196+
}
197+
};
198+
const d = getMonthBeginDay(totalMonth);
199+
const g = getGregorianDay(d);
200+
return `${fillZero(g.month, 2)}-${fillZero(g.day, 2)} ${getSexagenaryWord(d)}`;
201+
};
202+
203+
const getRowClass = function (totalYear) {
204+
return totalYear % 2 === 0 ? "odd" : "even";
205+
}
206+
207+
const generateCalendar = function () {
208+
let prevMonth = getYearMonth(MONTH_FIX_START - 1);
209+
let currMonth = getYearMonth(MONTH_FIX_START);
210+
let prevLeapTotalMonth = null;
211+
let html = HTML_HEAD;
212+
html += TABLE_HEAD;
213+
html += ` <tr class="${getRowClass(currMonth.year)}">\n <th rowspan="2">`;
214+
html += printYear(currMonth.year);
215+
html += '</th>\n';
216+
for (let i = 1; i < currMonth.month; ++i) {
217+
html += ' <td rowspan="2"></td>\n';
218+
}
219+
if (currMonth.isLeapMonth) {
220+
html += ' <td rowspan="1"></td>\n';
221+
}
222+
for (let i = 0; i < MONTH_FIX_TABLE.length; ++i) {
223+
const totalMonth = MONTH_FIX_START + i;
224+
const nextMonth = getYearMonth(totalMonth + 1);
225+
if (i !== 0 && currMonth.year !== prevMonth.year) {
226+
if (currMonth.year % 10 === 7) {
227+
html += TABLE_HEAD;
228+
}
229+
html += ` <tr class="${getRowClass(currMonth.year)}">\n <th rowspan="2">`;
230+
html += printYear(currMonth.year);
231+
html += '</th>\n';
232+
}
233+
if (currMonth.isLeapMonth) {
234+
prevLeapTotalMonth = totalMonth;
235+
} else {
236+
if (nextMonth.isLeapMonth) {
237+
html += ' <td rowspan="1">';
238+
} else {
239+
html += ' <td rowspan="2">';
240+
}
241+
html += printMonth(totalMonth);
242+
html += '</td>\n';
243+
}
244+
if (nextMonth.year !== currMonth.year) {
245+
if (prevLeapTotalMonth !== null) {
246+
html += ' </tr>\n <tr>\n <td rowspan="1" class="leap">';
247+
html += printMonth(prevLeapTotalMonth);
248+
html += '</td>\n </tr>\n';
249+
prevLeapTotalMonth = null;
250+
} else {
251+
html += ' </tr>\n <tr></tr>\n';
252+
}
253+
}
254+
prevMonth = currMonth;
255+
currMonth = nextMonth;
256+
}
257+
if (currMonth.year === prevMonth.year) {
258+
for (let i = prevMonth.month + 1; i <= 12; ++i) {
259+
html += ' <td rowspan="2"></td>\n';
260+
}
261+
if (prevLeapTotalMonth !== null) {
262+
html += ' </tr>\n <tr>\n <td rowspan="1" class="leap">';
263+
html += printMonth(prevLeapTotalMonth);
264+
html += '</td>\n </tr>\n';
265+
} else {
266+
html += ' </tr>\n <tr></tr>\n';
267+
}
268+
html += ' </tr>\n';
269+
}
270+
html += HTML_TAIL;
271+
return html;
272+
};
273+
274+
const fs = require("fs");
275+
fs.writeFileSync("../lib/曆表.wy", generateWenyan());
276+
fs.writeFileSync("./calendar.html", generateCalendar());

0 commit comments

Comments
 (0)