Compare commits

...

30 commits

Author SHA1 Message Date
Quantum 7a3e62df17 common: add tests for jdnLordOfNight 2025-07-10 01:09:33 -04:00
Quantum fb576a4a5e common: implement jdnLordOfNight 2025-07-08 22:29:05 -04:00
Quantum d0ddad26c5 jcal: use jdnWeekDay instead of ad hoc logic 2025-07-03 16:31:42 -04:00
Quantum 4381415fa0 gcal: use jdnWeekDay instead of ad hoc logic 2025-07-02 01:19:37 -04:00
Quantum ebe627d5de common: create shared jdnWeekDay function 2025-07-02 01:19:02 -04:00
Quantum 7c302fa44e jcal: fix firstWeekday calculation 2025-07-01 03:51:07 -04:00
Quantum a8d230f478 common: define type for Mayan Lords of the Night 2025-06-30 01:32:20 -04:00
Quantum fc6bc47f16 common: fix formatHaab tests 2025-06-28 08:15:53 -04:00
Quantum df24ecd8ec gcal: add Haabʼ information 2025-06-28 07:34:21 -04:00
Quantum 6e6a921475 gcal: add Haabʼ display 2025-06-27 22:25:46 -04:00
Quantum 5848fbd3f1 common: add tests for jdnHaab 2025-06-26 00:53:46 -04:00
Quantum 5cb6c2797f common: test Tzolkʼin conversions for some historical dates 2025-06-25 20:36:28 -04:00
Quantum ded9fa4c64 common: use consistent indentation in mayan.test.ts 2025-06-25 20:35:23 -04:00
Quantum 60d0ce3549 common: implement jdnHaab 2025-06-24 23:12:11 -04:00
Quantum 8988b5aa8b common: convert mayan.ts to 4 space indent for consistency 2025-06-23 20:29:26 -04:00
Quantum e32b4bf774 common: implement formatHaab 2025-06-22 04:29:21 -04:00
Quantum eb4a7253f1 jcal: fix missing first week on negative JDN months 2025-06-20 19:43:06 -04:00
Quantum fcecc7ae1a gcal: document Tzolkʼin 2025-06-20 00:49:09 -04:00
Quantum 88bf611c90 gcal: fix missing first week on negative JDN months 2025-06-19 22:25:19 -04:00
Quantum 59fe324126 gcal: add Tzolkʼin to calendar 2025-06-19 20:15:32 -04:00
Quantum 36484c19ab common: add tests for jdnTzolkin 2025-06-19 01:09:37 -04:00
Quantum d0d2ebcac5 common: implement jdnTzolkin 2025-06-17 22:04:14 -04:00
Quantum 273e8b1d6a common: define types for Mayan sacred calendar (Tzolk'in) 2025-06-17 00:35:04 -04:00
Quantum 3c269ab7c6 *cal: remove 2022 year detection 2025-06-15 01:39:56 -04:00
Quantum a018c47408 gcal: add demo and flesh out documentation 2025-06-10 19:08:43 -04:00
Quantum 73b4a92282 qcal: update README and reference gcal 2025-05-30 21:15:23 -04:00
Quantum 4f8d4f6cea jcal: link website in README 2025-05-30 21:09:59 -04:00
Quantum 4d24f1188e gcal: fix default month selection 2025-05-17 22:25:16 -04:00
Quantum 1c6802ecf7 gcal: clean up imports 2025-05-01 03:18:17 -04:00
Quantum 8acb0b276a Update isogram in gcal analytics 2025-04-18 20:05:03 -04:00
13 changed files with 460 additions and 23 deletions

View file

@ -4,5 +4,7 @@ This is **@quantum5**'s collection of calendar webapps, intend to cover all
calendars from major cultures. calendars from major cultures.
Currently, the following calendars are supported: Currently, the following calendars are supported:
* [French Republican Calendar](frcal/README.md)
* [Julian Calendar](jcal/README.md) * [French Republican Calendar](frcal/README.md) ([live version](https://frcal.qt.ax))
* [Julian Calendar](jcal/README.md) ([live version](https://jcal.qt.ax))
* [Gregorian Calendar](gcal/README.md) ([live version](https://gcal.qt.ax))

View file

@ -49,6 +49,8 @@ const monthNames: { [key in JulianMonth]: string } = {
12: 'December', 12: 'December',
}; };
export type Weekday = 0 | 1 | 2 | 3 | 4 | 5 | 6;
export const weekdayNames = [ export const weekdayNames = [
'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday',
]; ];
@ -91,6 +93,10 @@ export function monthName(month: JulianMonth): string {
return monthNames[month]; return monthNames[month];
} }
export function jdnWeekDay(jdn: number): Weekday {
return (jdn % 7 + 8) % 7 as Weekday;
}
export function gregorianMonthDays(year: number, month: JulianMonth, julian = false): 28 | 29 | 30 | 31 { export function gregorianMonthDays(year: number, month: JulianMonth, julian = false): 28 | 29 | 30 | 31 {
switch (month) { switch (month) {
case 1: case 1:

234
common/src/mayan.test.ts Normal file
View file

@ -0,0 +1,234 @@
import {
formatHaab,
formatLordOfNight,
formatTzolkin,
jdnHaab,
jdnLordOfNight,
jdnTzolkin,
TzolkinName,
tzolkinName,
} from './mayan';
describe('tzolkinName', () => {
it('should return correct name for IMIX', () => {
expect(tzolkinName(TzolkinName.IMIX)).toBe('Imix');
});
it('should return correct name for IK', () => {
expect(tzolkinName(TzolkinName.IK)).toBe('Ikʼ');
});
it('should return correct name for AJAW', () => {
expect(tzolkinName(TzolkinName.AJAW)).toBe('Ajaw');
});
});
describe('formatTzolkin', () => {
it('should format tzolkin with number 1 and IMIX', () => {
expect(formatTzolkin({number: 1, name: TzolkinName.IMIX})).toBe('1 Imix');
});
it('should format tzolkin with number 13 and AJAW', () => {
expect(formatTzolkin({number: 13, name: TzolkinName.AJAW})).toBe('13 Ajaw');
});
it('should format tzolkin with number 7 and KABAN', () => {
expect(formatTzolkin({number: 7, name: TzolkinName.KABAN})).toBe('7 Kabʼan');
});
});
describe('jdnTzolkin', () => {
it('converts sample consecutive dates in June 2025 correctly', () => {
expect(jdnTzolkin(2460828)).toEqual({number: 12, name: TzolkinName.CHIKCHAN});
expect(jdnTzolkin(2460829)).toEqual({number: 13, name: TzolkinName.KIMI});
expect(jdnTzolkin(2460830)).toEqual({number: 1, name: TzolkinName.MANIK});
expect(jdnTzolkin(2460831)).toEqual({number: 2, name: TzolkinName.LAMAT});
expect(jdnTzolkin(2460832)).toEqual({number: 3, name: TzolkinName.MULUK});
expect(jdnTzolkin(2460833)).toEqual({number: 4, name: TzolkinName.OK});
expect(jdnTzolkin(2460834)).toEqual({number: 5, name: TzolkinName.CHUWEN});
expect(jdnTzolkin(2460835)).toEqual({number: 6, name: TzolkinName.EB});
expect(jdnTzolkin(2460836)).toEqual({number: 7, name: TzolkinName.BEN});
expect(jdnTzolkin(2460837)).toEqual({number: 8, name: TzolkinName.IX});
expect(jdnTzolkin(2460838)).toEqual({number: 9, name: TzolkinName.MEN});
expect(jdnTzolkin(2460839)).toEqual({number: 10, name: TzolkinName.KIB});
expect(jdnTzolkin(2460840)).toEqual({number: 11, name: TzolkinName.KABAN});
expect(jdnTzolkin(2460841)).toEqual({number: 12, name: TzolkinName.ETZNAB});
expect(jdnTzolkin(2460842)).toEqual({number: 13, name: TzolkinName.KAWAK});
expect(jdnTzolkin(2460843)).toEqual({number: 1, name: TzolkinName.AJAW});
expect(jdnTzolkin(2460844)).toEqual({number: 2, name: TzolkinName.IMIX});
expect(jdnTzolkin(2460845)).toEqual({number: 3, name: TzolkinName.IK});
expect(jdnTzolkin(2460846)).toEqual({number: 4, name: TzolkinName.AKBAL});
expect(jdnTzolkin(2460847)).toEqual({number: 5, name: TzolkinName.KAN});
expect(jdnTzolkin(2460848)).toEqual({number: 6, name: TzolkinName.CHIKCHAN});
expect(jdnTzolkin(2460849)).toEqual({number: 7, name: TzolkinName.KIMI});
expect(jdnTzolkin(2460850)).toEqual({number: 8, name: TzolkinName.MANIK});
expect(jdnTzolkin(2460851)).toEqual({number: 9, name: TzolkinName.LAMAT});
expect(jdnTzolkin(2460852)).toEqual({number: 10, name: TzolkinName.MULUK});
expect(jdnTzolkin(2460853)).toEqual({number: 11, name: TzolkinName.OK});
expect(jdnTzolkin(2460854)).toEqual({number: 12, name: TzolkinName.CHUWEN});
expect(jdnTzolkin(2460855)).toEqual({number: 13, name: TzolkinName.EB});
expect(jdnTzolkin(2460856)).toEqual({number: 1, name: TzolkinName.BEN});
expect(jdnTzolkin(2460857)).toEqual({number: 2, name: TzolkinName.IX});
});
it('converts sample dates from history correctly', () => {
expect(jdnTzolkin(584283)).toEqual({number: 4, name: TzolkinName.AJAW}); // Mayan creation
expect(jdnTzolkin(1705426)).toEqual({number: 1, name: TzolkinName.AKBAL}); // Ides of March
expect(jdnTzolkin(2266296)).toEqual({number: 12, name: TzolkinName.BEN}); // Columbus reaches the Americas
expect(jdnTzolkin(2430336)).toEqual({number: 5, name: TzolkinName.BEN}); // a date which will live in infamy
expect(jdnTzolkin(2440423)).toEqual({number: 4, name: TzolkinName.AJAW}); // Moon landing
expect(jdnTzolkin(2458920)).toEqual({number: 2, name: TzolkinName.KABAN}); // COVID-19 pandemic
});
it('handles negative JDN correctly', () => {
expect(jdnTzolkin(-1)).toEqual({number: 5, name: TzolkinName.KIB});
expect(jdnTzolkin(-10)).toEqual({number: 9, name: TzolkinName.MANIK});
expect(jdnTzolkin(-100)).toEqual({number: 10, name: TzolkinName.KABAN});
expect(jdnTzolkin(-1000)).toEqual({number: 7, name: TzolkinName.KABAN});
});
it('completes a full Tzolkin cycle across JD0', () => {
const results = new Set();
for (let i = -130; i < 130; i++) {
results.add(formatTzolkin(jdnTzolkin(i)));
}
// Should have 260 unique combinations
expect(results.size).toBe(260);
});
});
describe('formatHaab', () => {
it('formats normal month and day', () => {
expect(formatHaab({month: 1, day: 5})).toBe('5 Pop');
expect(formatHaab({month: 7, day: 12})).toBe('12 Yaxkʼin');
});
it('formats Wayeb month', () => {
expect(formatHaab({month: 19, day: 4})).toBe('4 Wayebʼ');
});
it('formats zero day', () => {
expect(formatHaab({month: 3, day: 0})).toBe('0 Sip');
});
});
describe('jdnHaab', () => {
it('converts sample consecutive dates in June 2025 correctly', () => {
expect(jdnHaab(2460828)).toEqual({month: 4, day: 3});
expect(jdnHaab(2460829)).toEqual({month: 4, day: 4});
expect(jdnHaab(2460830)).toEqual({month: 4, day: 5});
expect(jdnHaab(2460831)).toEqual({month: 4, day: 6});
expect(jdnHaab(2460832)).toEqual({month: 4, day: 7});
expect(jdnHaab(2460833)).toEqual({month: 4, day: 8});
expect(jdnHaab(2460834)).toEqual({month: 4, day: 9});
expect(jdnHaab(2460835)).toEqual({month: 4, day: 10});
expect(jdnHaab(2460836)).toEqual({month: 4, day: 11});
expect(jdnHaab(2460837)).toEqual({month: 4, day: 12});
expect(jdnHaab(2460838)).toEqual({month: 4, day: 13});
expect(jdnHaab(2460839)).toEqual({month: 4, day: 14});
expect(jdnHaab(2460840)).toEqual({month: 4, day: 15});
expect(jdnHaab(2460841)).toEqual({month: 4, day: 16});
expect(jdnHaab(2460842)).toEqual({month: 4, day: 17});
expect(jdnHaab(2460843)).toEqual({month: 4, day: 18});
expect(jdnHaab(2460844)).toEqual({month: 4, day: 19});
expect(jdnHaab(2460845)).toEqual({month: 5, day: 0});
expect(jdnHaab(2460846)).toEqual({month: 5, day: 1});
expect(jdnHaab(2460847)).toEqual({month: 5, day: 2});
expect(jdnHaab(2460848)).toEqual({month: 5, day: 3});
expect(jdnHaab(2460849)).toEqual({month: 5, day: 4});
expect(jdnHaab(2460850)).toEqual({month: 5, day: 5});
expect(jdnHaab(2460851)).toEqual({month: 5, day: 6});
expect(jdnHaab(2460852)).toEqual({month: 5, day: 7});
expect(jdnHaab(2460853)).toEqual({month: 5, day: 8});
expect(jdnHaab(2460854)).toEqual({month: 5, day: 9});
expect(jdnHaab(2460855)).toEqual({month: 5, day: 10});
expect(jdnHaab(2460856)).toEqual({month: 5, day: 11});
expect(jdnHaab(2460857)).toEqual({month: 5, day: 12});
});
it('converts sample dates from history correctly', () => {
expect(jdnHaab(584283)).toEqual({month: 18, day: 8}); // Mayan creation
expect(jdnHaab(1705426)).toEqual({month: 11, day: 11}); // Ides of March
expect(jdnHaab(2266296)).toEqual({month: 4, day: 16}); // Columbus reaches the Americas
expect(jdnHaab(2430336)).toEqual({month: 12, day: 11}); // a date which will live in infamy
expect(jdnHaab(2440423)).toEqual({month: 5, day: 18}); // Moon landing
expect(jdnHaab(2458920)).toEqual({month: 18, day: 5}); // COVID-19 pandemic
});
it('handles negative JDN correctly', () => {
expect(jdnHaab(-365)).toEqual({month: 4, day: 5});
expect(jdnHaab(-1)).toEqual({month: 4, day: 4});
});
});
describe('formatLordOfNight', () => {
it('should format Lord of Night 1', () => {
expect(formatLordOfNight(1)).toBe('G1');
});
it('should format Lord of Night 2', () => {
expect(formatLordOfNight(2)).toBe('G2');
});
it('should format Lord of Night 3', () => {
expect(formatLordOfNight(3)).toBe('G3');
});
it('should format Lord of Night 4', () => {
expect(formatLordOfNight(4)).toBe('G4');
});
it('should format Lord of Night 5', () => {
expect(formatLordOfNight(5)).toBe('G5');
});
it('should format Lord of Night 6', () => {
expect(formatLordOfNight(6)).toBe('G6');
});
it('should format Lord of Night 7', () => {
expect(formatLordOfNight(7)).toBe('G7');
});
it('should format Lord of Night 8', () => {
expect(formatLordOfNight(8)).toBe('G8');
});
it('should format Lord of Night 9', () => {
expect(formatLordOfNight(9)).toBe('G9');
});
});
describe('jdnLordOfNight', () => {
it('converts two consecutive cycles correctly', () => {
expect(jdnLordOfNight(2460828)).toBe(1);
expect(jdnLordOfNight(2460829)).toBe(2);
expect(jdnLordOfNight(2460830)).toBe(3);
expect(jdnLordOfNight(2460831)).toBe(4);
expect(jdnLordOfNight(2460832)).toBe(5);
expect(jdnLordOfNight(2460833)).toBe(6);
expect(jdnLordOfNight(2460834)).toBe(7);
expect(jdnLordOfNight(2460835)).toBe(8);
expect(jdnLordOfNight(2460836)).toBe(9);
expect(jdnLordOfNight(2460837)).toBe(1);
expect(jdnLordOfNight(2460838)).toBe(2);
expect(jdnLordOfNight(2460839)).toBe(3);
expect(jdnLordOfNight(2460840)).toBe(4);
expect(jdnLordOfNight(2460841)).toBe(5);
expect(jdnLordOfNight(2460842)).toBe(6);
expect(jdnLordOfNight(2460843)).toBe(7);
expect(jdnLordOfNight(2460844)).toBe(8);
expect(jdnLordOfNight(2460845)).toBe(9);
});
it('converts sample dates from history correctly', () => {
expect(jdnLordOfNight(584283)).toBe(1); // Mayan creation
expect(jdnLordOfNight(1705426)).toBe(5); // Ides of March
expect(jdnLordOfNight(2266296)).toBe(4); // Columbus reaches the Americas
expect(jdnLordOfNight(2430336)).toBe(1); // a date which will live in infamy
expect(jdnLordOfNight(2440423)).toBe(8); // Moon landing
expect(jdnLordOfNight(2458920)).toBe(1); // COVID-19 pandemic
});
});

119
common/src/mayan.ts Normal file
View file

@ -0,0 +1,119 @@
export enum TzolkinName {
IMIX,
IK,
AKBAL,
KAN,
CHIKCHAN,
KIMI,
MANIK,
LAMAT,
MULUK,
OK,
CHUWEN,
EB,
BEN,
IX,
MEN,
KIB,
KABAN,
ETZNAB,
KAWAK,
AJAW
}
export type TzolkinNumber = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;
export type Tzolkin = {
number: TzolkinNumber,
name: TzolkinName,
};
const tzolkinNames: Record<TzolkinName, string> = {
[TzolkinName.IMIX]: 'Imix',
[TzolkinName.IK]: 'Ikʼ',
[TzolkinName.AKBAL]: 'Akʼbʼal',
[TzolkinName.KAN]: 'Kʼan',
[TzolkinName.CHIKCHAN]: 'Chikchan',
[TzolkinName.KIMI]: 'Kimi',
[TzolkinName.MANIK]: 'Manikʼ',
[TzolkinName.LAMAT]: 'Lamat',
[TzolkinName.MULUK]: 'Muluk',
[TzolkinName.OK]: 'Ok',
[TzolkinName.CHUWEN]: 'Chuwen',
[TzolkinName.EB]: 'Ebʼ',
[TzolkinName.BEN]: 'Bʼen',
[TzolkinName.IX]: 'Ix',
[TzolkinName.MEN]: 'Men',
[TzolkinName.KIB]: 'Kibʼ',
[TzolkinName.KABAN]: 'Kabʼan',
[TzolkinName.ETZNAB]: 'Etzʼnabʼ',
[TzolkinName.KAWAK]: 'Kawak',
[TzolkinName.AJAW]: 'Ajaw',
};
export function tzolkinName(name: TzolkinName): string {
return tzolkinNames[name];
}
export function formatTzolkin(tzolkin: Tzolkin): string {
return `${tzolkin.number} ${tzolkinName(tzolkin.name)}`;
}
export function jdnTzolkin(jdn: number): Tzolkin {
return {
number: ((jdn % 13 + 18) % 13 + 1) as TzolkinNumber,
name: (jdn % 20 + 36) % 20,
};
}
export type HaabMonth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19;
export type HaabDay = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19;
export type Haab = {
month: HaabMonth,
day: HaabDay,
};
const haabNames: Record<HaabMonth, string> = {
1: 'Pop',
2: 'Woʼ',
3: 'Sip',
4: 'Sotzʼ',
5: 'Sek',
6: 'Xul',
7: 'Yaxkʼin',
8: 'Mol',
9: 'Chʼen',
10: 'Yax',
11: 'Sakʼ',
12: 'Keh',
13: 'Mak',
14: 'Kʼankʼin',
15: 'Muwan',
16: 'Pax',
17: 'Kʼayabʼ',
18: 'Kumkʼu',
19: 'Wayebʼ',
};
export function formatHaab({month, day}: Haab): string {
return `${day} ${haabNames[month]}`;
}
export function jdnHaab(jdn: number): Haab {
const yearDay = (jdn % 365 + 430) % 365;
return {
month: (Math.floor(yearDay / 20) + 1) as HaabMonth,
day: yearDay % 20 as HaabDay,
};
}
export type LordOfNight = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
export function formatLordOfNight(lordOfNight: LordOfNight): string {
return `G${lordOfNight}`;
}
export function jdnLordOfNight(jdn: number): LordOfNight {
return (jdn % 9 + 15) % 9 + 1 as LordOfNight;
}

View file

@ -143,7 +143,7 @@
</div> </div>
<footer class="footer"> <footer class="footer">
<div class="container"> <div class="container">
<p class="text-muted">Copyright &copy; 2022<%= new Date().getFullYear() > 2022 ? `${new Date().getFullYear()}` : '' %> <p class="text-muted">Copyright &copy; 2022-<%= new Date().getFullYear() %>
<a href="https://quantum5.ca">Quantum</a>. <a href="https://quantum5.ca">Quantum</a>.
Licensed under <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">GNU AGPLv3</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> Source code available on <a href="https://github.com/quantum5/qcal">GitHub</a>.<br>

View file

@ -1 +1,49 @@
# Gregorian Calendar # Gregorian Calendar
![demo](demo.png)
## What is this?
This is an implementation of the [Gregorian calendar][0], which is the default
civil calendar in most countries. It is meant to help you cross-reference dates
by providing the conversions various other calendars and time-keeping systems,
such as:
1. the [Julian day number][2] (JDN);
2. the [Julian calendar][1] date;
3. the [Mesoamerican Long Count calendar][3] date; and
4. the [French Republican calendar][4] date.
You can [see it live here][5]! More conversions will be added in the future.
## The Gregorian calendar
The Gregorian calendar itself is based on the Julian calendar, originally
introduced in 46 BC by Julius Caesar (with aid of Sosigenes of Alexandria).
Unfortunately, the Julian calendar had leap days every four years without
question, resulting in the average year having 365.25 days. However, the actual
tropical year is roughly 365.2422 days, resulting in it gaining a day every 129
years. This means that seasons drift, starting earlier and earlier in the year.
In 1582, Pope Gregory XIII decided to fix this drift by reducing the number
of leap days, motivated by a desire to keep the March equinox on March 21st,
since that value was hardcoded in the calculation for the date of Easter. To
achieve this, he made years divisible by 100 but not by 400 non-leap years,
resulting in 97 leap years every 400 years. To bring the equinox back into
alignment, October 5th to October 14th in 1582 were deleted, creating the
Gregorian calendar we use today.
This website extends the Gregorian calendar indefinitely into the past for
reference reasons.
[0]: https://en.wikipedia.org/wiki/Gregorian_calendar
[1]: https://en.wikipedia.org/wiki/Julian_calendar
[2]: https://en.wikipedia.org/wiki/Julian_day
[3]: https://en.wikipedia.org/wiki/Mesoamerican_Long_Count_calendar
[4]: https://en.wikipedia.org/wiki/French_Republican_calendar
[5]: https://gcal.qt.ax/

BIN
gcal/demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 KiB

View file

@ -60,6 +60,7 @@
<ul> <ul>
<li>the <a href="https://en.wikipedia.org/wiki/Julian_calendar">Julian calendar</a> date;</li> <li>the <a href="https://en.wikipedia.org/wiki/Julian_calendar">Julian calendar</a> date;</li>
<li>the <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day (JD) number</a>;</li> <li>the <a href="https://en.wikipedia.org/wiki/Julian_day">Julian day (JD) number</a>;</li>
<li>the Mayan <a href="https://en.wikipedia.org/wiki/Tzolk%CA%BCin">Tzolkʼin</a> day cycle;</li>
<li>the <a href="https://en.wikipedia.org/wiki/Mesoamerican_Long_Count_calendar">Mesoamerican Long Count <li>the <a href="https://en.wikipedia.org/wiki/Mesoamerican_Long_Count_calendar">Mesoamerican Long Count
calendar</a> date; and calendar</a> date; and
</li> </li>
@ -119,6 +120,20 @@
Universal Time noon on that date.</p> Universal Time noon on that date.</p>
</div> </div>
</div> </div>
<div class="card">
<div class="card-body">
<h4 class="card-title">What is the TZ (Mayan Tzolkʼin) date?</h4>
<p>This is the day name in the 260-day <a href="https://en.wikipedia.org/wiki/Tzolk%CA%BCin">Tzolkʼin</a>
cycle used in the Mayan calendar. This cycle repeats indefinitely with each cycle unnumbered.</p>
</div>
</div>
<div class="card">
<div class="card-body">
<h4 class="card-title">What is the HA (Mayan Haabʼ) date?</h4>
<p>This is the day name in the 365-day <a href="https://en.wikipedia.org/wiki/Haab%CA%BC">Haabʼ</a>
cycle used in the Mayan calendar. This cycle repeats indefinitely with each cycle unnumbered.</p>
</div>
</div>
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h4 class="card-title">What is the LC (Mesoamerican Long Count) date?</h4> <h4 class="card-title">What is the LC (Mesoamerican Long Count) date?</h4>
@ -145,8 +160,7 @@
</div> </div>
<footer class="footer"> <footer class="footer">
<div class="container"> <div class="container">
<p class="text-muted">Copyright &copy; 2022<%= new Date().getFullYear() > 2022 ? `${new Date().getFullYear()}` <p class="text-muted">Copyright &copy; 2022-<%= new Date().getFullYear() %>
: '' %>
<a href="https://quantum5.ca">Quantum</a>. <a href="https://quantum5.ca">Quantum</a>.
Licensed under <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">GNU AGPLv3</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> Source code available on <a href="https://github.com/quantum5/qcal">GitHub</a>.<br>
@ -154,9 +168,9 @@
</div> </div>
</footer> </footer>
<script> <script>
(function(j,u,l,i,a,n,c){j['GoogleAnalyticsObject']=a;j[a]=j[a]||function(){ (function(g,r,e,G,o,R,y){g['GoogleAnalyticsObject']=o;g[o]=g[o]||function(){
(j[a].q=j[a].q||[]).push(arguments)},j[a].l=1*new Date();n=u.createElement(l), (g[o].q=g[o].q||[]).push(arguments)},g[o].l=1*new Date();R=r.createElement(e),
c=u.getElementsByTagName(l)[0];n.async=1;n.src=i;c.parentNode.insertBefore(n,c) y=r.getElementsByTagName(e)[0];R.async=1;R.src=G;y.parentNode.insertBefore(R,y)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-102581070-4', 'auto'); ga('create', 'UA-102581070-4', 'auto');

View file

@ -2,7 +2,7 @@ import React from 'react';
import {Calendar} from './Calendar'; import {Calendar} from './Calendar';
import {JulianMonth} from '@common/gregorian'; import {JulianMonth} from '@common/gregorian';
import {DayChanger} from '@common/ui/DayChanger'; import {DayChanger} from '@common/ui/DayChanger';
import {jdnJulian} from '@common/julian'; import {jdnGregorian} from '@common/gregorian';
import MonthBasedApp from '@common/ui/MonthBasedApp'; import MonthBasedApp from '@common/ui/MonthBasedApp';
export default class App extends MonthBasedApp<number, JulianMonth> { export default class App extends MonthBasedApp<number, JulianMonth> {
@ -13,7 +13,7 @@ export default class App extends MonthBasedApp<number, JulianMonth> {
} }
override defaultSelector(todayJDN: number) { override defaultSelector(todayJDN: number) {
const [year, month] = jdnJulian(todayJDN); const [year, month] = jdnGregorian(todayJDN);
return {year, month}; return {year, month};
} }

View file

@ -8,10 +8,12 @@ import {
JulianDay, JulianDay,
JulianMonth, JulianMonth,
monthName, monthName,
jdnWeekDay,
weekdayNames, weekdayNames,
} from '@common/gregorian'; } from '@common/gregorian';
import {formatHaab, formatTzolkin, jdnHaab, jdnTzolkin} from '@common/mayan';
import {jdnLongCount} from '@common/longCount'; import {jdnLongCount} from '@common/longCount';
import {jdnJulian, julianJDN, julianMonthDays} from '@common/julian'; import {jdnJulian} from '@common/julian';
import {frDateFormat, frEndJD, frStartJD, jdnFrench} from '@common/french'; import {frDateFormat, frEndJD, frStartJD, jdnFrench} from '@common/french';
import {useMobileTooltipProps} from '@common/ui/MobileTooltip'; import {useMobileTooltipProps} from '@common/ui/MobileTooltip';
import {MonthBasedCalendar} from '@common/ui/MonthBasedCalendar'; import {MonthBasedCalendar} from '@common/ui/MonthBasedCalendar';
@ -40,6 +42,14 @@ function DayDetail({jdn}: { jdn: number }): JSX.Element {
<abbr title="Julian date" {...mobile}>J.</abbr>{' '} <abbr title="Julian date" {...mobile}>J.</abbr>{' '}
{formatJG(jdnJulian(jdn))} {formatJG(jdnJulian(jdn))}
</div> </div>
<div className="DayDetail-tz">
<abbr title="Tzolkʼin (Mayan)" {...mobile}>TZ</abbr>{' '}
{formatTzolkin(jdnTzolkin(jdn))}
</div>
<div className="DayDetail-haab">
<abbr title="Haabʼ (Mayan)" {...mobile}>HA</abbr>{' '}
{formatHaab(jdnHaab(jdn))}
</div>
{lc && <div className="DayDetail-lc"> {lc && <div className="DayDetail-lc">
<abbr title="Mesoamerican long count date" {...mobile}>LC</abbr>{' '} <abbr title="Mesoamerican long count date" {...mobile}>LC</abbr>{' '}
{lc.join('.\u200b')} {lc.join('.\u200b')}
@ -55,7 +65,7 @@ function Day({year, month, day, todayJDN}: DateProps & { todayJDN: number }): JS
const jdn = gregorianJDN(year, month, day); const jdn = gregorianJDN(year, month, day);
return <div className={`Day NormalDay ${jdn === todayJDN ? 'Day-today' : ''}`}> return <div className={`Day NormalDay ${jdn === todayJDN ? 'Day-today' : ''}`}>
<div className="Day-name">{day}</div> <div className="Day-name">{day}</div>
<div className="Day-weekday">{weekdayNames[(jdn + 1) % 7]}</div> <div className="Day-weekday">{weekdayNames[jdnWeekDay(jdn)]}</div>
<DayDetail jdn={jdn}/> <DayDetail jdn={jdn}/>
</div>; </div>;
} }
@ -63,7 +73,7 @@ function Day({year, month, day, todayJDN}: DateProps & { todayJDN: number }): JS
function Month({year, month, todayJDN}: MonthProps & { todayJDN: number }): JSX.Element { function Month({year, month, todayJDN}: MonthProps & { todayJDN: number }): JSX.Element {
const decadeHeads = weekdayNames.map((name, i) => <WeekdayName key={i} name={name}/>); const decadeHeads = weekdayNames.map((name, i) => <WeekdayName key={i} name={name}/>);
const firstJDN = gregorianJDN(year, month, 1); const firstJDN = gregorianJDN(year, month, 1);
const firstWeekday = (firstJDN + 1) % 7; const firstWeekday = jdnWeekDay(firstJDN);
const daysTotal = gregorianMonthDays(year, month); const daysTotal = gregorianMonthDays(year, month);
return <div className="Month"> return <div className="Month">
<div className="Month-weekdayHead">{decadeHeads}</div> <div className="Month-weekdayHead">{decadeHeads}</div>

View file

@ -2,10 +2,12 @@
![demo](demo.png) ![demo](demo.png)
[See it live!][0]
## What is this? ## What is this?
The [Julian calendar][0] is a calendar introduced by Julius Caesar in 46 BC. A The [Julian calendar][1] is a calendar introduced by Julius Caesar in 46 BC. A
reformed version, the [Gregorian calendar][1], forms the basis of civil calendar reformed version, the [Gregorian calendar][2], forms the basis of civil calendar
in most countries. in most countries.
Julius Caesar (with aid of Sosigenes of Alexandria) introduced this calendar to Julius Caesar (with aid of Sosigenes of Alexandria) introduced this calendar to
@ -28,5 +30,8 @@ This website extends the Julian calendar indefinitely into the future for
reference reasons. Note that in the 20th and 21st centuries, the Julian reference reasons. Note that in the 20th and 21st centuries, the Julian
calendar is 13 days behind the Gregorian calendar. calendar is 13 days behind the Gregorian calendar.
[0]: https://en.wikipedia.org/wiki/Julian_calendar [0]: https://jcal.qt.ax/
[1]: https://en.wikipedia.org/wiki/Gregorian_calendar
[1]: https://en.wikipedia.org/wiki/Julian_calendar
[2]: https://en.wikipedia.org/wiki/Gregorian_calendar

View file

@ -120,8 +120,7 @@
</div> </div>
<footer class="footer"> <footer class="footer">
<div class="container"> <div class="container">
<p class="text-muted">Copyright &copy; 2022<%= new Date().getFullYear() > 2022 ? `${new Date().getFullYear()}` <p class="text-muted">Copyright &copy; 2022-<%= new Date().getFullYear() %>
: '' %>
<a href="https://quantum5.ca">Quantum</a>. <a href="https://quantum5.ca">Quantum</a>.
Licensed under <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">GNU AGPLv3</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> Source code available on <a href="https://github.com/quantum5/qcal">GitHub</a>.<br>

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import './Calendar.scss'; import './Calendar.scss';
import {formatJG, jdnGregorian, JulianDay, JulianMonth, monthName, weekdayNames} from '@common/gregorian'; import {formatJG, jdnGregorian, JulianDay, JulianMonth, monthName, jdnWeekDay, weekdayNames} from '@common/gregorian';
import {jdnLongCount} from '@common/longCount'; import {jdnLongCount} from '@common/longCount';
import {jdnJulian, julianJDN, julianMonthDays} from '@common/julian'; import {jdnJulian, julianJDN, julianMonthDays} from '@common/julian';
import {frDateFormat, frEndJD, frStartJD, jdnFrench} from '@common/french'; import {frDateFormat, frEndJD, frStartJD, jdnFrench} from '@common/french';
@ -46,7 +46,7 @@ function Day({year, month, day, todayJDN}: DateProps & { todayJDN: number }): JS
const jdn = julianJDN(year, month, day); const jdn = julianJDN(year, month, day);
return <div className={`Day NormalDay ${jdn === todayJDN ? 'Day-today' : ''}`}> return <div className={`Day NormalDay ${jdn === todayJDN ? 'Day-today' : ''}`}>
<div className="Day-name">{day}</div> <div className="Day-name">{day}</div>
<div className="Day-weekday">{weekdayNames[(jdn + 1) % 7]}</div> <div className="Day-weekday">{weekdayNames[jdnWeekDay(jdn)]}</div>
<DayDetail jdn={jdn}/> <DayDetail jdn={jdn}/>
</div>; </div>;
} }
@ -54,7 +54,7 @@ function Day({year, month, day, todayJDN}: DateProps & { todayJDN: number }): JS
function Month({year, month, todayJDN}: MonthProps & { todayJDN: number }): JSX.Element { function Month({year, month, todayJDN}: MonthProps & { todayJDN: number }): JSX.Element {
const decadeHeads = weekdayNames.map((name, i) => <WeekdayName key={i} name={name}/>); const decadeHeads = weekdayNames.map((name, i) => <WeekdayName key={i} name={name}/>);
const firstJDN = julianJDN(year, month, 1); const firstJDN = julianJDN(year, month, 1);
const firstWeekday = (firstJDN + 1) % 7; const firstWeekday = jdnWeekDay(firstJDN);
const daysTotal = julianMonthDays(year, month); const daysTotal = julianMonthDays(year, month);
return <div className="Month"> return <div className="Month">
<div className="Month-weekdayHead">{decadeHeads}</div> <div className="Month-weekdayHead">{decadeHeads}</div>