Skip to content

Commit bc96537

Browse files
committed
2 parents cb9e697 + 9a441c6 commit bc96537

37 files changed

Lines changed: 2565 additions & 2141 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ may easily be specified and used.
4444
|Customisable Gregorian calendar with switchover date|0|+|
4545
|French revolutionary calendar|0|+|
4646
|Milesian calendar|0|+|
47+
|Persian calendar (33 years cycle)|+|+|
4748

4849
Tentative new week numbering systems are also possible.
4950
### `timeUnits` object (time-units.js)
@@ -88,3 +89,4 @@ As defined in calendrical-demo.js, some calendars are instantiated from the clas
8889
* **julian**: the julian calendar. You can display date with ExtDateTimeFormat, using CLDR's names for days, months, eras.
8990
* **gregorian** : you instantiate the *GregorianCalendar* with a user-defined switching date to Gregorian reckoning. The *era* field is used to diffentiate "os" (Old Style, meaning Julian reckoning) from "ns" (New Style, Gregorian reckoning). pldr is required, in order to display these eras in a few languages.
9091
* **frenchRev**: the calendar defined by the French Convention in 1793. pldr is required.
92+
* **persian 33**: the official algorithmic calendar used in Iran and Afghanistan, same effect as Unicode's.

calendars.js

Lines changed: 132 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
* Passing non numeric values where numbers are expected will yield NaN results.
44
* Passing non integer values will yield erroneous results. Please control that figures are integer in your application.
55
* @module
6-
* @version M2022-11-10
6+
* @version M2024-06-31
77
* @requires module:time-units.js
88
* @requires module:chronos.js
99
* @requires module:extdate.js
1010
* @author Louis A. de Fouquières https://github.com/Louis-Aime
11-
* @license MIT 2016-2022
11+
* @license MIT 2016-2024
1212
// Character set is UTF-8
1313
*/
14-
/* Versions: M2022-11-10 Era codes in small letters, Gregorian and proleptic Gregorian calendar renamed.
14+
/* Versions: See Github
1515
*/
16-
/* Copyright Louis A. de Fouquières https://github.com/Louis-Aime 2016-2022
16+
/* Copyright Louis A. de Fouquières https://github.com/Louis-Aime 2016-2024
1717
Permission is hereby granted, free of charge, to any person obtaining
1818
a copy of this software and associated documentation files (the
1919
"Software"), to deal in the Software without restriction, including
@@ -92,13 +92,13 @@ export class MilesianCalendar {
9292
milesianWeek = new WeekClock (
9393
{
9494
originWeekday: 4, // Use day part of Posix timestamp, week of day of 1970-01-01 is Thursday
95-
daysInYear: (year) => (Cbcce.isGregorianLeapYear( year + 1 ) ? 366 : 365), // leap year rule for Milesian calendar
95+
daysInYear: (year) => (this.inLeapYear( { year : year } ) ? 366 : 365), // leap year rule for Milesian calendar
9696
characDayIndex: (year) => ( Math.floor(this.counterFromFields({year : year, month : 1, day : 7})/Milliseconds.DAY_UNIT) ),
97-
startOfWeek : 0, // week start with 0
97+
startOfWeek : 0, // week start with 0 (Sunday).
9898
characWeekNumber : 0, // we have a week 0 and the characteristic day for this week is 7 1m.
99-
dayBase : 0, // use 0..6 display for weekday
100-
weekBase : 0, // number of week begins with 0
101-
weekLength : 7 // the Milesian week is the 7-days well-known week
99+
dayBase : 0, // use 0..6 display for weekdays.
100+
weekBase : 0, // number of week begins with 0.
101+
weekLength : 7 // the Milesian week is the 7-days well-known week.
102102
}
103103
)
104104
/* Field control
@@ -113,7 +113,6 @@ export class MilesianCalendar {
113113
/* Basic conversion methods
114114
*/
115115
fieldsFromCounter (timeStamp) { // year, month, day, from Posix timestamp, UTC
116-
// let TZOffset = TZ == "UTC" ? 0 : new ExtDate("iso8601",timeStamp).getRealTZmsOffset(); // decide not to use TZ here
117116
let fields = this.milesianClockwork.getObject (timeStamp);
118117
fields.fullYear = fields.year;
119118
return fields
@@ -124,7 +123,6 @@ export class MilesianCalendar {
124123
return this.milesianClockwork.getNumber( myFields )
125124
}
126125
buildDateFromFields (fields, construct = ExtDate) { // Construct an ExtDate object from the date in this calendar (UTC)
127-
// let timeStamp = this.counterFromFields (fields, TZ);
128126
return new construct (this, this.counterFromFields(fields))
129127
}
130128
weekFieldsFromCounter (timeStamp) { // week coordinates : number of week, weekday, last/this/next year, weeks in weekyear
@@ -145,7 +143,7 @@ export class MilesianCalendar {
145143
*/
146144
eras = null // list of code values for eras. No era in Milesian calendar.
147145
inLeapYear (fields) { // is the Milesian year of this date a Milesian leap year.
148-
return Cbcce.isGregorianLeapYear ( fields.year + 1 )
146+
return Cbcce.isGregorianLeapYear ( this.solveAskedFields(fields).fullYear + 1 )
149147
}
150148
}
151149

@@ -169,10 +167,10 @@ export class ProlepticGregorianCalendar {
169167
gregorianWeek = new WeekClock (
170168
{
171169
originWeekday : 4, // 1 Jan. 1970 ISO is Thursday
172-
daysInYear : (year) => (Cbcce.isGregorianLeapYear ( year ) ? 366 : 365),
170+
daysInYear : (year) => (this.inLeapYear( { year : year } ) ? 366 : 365),
173171
characDayIndex: (year) => ( Math.floor(this.counterFromFields({fullYear : year, month : 1, day : 4})/Milliseconds.DAY_UNIT) ),
174-
startOfWeek : 1
175-
// the rest of by default
172+
startOfWeek : 1 // ISO week starts with Monday
173+
// the rest by default
176174
}
177175
) // set for gregorian week elements.
178176
solveAskedFields (askedFields) {
@@ -206,7 +204,7 @@ export class ProlepticGregorianCalendar {
206204
buildDateFromFields (fields, construct = ExtDate) {
207205
let myFields = { fullYear : 0, month : 1, day : 1, hours : 0, minutes : 0, seconds : 0, milliseconds : 0 };
208206
myFields = Object.assign (myFields, this.solveAskedFields(fields));
209-
return new construct (this, ExtDate.fullUTC(fields.fullYear, fields.month, fields.day, fields.hours, fields.minutes, fields.seconds, fields.milliseconds))
207+
return new construct (this, this.counterFromFields(fields))
210208
}
211209
weekFieldsFromCounter (timeStamp) {
212210
let myDate = new ExtDate ("iso8601", timeStamp),
@@ -222,7 +220,7 @@ export class ProlepticGregorianCalendar {
222220
+ myFields.seconds * Milliseconds.SECOND_UNIT + myFields.milliseconds;
223221
}
224222
inLeapYear (fields) {
225-
return Cbcce.isGregorianLeapYear ( fields.fullYear )
223+
return Cbcce.isGregorianLeapYear ( this.solveAskedFields(fields).fullYear )
226224
}
227225
}
228226

@@ -266,7 +264,7 @@ export class JulianCalendar {
266264
julianWeek = new WeekClock (
267265
{ // ISO 8601 rule applied to Julian calendar
268266
originWeekday: 4, // Use day part of Posix timestamp, week of day of ISO 1970-01-01 is Thursday
269-
daysInYear: (year) => (Cbcce.isJulianLeapYear( year ) ? 366 : 365), // leap year rule for this calendar
267+
daysInYear: (year) => (this.inLeapYear( { fullYear : year } ) ? 366 : 365), // leap year rule for this calendar
270268
characDayIndex: (year) => ( Math.floor(this.counterFromFields({fullYear : year, month : 1, day : 4})/Milliseconds.DAY_UNIT) ),
271269
startOfWeek : 1, // week start with 1 (Monday)
272270
characWeekNumber : 1, // we have a week 1 and the characteristic day for this week is 4 January.
@@ -332,8 +330,7 @@ export class JulianCalendar {
332330
return this.julianClockwork.getNumber(this.shiftYearStart(myFields,2,1));
333331
}
334332
buildDateFromFields (fields, construct = ExtDate) { // Construct an ExtDate object from the date in this calendar (deemed UTC)
335-
let timeStamp = this.setCounterFromFields (fields);
336-
return new construct (this, timeStamp)
333+
return new construct (this, this.counterFromFields(fields))
337334
}
338335
weekFieldsFromCounter (timeStamp) { // week fields, from a timestamp deemed UTC
339336
let year = this.fieldsFromCounter (timeStamp).fullYear,
@@ -350,7 +347,7 @@ export class JulianCalendar {
350347
/* properties and other methods
351348
*/
352349
inLeapYear (fields) { //
353-
return Cbcce.isJulianLeapYear(fields.fullYear)
350+
return Cbcce.isJulianLeapYear(this.solveAskedFields(fields).fullYear)
354351
}
355352
} // end of calendar class
356353

@@ -434,8 +431,7 @@ export class GregorianCalendar {
434431

435432
}
436433
buildDateFromFields (fields, construct = ExtDate) { // Construct an ExtDate object from the date in this calendar (deemed UTC)
437-
let number = this.setCounterFromFields (fields);
438-
return new construct (this, number)
434+
return new construct (this, this.counterFromFields(fields))
439435
}
440436
weekFieldsFromCounter (timeStamp) {
441437
if (timeStamp < this.switchingDate.valueOf())
@@ -565,13 +561,124 @@ export class FrenchRevCalendar {
565561
+ myFields.hours * Milliseconds.HOUR_UNIT + myFields.minutes * Milliseconds.MINUTE_UNIT
566562
+ myFields.seconds * Milliseconds.SECOND_UNIT + myFields.milliseconds;
567563
}
564+
buildDateFromFields (fields, construct = ExtDate) { // Construct an ExtDate object from the date in this calendar (UTC)
565+
return new construct (this, this.counterFromFields(fields))
566+
}
568567
inLeapYear (fields) {
569-
let myFields = {...fields};
570-
if (myFields.inSextileYear == undefined) myFields = this.frenchClockWork.getObject (this.frenchClockWork.getNumber (fields));
568+
let myFields = this.solveAskedFields(fields);
569+
if (myFields.inSextileYear == undefined) myFields = this.fieldsFromCounter (this.counterFromFields (myFields));
571570
return myFields.inSextileYear
572571
}
573572
valid (fields) { // enforced at date expressed by those fields
574573
let counter = this.counterFromFields (fields);
575574
return counter >= -5594227200000 && counter < -5175360000000
576575
}
577576
}
577+
578+
/** Generic algorithmic Persian calendar, as recommended by Mohammad Heydari-Malayeri and used by Unicode
579+
* The main point: the intercalation rule is based on a 33 years cycle where 8 years are long.
580+
* This implementation uses the integral postfix intercalation principle,
581+
* hence the technical epoch of the calendar is set to the beginning of year -10 A.P.
582+
* the computation of the day and month figures are done separately.
583+
* There is only one era, coded 'ap'.
584+
* Month names of CLDR are used.
585+
* @class
586+
* @param {string} id - the calendar identifier.
587+
*/
588+
export class Persian33Calendar {
589+
constructor (id) {
590+
this.id = id;
591+
}
592+
/* Basic references
593+
*/
594+
canvas = "persian"
595+
eras = ["ap"]
596+
stringFormat = "built-in" // This calendar is exactly the same as "persian" of Unicode.
597+
598+
persian33Clockwork = new Cbcce (
599+
{ //calendarRule object, used with Posix epoch
600+
timeepoch : -42879024000000, // Unix timestamp of 1 Farvardin -10 (1 4m 611) 00h00 UTC in ms
601+
coeff : [
602+
{cyclelength : 1041379200000, ceiling : Infinity, subCycleShift : 0, multiplier : 33, target : "year"}, // The main 33-years cycle.
603+
{cyclelength : 126230400000, ceiling : 7, subCycleShift : +1, multiplier : 4, target : "year"}, // The cycle. If last of upper cycle, add one year.
604+
{cyclelength : 31536000000, ceiling : 3, subCycleShift : 0, multiplier : 1, target : "year", notify : "inLongYear"}, // The year, grouped by 4 (3+1).
605+
{cyclelength : 16070400000, ceiling : Infinity, subCycleShift : 0, multiplier : 1, target : "semester"}, // First semester of 6 x 31 month.
606+
{cyclelength : 86400000, ceiling : Infinity, subCycleShift : 0, multiplier : 1, target : "semday"},
607+
{cyclelength : 3600000, ceiling : Infinity, subCycleShift : 0, multiplier : 1, target : "hours"},
608+
{cyclelength : 60000, ceiling : Infinity, subCycleShift : 0, multiplier : 1, target : "minutes"},
609+
{cyclelength : 1000, ceiling : Infinity, subCycleShift : 0, multiplier : 1, target : "seconds"},
610+
{cyclelength : 1, ceiling : Infinity, subCycleShift : 0, multiplier : 1, target : "milliseconds"}
611+
],
612+
canvas : [
613+
{name : "year", init : -10},
614+
{name : "semester", init : 0},
615+
{name : "semday", init : 0},
616+
{name : "hours", init : 0},
617+
{name : "minutes", init : 0},
618+
{name : "seconds", init : 0},
619+
{name : "milliseconds", init : 0},
620+
]
621+
}) // end of calendarRule
622+
persianWeek = new WeekClock (
623+
{
624+
originWeekday: 4, // Use day part of Posix timestamp, week of day of 1970-01-01 is Thursday
625+
daysInYear: (year) => (this.inLeapYear({ year : year, month : 1, day : 1, hours : 0, minutes : 0, seconds : 0, milliseconds : 0 }) ? 366 : 365),
626+
// leap year rule for this calendar
627+
characDayIndex: (year) => ( Math.floor(this.counterFromFields({year : year, month : 1, day : 4})/Milliseconds.DAY_UNIT) ),
628+
startOfWeek : 7 // week start with Sunday
629+
// The rest by default
630+
}
631+
)
632+
/* Field control
633+
*/
634+
solveAskedFields (askedFields) {
635+
var fields = {...askedFields};
636+
if (fields.year != undefined && fields.fullYear != undefined)
637+
{ if (fields.year != fields.fullYear) throw new TypeError ('Unconsistent year and fullYear fields: ' + fields.year + ', ' + fields.fullYear) }
638+
else { if (fields.year != undefined) { fields.fullYear = fields.year } else if (fields.fullYear != undefined) fields.year = fields.fullYear };
639+
if (fields.month != undefined) {
640+
fields.semester = Math.floor((fields.month - 1)/6);
641+
if (fields.day != undefined) fields.semday = (fields.month < 7 ? (fields.month-1) * 31 : (fields.month-7) * 30) + fields.day - 1;
642+
} else [fields.semester, fields.semday] = [undefined, undefined];
643+
return fields
644+
}
645+
/* Basic conversion methods
646+
*/
647+
fieldsFromCounter (timeStamp) { // year, month, day, from Posix timestamp, UTC
648+
let fields = this.persian33Clockwork.getObject (timeStamp);
649+
fields.fullYear = fields.year;
650+
// Computation of month and day in month
651+
[fields.month, fields.day] = Cbcce.divmod (fields.semday, 31 - fields.semester);
652+
[fields.month, fields.day] = [fields.month + 1 + 6*fields.semester, fields.day+1];
653+
return fields
654+
}
655+
counterFromFields (fields) { // Posix timestamp at UTC, from year, month, day and possibly time
656+
let myFields = { hours : 0, minutes : 0, seconds : 0, milliseconds : 0 };
657+
myFields = Object.assign (myFields, this.solveAskedFields(fields)); // Here semester and day in semester are computed
658+
return this.persian33Clockwork.getNumber( myFields )
659+
}
660+
buildDateFromFields (fields, construct = ExtDate) { // Construct an ExtDate object from the date in this calendar (UTC)
661+
return new construct (this, this.counterFromFields(fields))
662+
}
663+
weekFieldsFromCounter (timeStamp) { // week coordinates : number of week, weekday, last/this/next year, weeks in weekyear
664+
//let characDayFields = this.fieldsFromCounter (timeStamp); characDayFields.month = 1; characDayFields.day = 7;
665+
let fields = this.persian33Clockwork.getObject (timeStamp),
666+
myFigures = this.persianWeek.getWeekFigures(Math.floor(timeStamp/Milliseconds.DAY_UNIT), fields.year);
667+
return {weekYearOffset : myFigures[2], weekYear : fields.year + myFigures[2], weekNumber : myFigures[0], weekday : myFigures[1], weeksInYear : myFigures[3],
668+
hours : fields.hours, minutes : fields.minutes, seconds : fields.seconds, milliseconds : fields.milliseconds}
669+
}
670+
counterFromWeekFields (fields) { // Posix timestamp at UTC, from weekYear, weekNumber, dayOfWeek and time
671+
let myFields = { weekYear : 0, weekNumber : 0, weekday : 0, hours : 0, minutes : 0, seconds : 0, milliseconds : 0 };
672+
myFields = Object.assign (myFields, fields);
673+
return this.persianWeek.getNumberFromWeek (myFields.weekYear, myFields.weekNumber, myFields.weekday) * Milliseconds.DAY_UNIT
674+
+ myFields.hours * Milliseconds.HOUR_UNIT + myFields.minutes * Milliseconds.MINUTE_UNIT
675+
+ myFields.seconds * Milliseconds.SECOND_UNIT + myFields.milliseconds;
676+
}
677+
/* Simple properties and method as inspired by Temporal
678+
*/
679+
inLeapYear (fields) { // is this year a long year.
680+
let myFields = this.solveAskedFields(fields);
681+
if (myFields.inLongYear == undefined) myFields = this.fieldsFromCounter (this.counterFromFields (myFields));
682+
return myFields.inLongYear
683+
}
684+
}

0 commit comments

Comments
 (0)