mirror of
https://github.com/quantum5/qcal.git
synced 2025-07-26 19:34:10 -04:00
Compare commits
6 commits
6bbd0d0ad9
...
1fd038e900
Author | SHA1 | Date | |
---|---|---|---|
|
1fd038e900 | ||
|
2e007500c9 | ||
|
697ffe9ccd | ||
|
0c5186e8bb | ||
|
8fddece880 | ||
|
d344b44433 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -6,7 +6,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [16.x, 18.x]
|
node-version: [16.x, 18.x]
|
||||||
directory: [common, frcal, jcal]
|
directory: [common, frcal, jcal, mcal]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
|
|
@ -74,7 +74,7 @@ export type Haab = {
|
||||||
day: HaabDay,
|
day: HaabDay,
|
||||||
};
|
};
|
||||||
|
|
||||||
const haabNames: Record<HaabMonth, string> = {
|
export const haabNames: Record<HaabMonth, string> = {
|
||||||
1: 'Pop',
|
1: 'Pop',
|
||||||
2: 'Woʼ',
|
2: 'Woʼ',
|
||||||
3: 'Sip',
|
3: 'Sip',
|
||||||
|
|
9
mcal/config-overrides.js
Normal file
9
mcal/config-overrides.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
const {aliasWebpack, aliasJest} = require('react-app-alias-ex');
|
||||||
|
|
||||||
|
const options = {};
|
||||||
|
module.exports = aliasWebpack(options);
|
||||||
|
module.exports.jest = function (config) {
|
||||||
|
const result = aliasJest(options)(config);
|
||||||
|
result.moduleDirectories.unshift(require('path').resolve(__dirname, '../node_modules'));
|
||||||
|
return result;
|
||||||
|
};
|
163
mcal/index.html
Normal file
163
mcal/index.html
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<meta name="theme-color" content="#ff0000"/>
|
||||||
|
<meta name="description" content="An interactive French Republican Calendar (a.k.a. French Revolutionary Calendar) that uses the original equinox method and never drifts out of sync with the seasons, along with revolutionary decimal time."/>
|
||||||
|
<meta property="og:title" content="French Republican Calendar"/>
|
||||||
|
<meta property="og:type" content="website"/>
|
||||||
|
<meta property="og:url" content="%PUBLIC_URL%"/>
|
||||||
|
<meta property="og:description" content="An interactive French Republican Calendar (a.k.a. French Revolutionary Calendar) that uses the original equinox method and never drifts out of sync with the seasons, along with revolutionary decimal time."/>
|
||||||
|
<meta property="og:image" content="%PUBLIC_URL%/logo512.png"/>
|
||||||
|
<meta property="og:image:type" content="image/png"/>
|
||||||
|
<meta property="og:image:width" content="512"/>
|
||||||
|
<meta property="og:image:height" content="512"/>
|
||||||
|
<meta property="og:image:alt" content="A calendar icon that displays the date 18 Brumaire."/>
|
||||||
|
<link rel="canonical" href="%PUBLIC_URL%/"/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png"/>
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
|
||||||
|
<title>French Republican Calendar (a.k.a. French Revolutionary Calendar)</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-md navbar-light">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="#">French Republican Calendar</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#nav-anchors"
|
||||||
|
aria-controls="nav-anchors" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="nav-anchors">
|
||||||
|
<ul class="nav navbar-nav mr-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#explanation">Explanation</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="https://jcal.qt.ax">Julian Calendar</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="https://gcal.qt.ax">Gregorian Calendar</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<div class="main">
|
||||||
|
<h2 id="explanation">Explanation</h2>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">What is this?</h4>
|
||||||
|
<p class="lead">The <a href="https://en.wikipedia.org/wiki/French_Republican_calendar">French Republican
|
||||||
|
calendar</a> was a calendar created and implemented during the French Revolution.</p>
|
||||||
|
<p>It is also frequently referred to as the <em>French Revolutionary Calendar</em>, but this is a misnomer:
|
||||||
|
year 1 of the calendar started on 22 September 1792, the day after the
|
||||||
|
<a href="https://en.wikipedia.org/wiki/Proclamation_of_the_abolition_of_the_monarchy">abolition of the
|
||||||
|
monarchy</a> and the founding of the <a href="https://en.wikipedia.org/wiki/French_First_Republic">French
|
||||||
|
First Republic</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">How does it work?</h4>
|
||||||
|
<p class="lead">A year consists of 12 months of 30 days each, divided into three <em>décades</em> of 10 days
|
||||||
|
each, followed by 5 complementary days (6 in leap years).</p>
|
||||||
|
<p>The year starts on the day of the autumnal equinox at the Paris Observatory (longitude 2°20′14.03″ E). A
|
||||||
|
leap year follow directly from this definition: a year is a leap year when the next autumnal equinox
|
||||||
|
happens 366 days later instead of the normal 365. By this definition, the year will <b>never</b> drift
|
||||||
|
with respect to the seasons.</p>
|
||||||
|
<p>The 12 months are: <em>Vendémiaire</em>, <em>Brumaire</em>, <em>Frimaire</em>, <em>Nivôse</em>, <em>Pluviôse</em>,
|
||||||
|
<em>Ventôse</em>, <em>Germinal</em>, <em>Floréal</em>, <em>Prairial</em>, <em>Messidor</em>, <em>Thermidor</em>,
|
||||||
|
<em>Fructidor.</em> Every three months represent a season, and the endings of the names reflect this
|
||||||
|
fact.</p>
|
||||||
|
<p>The complementary days are: <em>la Fête de la Vertu</em>, <em>la Fête du Génie</em>, <em>la Fête du
|
||||||
|
Travail</em>, <em>la Fête de l'Opinion</em>, <em>la Fête des Récompenses,</em> and <em>la Fête de la
|
||||||
|
Révolution</em> (leap years only).</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">What's so special about this version?</h4>
|
||||||
|
<p class="lead">Most versions of the calendar floating around doesn't use the original definition above.</p>
|
||||||
|
<p>Most versions uses the so-called <em>Romme</em> method for leap years, using the same leap year rules as
|
||||||
|
the Gregorian calendar, i.e. every year divisible by four, except century years not divisible by 400.
|
||||||
|
This method might make sense, except years 3, 7, and 11 were leap years under the original rules and
|
||||||
|
were observed as such in real life, but the <em>Romme</em> method instead makes years 4, 8, 12 leap
|
||||||
|
years instead.</p>
|
||||||
|
<p>This version uses the original rules. The <a href="https://ssd.jpl.nasa.gov/planets/eph_export.html">JPL's
|
||||||
|
DE440 and DE441 ephemerides</a> were used to calculate the exact timings of the autumnal equinoxes
|
||||||
|
between the Gregorian years 13201 BCE and 17191 CE (corresponding to the French Republican years -14991
|
||||||
|
to 15399). The times were then converted to UT1+00:09:21, the exact local time at the Paris Observatory.
|
||||||
|
UT1 was chosen to keep track of the Earth's rotation without having to worry about the issues posed by
|
||||||
|
leap seconds in UTC. Note that due to the uncertainty over
|
||||||
|
<a href="https://en.wikipedia.org/wiki/%CE%94T_(timekeeping)">ΔT</a> — the difference between UT1 and
|
||||||
|
Terrestrial Time (TT) used in the ephemerides — it is theoretically possible for there to be
|
||||||
|
inaccuracies when the equinox occurs very close to midnight.</p>
|
||||||
|
<p>For more details about how I calculated this calendar, please see
|
||||||
|
<a href="https://quantum5.ca/2022/03/09/art-of-time-keeping-part-4-french-republican-calendar/">my blog
|
||||||
|
post on the topic</a>. This is the fourth part of a series on time-keeping, and you are highly
|
||||||
|
encouraged to read the
|
||||||
|
<a href="https://quantum5.ca/2022/02/16/art-of-time-keeping-part-1-years-dates/">first</a>
|
||||||
|
<a href="https://quantum5.ca/2022/02/23/art-of-time-keeping-part-2-time/">three</a>
|
||||||
|
<a href="https://quantum5.ca/2022/03/02/art-of-time-keeping-part-3-astronomy-equinoxes/">parts</a>
|
||||||
|
for a more complete understanding.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">What are those names above the Gregorian date?</h4>
|
||||||
|
<p>Those are the names of the days in the
|
||||||
|
<a href="https://en.wikipedia.org/wiki/French_Republican_calendar#Rural_calendar">rural version of the
|
||||||
|
calendar</a>. This was intended to replace the Catholic Church's calendar of saints, as the French
|
||||||
|
Revolution wanted to reduce the influence of the church. Every day of the year has a unique name
|
||||||
|
associated with the rural economy and these names are supposed to correspond with the season.</p>
|
||||||
|
<p>Every <em>quintidi</em> is named after an animal, every <em>décadi</em> is named after an agricultural
|
||||||
|
tool, and the remaining days are named after various plants or produce. The only exception is the winter
|
||||||
|
month of Nivôse, which has the remaining days named after minerals.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">What are those numbers below the Gregorian date?</h4>
|
||||||
|
<p>The five (or more) numbers separated by dots is the corresponding
|
||||||
|
<a href="https://en.wikipedia.org/wiki/Mesoamerican_Long_Count_calendar">Mesoamerican Long Count
|
||||||
|
calendar</a> date. This is commonly known as the “Mayan calendar.” This calendar is not
|
||||||
|
available for dates before August 11, 3114 BCE (25 Thermidor -4905).</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">What is decimal time?</h4>
|
||||||
|
<p class="lead">Decimal time is a time system used during the French Revolution that divided the day into 10
|
||||||
|
hours, each with 100 minutes, which contained 100 seconds each.</p>
|
||||||
|
<p>The result is 100,000 seconds in one day, compared to the 86,400 seconds with the normal 24-hour
|
||||||
|
system. This makes it very easy to denote time as a decimal fraction of a day. For example, decimal time
|
||||||
|
5:67:72 (around 13:37:31) on January 1, 2000 can be represented as <code>2000-01-01.56772</code>.</p>
|
||||||
|
<p>Also note that each decimal hour is 2.4 normal hours, each decimal minute is 1.44 normal minutes, and
|
||||||
|
each decimal second is 0.864 normal seconds.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<p class="text-muted">Copyright © 2022-<%= new Date().getFullYear() %>
|
||||||
|
<a href="https://quantum5.ca">Quantum</a>.
|
||||||
|
Licensed under <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">GNU AGPLv3</a>.
|
||||||
|
Source code available on <a href="https://github.com/quantum5/qcal">GitHub</a>.<br>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
(function(r,e,p,u,b,l,i,c){r['GoogleAnalyticsObject']=b;r[b]=r[b]||function(){
|
||||||
|
(r[b].q=r[b].q||[]).push(arguments)},r[b].l=1*new Date();l=e.createElement(p),
|
||||||
|
i=e.getElementsByTagName(p)[0];l.async=1;l.src=u;i.parentNode.insertBefore(l,i)
|
||||||
|
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||||
|
|
||||||
|
ga('create', 'UA-102581070-4', 'auto');
|
||||||
|
ga('send', 'pageview');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
25
mcal/manifest.json
Normal file
25
mcal/manifest.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"short_name": "French Republican Calendar",
|
||||||
|
"name": "French Republican Calendar",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "128x128 64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#ff0000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
51
mcal/package.json
Normal file
51
mcal/package.json
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"name": "mcal",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@testing-library/jest-dom": "^5.16.2",
|
||||||
|
"@testing-library/react": "^12.1.2",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/jest": "^27.4.0",
|
||||||
|
"@types/node": "^16.11.24",
|
||||||
|
"@types/react": "^17.0.39",
|
||||||
|
"@types/react-dom": "^17.0.11",
|
||||||
|
"bootstrap": "~5.1.3",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"react-scripts": "5.0.0",
|
||||||
|
"sass": "^1.49.7",
|
||||||
|
"sass-loader": "^12.4.0",
|
||||||
|
"typescript": "^4.5.5",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-app-rewired start",
|
||||||
|
"build": "react-app-rewired build",
|
||||||
|
"test": "react-app-rewired test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bootstrap": "^5.1.9",
|
||||||
|
"react-app-alias-ex": "^2.1.0",
|
||||||
|
"react-app-rewired": "^2.2.1"
|
||||||
|
}
|
||||||
|
}
|
BIN
mcal/public/favicon.ico
Normal file
BIN
mcal/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
163
mcal/public/index.html
Normal file
163
mcal/public/index.html
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<meta name="theme-color" content="#ff0000"/>
|
||||||
|
<meta name="description" content="An interactive French Republican Calendar (a.k.a. French Revolutionary Calendar) that uses the original equinox method and never drifts out of sync with the seasons, along with revolutionary decimal time."/>
|
||||||
|
<meta property="og:title" content="French Republican Calendar"/>
|
||||||
|
<meta property="og:type" content="website"/>
|
||||||
|
<meta property="og:url" content="%PUBLIC_URL%"/>
|
||||||
|
<meta property="og:description" content="An interactive French Republican Calendar (a.k.a. French Revolutionary Calendar) that uses the original equinox method and never drifts out of sync with the seasons, along with revolutionary decimal time."/>
|
||||||
|
<meta property="og:image" content="%PUBLIC_URL%/logo512.png"/>
|
||||||
|
<meta property="og:image:type" content="image/png"/>
|
||||||
|
<meta property="og:image:width" content="512"/>
|
||||||
|
<meta property="og:image:height" content="512"/>
|
||||||
|
<meta property="og:image:alt" content="A calendar icon that displays the date 18 Brumaire."/>
|
||||||
|
<link rel="canonical" href="%PUBLIC_URL%/"/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png"/>
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
|
||||||
|
<title>French Republican Calendar (a.k.a. French Revolutionary Calendar)</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-expand-md navbar-light">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="#">French Republican Calendar</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#nav-anchors"
|
||||||
|
aria-controls="nav-anchors" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="nav-anchors">
|
||||||
|
<ul class="nav navbar-nav mr-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#explanation">Explanation</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="https://jcal.qt.ax">Julian Calendar</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="https://gcal.qt.ax">Gregorian Calendar</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<div class="main">
|
||||||
|
<h2 id="explanation">Explanation</h2>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">What is this?</h4>
|
||||||
|
<p class="lead">The <a href="https://en.wikipedia.org/wiki/French_Republican_calendar">French Republican
|
||||||
|
calendar</a> was a calendar created and implemented during the French Revolution.</p>
|
||||||
|
<p>It is also frequently referred to as the <em>French Revolutionary Calendar</em>, but this is a misnomer:
|
||||||
|
year 1 of the calendar started on 22 September 1792, the day after the
|
||||||
|
<a href="https://en.wikipedia.org/wiki/Proclamation_of_the_abolition_of_the_monarchy">abolition of the
|
||||||
|
monarchy</a> and the founding of the <a href="https://en.wikipedia.org/wiki/French_First_Republic">French
|
||||||
|
First Republic</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">How does it work?</h4>
|
||||||
|
<p class="lead">A year consists of 12 months of 30 days each, divided into three <em>décades</em> of 10 days
|
||||||
|
each, followed by 5 complementary days (6 in leap years).</p>
|
||||||
|
<p>The year starts on the day of the autumnal equinox at the Paris Observatory (longitude 2°20′14.03″ E). A
|
||||||
|
leap year follow directly from this definition: a year is a leap year when the next autumnal equinox
|
||||||
|
happens 366 days later instead of the normal 365. By this definition, the year will <b>never</b> drift
|
||||||
|
with respect to the seasons.</p>
|
||||||
|
<p>The 12 months are: <em>Vendémiaire</em>, <em>Brumaire</em>, <em>Frimaire</em>, <em>Nivôse</em>, <em>Pluviôse</em>,
|
||||||
|
<em>Ventôse</em>, <em>Germinal</em>, <em>Floréal</em>, <em>Prairial</em>, <em>Messidor</em>, <em>Thermidor</em>,
|
||||||
|
<em>Fructidor.</em> Every three months represent a season, and the endings of the names reflect this
|
||||||
|
fact.</p>
|
||||||
|
<p>The complementary days are: <em>la Fête de la Vertu</em>, <em>la Fête du Génie</em>, <em>la Fête du
|
||||||
|
Travail</em>, <em>la Fête de l'Opinion</em>, <em>la Fête des Récompenses,</em> and <em>la Fête de la
|
||||||
|
Révolution</em> (leap years only).</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">What's so special about this version?</h4>
|
||||||
|
<p class="lead">Most versions of the calendar floating around doesn't use the original definition above.</p>
|
||||||
|
<p>Most versions uses the so-called <em>Romme</em> method for leap years, using the same leap year rules as
|
||||||
|
the Gregorian calendar, i.e. every year divisible by four, except century years not divisible by 400.
|
||||||
|
This method might make sense, except years 3, 7, and 11 were leap years under the original rules and
|
||||||
|
were observed as such in real life, but the <em>Romme</em> method instead makes years 4, 8, 12 leap
|
||||||
|
years instead.</p>
|
||||||
|
<p>This version uses the original rules. The <a href="https://ssd.jpl.nasa.gov/planets/eph_export.html">JPL's
|
||||||
|
DE440 and DE441 ephemerides</a> were used to calculate the exact timings of the autumnal equinoxes
|
||||||
|
between the Gregorian years 13201 BCE and 17191 CE (corresponding to the French Republican years -14991
|
||||||
|
to 15399). The times were then converted to UT1+00:09:21, the exact local time at the Paris Observatory.
|
||||||
|
UT1 was chosen to keep track of the Earth's rotation without having to worry about the issues posed by
|
||||||
|
leap seconds in UTC. Note that due to the uncertainty over
|
||||||
|
<a href="https://en.wikipedia.org/wiki/%CE%94T_(timekeeping)">ΔT</a> — the difference between UT1 and
|
||||||
|
Terrestrial Time (TT) used in the ephemerides — it is theoretically possible for there to be
|
||||||
|
inaccuracies when the equinox occurs very close to midnight.</p>
|
||||||
|
<p>For more details about how I calculated this calendar, please see
|
||||||
|
<a href="https://quantum5.ca/2022/03/09/art-of-time-keeping-part-4-french-republican-calendar/">my blog
|
||||||
|
post on the topic</a>. This is the fourth part of a series on time-keeping, and you are highly
|
||||||
|
encouraged to read the
|
||||||
|
<a href="https://quantum5.ca/2022/02/16/art-of-time-keeping-part-1-years-dates/">first</a>
|
||||||
|
<a href="https://quantum5.ca/2022/02/23/art-of-time-keeping-part-2-time/">three</a>
|
||||||
|
<a href="https://quantum5.ca/2022/03/02/art-of-time-keeping-part-3-astronomy-equinoxes/">parts</a>
|
||||||
|
for a more complete understanding.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">What are those names above the Gregorian date?</h4>
|
||||||
|
<p>Those are the names of the days in the
|
||||||
|
<a href="https://en.wikipedia.org/wiki/French_Republican_calendar#Rural_calendar">rural version of the
|
||||||
|
calendar</a>. This was intended to replace the Catholic Church's calendar of saints, as the French
|
||||||
|
Revolution wanted to reduce the influence of the church. Every day of the year has a unique name
|
||||||
|
associated with the rural economy and these names are supposed to correspond with the season.</p>
|
||||||
|
<p>Every <em>quintidi</em> is named after an animal, every <em>décadi</em> is named after an agricultural
|
||||||
|
tool, and the remaining days are named after various plants or produce. The only exception is the winter
|
||||||
|
month of Nivôse, which has the remaining days named after minerals.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">What are those numbers below the Gregorian date?</h4>
|
||||||
|
<p>The five (or more) numbers separated by dots is the corresponding
|
||||||
|
<a href="https://en.wikipedia.org/wiki/Mesoamerican_Long_Count_calendar">Mesoamerican Long Count
|
||||||
|
calendar</a> date. This is commonly known as the “Mayan calendar.” This calendar is not
|
||||||
|
available for dates before August 11, 3114 BCE (25 Thermidor -4905).</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title">What is decimal time?</h4>
|
||||||
|
<p class="lead">Decimal time is a time system used during the French Revolution that divided the day into 10
|
||||||
|
hours, each with 100 minutes, which contained 100 seconds each.</p>
|
||||||
|
<p>The result is 100,000 seconds in one day, compared to the 86,400 seconds with the normal 24-hour
|
||||||
|
system. This makes it very easy to denote time as a decimal fraction of a day. For example, decimal time
|
||||||
|
5:67:72 (around 13:37:31) on January 1, 2000 can be represented as <code>2000-01-01.56772</code>.</p>
|
||||||
|
<p>Also note that each decimal hour is 2.4 normal hours, each decimal minute is 1.44 normal minutes, and
|
||||||
|
each decimal second is 0.864 normal seconds.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<p class="text-muted">Copyright © 2022-<%= new Date().getFullYear() %>
|
||||||
|
<a href="https://quantum5.ca">Quantum</a>.
|
||||||
|
Licensed under <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">GNU AGPLv3</a>.
|
||||||
|
Source code available on <a href="https://github.com/quantum5/qcal">GitHub</a>.<br>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
(function(r,e,p,u,b,l,i,c){r['GoogleAnalyticsObject']=b;r[b]=r[b]||function(){
|
||||||
|
(r[b].q=r[b].q||[]).push(arguments)},r[b].l=1*new Date();l=e.createElement(p),
|
||||||
|
i=e.getElementsByTagName(p)[0];l.async=1;l.src=u;i.parentNode.insertBefore(l,i)
|
||||||
|
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||||
|
|
||||||
|
ga('create', 'UA-102581070-4', 'auto');
|
||||||
|
ga('send', 'pageview');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
mcal/public/logo.svg
Normal file
1
mcal/public/logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.1 KiB |
BIN
mcal/public/logo192.png
Normal file
BIN
mcal/public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
mcal/public/logo512.png
Normal file
BIN
mcal/public/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
25
mcal/public/manifest.json
Normal file
25
mcal/public/manifest.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"short_name": "French Republican Calendar",
|
||||||
|
"name": "French Republican Calendar",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "128x128 64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#ff0000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
3
mcal/public/robots.txt
Normal file
3
mcal/public/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
3
mcal/robots.txt
Normal file
3
mcal/robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
42
mcal/src/App.tsx
Normal file
42
mcal/src/App.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Calendar} from './Calendar';
|
||||||
|
import {GregorianJumper} from '@common/dateJump';
|
||||||
|
import MonthBasedApp from '@common/ui/MonthBasedApp';
|
||||||
|
import {HaabMonth, HaabYear, jdnHaabExt} from '@common/mayan';
|
||||||
|
import {gregorianJDN} from '@common/gregorian';
|
||||||
|
|
||||||
|
// Not real limitations other than JS number precision.
|
||||||
|
const START_JDN = gregorianJDN(-10_000_000_000_000, 1, 1);
|
||||||
|
const END_JDN = gregorianJDN(10_000_000_000_000, 12, 31);
|
||||||
|
|
||||||
|
export default class App extends MonthBasedApp<HaabYear, HaabMonth> {
|
||||||
|
override parseYearMonth(year: string, month: string) {
|
||||||
|
if (+month < 1 || +month > 19)
|
||||||
|
return null;
|
||||||
|
return {year: +year, month: +month as HaabMonth};
|
||||||
|
}
|
||||||
|
|
||||||
|
override defaultSelector(todayJDN: number) {
|
||||||
|
const {year, month} = jdnHaabExt(todayJDN);
|
||||||
|
return {year, month};
|
||||||
|
}
|
||||||
|
|
||||||
|
goToJDN = (jdn: number) => {
|
||||||
|
const {year, month} = jdnHaabExt(jdn);
|
||||||
|
this.setState({selector: {year, month}});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {selector: {year, month}, todayJDN} = this.state;
|
||||||
|
return <>
|
||||||
|
<Calendar
|
||||||
|
year={year} month={month} todayJDN={todayJDN}
|
||||||
|
onSwitch={(year, month) => this.setState({selector: {year, month}})}/>
|
||||||
|
|
||||||
|
<div className="navigate">
|
||||||
|
<h4>Go to a date</h4>
|
||||||
|
<GregorianJumper minJDN={START_JDN} maxJDN={END_JDN} initialJDN={todayJDN} onJump={this.goToJDN}/>
|
||||||
|
</div>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
}
|
43
mcal/src/Calendar.scss
Normal file
43
mcal/src/Calendar.scss
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
@import 'bootstrap/scss/functions';
|
||||||
|
@import 'bootstrap/scss/variables';
|
||||||
|
@import 'bootstrap/scss/mixins';
|
||||||
|
@import 'bootstrap/scss/forms';
|
||||||
|
@import 'bootstrap/scss/grid';
|
||||||
|
@import 'bootstrap/scss/buttons';
|
||||||
|
@import '@common/ui/consts.scss';
|
||||||
|
@import '@common/ui/MonthBasedCalendar.scss';
|
||||||
|
|
||||||
|
.Day-haabMonth {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(xs) {
|
||||||
|
.DayOuter {
|
||||||
|
@include make-col($size: 1, $columns: 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
.DayOuter {
|
||||||
|
@include make-col($size: 1, $columns: 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
.DayOuter {
|
||||||
|
@include make-col($size: 1, $columns: 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(lg) {
|
||||||
|
.DayOuter {
|
||||||
|
@include make-col($size: 1, $columns: 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(xxl) {
|
||||||
|
.DayOuter {
|
||||||
|
@include make-col($size: 1, $columns: 8);
|
||||||
|
}
|
||||||
|
}
|
117
mcal/src/Calendar.tsx
Normal file
117
mcal/src/Calendar.tsx
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './Calendar.scss';
|
||||||
|
import {jdnDate} from '@common/gregorian';
|
||||||
|
import {jdnLongCount} from '@common/longCount';
|
||||||
|
import {MonthBasedCalendar} from '@common/ui/MonthBasedCalendar';
|
||||||
|
import {
|
||||||
|
formatLordOfNight, formatTzolkin,
|
||||||
|
HaabDay,
|
||||||
|
haabExtJDN,
|
||||||
|
HaabMonth,
|
||||||
|
haabMonthDays,
|
||||||
|
haabNames,
|
||||||
|
HaabYear,
|
||||||
|
jdnHaabExt,
|
||||||
|
jdnLordOfNight, jdnTzolkin,
|
||||||
|
} from '@common/mayan';
|
||||||
|
|
||||||
|
type MonthProps = {
|
||||||
|
year: HaabYear;
|
||||||
|
month: HaabMonth;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DateProps = MonthProps & {
|
||||||
|
day: HaabDay;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Day({year, month, day, todayJDN}: DateProps & { todayJDN: number }): JSX.Element {
|
||||||
|
const jdn = haabExtJDN({year, month, day});
|
||||||
|
return <div className={`Day ${jdn === todayJDN ? 'Day-today' : ''}`}>
|
||||||
|
<div className="Day-name">{day}<span className="Day-haabMonth"> {haabNames[month]}</span></div>
|
||||||
|
<div className="Day-tzolkin">{formatTzolkin(jdnTzolkin(jdn))}</div>
|
||||||
|
<div className="Day-lc">{jdnLongCount(jdn)?.join('.')}</div>
|
||||||
|
<div className="Day-lordOfNight">{formatLordOfNight(jdnLordOfNight(jdn))}</div>
|
||||||
|
<div className="Day-gregorian">{jdnDate(jdn).toDateString()}</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Month({year, month, todayJDN}: MonthProps & { todayJDN: number }): JSX.Element {
|
||||||
|
return <div className="Month">
|
||||||
|
<div className="Month-days">{
|
||||||
|
Array.from(Array(haabMonthDays(month)).keys()).map(i => <div key={i} className="DayOuter">
|
||||||
|
<Day year={year} month={month} day={i + 1 as HaabDay} todayJDN={todayJDN}/>
|
||||||
|
</div>)
|
||||||
|
}</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Calendar extends MonthBasedCalendar<HaabYear, HaabMonth> {
|
||||||
|
override parseYear(year: string): HaabYear {
|
||||||
|
return +year;
|
||||||
|
}
|
||||||
|
|
||||||
|
override parseMonth(month: string): HaabMonth {
|
||||||
|
return +month as HaabMonth;
|
||||||
|
}
|
||||||
|
|
||||||
|
override yearToString(year: HaabYear): string {
|
||||||
|
return year.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
override monthToString(month: HaabMonth): string {
|
||||||
|
return month.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private goToNormalized(year: number, month: number) {
|
||||||
|
while (month < 1) {
|
||||||
|
--year;
|
||||||
|
month += 19;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (month > 19) {
|
||||||
|
++year;
|
||||||
|
month -= 19;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.goTo(year, month as HaabMonth);
|
||||||
|
}
|
||||||
|
|
||||||
|
override prevYear = () => {
|
||||||
|
this.goToNormalized(this.props.year - 1, this.props.month);
|
||||||
|
};
|
||||||
|
|
||||||
|
override prevMonth = () => {
|
||||||
|
this.goToNormalized(this.props.year, this.props.month - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
override nextYear = () => {
|
||||||
|
this.goToNormalized(this.props.year + 1, this.props.month);
|
||||||
|
};
|
||||||
|
|
||||||
|
override nextMonth = () => {
|
||||||
|
this.goToNormalized(this.props.year, this.props.month + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
override isValidYear(year: string): boolean {
|
||||||
|
return /^-?\d+/.test(year);
|
||||||
|
}
|
||||||
|
|
||||||
|
override jdnLookup(jdn: number): { year: HaabYear; month: HaabMonth } {
|
||||||
|
return jdnHaabExt(jdn);
|
||||||
|
}
|
||||||
|
|
||||||
|
override monthName(year: HaabYear, month: HaabMonth): string {
|
||||||
|
return `${haabNames[month]} ${year}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
override renderMonthOptions(): JSX.Element[] {
|
||||||
|
return Array.from(Array(19).keys()).map(i => {
|
||||||
|
const month = i + 1 as HaabMonth;
|
||||||
|
return <option key={i} value={month}>{haabNames[month]}</option>;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override renderBody(): JSX.Element {
|
||||||
|
return <Month year={this.props.year} month={this.props.month} todayJDN={this.props.todayJDN}/>;
|
||||||
|
}
|
||||||
|
}
|
30
mcal/src/index.scss
Normal file
30
mcal/src/index.scss
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
@import '@common/ui/index.scss';
|
||||||
|
|
||||||
|
.download {
|
||||||
|
max-width: $calendar-width;
|
||||||
|
margin-top: $spacer;
|
||||||
|
@include make-container();
|
||||||
|
|
||||||
|
.input-group-text {
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
max-width: 5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-breakpoint-up(md) {
|
||||||
|
.download form {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
width: fit-content;
|
||||||
|
margin-right: $spacer / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-text {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
mcal/src/index.tsx
Normal file
21
mcal/src/index.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import 'bootstrap/js/dist/collapse';
|
||||||
|
import './index.scss';
|
||||||
|
import App from './App';
|
||||||
|
import reportWebVitals from '@common/ui/reportWebVitals';
|
||||||
|
import {MobileTooltipProvider} from '@common/ui/MobileTooltip';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<MobileTooltipProvider>
|
||||||
|
<App/>
|
||||||
|
</MobileTooltipProvider>
|
||||||
|
</React.StrictMode>,
|
||||||
|
document.getElementById('root'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
|
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||||
|
reportWebVitals();
|
1
mcal/src/react-app-env.d.ts
vendored
Normal file
1
mcal/src/react-app-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="react-scripts" />
|
5
mcal/src/setupTests.ts
Normal file
5
mcal/src/setupTests.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||||
|
// allows you to do things like:
|
||||||
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
|
import '@testing-library/jest-dom';
|
27
mcal/tsconfig.json
Normal file
27
mcal/tsconfig.json
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.paths.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
8
mcal/tsconfig.paths.json
Normal file
8
mcal/tsconfig.paths.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@common/*": ["../common/src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue