Source: chronos.js

/** Basic functions for calendrical computations. 
	All parameters should be integer numbers. In order to increase efficiency, almost no check is done. 
	Any NaN parameter will yield NaN values.
	Non-integer parameter will yield erroneous non-integer values.
	Default parameters assume that computations are done using 1 for the first month of any calendar.
 * @module chronos
 * @version M2024-07-02
 * @author Louis A. de Fouquières https://github.com/Louis-Aime
 * @license MIT 2016-2024
 */
// Character set is UTF-8
/* Versions: see GitHub
*/
/* Copyright Louis A. de Fouquières https://github.com/Louis-Aime 2016-2024
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
	1. The above copyright notice and this permission notice shall be included
	in all copies or substantial portions of the Software.
	2. Changes with respect to any former version shall be documented.

The software is provided "as is", without warranty of any kind,
express of implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose and noninfringement.
In no event shall the authors of copyright holders be liable for any
claim, damages or other liability, whether in an action of contract,
tort or otherwise, arising from, out of or in connection with the software
or the use or other dealings in the software.
*/
"use strict";
/** Structure of the calendar rule parameter that describes a calendar's computation rules 
 * and that is passed to the Cycle Bases Calendar Computation Engine (Cbcce), a class constructor.
 * The calendar structure is made up of nested cycles. Each cycle finishes with an intercalary or missing element. 
 * Computations are made on an intermediary calendar which is derived from a real-life calendar.
 * E.g. for the Julian-Gregorian calendar, the derived calendar's year finishes in February, the intercalary day (29 February) is at the very end,
 * and the olympiade (4-years cycle), finishes with the leap year.
 * Non checked constraints: 
 *	1. 	The cycles and the canvas elements shall be defined from the largest to the smallest
 *		e.g. four-centuries cycle, then century, then four-year cycle, then year, etc.
 *	2. 	The same names shall be used for the "coeff" and the "canvas" properties, otherwise functions shall give erroneous results.
 * @typedef {Object} calendarRule
 * @property {Number} timeepoch - origin date or timestamp in elementary units (days, milliseconds...) to be used for the decomposition, with respect to instant 0 of used timestamp.
 * @property {Array} coeff - Array of coefficients used to decompose a timestamp into time cycles like eras, quadrisaeculae, centuries, ... down to the elementary unit.
 * @property {Number} coeff[].cyclelength 	- length of the cycle, in elementary units.
 * @property {Number} coeff[].ceiling 		- Infinity, or the maximum number of cycles of this size minus one in the upper cycle; 
	* the last cycle may hold an intercalation remainder up to the next level,
	* example: this level is year of 365 days, upper level is 1461 days i.e. the last year holds more than 365 days.
 * @property {Number} coeff[].subCycleShift - number (-1, 0 or +1) to add to the ceiling of the cycle of the next level when the ceiling is reached at this level;
	* to be used for common/embolismic years in a Meton cycle, or for 128-years cycles of 4 or 5 years elementary cycles.
 * @property {Number} coeff[].multiplier	- multiplies the number of cycles of this level to convert into target units.
 * @property {String} coeff[].target 		- the unit (e.g. "year") of the decomposition element at this level. 
 * @property {String} coeff[].notify 		- optional, the boolean field (e.g. "leapyear") where to indicate that the element's length is "singular" (i.e. not "common"). 
 * @property {Array} canvas - Canvas of the decomposition , e.g. "year", "month", "day", with suitable properties at each level.
 * @property {String} canvas[].name 	- the name of the property at this level, which must match one target property of the coeff component,
 * @property {Number} canvas[].init 	- value of this component at epoch, which is the lowest value (except for the first component), 
	 * e.g. 0 or 1 for month, 1 for date, 0 for hours, minutes, seconds.
*/
/** The Cycle Based Calendar Computation Engine (Cbcce) class.
 * @see module:chronos for the definition of the calendarRule structure.
 * @class
 * @param {calendarRule} calendarRule - The object that describes the rules of the calendar to be implemented.
 */
export class Cbcce 	{
	constructor (calendarRule) {
		this.calendarRule = calendarRule;
	}
	/** The Modulo function for calendrical computations.
	 * @static
	 * @param {number} a - dividend, may be positive, null or negative.
	 * @param {number} d - divisor, shall be non-zero.
	 * @return {number} modulo of a divided by d, with 0 <= modulo < d or d < modulo <= 0; e.g. -2 mod 3 is +1, not -2.
	 */
	static mod (a, d) {		
		return ( a*Math.sign(d) >= 0 ? a % d : (a % d + +d) % d)
	}
	/** Division with modulo for calendrical computations
	 * @static 
	 * @param {number} a - dividend; integer recommended.
	 * @param {number} d - divisor; integer recommended.
	 * @return {number[]} [quotient, modulo] with 0 <= modulo < d or d < modulo <= 0.
	*/
	static divmod (a, d) {	
		if (d >=0) {
			let quotient = Math.floor (a/d);
			return [ quotient, a - d * quotient ]
		}
		else if (d < 0) { return Cbcce.divmod (-a, -d).map (x => -x) }
		else { return [NaN, NaN] }
	}
	/** Cycle start shifting, keeping phase measure. Example : (20, 1) shifted by 2 in a 12-cycle with base 1 yields (19, 13), but (20, 6) yields (20, 6)
		This operation is used for calendrical computations on Julian-Gregorian calendars (shift year start to March), but also for computations on weeks.
	 * @static 
	 * @param {number} cycle	- the rank of cycle, which is increased by 1 each time 'phase' reaches (mod (cycleBase, period)).
	 * @param {number} phase	- indicates the phase within the cycle, e.g. for the month or the day of week. (phase == cycleBase) means the start of a new cycle.
	 * @param {number} period	- the cycle's period, typically 12 or 7 for calendrical computations, but may also be the moon's month mean duration in milliseconds.
	 * @param {number} shift	- the number of units for shifting. After shifting, cycleBase is cycleBase + shift.
	 * @param {number} cycleBase	- which phase is that of a new cycle, in the parameter [cycle, phase] representation. 0 by default (like month representation with Date objects).
	 * @return {number[]} [returnCycle, returnPhase] with: (returnCycle * period + returnPhase == cycle * period + phase) && (shift + cycleBase) <= returnPhase < (shift + cycleBase)+period.
	*/
	static shiftCycle (cycle, phase, period, shift, cycleBase=0) {
		// if (Array.from(arguments).some(isNaN)) throw new TypeError ("Non numeric value among arguments: " + Array.from(arguments).toString()); 
		if (phase < cycleBase || phase >= cycleBase + period) throw new RangeError 
			("Phase out of range: " + cycleBase + " <= " + phase + " < " + (cycleBase + period)); //Cbcce.cycleShifting;
		return Cbcce.divmod (cycle * period + phase - cycleBase - shift, period).map
				((value, index) => (index == 1 ? value + cycleBase + shift : value) )
	}
	/** Whether a year is a leap year in the Julian calendar, with the year origin Anno Domini as defined by Dionysius Exiguus in the 6th century. 
	 * @static
	 * @param {number} a signed integer number representing the year. 0 means 1 B.C. and so on. Leap years, either positive or negative, are divisible by 4.
	 * @return {boolean} true if year is a leap year i.e. there is a 29 February in this year.
	 */
	static isJulianLeapYear (year) {
		return Cbcce.mod (year, 4) == 0
	}
	/** Whether a year is a leap year in the Gregorian calendar, with the year origin Anno Domini as defined by Dionysius Exiguus in the 6th century. 
	 * @static
	 * @param {number} a signed integer number representing the year. 0 means 1 B.C. Leap years, are either not divisible by 100 but by 4, or divisible by 400.
	 * @return {boolean} true if year is a leap year i.e. there is a 29 February in this year.
	 */
	static isGregorianLeapYear (year) {
		return Cbcce.mod (year, 4) == 0 && (Cbcce.mod(year, 100) != 0 || Cbcce.mod(year, 400) ==0)
	}
	/** Build a compound object from a timestamp holding the elements as required by a given cycle hierarchy model.
	 * @param {number} askedNumber	- a timestamp representing the date to convert.
	 * @returns {Object} the calendar elements in the structure that calendarRule prescribes.
	*/
	getObject (askedNumber) {
	  let quantity = askedNumber - this.calendarRule.timeepoch; // set at initial value the quantity to decompose into cycles.
	  var result = new Object(); // Construct initial compound result 
	  for (let i = 0; i < this.calendarRule.canvas.length; i++) 	// Define property of result object (a date or date-time)
		Object.defineProperty (result, this.calendarRule.canvas[i].name, {enumerable : true, writable : true, value : this.calendarRule.canvas[i].init}); 
	  let addCycle = 0; 	// flag that upper cycle has one element more or less (i.e. a 5 years franciade or 13 months luni-solar year)
	  for (let i = 0; i < this.calendarRule.coeff.length; ++i) {	// Perform decomposition by dividing by the successive cycle length
		if (isNaN(quantity)) 
			result[this.calendarRule.coeff[i].target] = NaN	// Case where timestamp is not a number, e.g. out of bounds.
		else {	// Here we make the suitable Euclidian division, with ceiling.
			let ceiling = this.calendarRule.coeff[i].ceiling + addCycle,
				[q, m] = Cbcce.divmod (quantity, this.calendarRule.coeff[i].cyclelength);
			if (q > ceiling) {
				if ( q > ceiling + 1 ) throw new RangeError 
					("Unsuitable quotient in ceiled division: " + quantity + " by " + this.calendarRule.coeff[i].cyclelength + " ceiled with " + ceiling);
				--q;
			}
			quantity -= q * this.calendarRule.coeff[i].cyclelength;
			addCycle = (q == ceiling) ? this.calendarRule.coeff[i].subCycleShift : 0; // if at last section of this cycle, add or subtract 1 to the ceiling of next cycle
			if (this.calendarRule.coeff[i].notify != undefined) result[this.calendarRule.coeff[i].notify] = (q == ceiling); // notify special cycle, like leap year etc. 
			result[this.calendarRule.coeff[i].target] += q*this.calendarRule.coeff[i].multiplier; // add result to suitable part of result array
		}
	  }	
	  return result;
}
	/** Compute the timestamp from the element of a date in a given calendar.
	 * @param {Object} askedObject	- the numeric elements of the date, collected in an object containing the elements that calendarRule prescribes.
	 * @returns {number} the timestamp.
	*/
	getNumber (askedObject) { // from an object askedObject structured as calendarRule.canvas, compute the chronological number
		var cells = {...askedObject}, quantity = this.calendarRule.timeepoch; // initialise Unix quantity to computation epoch
		for (let i = 0; i < this.calendarRule.canvas.length; i++) { // cells value shifted as to have all 0 if at epoch
			cells[this.calendarRule.canvas[i].name] -= this.calendarRule.canvas[i].init;
			if (isNaN (cells[this.calendarRule.canvas[i].name])) throw new TypeError ('Missing or invalid expected field ' + this.calendarRule.canvas[i].name);
		}
		let currentTarget = this.calendarRule.coeff[0].target; 	// Set to uppermost unit used for date (year, most often)
		let currentCounter = cells[this.calendarRule.coeff[0].target];	// This counter shall hold the successive remainders
		let addCycle = 0; 	// This flag says whether there is an additional period at end of cycle, e.g. a 5th year in the Franciade or a 13th month
		for (let i = 0; i < this.calendarRule.coeff.length; i++) {
			if (currentTarget != this.calendarRule.coeff[i].target) {	// If we go to the next level (e.g. year to month), reset variables
				currentTarget = this.calendarRule.coeff[i].target;
				currentCounter = cells[currentTarget];
			}
			let ceiling = this.calendarRule.coeff[i].ceiling + addCycle;	// Ceiling of this level may be increased 
																// i.e. Franciade is 5 years if at end of upper cycle
			let [f,m] = Cbcce.divmod (currentCounter,this.calendarRule.coeff[i].multiplier);
			if (f > ceiling) {
				if ( f > ceiling + 1 || m != 0 ) {
					throw new RangeError 
					("Unsuitable quotient in ceiled division: " + currentCounter + " by " + this.calendarRule.coeff[i].multiplier + " ceiled with " + ceiling);
					};
				--f;
			}
			currentCounter -= f * this.calendarRule.coeff[i].multiplier;
			addCycle = (f == ceiling) ? this.calendarRule.coeff[i].subCycleShift : 0;	// If at end of this cycle, the ceiling of the lower cycle may be increased or decreased.
			quantity += f * this.calendarRule.coeff[i].cyclelength;				// contribution to quantity at this level.
		}
		return quantity ;
	}
} // end of Cbcce class
/** Structure of the weekRule parameter passed to WeekClock that describes the structure of the week. 
 * Non checked constraints: 
 * 1. characWeekNumber shall be at beginning of year, before any intercalary month or day.
 * 2. weekLength shall be > 0.
 * @typedef {Object} weekRule 	-  set of parameters for the computation of week elements.
 * @property {Number} originWeekday - weekday number of day 0; value is renormalised to 0..weekLength-1.
 * @property {Function} daysInYear	- function (year), number of days in year; year is specified as "fullyear" (unambiguous); 
	* with solar calendars, result is 365 or 366.
 * @property {Function} characDayIndex	- function (year): the day index of one day of week number characWeekNumber of year;
	* if weekReset is true, this day shall be the first day of the week characWeekNumber ; if not, all weeks are of same length.
 * @property {Number} [startOfWeek=1]	- weekday number of the first day of the week for this calendar, e.g. 0 for Sunday, 1 for Monday etc. Default is 1;
	* value is renormalised to 0..weekLength-1.
 * @property {Number} [characWeekNumber=1]	- number of the week of the characDayIndex; default is 1.
 * @property {Number} [dayBase=1]		- the lowest number a weekday may have, normally only 0 or 1 are possible; not necessarily startOfWeek; default is 1.
 * @property {Number} [weekBase=1]		- the lowest number for a week, normally only 0 or 1 are possible; not necessarily characWeekNumber; default is 1.
 * @property {Number} [weekLength=7]	- number of days, or minimum number of days in one week; default is 7.
 * @property {Number} [weekReset=false]	- whether weekday is forced to a constant value at beginning of year; default is false.
 * @property {Number[]} [uncappedWeeks=null]	- an array of the week numbers that have one or more day above weekLength; possible cases:
	* undefined (and set to null): all weeks have always the same duration, weekLength;
	* .length = 1: last complete week of year is followed by several epagomenal days. These days are attached to the last week and hold numbers above weekLength;
	* .length > 1: each indentified week is followed by one (unique) epagomenal day. Any such week has weekLength + 1 days;
	* e.g. for French revolutionary calendar: [36], and the epagomenal days are indexed above Décadi, last day of the ordinary decade;
	* e.g. for ONU projected calendar: [26, 52], the Mondial day in the middle of year and the Bissextile day at the very end.
	* These days are only considered if weekReset is true, and in this case, uncappedWeeks should at least have one value.
*/
/** Get and set week figures using a specified reckoning sytems for weeks.
 * @see module:chronos for the definition of the weekRule structure.
 * @class
 * @param {weekRule} weekRule - the description of the week.
 */
export class WeekClock {
	constructor (weekRule) {
		this.originWeekday = weekRule.originWeekday;
		this.daysInYear = weekRule.daysInYear;
		this.characDayIndex = weekRule.characDayIndex; 
		this.weekLength = weekRule.weekLength != undefined ? weekRule.weekLength : 7 ;
		this.startOfWeek = weekRule.startOfWeek != undefined ? Cbcce.mod (weekRule.startOfWeek, this.weekLength) : 1 ;
		this.characWeekNumber = weekRule.characWeekNumber != undefined ? weekRule.characWeekNumber : 1 ;
		this.dayBase = weekRule.dayBase != undefined ? Cbcce.mod (weekRule.dayBase, this.weekLength) : 1 ;
		this.weekBase = weekRule.weekBase != undefined ? weekRule.weekBase : 1; 
		this.weekReset = weekRule.weekReset != undefined ? weekRule.weekReset : false;
		this.uncappedWeeks = weekRule.uncappedWeeks != undefined ? weekRule.uncappedWeeks : null;
	}
	/**	Compute week figures in the defined week structure.
	 * @param {number} dayIndex 	- date stamp, in day unit, of the day whose figures are computed.
	 * @param {number} year 		- the algebraic year the dayIndex date belongs to.
	 * @return {Array} 
		* [0] : week number;
		* [1] : week day number, depending on dayBase. Modulo this.weekLength value is always : 0 (or this.weekLength) for Sunday of first day, 1 for Monday etc.
		* [2] : year offset: -1 if week belongs to last week year, 0 if in same year, 1 if in next year.
		* [3] : weeks in year: number of weeks for the week year the date belongs to. This is not always the number of the last week (check this.weekBase)!
	*/
	getWeekFigures (dayIndex, year) {	// (dayIndex, characDayIndex, year)
		let cDayIndex = this.characDayIndex (year),
			weekNumberShift = this.weekBase - 1, 	// used to compute last week's number
			[weeksInYear, weekShiftNextYear] = Cbcce.divmod (this.daysInYear(year), this.weekLength),	// Integer division of calendar year in weeks. 
			// this figure characterises the week year, in particular versus number of weeks.
			weekYearPhase = (this.weekReset ? 0 : Cbcce.mod (cDayIndex + this.originWeekday - this.startOfWeek, this.weekLength)); 	
		// Compute basic coordinates: week cycle number (base 0) from referenceDay, day number 0..this.weekLength-1 in week beginning at 0 then shift to this.startOfWeek.
		var result = Cbcce.divmod ( dayIndex - cDayIndex + this.startOfWeek + weekYearPhase, this.weekLength ); // Here, first week is 0 and first day of week is 0.
		if (this.uncappedWeeks != null && this.uncappedWeeks.length > 1) 	// one or several weeks with one epagomenal days within year. One epagomenal day per singular week.
			 this.uncappedWeeks.forEach (item => {if (result[0] > item) {
				result[1]--; result = Cbcce.divmod (result[0] * this.weekLength + result[1], this.weekLength) }})	// shift week counts by one day for each singular week.
		result[0] += this.characWeekNumber;		// set week number with respect to number of week of the reference day
		result = Cbcce.shiftCycle ( result[0], result [1], this.weekLength, this.startOfWeek );	// shift week cycle, first day is this.startOfWeek and last day is this.startOfWeek + this.weekLength-1
		if (this.dayBase != this.startOfWeek) result[1] = Cbcce.mod (result[1]-this.dayBase, this.weekLength) + this.dayBase;		// day number forced to range this.dayBase .. this.dayBase + this.weekLength-1;
		if (this.uncappedWeeks != null && this.uncappedWeeks.length == 1)
			if (result[0] > this.uncappedWeeks[0]) {result[0]-- ; result[1] += this.weekLength } // epagomenal days at end of year have indexes above ordinary weekLength
		// Solve overflow
		let WeeksInDateYear = weeksInYear + ( !this.weekReset && weekYearPhase >= Cbcce.mod (-weekShiftNextYear, this.weekLength) ? 1 : 0); // Number of weeks for present week year
		if (result[0] < this.weekBase) { // the week belongs to the preceding weekyear
			[weeksInYear, weekShiftNextYear] = Cbcce.divmod (this.daysInYear(year-1), this.weekLength);	// recompute week year parameters for preceding year
			result.push(-1,		// reference year for the week number is the year before the date's year
				Cbcce.mod (weekYearPhase - weekShiftNextYear, this.weekLength) >= Cbcce.mod (-weekShiftNextYear, this.weekLength)	// weekYearPhase of preceding year computed after this year's
					? weeksInYear + 1 : weeksInYear );	// Number of weeks in the preceding year
			result[0] = result[3] + weekNumberShift;	// Set to last week of preceding year
		} else if (result[0] > WeeksInDateYear + weekNumberShift) {	// The week belongs to the following year
			let [ weeksInYearPlus, weekShiftNextYearPlus ] = Cbcce.divmod (this.daysInYear(year+1), this.weekLength);	// Integer division of next calendar year in weeks
			result.push (1, 	// reference year for the week number is the year after the date's year
				Cbcce.mod (weekYearPhase + weekShiftNextYear, this.weekLength) >= Cbcce.mod (-weekShiftNextYearPlus, this.weekLength) 	// weekYearPhase cycle of next year computed from this year's
					?  weeksInYearPlus + 1 : weeksInYearPlus);		// Number of weeks in the next wwek year
			result[0] = this.weekBase;				// set to first week
		} else result.push (0, WeeksInDateYear); 	// most cases.
		return result;
	}
	/**	Compute day index from week figures in the defined week structure.
	 * @param {number} weekYear 	- the year (full year, a relative integer) of the week figures. There may be 1 year difference with the year of the calendar's date.
	 * @param {number} [weekNumber] - the week number; if not specified, this.weekBase i.e. the first week of any year.
	 * @param {number} [dayOfWeek] 	- the weekday number; if not specified, this.startOfWeek i.e. the first day of the week for this culture.
	 * @param {boolean} [check] 	- check consistency of result; no check by default.
	 * @return {number} day index of the day that corresponds to the week figures.
	*/
	getNumberFromWeek (weekYear, weekNumber = this.weekBase, dayOfWeek = this.startOfWeek, check = false) {
		if (weekNumber < this.weekBase) throw new RangeError ('Week number too low: ' + weekNumber);
		if (dayOfWeek < this.dayBase) throw new RangeError ('Day of week too low: ' + dayOfWeek);
		let	cDayIndex = this.characDayIndex (weekYear);	// Find a day within week number characWeekNumber of this year.
		cDayIndex -= (this.weekReset ? 0 : Cbcce.mod ( cDayIndex + this.originWeekday - this.startOfWeek, this.weekLength )) ;	// Find first day of said week.
		let weekcomponents = Cbcce.divmod (dayOfWeek - this.startOfWeek, this.weekLength);	// Take note of epagomenal days
		let	result = cDayIndex + (weekNumber - this.weekBase) * this.weekLength + weekcomponents[1] ;	// Add weeks and week "phase".
		if (this.uncappedWeeks != null) {
			this.uncappedWeeks.forEach ((value) => ( result += (weekNumber > value ? 1 :0) )) ;	// Add shift due to days added in middle of year.
			if (this.uncappedWeeks.length == 1 && weekNumber == this.uncappedWeeks[0]) result += weekcomponents[0] * this.weekLength;	// Add additional days
		}
		// Control by doing the reverse computation
		let test = this.getWeekFigures (cDayIndex, weekYear);
		if (check) {
			if (test [3] < weekNumber) throw new RangeError ('Week number too high: ' + weekNumber);
			if (test [0] != weekNumber || test [1] != dayOfWeek) throw new RangeError
				('Recomputed week figures do not match asked ones: '+ weekYear + ', ' + weekNumber + ', ' + dayOfWeek);
		}
		return result;
	}
}
/** Convert number of days since epoch to or from a date expressed after ISO 8601.
	* @class
	* @param {number} [originYear=1970]	- the epoch year in ISO, a signed integer, default: 1970 for the Unix epoch.
	* @param {number} [originMonth=1]	- the epoch month in ISO, a number in range 1..12, default: 1.
	* @param {number} [originDay=1]		- the epoch day of the month in ISO, a number in range 1..31, default: 1. If this day does not exist in this month, date is balanced to next month.
	*/
export class IsoCounter { 
	constructor (originYear=1970, originMonth=1, originDay=1) {
		const 
			isoCoeff = [ 
			  {cyclelength : 146097, ceiling : Infinity, subCycleShift : 0, multiplier : 400, target : "isoYear"}, // 400 Gregorian years
			  {cyclelength : 36524, ceiling :  3, subCycleShift : 0, multiplier : 100, target : "isoYear"}, // 1 Gregorian short century
			  {cyclelength : 1461, ceiling : Infinity, subCycleShift : 0, multiplier : 4, target : "isoYear"}, // 4 Julian years
			  {cyclelength : 365, ceiling : 3, subCycleShift : 0, multiplier : 1, target : "isoYear"}, // One 365-days year
			  {cyclelength : 153, ceiling : Infinity, subCycleShift : 0, multiplier : 5, target : "isoMonth"}, // Five-months cycle
			  {cyclelength : 61, ceiling : Infinity, subCycleShift : 0, multiplier : 2, target : "isoMonth"}, // 61-days bimester
			  {cyclelength : 31, ceiling : Infinity, subCycleShift : 0, multiplier : 1, target : "isoMonth"}, // 31-days month
			  {cyclelength : 1, ceiling : Infinity, subCycleShift : 0, multiplier : 1, target : "isoDay"}
			],
			isoCanvas = [ 
			  {name : "isoYear", init : 0},
			  {name : "isoMonth", init : 3}, // this.monthBase + 2
			  {name : "isoDay", init : 1}
			];
		// Compute counter of specified date as if it were since 0000-03-01
		this.clockwork = new Cbcce ({
			timeepoch : 0, // 0 on 0000-03-01.
			coeff : isoCoeff, canvas : isoCanvas
			})
		let shift = this.toCounter ( {isoYear: originYear, isoMonth: originMonth, isoDay: originDay} ); 
		// Then establish new clockwork taking the opposite value for epoch counter
		this.clockwork = new Cbcce ({
			timeepoch : -shift, // re-instatiated to the value that corresponds to the asked date
			coeff : isoCoeff, canvas : isoCanvas
			})
	}
	/** Compute day counter, an integer number for the date specified under ISO 8601.
	 * @function
	 * @param {Object} isoFields 	- fields isoYear, isoMonth and isoDay must be specified as integer. isoMonth must lay in range 1..12 
		if day is out of range of valid days for the month, date is balanced to the number of days out of the range.
	 * @return {number} the counter, an integer number representing the date with the day counter.
	*/
	toCounter = function ( isoFields ) {
		let myFields = {...isoFields};
		// for missing fields: getNumber shall throw 
		[myFields.isoYear, myFields.isoMonth] = Cbcce.shiftCycle (isoFields.isoYear, isoFields.isoMonth, 12, 2, 1); // replace last parameter if monthBase 0 is required
		return this.clockwork.getNumber (myFields)
	}
	/** Compute ISO8601 date figures from a number of days since epoch.
	 * @function
	 * @param {number} counter	- the day counter, a counter representing a date; if not integer, the floor value is taken.
	 * @return {Object} fields isoYear, isoMonth and isoDay specify the date in ISO8601 calendar.
	*/
	toIsoFields = function ( counter ) {
		let myFields = this.clockwork.getObject (Math.floor (counter));
		[myFields.isoYear, myFields.isoMonth] = Cbcce.shiftCycle (myFields.isoYear, myFields.isoMonth, 12, -2, 3); // replace last parameter if monthBase 0 is required
		return myFields
	}
}