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
1717Permission is hereby granted, free of charge, to any person obtaining
1818a 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