├── .eslintignore ├── .gitignore ├── dist ├── dts │ ├── helpers │ │ ├── get-date-str.d.ts │ │ ├── year-attributes.d.ts │ │ ├── mean-date.d.ts │ │ ├── get-day-before.d.ts │ │ ├── is-doubled-month.d.ts │ │ ├── index.d.ts │ │ ├── math.d.ts │ │ ├── moon.d.ts │ │ └── sun.d.ts │ ├── index.d.ts │ ├── generators │ │ ├── get-losar-for-year.d.ts │ │ ├── get-year-from-tibetan.d.ts │ │ ├── get-year-from-western.d.ts │ │ ├── get-month-from-month-count.d.ts │ │ ├── get-day-from-western.d.ts │ │ ├── get-year-from-rabjung.d.ts │ │ ├── get-month-from-tibetan.d.ts │ │ └── get-day-from-tibetan.d.ts │ ├── conversions │ │ ├── julian-from-true-date.d.ts │ │ ├── julian-from-unix.d.ts │ │ ├── month-count-from-tibetan.d.ts │ │ ├── unix-from-julian.d.ts │ │ ├── index.d.ts │ │ ├── true-date-from-month-count-day.d.ts │ │ └── julian-from-tibetan.d.ts │ ├── types.d.ts │ ├── classes │ │ ├── tibetan-month.d.ts │ │ ├── tibetan-year.d.ts │ │ └── tibetan-date.d.ts │ └── constants │ │ └── index.d.ts └── index.es.js ├── src ├── index.ts ├── __mocks__ │ ├── year-mock.ts │ ├── index.ts │ ├── month-mock.ts │ ├── day-mock.ts │ ├── 214008leap.ts │ ├── 214502.ts │ └── 214008main.ts ├── helpers │ ├── get-date-str.ts │ ├── __tests__ │ │ ├── mean-date.spec.ts │ │ ├── get-day-before.spec.ts │ │ ├── math.spec.ts │ │ ├── has-leap-month.spec.ts │ │ ├── get-date-str.spec.ts │ │ ├── year-attributes.spec.ts │ │ ├── moon.spec.ts │ │ └── sun.spec.ts │ ├── get-day-before.ts │ ├── mean-date.ts │ ├── index.ts │ ├── is-doubled-month.ts │ ├── year-attributes.ts │ ├── math.ts │ ├── moon.ts │ └── sun.ts ├── conversions │ ├── __tests__ │ │ ├── julian-from-unix.spec.ts │ │ ├── true-date-from-month-count-day.spec.ts │ │ ├── unix-from-julian.spec.ts │ │ ├── julian-from-true-date.spec.ts │ │ ├── julian-from-tibetan.spec.ts │ │ └── month-count-from-tibetan.spec.ts │ ├── index.ts │ ├── julian-from-true-date.ts │ ├── julian-from-unix.ts │ ├── unix-from-julian.ts │ ├── true-date-from-month-count-day.ts │ ├── julian-from-tibetan.ts │ └── month-count-from-tibetan.ts ├── generators │ ├── __tests__ │ │ ├── get-year-from-tibetan.spec.ts │ │ ├── get-year-from-western.spec.ts │ │ ├── get-losar-for-year.spec.ts │ │ ├── get-year-from-rabjung.spec.ts │ │ ├── get-month-from-month-count.spec.ts │ │ ├── get-month-from-tibetan.spec.ts │ │ ├── get-day-from-western.spec.ts │ │ ├── get-day-from-tibetan.spec.ts │ │ └── __snapshots__ │ │ │ ├── get-day-from-western.spec.ts.snap │ │ │ └── get-day-from-tibetan.spec.ts.snap │ ├── get-year-from-tibetan.ts │ ├── get-losar-for-year.ts │ ├── get-year-from-western.ts │ ├── get-month-from-month-count.ts │ ├── get-year-from-rabjung.ts │ ├── get-month-from-tibetan.ts │ ├── get-day-from-western.ts │ └── get-day-from-tibetan.ts ├── types.ts ├── classes │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ ├── tibetan-year.spec.ts.snap │ │ │ ├── tibetan-date.spec.ts.snap │ │ │ └── tibetan-month.spec.ts.snap │ │ ├── tibetan-year.spec.ts │ │ ├── tibetan-date.spec.ts │ │ └── tibetan-month.spec.ts │ ├── tibetan-year.ts │ ├── tibetan-month.ts │ └── tibetan-date.ts └── constants │ └── index.ts ├── .eslintrc.json ├── rollup.config.js ├── package.json ├── tsconfig.json └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | /node-modules 2 | /coverage 3 | /dist 4 | /**/*.d.ts -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | 4 | // IDE specific 5 | .idea 6 | .vscode 7 | -------------------------------------------------------------------------------- /dist/dts/helpers/get-date-str.d.ts: -------------------------------------------------------------------------------- 1 | declare const getDateStr: (date: Date) => string; 2 | export default getDateStr; 3 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as getLosarForYear } from './generators/get-losar-for-year'; 2 | export { default as TibetanDate } from './classes/tibetan-date'; 3 | export { default as TibetanMonth } from './classes/tibetan-month'; 4 | export { default as TibetanYear } from './classes/tibetan-year'; 5 | -------------------------------------------------------------------------------- /dist/dts/index.d.ts: -------------------------------------------------------------------------------- 1 | export { default as getLosarForYear } from './generators/get-losar-for-year'; 2 | export { default as TibetanDate } from './classes/tibetan-date'; 3 | export { default as TibetanMonth } from './classes/tibetan-month'; 4 | export { default as TibetanYear } from './classes/tibetan-year'; 5 | -------------------------------------------------------------------------------- /src/__mocks__/year-mock.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | const yearMock = { 3 | rabjungCycle: 17, 4 | rabjungYear: 32, 5 | westernYear: 2018, 6 | tibYear: 2145, 7 | element: 'Earth', 8 | gender: 'Male', 9 | animal: 'Dog' 10 | }; 11 | 12 | export { 13 | yearMock 14 | }; 15 | -------------------------------------------------------------------------------- /src/helpers/get-date-str.ts: -------------------------------------------------------------------------------- 1 | const getDateStr = (date: Date): string => { 2 | const year = date.getFullYear(); 3 | const month = date.getMonth() + 1; 4 | const dayNr = date.getDate(); 5 | 6 | return `${year}-${(`0${month}`).slice(-2)}-${(`0${dayNr}`).slice(-2)}`; 7 | }; 8 | 9 | export default getDateStr; 10 | -------------------------------------------------------------------------------- /src/conversions/__tests__/julian-from-unix.spec.ts: -------------------------------------------------------------------------------- 1 | import julianFromUnix from '../julian-from-unix'; 2 | 3 | describe('julianFromUnix(date)', () => { 4 | it('should return the correct Julian date', () => { 5 | const date = new Date('2018-07-21T18:00'); 6 | expect(julianFromUnix(date)).toEqual(2458321); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/helpers/__tests__/mean-date.spec.ts: -------------------------------------------------------------------------------- 1 | import meanDate from '../mean-date'; 2 | import { astroMock } from '../../__mocks__'; 3 | 4 | describe('meanDate()', () => { 5 | it('should return the correct mean date', () => { 6 | expect(meanDate(astroMock.day, astroMock.monthCount)).toEqual(astroMock.meanDate); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /dist/dts/helpers/year-attributes.d.ts: -------------------------------------------------------------------------------- 1 | import { Year } from '../types'; 2 | /** 3 | * figure out the animal and element for a tibetan year 4 | * 5 | * @param {Year} year 6 | * @return {Year} with additional attributes 7 | */ 8 | declare const yearAttributes: (year: Year) => Year; 9 | export default yearAttributes; 10 | -------------------------------------------------------------------------------- /src/generators/__tests__/get-year-from-tibetan.spec.ts: -------------------------------------------------------------------------------- 1 | import getYearFromTibetan from '../get-year-from-tibetan'; 2 | import { yearMock } from '../../__mocks__'; 3 | 4 | describe('getYearFromTibetan(tYear', () => { 5 | it('should return correct year attributes', () => { 6 | expect(getYearFromTibetan(yearMock.tibYear)).toMatchObject(yearMock); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /dist/dts/generators/get-losar-for-year.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculates the Western date for Losar (Tibetan new year) of a given Tibetan 3 | * year number (ex. 2137). 4 | * @param {number} tibYear - Tibetan year number 5 | * @returns {Date} 6 | */ 7 | declare const getLosarForYear: (year: number, isTibetan?: boolean) => string; 8 | export default getLosarForYear; 9 | -------------------------------------------------------------------------------- /dist/dts/helpers/mean-date.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * meanDate(day, monthCount) - corresponding to the linear mean motion of the moon 3 | * @param {number} day - the tibetan day 4 | * @param {number} monthCount - month count since beginning of epoch 5 | * @returns {number} 6 | */ 7 | declare const meanDate: (day: number, monthCount: number) => number; 8 | export default meanDate; 9 | -------------------------------------------------------------------------------- /src/conversions/__tests__/true-date-from-month-count-day.spec.ts: -------------------------------------------------------------------------------- 1 | import getTrueDate from '../true-date-from-month-count-day'; 2 | import { astroMock } from '../../__mocks__'; 3 | 4 | describe('getTrueDate()', () => { 5 | it('should return the correct vaule', () => { 6 | expect(getTrueDate(astroMock.day, astroMock.monthCount)).toEqual(astroMock.trueDate); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /dist/dts/conversions/julian-from-true-date.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * calculates the julian date from the day count a variant of true_date 3 | * @param {number} dayCount - the day count since beginning of epoch defined as dayNo + 30 * monthCount 4 | * @return {number} - julian date 5 | */ 6 | declare const julianFromTrueDate: (dayCount: number) => number; 7 | export default julianFromTrueDate; 8 | -------------------------------------------------------------------------------- /src/conversions/__tests__/unix-from-julian.spec.ts: -------------------------------------------------------------------------------- 1 | import unixFromJulian from '../unix-from-julian'; 2 | 3 | describe('unixFromJulian(jd)', () => { 4 | it('should return the correct unix date', () => { 5 | expect(unixFromJulian(2458321).toISOString()).toEqual('2018-07-21T10:00:00.000Z'); 6 | expect(unixFromJulian(2457422).toISOString()).toEqual('2016-02-03T10:00:00.000Z'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /dist/dts/generators/get-year-from-tibetan.d.ts: -------------------------------------------------------------------------------- 1 | import { Year } from '../types'; 2 | /** 3 | * Figures out a year's info from a Tibetan calendar year number, ex. 2135. 4 | * 5 | * @param {number} tYear - Tibetan calendar year number, ex. 2135. 6 | * @returns {Year} 7 | */ 8 | declare const getYearFromTibetan: (tYear: number) => Year; 9 | export default getYearFromTibetan; 10 | -------------------------------------------------------------------------------- /dist/dts/generators/get-year-from-western.d.ts: -------------------------------------------------------------------------------- 1 | import { Year } from '../types'; 2 | /** 3 | * Figures out a year's info from a Western calendar year number, ex. 2008. 4 | * 5 | * @param {number} wYear: Western calendar year number, ex. 2008 6 | * @returns {Year} 7 | */ 8 | declare const getYearFromWestern: (wYear: number) => Year; 9 | export default getYearFromWestern; 10 | -------------------------------------------------------------------------------- /dist/dts/helpers/get-day-before.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Subtract 1 day from a date 3 | * @param {number} day - the tibetan day 4 | * @param {number} monthCount - month count since beginning of epoch 5 | * @returns {{day, monthCount}} 6 | */ 7 | declare const getDayBefore: (day: number, monthCount: number) => { 8 | day: number; 9 | monthCount: number; 10 | }; 11 | export default getDayBefore; 12 | -------------------------------------------------------------------------------- /src/generators/__tests__/get-year-from-western.spec.ts: -------------------------------------------------------------------------------- 1 | import getYearFromWestern from '../get-year-from-western'; 2 | import { yearMock } from '../../__mocks__'; 3 | 4 | 5 | describe('getYearFromWestern(wYear)', () => { 6 | it('should retrun the correct year attributes', () => { 7 | const year = getYearFromWestern(yearMock.westernYear); 8 | expect(year).toMatchObject(yearMock); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /dist/dts/helpers/is-doubled-month.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculates whether a given Tibetan year and month number is doubled, i.e 3 | * is preceded by a leap month. 4 | * 5 | * @param {number} tYear - tibetan year 6 | * @param {number} month - month number 7 | * @returns {boolean} 8 | */ 9 | declare const isDoubledMonth: (tYear: number, month: number) => boolean; 10 | export default isDoubledMonth; 11 | -------------------------------------------------------------------------------- /dist/dts/conversions/julian-from-unix.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * get Julian date from UNIX date 3 | * see explanation: 4 | * https://stackoverflow.com/questions/11759992/calculating-jdayjulian-day-in-javascript 5 | * 6 | * @property {Date} unixTime - the date object to be converted 7 | * @return {number} - julian date 8 | */ 9 | declare const julianFromUnix: (unixTime: Date) => number; 10 | export default julianFromUnix; 11 | -------------------------------------------------------------------------------- /dist/dts/helpers/index.d.ts: -------------------------------------------------------------------------------- 1 | import { frac, amod } from './math'; 2 | import getDateStr from './get-date-str'; 3 | import getDayBefore from './get-day-before'; 4 | import isDoubledMonth from './is-doubled-month'; 5 | import meanDate from './mean-date'; 6 | import moon from './moon'; 7 | import sun from './sun'; 8 | import yearAttributes from './year-attributes'; 9 | export { yearAttributes, getDateStr, getDayBefore, isDoubledMonth, meanDate, moon, sun, frac, amod, }; 10 | -------------------------------------------------------------------------------- /src/helpers/get-day-before.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Subtract 1 day from a date 3 | * @param {number} day - the tibetan day 4 | * @param {number} monthCount - month count since beginning of epoch 5 | * @returns {{day, monthCount}} 6 | */ 7 | const getDayBefore = (day: number, monthCount: number): {day: number, monthCount: number} => ( 8 | day === 1 9 | ? { day: 30, monthCount: monthCount - 1 } 10 | : { day: day - 1, monthCount } 11 | ); 12 | 13 | export default getDayBefore; 14 | -------------------------------------------------------------------------------- /src/conversions/__tests__/julian-from-true-date.spec.ts: -------------------------------------------------------------------------------- 1 | import julianFromTrueDate from '../julian-from-true-date'; 2 | 3 | const mockTrueDate = [2446600.578301787, 30]; 4 | const mockJulian = [4423819, 2015531]; 5 | 6 | describe('julianFromTrueDate()', () => { 7 | it('shoutd return the correct Julian date', () => { 8 | expect(julianFromTrueDate(mockTrueDate[0])).toEqual(mockJulian[0]); 9 | expect(julianFromTrueDate(mockTrueDate[1])).toEqual(mockJulian[1]); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/helpers/mean-date.ts: -------------------------------------------------------------------------------- 1 | // Implement partial formulas in Svante's paper. 2 | import { 3 | M0, M1, M2 4 | } from '../constants'; 5 | 6 | /** 7 | * meanDate(day, monthCount) - corresponding to the linear mean motion of the moon 8 | * @param {number} day - the tibetan day 9 | * @param {number} monthCount - month count since beginning of epoch 10 | * @returns {number} 11 | */ 12 | const meanDate = (day: number, monthCount: number): number => monthCount * M1 + day * M2 + M0; 13 | 14 | export default meanDate; 15 | -------------------------------------------------------------------------------- /dist/dts/conversions/month-count-from-tibetan.d.ts: -------------------------------------------------------------------------------- 1 | import { Month } from '../types'; 2 | /** 3 | * This is the reverse of fromMonthCount(n): from a Tibetan year, month number 4 | * and leap month indicator, calculates the "month count" based on the epoch. 5 | * 6 | * @param {Month} monthObject 7 | * @returns {number} month count since epoch 8 | */ 9 | declare const monthCountFromTibetan: ({ year, month, isLeapMonth }: Month) => number; 10 | export default monthCountFromTibetan; 11 | -------------------------------------------------------------------------------- /dist/dts/conversions/unix-from-julian.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * get Unix date from Julian date 3 | * since we use only date calculations here, there is no need to correct for timezone differences 4 | * see explanation: 5 | * https://stackoverflow.com/questions/11759992/calculating-jdayjulian-day-in-javascript 6 | * 7 | * @param {number} julianDate - the julian date 8 | * @return {Date} 9 | */ 10 | declare const unixFromJulian: (julianDate: number) => Date; 11 | export default unixFromJulian; 12 | -------------------------------------------------------------------------------- /dist/dts/conversions/index.d.ts: -------------------------------------------------------------------------------- 1 | import julianFromUnix from './julian-from-unix'; 2 | import monthCountFromTibetan from './month-count-from-tibetan'; 3 | import julianFromTibetan from './julian-from-tibetan'; 4 | import trueDateFromMonthCountDay from './true-date-from-month-count-day'; 5 | import julianFromTrueDate from './julian-from-true-date'; 6 | import unixFromJulian from './unix-from-julian'; 7 | export { julianFromUnix, monthCountFromTibetan, julianFromTibetan, trueDateFromMonthCountDay, julianFromTrueDate, unixFromJulian }; 8 | -------------------------------------------------------------------------------- /src/generators/get-year-from-tibetan.ts: -------------------------------------------------------------------------------- 1 | import { YEAR_DIFF } from '../constants'; 2 | import getYearFromWestern from './get-year-from-western'; 3 | import { Year } from '../types' 4 | 5 | /** 6 | * Figures out a year's info from a Tibetan calendar year number, ex. 2135. 7 | * 8 | * @param {number} tYear - Tibetan calendar year number, ex. 2135. 9 | * @returns {Year} 10 | */ 11 | const getYearFromTibetan = (tYear: number): Year => getYearFromWestern(tYear - YEAR_DIFF); 12 | 13 | export default getYearFromTibetan; 14 | -------------------------------------------------------------------------------- /src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { frac, amod } from './math'; 2 | import getDateStr from './get-date-str'; 3 | import getDayBefore from './get-day-before'; 4 | import isDoubledMonth from './is-doubled-month'; 5 | import meanDate from './mean-date'; 6 | import moon from './moon'; 7 | import sun from './sun'; 8 | import yearAttributes from './year-attributes'; 9 | 10 | 11 | export { 12 | yearAttributes, 13 | getDateStr, 14 | getDayBefore, 15 | isDoubledMonth, 16 | meanDate, 17 | moon, 18 | sun, 19 | frac, 20 | amod, 21 | }; 22 | -------------------------------------------------------------------------------- /dist/dts/generators/get-month-from-month-count.d.ts: -------------------------------------------------------------------------------- 1 | import { Month } from '../types'; 2 | /** 3 | * Figures out the Tibetan year number, month number within the year, and whether 4 | * this is a leap month, from a "month count" number. See Svante Janson, 5 | * "Tibetan Calendar Mathematics", p.8 ff. 6 | * 7 | * @param {number} monthCount: the "month count" since beginning of epoch 8 | * @returns {Month} 9 | */ 10 | declare const getMonthFromMonthCount: (monthCount: number) => Month; 11 | export default getMonthFromMonthCount; 12 | -------------------------------------------------------------------------------- /src/generators/__tests__/get-losar-for-year.spec.ts: -------------------------------------------------------------------------------- 1 | import getLosarForYear from '../get-losar-for-year'; 2 | 3 | const losar2018 = '2018-02-16'; 4 | 5 | describe('getLosarForYear()', () => { 6 | it('should return the correct date if Tibetan year is given', () => { 7 | expect(getLosarForYear(2145)).toEqual(losar2018); 8 | expect(getLosarForYear(2145, true)).toEqual(losar2018); 9 | }); 10 | it('should return the correct date if Tibetan year is given', () => { 11 | expect(getLosarForYear(2018, false)).toEqual(losar2018); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/conversions/index.ts: -------------------------------------------------------------------------------- 1 | import julianFromUnix from './julian-from-unix'; 2 | import monthCountFromTibetan from './month-count-from-tibetan'; 3 | import julianFromTibetan from './julian-from-tibetan'; 4 | import trueDateFromMonthCountDay from './true-date-from-month-count-day'; 5 | import julianFromTrueDate from './julian-from-true-date'; 6 | import unixFromJulian from './unix-from-julian'; 7 | 8 | export { 9 | julianFromUnix, 10 | monthCountFromTibetan, 11 | julianFromTibetan, 12 | trueDateFromMonthCountDay, 13 | julianFromTrueDate, 14 | unixFromJulian 15 | }; 16 | -------------------------------------------------------------------------------- /src/conversions/julian-from-true-date.ts: -------------------------------------------------------------------------------- 1 | import getTrueDate from './true-date-from-month-count-day'; 2 | /** 3 | * calculates the julian date from the day count a variant of true_date 4 | * @param {number} dayCount - the day count since beginning of epoch defined as dayNo + 30 * monthCount 5 | * @return {number} - julian date 6 | */ 7 | const julianFromTrueDate = (dayCount: number): number => { 8 | const monthCount = Math.floor((dayCount - 1) / 30); 9 | const calculatedDay = dayCount % 30 || 30; 10 | 11 | return Math.floor(getTrueDate(calculatedDay, monthCount)); 12 | }; 13 | 14 | export default julianFromTrueDate; 15 | -------------------------------------------------------------------------------- /src/helpers/__tests__/get-day-before.spec.ts: -------------------------------------------------------------------------------- 1 | import getDayBefore from '../get-day-before'; 2 | 3 | const day = 11; 4 | const monthCount = 14598; 5 | const mockDayBefore = [ 6 | { day: day - 1, monthCount }, 7 | { day: 30, monthCount: monthCount - 1 }, 8 | ]; 9 | 10 | describe('getDayBefore()', () => { 11 | it('should return the correct value for days in the month', () => { 12 | expect(getDayBefore(day, monthCount)).toEqual(mockDayBefore[0]); 13 | }); 14 | it('should return the correct value for first days of the month', () => { 15 | expect(getDayBefore(1, monthCount)).toEqual(mockDayBefore[1]); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /dist/dts/generators/get-day-from-western.d.ts: -------------------------------------------------------------------------------- 1 | import { Day } from '../types'; 2 | /** 3 | * Calculates a Tibetan date for a given western date. This does a binary search, and is therefore 4 | * much slower than tibToWestern(). 5 | * 6 | * The algorithm could be much improved by using the reverse of meanDate() to start with, 7 | * and then using the fact that julian dates and "tibetan day numbers" have a quasi-linear relation. 8 | * 9 | * @param {Date} date - the western date 10 | * @return {Day} 11 | */ 12 | declare const getDayFromWestern: (date: Date) => Day; 13 | export default getDayFromWestern; 14 | -------------------------------------------------------------------------------- /dist/dts/generators/get-year-from-rabjung.d.ts: -------------------------------------------------------------------------------- 1 | import { Year } from '../types'; 2 | /** 3 | * Figures out a year's info based on the Tibetan calendar, ex. the 3rd year of the 15th Rabjung calendrical cycle. 4 | * @param {object} arg 5 | * @param {number} arg.rabjungCycle : number of the cycle 6 | * @param {number} arg.rabjungYear : number of the year within the cycle, from 1 to 60. 7 | * @returns {null | Year} 8 | */ 9 | declare const getYearFromRabjung: ({ rabjungCycle, rabjungYear }: { 10 | rabjungCycle: number; 11 | rabjungYear: number; 12 | }) => Year; 13 | export default getYearFromRabjung; 14 | -------------------------------------------------------------------------------- /src/helpers/is-doubled-month.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BETA, 3 | YEAR0, 4 | YEAR_DIFF, 5 | } from '../constants'; 6 | 7 | /** 8 | * Calculates whether a given Tibetan year and month number is doubled, i.e 9 | * is preceded by a leap month. 10 | * 11 | * @param {number} tYear - tibetan year 12 | * @param {number} month - month number 13 | * @returns {boolean} 14 | */ 15 | const isDoubledMonth = (tYear: number, month: number): boolean => { 16 | const mp = 12 * (tYear - YEAR_DIFF - YEAR0) + month; 17 | 18 | return ((2 * mp) % 65 === BETA % 65) || ((2 * mp) % 65 === (BETA + 1) % 65); 19 | }; 20 | 21 | export default isDoubledMonth; 22 | -------------------------------------------------------------------------------- /src/conversions/__tests__/julian-from-tibetan.spec.ts: -------------------------------------------------------------------------------- 1 | import { dayInDuplicateMonthMock } from '../../__mocks__'; 2 | import julianFromTibetan from '../julian-from-tibetan'; 3 | 4 | describe('julianFromTibetan()', () => { 5 | const { year, month, day } = dayInDuplicateMonthMock; 6 | 7 | it('should return the correct julian date for not leap month', () => { 8 | expect(julianFromTibetan(year, month, false, day)) 9 | .toEqual(dayInDuplicateMonthMock.julianLeap); 10 | }); 11 | it('should return the correct julian date for leap month', () => { 12 | expect(julianFromTibetan(year, month, true, day)) 13 | .toEqual(dayInDuplicateMonthMock.julianMain); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /dist/dts/helpers/math.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fraction part a number 3 | * 4 | * @param {number} a - a number to check 5 | * @returns {number} the fractional part of a number 6 | */ 7 | declare const frac: (a: number) => number; 8 | /** 9 | * Modulo of a number (a % b), but from 1..b instead of 0..b-1. 10 | * This means that 11 | * amod(a, b) = a % b 12 | * unless a is a multiple of b. In that case: 13 | * a % b = 0 but amod(a, b) = b. 14 | * 15 | * @param {number} a - number to be divided 16 | * @param {number} b - the number to be divided with 17 | * @return {number} 18 | */ 19 | declare const amod: (a: number, b: number) => number; 20 | export { frac, amod }; 21 | -------------------------------------------------------------------------------- /src/helpers/__tests__/math.spec.ts: -------------------------------------------------------------------------------- 1 | import { frac, amod } from '../math'; 2 | 3 | describe('Frac(a)', () => { 4 | it('should return 0 if integer is give', () => { 5 | expect(frac(7)).toEqual(0); 6 | }); 7 | it('should return the fractional part of 1.1', () => { 8 | // correct for JS handling of fractions 9 | const result = Math.trunc(frac(1.1) * 10000) / 10000; 10 | expect(result).toEqual(0.1); 11 | }); 12 | }); 13 | 14 | describe('amod(a,b)', () => { 15 | it('should return the correct modulo of 2 numbers', () => { 16 | expect(amod(9, 2)).toEqual(1); 17 | }); 18 | it('should return b instead of 0', () => { 19 | expect(amod(9, 9)).toEqual(9); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/generators/__tests__/get-year-from-rabjung.spec.ts: -------------------------------------------------------------------------------- 1 | import getYearFromRabjung from '../get-year-from-rabjung'; 2 | import { yearMock } from '../../__mocks__'; 3 | 4 | describe('getYearFromRabjung(cycle, year)', () => { 5 | it('should give correct western year', () => { 6 | const year = getYearFromRabjung({ rabjungCycle: yearMock.rabjungCycle, rabjungYear: yearMock.rabjungYear }); 7 | expect(year).toEqual(yearMock); 8 | }); 9 | it('should throw error on impossible dates', () => { 10 | expect(() => getYearFromRabjung({ rabjungCycle: 0, rabjungYear: 5 })).toThrowError(); 11 | expect(() => getYearFromRabjung({ rabjungCycle: 5, rabjungYear: 63 })).toThrowError(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/generators/get-losar-for-year.ts: -------------------------------------------------------------------------------- 1 | import { YEAR_DIFF } from '../constants'; 2 | import { julianFromTibetan, unixFromJulian } from '../conversions'; 3 | import { getDateStr } from '../helpers'; 4 | 5 | /** 6 | * Calculates the Western date for Losar (Tibetan new year) of a given Tibetan 7 | * year number (ex. 2137). 8 | * @param {number} tibYear - Tibetan year number 9 | * @returns {Date} 10 | */ 11 | const getLosarForYear = (year: number, isTibetan = true): string => { 12 | const tibYear = isTibetan ? year : year + YEAR_DIFF; 13 | const julianDay = 1 + julianFromTibetan(tibYear - 1, 12, false, 30); 14 | 15 | return getDateStr(unixFromJulian(julianDay)); 16 | }; 17 | 18 | export default getLosarForYear; 19 | -------------------------------------------------------------------------------- /src/helpers/year-attributes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | YEAR_ANIMALS, 3 | YEAR_ELEMENTS, 4 | YEAR_GENDER, 5 | } from '../constants'; 6 | import { Year } from '../types'; 7 | 8 | /** 9 | * figure out the animal and element for a tibetan year 10 | * 11 | * @param {Year} year 12 | * @return {Year} with additional attributes 13 | */ 14 | const yearAttributes = (year: Year): Year => { 15 | const thisYear = { ...year }; 16 | const y = thisYear.tibYear; 17 | 18 | thisYear.animal = YEAR_ANIMALS[(y + 1) % 12]; 19 | thisYear.element = YEAR_ELEMENTS[Math.floor(((y - 1) / 2) % 5)]; 20 | thisYear.gender = YEAR_GENDER[(y + 1) % 2]; 21 | 22 | return thisYear; 23 | }; 24 | 25 | export default yearAttributes; 26 | -------------------------------------------------------------------------------- /dist/dts/conversions/true-date-from-month-count-day.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The date at the end of the lunar day (similar to month count since beginning of epoch, but for days) 3 | * It is calculated by first calculating a simpler mean date, corresponding to the linear mean motion of 4 | * the moon, and then adjusting it by the equations of the moon and sun, which are determined by the 5 | * anomalies of the moon and sun together with tables. 6 | * @param {number} day - the tibetan day 7 | * @param {number} monthCount - month count since beginning of epoch 8 | * @returns {number} 9 | */ 10 | declare const trueDateFromMonthCountDay: (day: number, monthCount: number) => number; 11 | export default trueDateFromMonthCountDay; 12 | -------------------------------------------------------------------------------- /src/helpers/__tests__/has-leap-month.spec.ts: -------------------------------------------------------------------------------- 1 | import isDoubledMonth from '../is-doubled-month'; 2 | import { simpleMonthMock, mainMonthMock } from '../../__mocks__'; 3 | 4 | describe('isDoubledMonth()', () => { 5 | it('should return false when there is no leap month', () => { 6 | expect(isDoubledMonth(simpleMonthMock.info.year, simpleMonthMock.info.month)).toBeFalsy(); 7 | expect(isDoubledMonth(2146, 2)).toBeFalsy(); 8 | expect(isDoubledMonth(2151, 5)).toBeFalsy(); 9 | }); 10 | it('should return true when there is leap month', () => { 11 | expect(isDoubledMonth(mainMonthMock.info.year, mainMonthMock.info.month)).toBeTruthy(); 12 | expect(isDoubledMonth(2146, 1)).toBeTruthy(); 13 | expect(isDoubledMonth(2151, 6)).toBeTruthy(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/__mocks__/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | simpleMonthMock, leapMonthMock, mainMonthMock 3 | } from './month-mock'; 4 | import { dayInDuplicateMonthMock, skippedDayMock, duplicateDayMock } from './day-mock'; 5 | import { yearMock } from './year-mock'; 6 | 7 | const astroMock = { 8 | day: 11, 9 | monthCount: 14598, 10 | meanDate: 2446600.182372702, 11 | meanSun: 1181.1403399668327, 12 | moonAnomaly: 1047.3795351473923, 13 | moonEqu: 16.492063492070884, 14 | sunAnomaly: 1180.8903399668327, 15 | sunEqu: -7.263681592026842, 16 | trueDate: 2446600.578301787, 17 | }; 18 | 19 | export { 20 | dayInDuplicateMonthMock, 21 | skippedDayMock, 22 | duplicateDayMock, 23 | astroMock, 24 | simpleMonthMock, 25 | leapMonthMock, 26 | mainMonthMock, 27 | yearMock 28 | }; 29 | -------------------------------------------------------------------------------- /src/generators/__tests__/get-month-from-month-count.spec.ts: -------------------------------------------------------------------------------- 1 | import getMonthFromMonthCount from '../get-month-from-month-count'; 2 | import { simpleMonthMock, leapMonthMock, mainMonthMock } from '../../__mocks__'; 3 | 4 | describe('getMonthFromMonthCount()', () => { 5 | it('should return the correct month', () => { 6 | expect(getMonthFromMonthCount(simpleMonthMock.monthCount)).toEqual(simpleMonthMock.info); 7 | }); 8 | it('should return the correct leap month', () => { 9 | expect(getMonthFromMonthCount(leapMonthMock.monthCount)).toEqual(leapMonthMock.info); 10 | }); 11 | it('should return the correct month for not leap month that has a leap', () => { 12 | expect(getMonthFromMonthCount(mainMonthMock.monthCount)).toEqual(mainMonthMock.info); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/helpers/math.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * fraction part a number 3 | * 4 | * @param {number} a - a number to check 5 | * @returns {number} the fractional part of a number 6 | */ 7 | const frac = (a: number) => a % 1; 8 | 9 | /** 10 | * Modulo of a number (a % b), but from 1..b instead of 0..b-1. 11 | * This means that 12 | * amod(a, b) = a % b 13 | * unless a is a multiple of b. In that case: 14 | * a % b = 0 but amod(a, b) = b. 15 | * 16 | * @param {number} a - number to be divided 17 | * @param {number} b - the number to be divided with 18 | * @return {number} 19 | */ 20 | // TODO: add control for b <= 0. Not urgent as this is not what we expect in calendar calculations (?) 21 | const amod = (a: number, b: number): number => (a % b) || b; 22 | 23 | export { frac, amod }; 24 | -------------------------------------------------------------------------------- /dist/dts/conversions/julian-from-tibetan.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Gives the Julian date for a Tibetan year, month number (leap or not) and Tibetan day. 3 | * 4 | * Does not check that the tibetan day actually exists: 5 | * - If given the date of a skipped day, will return the same Julian date as the day before. 6 | * - If given the date of a duplicate day, returns the Julian date of the second of the two. 7 | * 8 | * @param {number} year - Tibetan year 9 | * @param {number} month - Tibetan month 10 | * @param {boolean} isLeapMonth - true if leap month 11 | * @param {number} day - Tibetan day 12 | * @returns {number} - Julian date 13 | */ 14 | declare const julianFromTibetan: (year: number, month: number, isLeapMonth: boolean, day: number) => number; 15 | export default julianFromTibetan; 16 | -------------------------------------------------------------------------------- /src/generators/get-year-from-western.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RABJUNG_BEGINNING, 3 | RABJUNG_CYCLE_LENGTH, 4 | YEAR_DIFF 5 | } from '../constants'; 6 | import { amod, yearAttributes } from '../helpers'; 7 | import { Year } from '../types' 8 | 9 | /** 10 | * Figures out a year's info from a Western calendar year number, ex. 2008. 11 | * 12 | * @param {number} wYear: Western calendar year number, ex. 2008 13 | * @returns {Year} 14 | */ 15 | const getYearFromWestern = (wYear: number): Year => (yearAttributes({ 16 | rabjungCycle: Math.ceil((wYear - RABJUNG_BEGINNING + 1) / RABJUNG_CYCLE_LENGTH), 17 | rabjungYear: amod(wYear - RABJUNG_BEGINNING + 1, RABJUNG_CYCLE_LENGTH), 18 | tibYear: wYear + YEAR_DIFF, 19 | westernYear: wYear, 20 | })); 21 | 22 | export default getYearFromWestern; 23 | -------------------------------------------------------------------------------- /src/conversions/__tests__/month-count-from-tibetan.spec.ts: -------------------------------------------------------------------------------- 1 | import monthCountFromTibetan from '../month-count-from-tibetan'; 2 | import { simpleMonthMock, leapMonthMock, mainMonthMock } from '../../__mocks__'; 3 | 4 | describe('monthCountFromTibetan()', () => { 5 | it('should return the correct month count for non leap month', () => { 6 | expect(monthCountFromTibetan(simpleMonthMock.info)).toEqual(simpleMonthMock.monthCount); 7 | }); 8 | it('should return the correct month count for leap month', () => { 9 | expect(monthCountFromTibetan(leapMonthMock.info)).toEqual(leapMonthMock.monthCount); 10 | }); 11 | it('should return the correct month count for not leap month that has a leap', () => { 12 | expect(monthCountFromTibetan(mainMonthMock.info)).toEqual(mainMonthMock.monthCount); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/conversions/julian-from-unix.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MS_IN_YEAR, 3 | MIN_IN_DAY, 4 | JULIAN_TO_UNIX 5 | } from '../constants'; 6 | import { getDateStr } from '../helpers'; 7 | 8 | /** 9 | * get Julian date from UNIX date 10 | * see explanation: 11 | * https://stackoverflow.com/questions/11759992/calculating-jdayjulian-day-in-javascript 12 | * 13 | * @property {Date} unixTime - the date object to be converted 14 | * @return {number} - julian date 15 | */ 16 | const julianFromUnix = (unixTime: Date): number => { 17 | const dateOnly = getDateStr(unixTime); 18 | const timeAfterNoon = new Date(`${dateOnly}T18:00:00`); 19 | 20 | return Math.floor( 21 | + timeAfterNoon / MS_IN_YEAR 22 | - unixTime.getTimezoneOffset() / MIN_IN_DAY 23 | + JULIAN_TO_UNIX 24 | ); 25 | }; 26 | 27 | export default julianFromUnix; 28 | -------------------------------------------------------------------------------- /src/helpers/__tests__/get-date-str.spec.ts: -------------------------------------------------------------------------------- 1 | import MockDate from 'mockdate'; 2 | import getDateStr from '../get-date-str'; 3 | 4 | describe('getDateStr()', () => { 5 | it('should return a date str for now', () => { 6 | MockDate.set('2018-07-21T12:00'); 7 | const date = new Date(); 8 | 9 | expect(getDateStr(date)).toEqual('2018-07-21'); 10 | MockDate.reset(); 11 | }); 12 | it('should work in different time of the day', () => { 13 | const dateStrings = [ 14 | '2018-07-21T00:00', 15 | '2018-07-21T06:30', 16 | '2018-07-21T12:00', 17 | '2018-07-21T18:30', 18 | '2018-07-21T23:30', 19 | ]; 20 | const dates = dateStrings.map(str => new Date(str)); 21 | dates.forEach((date, i) => { 22 | expect(getDateStr(date)).toEqual(dateStrings[i].slice(0, 10)); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/conversions/unix-from-julian.ts: -------------------------------------------------------------------------------- 1 | import { 2 | MS_IN_YEAR, 3 | MIN_IN_DAY, 4 | JULIAN_TO_UNIX 5 | } from '../constants'; 6 | 7 | 8 | /** 9 | * get Unix date from Julian date 10 | * since we use only date calculations here, there is no need to correct for timezone differences 11 | * see explanation: 12 | * https://stackoverflow.com/questions/11759992/calculating-jdayjulian-day-in-javascript 13 | * 14 | * @param {number} julianDate - the julian date 15 | * @return {Date} 16 | */ 17 | const unixFromJulian = (julianDate: number): Date => { 18 | const localTimezoneOffset = new Date().getTimezoneOffset(); 19 | const unixDate = (julianDate - JULIAN_TO_UNIX + localTimezoneOffset / MIN_IN_DAY) * MS_IN_YEAR; 20 | const unix = new Date(unixDate); 21 | 22 | return unix; 23 | }; 24 | 25 | 26 | export default unixFromJulian; 27 | -------------------------------------------------------------------------------- /dist/dts/generators/get-month-from-tibetan.d.ts: -------------------------------------------------------------------------------- 1 | import { Month } from '../types'; 2 | /** 3 | * Calculates full information about a Tibetan month: whether it is doubled or not, 4 | * and the western start and end date for it. 5 | * The start_date and end_date correspond to the leap month if isLeapMonth is true, 6 | * otherwise to the main month (i.e the second of the two). 7 | * 8 | * @param {object} arg 9 | * @param {number} arg.year - the Tibetan year 10 | * @param {number} arg.month - the Tibetan month number (1 to 12) 11 | * @param {boolean} [arg.isLeapMonth=false] - if leap month or not 12 | * @returns {Month} 13 | */ 14 | declare const getMonthFromTibetan: ({ year, month, isLeapMonth }: { 15 | year: number; 16 | month: number; 17 | isLeapMonth?: boolean; 18 | }) => Month; 19 | export default getMonthFromTibetan; 20 | -------------------------------------------------------------------------------- /src/helpers/__tests__/year-attributes.spec.ts: -------------------------------------------------------------------------------- 1 | import yearAttributes from '../year-attributes'; 2 | import { yearMock } from '../../__mocks__'; 3 | 4 | describe('yearAttributes(year)', () => { 5 | const basicInfo = { 6 | rabjungCycle: yearMock.rabjungCycle, 7 | rabjungYear: yearMock.rabjungYear, 8 | westernYear: yearMock.westernYear, 9 | tibYear: yearMock.tibYear, 10 | }; 11 | it('should return correct year attributes', () => { 12 | expect(yearAttributes(basicInfo)).toMatchObject(yearMock); 13 | expect(yearAttributes({ 14 | rabjungCycle: 17, rabjungYear: 27, tibYear: 2140, westernYear: 2013 15 | })).toMatchObject({ 16 | rabjungCycle: 17, 17 | rabjungYear: 27, 18 | tibYear: 2140, 19 | westernYear: 2013, 20 | animal: 'Snake', 21 | element: 'Water', 22 | gender: 'Female', 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/conversions/true-date-from-month-count-day.ts: -------------------------------------------------------------------------------- 1 | import { meanDate, moon, sun } from '../helpers'; 2 | 3 | /** 4 | * The date at the end of the lunar day (similar to month count since beginning of epoch, but for days) 5 | * It is calculated by first calculating a simpler mean date, corresponding to the linear mean motion of 6 | * the moon, and then adjusting it by the equations of the moon and sun, which are determined by the 7 | * anomalies of the moon and sun together with tables. 8 | * @param {number} day - the tibetan day 9 | * @param {number} monthCount - month count since beginning of epoch 10 | * @returns {number} 11 | */ 12 | const trueDateFromMonthCountDay = (day: number, monthCount: number): number => ( 13 | meanDate(day, monthCount) 14 | + moon(day, monthCount) / 60 15 | - sun(day, monthCount) / 60 16 | ); 17 | 18 | export default trueDateFromMonthCountDay; 19 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { YEAR_ANIMALS, YEAR_ELEMENTS, YEAR_GENDER} from './constants'; 2 | 3 | export type Year = { 4 | tibYear: number, 5 | westernYear: number, 6 | rabjungCycle: number, 7 | rabjungYear: number, 8 | animal?: typeof YEAR_ANIMALS[number], 9 | element?: typeof YEAR_ELEMENTS[number], 10 | gender?: typeof YEAR_GENDER[number] 11 | } 12 | 13 | export type Month = { 14 | year: number, 15 | month: number, 16 | isLeapMonth: boolean, 17 | isDoubledMonth?: boolean, 18 | startDate?: string, 19 | endDate?: string 20 | }; 21 | 22 | export type Day = { 23 | year: number, 24 | month: { 25 | month: number, 26 | isLeapMonth: boolean, 27 | isDoubledMonth: boolean, 28 | }, 29 | day: number, 30 | skippedDay: boolean, 31 | isPreviousSkipped: boolean, 32 | isLeapDay: boolean, 33 | isDoubledDay: boolean, 34 | westernDate: string 35 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base", "plugin:@typescript-eslint/recommended"], 3 | "plugins": ["@typescript-eslint"], 4 | "parser": "@typescript-eslint/parser", 5 | "parserOptions": { 6 | "warnOnUnsupportedTypeScriptVersion": false, 7 | "project": "tsconfig.json", 8 | "ecmaVersion": 2018, 9 | "sourceType": "module", 10 | "createDefaultProgram": true 11 | }, 12 | "rules": { 13 | "max-len": [1, 120, 2], 14 | "quote-props": [1, "consistent-as-needed"], 15 | "no-cond-assign": [2, "except-parens"], 16 | "radix": 0, 17 | "no-unused-vars": "warn", 18 | "no-plusplus": "off", 19 | "comma-dangle": "off" 20 | }, 21 | "env": { 22 | "jest": true 23 | }, 24 | "settings": { 25 | "import/resolver": { 26 | "node": { 27 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /dist/dts/types.d.ts: -------------------------------------------------------------------------------- 1 | import { YEAR_ANIMALS, YEAR_ELEMENTS, YEAR_GENDER } from './constants'; 2 | export declare type Year = { 3 | tibYear: number; 4 | westernYear: number; 5 | rabjungCycle: number; 6 | rabjungYear: number; 7 | animal?: typeof YEAR_ANIMALS[number]; 8 | element?: typeof YEAR_ELEMENTS[number]; 9 | gender?: typeof YEAR_GENDER[number]; 10 | }; 11 | export declare type Month = { 12 | year: number; 13 | month: number; 14 | isLeapMonth: boolean; 15 | isDoubledMonth?: boolean; 16 | startDate?: string; 17 | endDate?: string; 18 | }; 19 | export declare type Day = { 20 | year: number; 21 | month: { 22 | month: number; 23 | isLeapMonth: boolean; 24 | isDoubledMonth: boolean; 25 | }; 26 | day: number; 27 | skippedDay: boolean; 28 | isPreviousSkipped: boolean; 29 | isLeapDay: boolean; 30 | isDoubledDay: boolean; 31 | westernDate: string; 32 | }; 33 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import commonjs from 'rollup-plugin-commonjs'; 3 | import typescript from 'rollup-plugin-typescript2'; 4 | import pkg from './package.json'; 5 | 6 | export default [ 7 | // browser-friendly UMD build 8 | { 9 | input: 'src/index.ts', 10 | output: { 11 | file: pkg.browser, 12 | format: 'umd', 13 | name: 'tibetanCalendarCalculator' 14 | }, 15 | plugins: [ 16 | resolve(), 17 | commonjs(), 18 | typescript({ 19 | useTsconfigDeclarationDir: true, 20 | }), 21 | ] 22 | }, 23 | // CommonJS (for Node) and ES module (for bundlers) build. 24 | { 25 | input: 'src/index.ts', 26 | plugins: [ 27 | resolve(), 28 | typescript({ 29 | useTsconfigDeclarationDir: true, 30 | }), 31 | ], 32 | output: [ 33 | { file: pkg.main, format: 'cjs' }, 34 | { file: pkg.module, format: 'es' }, 35 | ] 36 | } 37 | ]; 38 | -------------------------------------------------------------------------------- /dist/dts/helpers/moon.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * the anomaly of the moon 3 | * @param {number} day - the tibetan day 4 | * @param {number} monthCount - month count since beginning of epoch 5 | * @returns {number} 6 | */ 7 | declare const moonAnomaly: (day: number, monthCount: number) => number; 8 | /** 9 | * Moon tab for integer values 10 | * @param {number} i 11 | * @returns {number} 12 | */ 13 | declare const moonTabInt: (i: number) => number; 14 | /** 15 | * Moon tab, with linear interpolation 16 | * @param {number} i 17 | * @returns {number} 18 | */ 19 | declare const moonTab: (i: number) => number; 20 | /** 21 | * Equation of the moon. 22 | * @param {number} day - the tibetan day 23 | * @param {number} monthCount - month count since beginning of epoch 24 | * @returns {number} 25 | */ 26 | declare const moonEqu: (day: number, monthCount: number) => number; 27 | export default moonEqu; 28 | export { moonAnomaly, moonTab, moonTabInt, moonEqu }; 29 | -------------------------------------------------------------------------------- /src/generators/get-month-from-month-count.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BETA, 3 | YEAR0, 4 | YEAR_DIFF, 5 | } from '../constants'; 6 | import { amod } from '../helpers'; 7 | import { Month } from '../types' 8 | 9 | 10 | /** 11 | * Figures out the Tibetan year number, month number within the year, and whether 12 | * this is a leap month, from a "month count" number. See Svante Janson, 13 | * "Tibetan Calendar Mathematics", p.8 ff. 14 | * 15 | * @param {number} monthCount: the "month count" since beginning of epoch 16 | * @returns {Month} 17 | */ 18 | const getMonthFromMonthCount = (monthCount: number): Month => { 19 | // const x = ceil(12 * S1 * n + ALPHA); 20 | const x = Math.ceil((65 * monthCount + BETA) / 67); 21 | const tMonth = amod(x, 12); 22 | const tYear = Math.ceil(x / 12) - 1 + YEAR0 + YEAR_DIFF; 23 | const isLeapMonth = Math.ceil((65 * (monthCount + 1) + BETA) / 67) === x; 24 | 25 | return { year: tYear, month: tMonth, isLeapMonth }; 26 | }; 27 | 28 | export default getMonthFromMonthCount; 29 | -------------------------------------------------------------------------------- /src/conversions/julian-from-tibetan.ts: -------------------------------------------------------------------------------- 1 | import monthCountFromTibetan from './month-count-from-tibetan'; 2 | import getTrueDate from './true-date-from-month-count-day'; 3 | 4 | /** 5 | * Gives the Julian date for a Tibetan year, month number (leap or not) and Tibetan day. 6 | * 7 | * Does not check that the tibetan day actually exists: 8 | * - If given the date of a skipped day, will return the same Julian date as the day before. 9 | * - If given the date of a duplicate day, returns the Julian date of the second of the two. 10 | * 11 | * @param {number} year - Tibetan year 12 | * @param {number} month - Tibetan month 13 | * @param {boolean} isLeapMonth - true if leap month 14 | * @param {number} day - Tibetan day 15 | * @returns {number} - Julian date 16 | */ 17 | const julianFromTibetan = (year: number, month: number, isLeapMonth: boolean, day: number): number => { 18 | const monthCount = monthCountFromTibetan({ year, month, isLeapMonth }); 19 | 20 | return Math.floor(getTrueDate(day, monthCount)); 21 | }; 22 | 23 | export default julianFromTibetan; 24 | -------------------------------------------------------------------------------- /src/helpers/__tests__/moon.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | moonAnomaly, 3 | moonTab, 4 | moonTabInt, 5 | moonEqu 6 | } from '../moon'; 7 | import { astroMock } from '../../__mocks__'; 8 | 9 | describe('moonAnomaly()', () => { 10 | it('shold return the correct value', () => { 11 | expect(moonAnomaly(astroMock.day, astroMock.monthCount)).toEqual(astroMock.moonAnomaly); 12 | }); 13 | }); 14 | 15 | describe('moonTabInt()', () => { 16 | it('should return the correct value for all cases of i%28', () => { 17 | expect(moonTabInt(3)).toEqual(15); 18 | expect(moonTabInt(9)).toEqual(22); 19 | expect(moonTabInt(15)).toEqual(-5); 20 | expect(moonTabInt(28)).toEqual(0); 21 | }); 22 | }); 23 | 24 | describe('moonTab()', () => { 25 | it('should return the correct value', () => { 26 | expect(moonTab(4)).toEqual(19); 27 | expect(moonTab(3.4)).toEqual(16.6); 28 | }); 29 | }); 30 | 31 | describe('moonEqu()', () => { 32 | it('should return the correct value', () => { 33 | expect(moonEqu(astroMock.day, astroMock.monthCount)).toEqual(astroMock.moonEqu); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /dist/dts/generators/get-day-from-tibetan.d.ts: -------------------------------------------------------------------------------- 1 | import { Day } from '../types'; 2 | /** 3 | * Calculates full information for a given Tibetan date 4 | * 5 | * For doubled days, just as with doubled months, the "main" day or month is 6 | * the second, and the "leap" day or month is the first. 7 | * 8 | * @param {object} arg 9 | * @param {number} arg.year - Tibetan year number (ex. 2135) 10 | * @param {number} arg.month - month number (1 to 12) 11 | * @param {boolean} [arg.isLeapMonth=false] - is this month a leap month 12 | * @param {number} arg.day - day number (1 to 30) 13 | * @param {boolean} [arg.isLeapDay=false] - is this day a leap day 14 | * 15 | * @returns {Day} day - with all its attributes. isLeapMonth and isLeapDay are checked and corrected compared to input 16 | */ 17 | declare const getDayFromTibetan: ({ year, month, isLeapMonth, day, isLeapDay }: { 18 | year: number; 19 | month: number; 20 | isLeapMonth?: boolean; 21 | day: number; 22 | isLeapDay?: boolean; 23 | }) => Day; 24 | export default getDayFromTibetan; 25 | -------------------------------------------------------------------------------- /src/conversions/month-count-from-tibetan.ts: -------------------------------------------------------------------------------- 1 | import { 2 | YEAR0, 3 | YEAR_DIFF, 4 | MONTH0, 5 | BETA_STAR 6 | } from '../constants'; 7 | import { isDoubledMonth } from '../helpers'; 8 | import { Month } from '../types' 9 | 10 | /** 11 | * This is the reverse of fromMonthCount(n): from a Tibetan year, month number 12 | * and leap month indicator, calculates the "month count" based on the epoch. 13 | * 14 | * @param {Month} monthObject 15 | * @returns {number} month count since epoch 16 | */ 17 | const monthCountFromTibetan = ({ year, month, isLeapMonth }: Month): number => { 18 | // the formulas on Svante's paper use western year numbers 19 | const wYear = year - YEAR_DIFF; 20 | const solarMonth = 12 * (wYear - YEAR0) + month - MONTH0; 21 | const hasLeap = isDoubledMonth(year, month); 22 | const isLeap = hasLeap && isLeapMonth ? 1 : 0; 23 | 24 | return Math.floor((67 * solarMonth + BETA_STAR + 17) / 65) - isLeap; 25 | // return Math.floor((12 * (year - Y0) + monthObject.month - ALPHA - (1 - 12 * S1) * isLeap) / (12 * S1)); 26 | }; 27 | 28 | export default monthCountFromTibetan; 29 | -------------------------------------------------------------------------------- /src/__mocks__/month-mock.ts: -------------------------------------------------------------------------------- 1 | const simpleMonthMock = { 2 | info: { 3 | year: 2145, 4 | month: 1, 5 | isLeapMonth: false 6 | }, 7 | fullInfo: { 8 | year: 2145, 9 | month: 1, 10 | isLeapMonth: false, 11 | isDoubledMonth: false, 12 | startDate: '2018-02-16', 13 | endDate: '2018-03-17' 14 | }, 15 | monthCount: 14990 16 | }; 17 | 18 | const leapMonthMock = { 19 | info: { 20 | year: 2140, 21 | month: 8, 22 | isLeapMonth: true 23 | }, 24 | fullInfo: { 25 | year: 2140, 26 | month: 8, 27 | isLeapMonth: true, 28 | isDoubledMonth: true, 29 | startDate: '2013-09-06', 30 | endDate: '2013-10-04' 31 | }, 32 | monthCount: 14935 33 | }; 34 | 35 | const mainMonthMock = { 36 | info: { 37 | year: 2140, 38 | month: 8, 39 | isLeapMonth: false 40 | }, 41 | fullInfo: { 42 | year: 2140, 43 | month: 8, 44 | isLeapMonth: false, 45 | isDoubledMonth: true, 46 | startDate: '2013-10-05', 47 | endDate: '2013-11-03' 48 | }, 49 | monthCount: 14936 50 | }; 51 | 52 | export { 53 | simpleMonthMock, 54 | leapMonthMock, 55 | mainMonthMock 56 | }; 57 | -------------------------------------------------------------------------------- /dist/dts/classes/tibetan-month.d.ts: -------------------------------------------------------------------------------- 1 | import TibetanDate from './tibetan-date'; 2 | import TibetanYear from './tibetan-year'; 3 | declare type Arg = (string | { 4 | year: number; 5 | month: number; 6 | isLeapMonth?: boolean; 7 | }); 8 | /** 9 | * A TibetanMonth class 10 | * @param {...(object,string)} [arg] undefined will return tibetan month for 11 | * current month | string will return tibetan day for month of `new Date(arg)` | 12 | * object will return tibetan month according to object definition 13 | * @param {number} arg.year - Tibetan year number (ex. 2135) 14 | * @param {number} arg.month - month number (1 to 12) 15 | * @param {boolean} [arg.isLeapMonth=false] - is this month a leap month 16 | */ 17 | declare class TibetanMonth { 18 | year: number; 19 | month: number; 20 | isLeapMonth: boolean; 21 | isDoubledMonth: boolean; 22 | startDateStr: string; 23 | endDateStr: string; 24 | days: TibetanDate[]; 25 | constructor(arg?: Arg); 26 | get yearObj(): TibetanYear; 27 | getDays(): TibetanDate[]; 28 | toString(): string; 29 | } 30 | export default TibetanMonth; 31 | -------------------------------------------------------------------------------- /src/generators/get-year-from-rabjung.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RABJUNG_BEGINNING, 3 | RABJUNG_CYCLE_LENGTH, 4 | YEAR_DIFF 5 | } from '../constants'; 6 | import { yearAttributes } from '../helpers'; 7 | import { Year } from '../types' 8 | 9 | /** 10 | * Figures out a year's info based on the Tibetan calendar, ex. the 3rd year of the 15th Rabjung calendrical cycle. 11 | * @param {object} arg 12 | * @param {number} arg.rabjungCycle : number of the cycle 13 | * @param {number} arg.rabjungYear : number of the year within the cycle, from 1 to 60. 14 | * @returns {null | Year} 15 | */ 16 | const getYearFromRabjung = ({ rabjungCycle, rabjungYear }: { rabjungCycle: number, rabjungYear: number }): (Year | null) => { 17 | if (rabjungCycle < 1 || rabjungYear > RABJUNG_CYCLE_LENGTH) { 18 | throw new Error(`Year number must be between 1 and ${RABJUNG_CYCLE_LENGTH}`); 19 | } 20 | const wYear = RABJUNG_BEGINNING + (rabjungCycle - 1) * RABJUNG_CYCLE_LENGTH + (rabjungYear - 1); 21 | const year = yearAttributes({ 22 | rabjungCycle, 23 | rabjungYear, 24 | tibYear: wYear + YEAR_DIFF, 25 | westernYear: wYear, 26 | }); 27 | 28 | return year; 29 | }; 30 | 31 | export default getYearFromRabjung; 32 | -------------------------------------------------------------------------------- /dist/dts/constants/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare const RABJUNG_BEGINNING: 1027; 2 | export declare const RABJUNG_CYCLE_LENGTH: 60; 3 | export declare const YEAR_DIFF: 127; 4 | export declare const JULIAN_TO_UNIX: 2440587.5; 5 | export declare const MS_IN_YEAR: 86400000; 6 | export declare const MIN_IN_DAY: 1440; 7 | export declare const YEAR0: 806; 8 | export declare const MONTH0: 3; 9 | export declare const BETA_STAR: 61; 10 | export declare const BETA: 123; 11 | export declare const M0: number; 12 | export declare const M1: number; 13 | export declare const M2: number; 14 | export declare const S0: number; 15 | export declare const S1: number; 16 | export declare const S2: number; 17 | export declare const A0: number; 18 | export declare const A1: number; 19 | export declare const A2: number; 20 | export declare const MOON_TAB: readonly [0, 5, 10, 15, 19, 22, 24, 25]; 21 | export declare const SUN_TAB: readonly [0, 6, 10, 11]; 22 | export declare const YEAR_ELEMENTS: readonly ["Wood", "Fire", "Earth", "Iron", "Water"]; 23 | export declare const YEAR_ANIMALS: readonly ["Mouse", "Ox", "Tiger", "Rabbit", "Dragon", "Snake", "Horse", "Sheep", "Monkey", "Bird", "Dog", "Pig"]; 24 | export declare const YEAR_GENDER: readonly ["Male", "Female"]; 25 | -------------------------------------------------------------------------------- /dist/dts/helpers/sun.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * the mean longitude of the sun 3 | * @param {number} day - the tibetan day 4 | * @param {number} monthCount - month count since beginning of epoch 5 | * @returns {number} 6 | */ 7 | declare const meanSun: (day: number, monthCount: number) => number; 8 | /** 9 | * sunAnomaly(day, monthCount) 10 | * @param {number} day - the tibetan day 11 | * @param {number} monthCount - month count since beginning of epoch 12 | * @returns {number} 13 | */ 14 | declare const sunAnomaly: (day: number, monthCount: number) => number; 15 | /** 16 | * sun tab for integer values 17 | * @param {number} i 18 | * @returns {number} 19 | */ 20 | declare const sunTabInt: (i: number) => number; 21 | /** 22 | * sun tab, with linear interpolation 23 | * @param {number} i 24 | * @returns {number} 25 | */ 26 | declare const sunTab: (i: number) => number; 27 | /** 28 | * Equation of the sun. 29 | * @param {number} day - the tibetan day 30 | * @param {number} monthCount - month count since beginning of epoch 31 | * @returns {number} 32 | */ 33 | declare const sunEqu: (day: number, monthCount: number) => number; 34 | export default sunEqu; 35 | export { meanSun, sunAnomaly, sunTab, sunTabInt, sunEqu }; 36 | -------------------------------------------------------------------------------- /src/helpers/__tests__/sun.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | meanSun, 3 | sunAnomaly, 4 | sunTab, 5 | sunTabInt, 6 | sunEqu 7 | } from '../sun'; 8 | import { astroMock } from '../../__mocks__'; 9 | 10 | describe('meanSun()', () => { 11 | it('should retrun the correct value', () => { 12 | expect(meanSun(astroMock.day, astroMock.monthCount)).toEqual(astroMock.meanSun); 13 | }); 14 | }); 15 | 16 | 17 | describe('sunAnomaly()', () => { 18 | it('shold return the correct value', () => { 19 | expect(sunAnomaly(astroMock.day, astroMock.monthCount)).toEqual(astroMock.sunAnomaly); 20 | }); 21 | }); 22 | 23 | describe('sunTabInt()', () => { 24 | it('should return the correct value for all cases of i%12', () => { 25 | expect(sunTabInt(3)).toEqual(11); 26 | expect(sunTabInt(4)).toEqual(10); 27 | expect(sunTabInt(8)).toEqual(-10); 28 | expect(sunTabInt(11)).toEqual(-6); 29 | }); 30 | }); 31 | 32 | describe('sunTab()', () => { 33 | it('should return the correct value', () => { 34 | expect(sunTab(4)).toEqual(10); 35 | expect(sunTab(3.4)).toEqual(10.6); 36 | }); 37 | }); 38 | 39 | describe('sunEqu()', () => { 40 | it('should return the correct value', () => { 41 | expect(sunEqu(astroMock.day, astroMock.monthCount)).toEqual(astroMock.sunEqu); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /dist/dts/classes/tibetan-year.d.ts: -------------------------------------------------------------------------------- 1 | import TibetanMonth from './tibetan-month'; 2 | import { YEAR_ANIMALS, YEAR_ELEMENTS, YEAR_GENDER } from '../constants'; 3 | declare type Arg = (number | { 4 | rabjungCycle: number; 5 | rabjungYear: number; 6 | }); 7 | /** 8 | * A TibetanYear class 9 | * @param {...(object,number)} [arg] undefined will return tibetan year for 10 | * current year | number will return tibetan year unless isWestern is true | 11 | * object will return tibetan year according to rabjung cycle 12 | * @param {number} arg.rabjungCycle number of the cycle 13 | * @param {number} arg.rabjungYear number of the year within the cycle, 14 | * from 1 to 60. 15 | * @param {bool} [isWestern=false] optional second argument, if set to true 16 | * and fist arg is a number it will be treated as western year date 17 | */ 18 | declare class TibetanYear { 19 | rabjungCycle: number; 20 | rabjungYear: number; 21 | tibYearNum: number; 22 | westernYear: number; 23 | animal: typeof YEAR_ANIMALS[number]; 24 | element: typeof YEAR_ELEMENTS[number]; 25 | gender: typeof YEAR_GENDER[number]; 26 | months: TibetanMonth[]; 27 | constructor(arg?: Arg, isWestern?: boolean); 28 | getMonths(): TibetanMonth[]; 29 | toString(): string; 30 | toRabjungString(): string; 31 | } 32 | export default TibetanYear; 33 | -------------------------------------------------------------------------------- /src/classes/__tests__/__snapshots__/tibetan-year.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TibetanYear should create a correct class when invoked with number arg 1`] = ` 4 | TibetanYear { 5 | "animal": "Dog", 6 | "element": "Earth", 7 | "gender": "Male", 8 | "months": Array [], 9 | "rabjungCycle": 17, 10 | "rabjungYear": 32, 11 | "tibYearNum": 2145, 12 | "westernYear": 2018, 13 | } 14 | `; 15 | 16 | exports[`TibetanYear should create a correct class when invoked with number arg and western set to true 1`] = ` 17 | TibetanYear { 18 | "animal": "Snake", 19 | "element": "Water", 20 | "gender": "Female", 21 | "months": Array [], 22 | "rabjungCycle": 17, 23 | "rabjungYear": 27, 24 | "tibYearNum": 2140, 25 | "westernYear": 2013, 26 | } 27 | `; 28 | 29 | exports[`TibetanYear should create a correct class when invoked with object arg 1`] = ` 30 | TibetanYear { 31 | "animal": "Pig", 32 | "element": "Earth", 33 | "gender": "Female", 34 | "months": Array [], 35 | "rabjungCycle": 17, 36 | "rabjungYear": 33, 37 | "tibYearNum": 2146, 38 | "westernYear": 2019, 39 | } 40 | `; 41 | 42 | exports[`TibetanYear should create a correct class when invoked without arg 1`] = ` 43 | TibetanYear { 44 | "animal": "Pig", 45 | "element": "Earth", 46 | "gender": "Female", 47 | "months": Array [], 48 | "rabjungCycle": 17, 49 | "rabjungYear": 33, 50 | "tibYearNum": 2146, 51 | "westernYear": 2019, 52 | } 53 | `; 54 | -------------------------------------------------------------------------------- /src/generators/__tests__/get-month-from-tibetan.spec.ts: -------------------------------------------------------------------------------- 1 | import getMonthFromTibetan from '../get-month-from-tibetan'; 2 | import { simpleMonthMock, leapMonthMock, mainMonthMock } from '../../__mocks__'; 3 | 4 | 5 | describe('getMonthFromTibetan()', () => { 6 | it('should give the correct info for simple month', () => { 7 | const result = getMonthFromTibetan({ 8 | year: simpleMonthMock.info.year, 9 | month: simpleMonthMock.info.month, 10 | isLeapMonth: simpleMonthMock.info.isLeapMonth 11 | }); 12 | expect(result).toEqual(simpleMonthMock.fullInfo); 13 | }); 14 | it('should give the correct info for simple month when leap is wrongly indicated', () => { 15 | const result = getMonthFromTibetan({ 16 | year: simpleMonthMock.info.year, 17 | month: simpleMonthMock.info.month, 18 | isLeapMonth: true 19 | }); 20 | expect(result).toEqual(simpleMonthMock.fullInfo); 21 | }); 22 | it('should give the correct info for a leap month', () => { 23 | const result = getMonthFromTibetan({ 24 | year: leapMonthMock.info.year, 25 | month: leapMonthMock.info.month, 26 | isLeapMonth: leapMonthMock.info.isLeapMonth 27 | }); 28 | expect(result).toEqual(leapMonthMock.fullInfo); 29 | }); 30 | it('should give the correct info for a main month that has leap', () => { 31 | const result = getMonthFromTibetan({ 32 | year: mainMonthMock.info.year, 33 | month: mainMonthMock.info.month, 34 | isLeapMonth: mainMonthMock.info.isLeapMonth 35 | }); 36 | expect(result).toEqual(mainMonthMock.fullInfo); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/generators/get-month-from-tibetan.ts: -------------------------------------------------------------------------------- 1 | import { unixFromJulian, monthCountFromTibetan, trueDateFromMonthCountDay as getTrueDate } from '../conversions'; 2 | import { isDoubledMonth, getDateStr } from '../helpers'; 3 | import { Month } from '../types'; 4 | 5 | 6 | /** 7 | * Calculates full information about a Tibetan month: whether it is doubled or not, 8 | * and the western start and end date for it. 9 | * The start_date and end_date correspond to the leap month if isLeapMonth is true, 10 | * otherwise to the main month (i.e the second of the two). 11 | * 12 | * @param {object} arg 13 | * @param {number} arg.year - the Tibetan year 14 | * @param {number} arg.month - the Tibetan month number (1 to 12) 15 | * @param {boolean} [arg.isLeapMonth=false] - if leap month or not 16 | * @returns {Month} 17 | */ 18 | const getMonthFromTibetan = ({ year, month, isLeapMonth = false }: { year: number; month: number; isLeapMonth?: boolean }): Month => { 19 | const hasLeap = isDoubledMonth(year, month); 20 | const isLeap = isLeapMonth && hasLeap; 21 | 22 | // calculate the Julian date 1st and last of the month 23 | const monthCount = monthCountFromTibetan({ year, month, isLeapMonth: isLeap }); 24 | const jdFirst = 1 + Math.floor(getTrueDate(30, monthCount - 1)); 25 | const jdLast = Math.floor(getTrueDate(30, monthCount)); 26 | const startDate = getDateStr(unixFromJulian(jdFirst)); 27 | const endDate = getDateStr(unixFromJulian(jdLast)); 28 | 29 | return { 30 | year, month, isLeapMonth: isLeap, isDoubledMonth: hasLeap, startDate, endDate, 31 | }; 32 | }; 33 | 34 | export default getMonthFromTibetan; 35 | -------------------------------------------------------------------------------- /src/helpers/moon.ts: -------------------------------------------------------------------------------- 1 | import { 2 | A0, A1, A2, 3 | MOON_TAB 4 | } from '../constants'; 5 | import { frac } from './math'; 6 | 7 | 8 | /** 9 | * the anomaly of the moon 10 | * @param {number} day - the tibetan day 11 | * @param {number} monthCount - month count since beginning of epoch 12 | * @returns {number} 13 | */ 14 | const moonAnomaly = (day: number, monthCount: number) => monthCount * A1 + day * A2 + A0; 15 | 16 | /** 17 | * Moon tab for integer values 18 | * @param {number} i 19 | * @returns {number} 20 | */ 21 | const moonTabInt = (i: number) => { 22 | const iMod = i % 28; 23 | if (iMod <= 7) { 24 | return MOON_TAB[iMod]; 25 | } 26 | if (iMod <= 14) { 27 | return MOON_TAB[14 - iMod]; 28 | } 29 | if (iMod <= 21) { 30 | return -MOON_TAB[iMod - 14]; 31 | } 32 | return -MOON_TAB[28 - iMod]; 33 | }; 34 | 35 | /** 36 | * Moon tab, with linear interpolation 37 | * @param {number} i 38 | * @returns {number} 39 | */ 40 | const moonTab = (i: number) => { 41 | let a = moonTabInt(Math.floor(i)); 42 | const x = frac(i); 43 | if (x) { 44 | const b = moonTabInt(Math.floor(i) + 1); 45 | a += (b - a) * x; 46 | } 47 | 48 | return a; 49 | }; 50 | 51 | /** 52 | * Equation of the moon. 53 | * @param {number} day - the tibetan day 54 | * @param {number} monthCount - month count since beginning of epoch 55 | * @returns {number} 56 | */ 57 | const moonEqu = (day: number, monthCount: number): number => moonTab(28 * moonAnomaly(day, monthCount)); 58 | 59 | export default moonEqu; 60 | export { 61 | moonAnomaly, 62 | moonTab, 63 | moonTabInt, 64 | moonEqu 65 | }; 66 | -------------------------------------------------------------------------------- /src/classes/__tests__/tibetan-year.spec.ts: -------------------------------------------------------------------------------- 1 | import MockDate from 'mockdate'; 2 | import TibetanYear from '../tibetan-year'; 3 | 4 | describe('TibetanYear', () => { 5 | beforeAll(() => { 6 | MockDate.set('2019/06/07'); 7 | }); 8 | afterAll(() => { 9 | MockDate.reset(); 10 | }); 11 | 12 | it('should create a correct class when invoked without arg', () => { 13 | expect(new TibetanYear()).toMatchSnapshot(); 14 | }); 15 | it('should create a correct class when invoked with number arg', () => { 16 | expect(new TibetanYear(2145)).toMatchSnapshot(); 17 | }); 18 | it('should create a correct class when invoked with number arg and western set to true', () => { 19 | expect(new TibetanYear(2013, true)).toMatchSnapshot(); 20 | }); 21 | it('should create a correct class when invoked with object arg', () => { 22 | expect(new TibetanYear({ 23 | rabjungCycle: 17, rabjungYear: 33 24 | })).toMatchSnapshot(); 25 | }); 26 | it('should not calculate months when initially incoved', () => { 27 | const year = new TibetanYear(); 28 | expect(year.months.length).toEqual(0); 29 | }); 30 | it('should not calculate months when requested', () => { 31 | const year = new TibetanYear(); 32 | const months = year.getMonths(); 33 | 34 | expect(year.months.length).not.toEqual(0); 35 | expect(months.length).toEqual(13); 36 | }); 37 | it('should return the correct string when `toRabjungString` is called', () => { 38 | const year = new TibetanYear(); 39 | expect(year.toRabjungString()).toEqual('The 33. year of the 17. rabjung cycle'); 40 | }); 41 | it('should return the correct string when `toString` is called', () => { 42 | const year = new TibetanYear(); 43 | expect(year.toString()).toEqual('2146'); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/generators/__tests__/get-day-from-western.spec.ts: -------------------------------------------------------------------------------- 1 | import getDayFromWestern from '../get-day-from-western'; 2 | import { dayInDuplicateMonthMock, skippedDayMock, duplicateDayMock } from '../../__mocks__'; 3 | 4 | describe('getDayFromWestern()', () => { 5 | it('should return the correct object for day in leap month', () => { 6 | const day = getDayFromWestern(new Date(`${dayInDuplicateMonthMock.westernLeap}T12:00`)); 7 | expect(day) 8 | .toMatchSnapshot(); 9 | expect(day.month.isDoubledMonth).toBeTruthy(); 10 | expect(day.month.isLeapMonth).toBeTruthy(); 11 | }); 12 | it('should return the correct object for day in main doubled month', () => { 13 | const day = getDayFromWestern(new Date(`${dayInDuplicateMonthMock.westernMain}T12:00`)); 14 | expect(day).toMatchSnapshot(); 15 | expect(day.month.isDoubledMonth).toBeTruthy(); 16 | expect(day.month.isLeapMonth).toBeFalsy(); 17 | }); 18 | it('should not return the object with skipped day', () => { 19 | const day = getDayFromWestern(new Date(`${skippedDayMock.western}T12:00`)); 20 | expect(day).not.toEqual(skippedDayMock.dayObject); 21 | expect(day.day).toEqual(skippedDayMock.day - 1); 22 | }); 23 | it('should return the correct object for leap day', () => { 24 | const day = getDayFromWestern(new Date(`${duplicateDayMock.westernLeap}T12:00`)); 25 | expect(day).toMatchSnapshot(); 26 | expect(day.isDoubledDay).toBeTruthy(); 27 | expect(day.isLeapDay).toBeTruthy(); 28 | }); 29 | it('should return the correct object for main duplicate day', () => { 30 | const day = getDayFromWestern(new Date(`${duplicateDayMock.westernMain}T12:00`)); 31 | expect(day).toMatchSnapshot(); 32 | expect(day.isDoubledDay).toBeTruthy(); 33 | expect(day.isLeapDay).toBeFalsy(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /dist/dts/classes/tibetan-date.d.ts: -------------------------------------------------------------------------------- 1 | import TibetanMonth from './tibetan-month'; 2 | import TibetanYear from './tibetan-year'; 3 | declare type Arg = (string | { 4 | year: number; 5 | month: number; 6 | isLeapMonth?: boolean; 7 | day: number; 8 | isLeapDay?: boolean; 9 | }); 10 | declare type SimpleMonth = { 11 | month: number; 12 | isLeapMonth: boolean; 13 | isDoubledMonth: boolean; 14 | }; 15 | /** 16 | * A TibetanDate class 17 | * @param {...(object,string)} [arg] undefined will return tibetan date 18 | * for today | string will return tibetan day for `new Date(arg)` | object 19 | * will return tibetan day according to object definition 20 | * @param {number} arg.year - Tibetan year number (ex. 2135) 21 | * @param {number} arg.month - month number (1 to 12) 22 | * @param {boolean} [arg.isLeapMonth=false] - is this month a leap month 23 | * @param {number} arg.day - day number (1 to 30) 24 | * @param {boolean} [arg.isLeapDay=false] - is this day a leap day 25 | */ 26 | declare class TibetanDate { 27 | year: number; 28 | month: SimpleMonth; 29 | skippedDay: boolean; 30 | isPreviousSkipped: boolean; 31 | isLeapDay: boolean; 32 | isDoubledDay: boolean; 33 | westernDate: Date; 34 | isSkippedDay: boolean; 35 | date: number; 36 | monthObj: TibetanMonth; 37 | constructor(arg?: Arg); 38 | /** GETTERS */ 39 | get day(): number; 40 | get yearObj(): TibetanYear; 41 | get westernDateStr(): string; 42 | /** METHODS */ 43 | getWesternDate(): Date; 44 | getDate(): number; 45 | getDay(): number; 46 | getMonth(): SimpleMonth; 47 | getMonthObj(): TibetanMonth; 48 | getYear(): number; 49 | getYearObj(): TibetanYear; 50 | toString(): string; 51 | } 52 | export default TibetanDate; 53 | -------------------------------------------------------------------------------- /src/generators/__tests__/get-day-from-tibetan.spec.ts: -------------------------------------------------------------------------------- 1 | import getDayFromTibetan from '../get-day-from-tibetan'; 2 | import { dayInDuplicateMonthMock, skippedDayMock, duplicateDayMock } from '../../__mocks__'; 3 | 4 | describe('getDayFromTibetan()', () => { 5 | it('should return the correct western date in leap month', () => { 6 | const { 7 | year, month, day 8 | } = dayInDuplicateMonthMock; 9 | expect(getDayFromTibetan({ 10 | year, month, isLeapMonth: true, day, isLeapDay: false 11 | })).toMatchSnapshot(); 12 | }); 13 | it('should return the correct western date in non-leap month', () => { 14 | const { 15 | year, month, day 16 | } = dayInDuplicateMonthMock; 17 | expect(getDayFromTibetan({ year, month, day })).toMatchSnapshot(); 18 | }); 19 | it('should return the correct western date for day marked as leap even though it isn\'t', () => { 20 | const { 21 | year, month, day 22 | } = dayInDuplicateMonthMock; 23 | expect(getDayFromTibetan({ 24 | year, month, day, isLeapDay: true 25 | })).toMatchSnapshot(); 26 | }); 27 | it('should return the correct western date for skipped day', () => { 28 | const { 29 | year, month, day 30 | } = skippedDayMock; 31 | expect(getDayFromTibetan({ 32 | year, month, isLeapMonth: true, day 33 | })).toMatchSnapshot(); 34 | }); 35 | it('should return the correct western date for leap day', () => { 36 | const { 37 | year, month, day 38 | } = duplicateDayMock; 39 | expect(getDayFromTibetan({ 40 | year, month, day, isLeapDay: true 41 | })).toMatchSnapshot(); 42 | }); 43 | it('should return the correct western date for main day that has leap day', () => { 44 | const { 45 | year, month, day 46 | } = duplicateDayMock; 47 | expect(getDayFromTibetan({ year, month, day })).toMatchSnapshot(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/generators/__tests__/__snapshots__/get-day-from-western.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`getDayFromWestern() should return the correct object for day in leap month 1`] = ` 4 | Object { 5 | "day": 4, 6 | "isDoubledDay": false, 7 | "isLeapDay": false, 8 | "isPreviousSkipped": false, 9 | "month": Object { 10 | "isDoubledMonth": true, 11 | "isLeapMonth": true, 12 | "month": 8, 13 | }, 14 | "skippedDay": false, 15 | "westernDate": 2013-09-09T10:00:00.000Z, 16 | "year": 2140, 17 | } 18 | `; 19 | 20 | exports[`getDayFromWestern() should return the correct object for day in main doubled month 1`] = ` 21 | Object { 22 | "day": 4, 23 | "isDoubledDay": false, 24 | "isLeapDay": false, 25 | "isPreviousSkipped": false, 26 | "month": Object { 27 | "isDoubledMonth": true, 28 | "isLeapMonth": false, 29 | "month": 8, 30 | }, 31 | "skippedDay": false, 32 | "westernDate": 2013-10-08T10:00:00.000Z, 33 | "year": 2140, 34 | } 35 | `; 36 | 37 | exports[`getDayFromWestern() should return the correct object for leap day 1`] = ` 38 | Object { 39 | "day": 8, 40 | "isDoubledDay": true, 41 | "isLeapDay": true, 42 | "isPreviousSkipped": false, 43 | "month": Object { 44 | "isDoubledMonth": false, 45 | "isLeapMonth": false, 46 | "month": 2, 47 | }, 48 | "skippedDay": false, 49 | "westernDate": 2013-03-19T10:00:00.000Z, 50 | "year": 2140, 51 | } 52 | `; 53 | 54 | exports[`getDayFromWestern() should return the correct object for main duplicate day 1`] = ` 55 | Object { 56 | "day": 8, 57 | "isDoubledDay": true, 58 | "isLeapDay": false, 59 | "isPreviousSkipped": false, 60 | "month": Object { 61 | "isDoubledMonth": false, 62 | "isLeapMonth": false, 63 | "month": 2, 64 | }, 65 | "skippedDay": false, 66 | "westernDate": 2013-03-20T10:00:00.000Z, 67 | "year": 2140, 68 | } 69 | `; 70 | -------------------------------------------------------------------------------- /src/helpers/sun.ts: -------------------------------------------------------------------------------- 1 | import { 2 | S0, S1, S2, SUN_TAB 3 | } from '../constants'; 4 | import { frac } from './math'; 5 | 6 | /** 7 | * the mean longitude of the sun 8 | * @param {number} day - the tibetan day 9 | * @param {number} monthCount - month count since beginning of epoch 10 | * @returns {number} 11 | */ 12 | const meanSun = (day: number, monthCount: number) => monthCount * S1 + day * S2 + S0; 13 | 14 | 15 | /** 16 | * sunAnomaly(day, monthCount) 17 | * @param {number} day - the tibetan day 18 | * @param {number} monthCount - month count since beginning of epoch 19 | * @returns {number} 20 | */ 21 | const sunAnomaly = (day: number, monthCount: number) => meanSun(day, monthCount) - 1 / 4; 22 | 23 | /** 24 | * sun tab for integer values 25 | * @param {number} i 26 | * @returns {number} 27 | */ 28 | const sunTabInt = (i: number) => { 29 | const iMod = i % 12; 30 | if (iMod <= 3) { 31 | return SUN_TAB[iMod]; 32 | } 33 | if (iMod <= 6) { 34 | return SUN_TAB[6 - iMod]; 35 | } 36 | if (iMod <= 9) { 37 | return -SUN_TAB[iMod - 6]; 38 | } 39 | return -SUN_TAB[12 - iMod]; 40 | }; 41 | 42 | /** 43 | * sun tab, with linear interpolation 44 | * @param {number} i 45 | * @returns {number} 46 | */ 47 | const sunTab = (i: number) => { 48 | let a = sunTabInt(Math.floor(i)); 49 | const x = frac(i); 50 | if (x) { 51 | const b = sunTabInt(Math.floor(i) + 1); 52 | a += (b - a) * x; 53 | } 54 | 55 | return a; 56 | }; 57 | 58 | /** 59 | * Equation of the sun. 60 | * @param {number} day - the tibetan day 61 | * @param {number} monthCount - month count since beginning of epoch 62 | * @returns {number} 63 | */ 64 | const sunEqu = (day: number, monthCount: number): number => sunTab(12 * sunAnomaly(day, monthCount)); 65 | 66 | export default sunEqu; 67 | export { 68 | meanSun, 69 | sunAnomaly, 70 | sunTab, 71 | sunTabInt, 72 | sunEqu 73 | }; 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tibetan-date-calculator", 3 | "description": "A library to calculate tibetan calendar details. It is based on Svante Janson's paper www2.math.uu.se/~svante/papers/calendars/tibet.pdf", 4 | "version": "1.2.1", 5 | "author": "Rigpa", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "module": "dist/index.es.js", 9 | "browser": "dist/index.umd.js", 10 | "scripts": { 11 | "lint": "eslint . --fix", 12 | "test": "jest --coverage", 13 | "build": "rollup -c" 14 | }, 15 | "dependencies": {}, 16 | "devDependencies": { 17 | "@types/jest": "^24.0.22", 18 | "@types/node": "^12.12.7", 19 | "@types/react": "^16.9.11", 20 | "@typescript-eslint/eslint-plugin": "^2.7.0", 21 | "@typescript-eslint/parser": "^2.7.0", 22 | "eslint": "^6.6.0", 23 | "eslint-config-airbnb-base": "^13.0.0", 24 | "eslint-plugin-import": "^2.13.0", 25 | "jest": "^24.9.0", 26 | "mockdate": "^2.0.2", 27 | "rollup": "^1.27.0", 28 | "rollup-plugin-commonjs": "^10.1.0", 29 | "rollup-plugin-node-resolve": "^5.2.0", 30 | "rollup-plugin-typescript2": "^0.25.2", 31 | "ts-jest": "^24.1.0", 32 | "typescript": "^3.7.2" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+ssh://git@gitlab.com/TibetanCalendar/TibetanDateCalcualtor.git" 37 | }, 38 | "keywords": [ 39 | "tibetan", 40 | "calendar", 41 | "lunar", 42 | "lunar-calendar", 43 | "dates", 44 | "date converter", 45 | "javascript" 46 | ], 47 | "bugs": { 48 | "url": "https://gitlab.com/TibetanCalendar/TibetanDateCalcualtor/issues" 49 | }, 50 | "homepage": "https://gitlab.com/TibetanCalendar/TibetanDateCalcualtor#README", 51 | "jest": { 52 | "transform": { 53 | "^.+\\.ts$": "ts-jest" 54 | }, 55 | "collectCoverageFrom": [ 56 | "src/**/*.{js,jsx,ts,tsx}" 57 | ], 58 | "moduleDirectories": [ 59 | "node_modules", 60 | "src" 61 | ] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/__mocks__/day-mock.ts: -------------------------------------------------------------------------------- 1 | const dayInDuplicateMonthMock = { 2 | year: 2140, 3 | month: 8, 4 | day: 4, 5 | julianLeap: 2456574, 6 | julianMain: 2456545, 7 | westernLeap: '2013-09-09', 8 | westernMain: '2013-10-08', 9 | dayInLeapMonth: { 10 | day: 4, 11 | isDoubledDay: false, 12 | isLeapDay: false, 13 | month: { 14 | isDoubledMonth: true, isLeapMonth: true, month: 8 15 | }, 16 | skippedDay: false, 17 | westernDate: '2013-09-09', 18 | year: 2140, 19 | }, 20 | dayInMainMonth: { 21 | day: 4, 22 | isDoubledDay: false, 23 | isLeapDay: false, 24 | month: { 25 | isDoubledMonth: true, isLeapMonth: false, month: 8, 26 | }, 27 | skippedDay: false, 28 | westernDate: '2013-10-08', 29 | year: 2140, 30 | }, 31 | }; 32 | 33 | const skippedDayMock = { 34 | year: 2140, 35 | month: 2, 36 | day: 20, 37 | western: '2013-03-31', 38 | dayObject: { 39 | day: 20, 40 | isDoubledDay: false, 41 | isLeapDay: false, 42 | month: { 43 | isDoubledMonth: false, isLeapMonth: false, month: 2 44 | }, 45 | skippedDay: true, 46 | westernDate: '2013-03-31', 47 | year: 2140, 48 | }, 49 | }; 50 | 51 | const duplicateDayMock = { 52 | year: 2140, 53 | month: 2, 54 | day: 8, 55 | westernLeap: '2013-03-19', 56 | westernMain: '2013-03-20', 57 | leapDay: { 58 | day: 8, 59 | isDoubledDay: true, 60 | isLeapDay: true, 61 | month: { 62 | isDoubledMonth: false, isLeapMonth: false, month: 2 63 | }, 64 | skippedDay: false, 65 | westernDate: '2013-03-19', 66 | year: 2140, 67 | }, 68 | mainDay: { 69 | day: 8, 70 | isDoubledDay: true, 71 | isLeapDay: false, 72 | month: { 73 | isDoubledMonth: false, isLeapMonth: false, month: 2 74 | }, 75 | skippedDay: false, 76 | westernDate: '2013-03-20', 77 | year: 2140, 78 | }, 79 | }; 80 | 81 | export { 82 | dayInDuplicateMonthMock, 83 | skippedDayMock, 84 | duplicateDayMock 85 | }; 86 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | // CONSTANTS 2 | 3 | // conversion constants 4 | 5 | // The beginning of the Rabjung count according to Western calendar, A.D. 1027 6 | export const RABJUNG_BEGINNING = 1027 as const; 7 | // length of rabjung cycle in years 8 | export const RABJUNG_CYCLE_LENGTH = 60 as const; 9 | // difference between Western and Tibetan year count 10 | export const YEAR_DIFF = 127 as const; 11 | // difference between Unix and Julian date starts 12 | export const JULIAN_TO_UNIX = 2440587.5 as const; 13 | // number of milliseconds in a year 14 | export const MS_IN_YEAR = 86400000 as const; 15 | // number of minutes in day 16 | export const MIN_IN_DAY = 1440 as const; 17 | 18 | // calendrical constants: month calculations 19 | 20 | // beginning of epoch based on Kalachakra. Used as 0 for month counts since this time 21 | export const YEAR0 = 806 as const; 22 | export const MONTH0 = 3 as const; 23 | // constants given in Svante's article 24 | export const BETA_STAR = 61 as const; 25 | export const BETA = 123 as const; 26 | // const P1 = 77 / 90; 27 | // const P0 = 139 / 180; 28 | // const ALPHA = 1 + 827 / 1005; 29 | 30 | // calendrical constants: day calculations 31 | // mean date 32 | export const M0 = 2015501 + 4783 / 5656; 33 | export const M1 = 167025 / 5656; 34 | export const M2 = M1 / 30; 35 | // mean sun 36 | export const S0 = 743 / 804; 37 | export const S1 = 65 / 804; 38 | export const S2 = S1 / 30; 39 | // anomaly moon 40 | export const A0 = 475 / 3528; 41 | export const A1 = 253 / 3528; 42 | export const A2 = 1 / 28; 43 | 44 | // fixed tables 45 | export const MOON_TAB = [0, 5, 10, 15, 19, 22, 24, 25] as const; 46 | export const SUN_TAB = [0, 6, 10, 11] as const; 47 | 48 | // year elements & animals 49 | export const YEAR_ELEMENTS = ['Wood', 'Fire', 'Earth', 'Iron', 'Water'] as const; 50 | export const YEAR_ANIMALS = ['Mouse', 'Ox', 'Tiger', 'Rabbit', 51 | 'Dragon', 'Snake', 'Horse', 'Sheep', 'Monkey', 'Bird', 'Dog', 'Pig'] as const; 52 | export const YEAR_GENDER = ['Male', 'Female'] as const; 53 | -------------------------------------------------------------------------------- /src/classes/__tests__/__snapshots__/tibetan-date.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TibetanDate should create a correct class when invoked with object arg 1`] = ` 4 | TibetanDate { 5 | "date": 10, 6 | "isDoubledDay": false, 7 | "isLeapDay": false, 8 | "isPreviousSkipped": false, 9 | "isSkippedDay": false, 10 | "month": 3, 11 | "monthObj": TibetanMonth { 12 | "days": Array [], 13 | "endDateStr": "2019-06-03", 14 | "isDoubledMonth": false, 15 | "isLeapMonth": false, 16 | "month": 3, 17 | "startDateStr": "2019-05-05", 18 | "year": 2146, 19 | }, 20 | "westernDate": 2019-05-14T10:00:00.000Z, 21 | "year": 2146, 22 | } 23 | `; 24 | 25 | exports[`TibetanDate should create a correct class when invoked with string arg 1`] = ` 26 | TibetanDate { 27 | "date": 20, 28 | "isDoubledDay": false, 29 | "isLeapDay": false, 30 | "isPreviousSkipped": false, 31 | "isSkippedDay": false, 32 | "month": 4, 33 | "monthObj": TibetanMonth { 34 | "days": Array [], 35 | "endDateStr": "2019-07-02", 36 | "isDoubledMonth": false, 37 | "isLeapMonth": false, 38 | "month": 4, 39 | "startDateStr": "2019-06-04", 40 | "year": 2146, 41 | }, 42 | "westernDate": 2019-06-22T10:00:00.000Z, 43 | "year": 2146, 44 | } 45 | `; 46 | 47 | exports[`TibetanDate should create a correct class when invoked without arg 1`] = ` 48 | TibetanDate { 49 | "date": 4, 50 | "isDoubledDay": false, 51 | "isLeapDay": false, 52 | "isPreviousSkipped": false, 53 | "isSkippedDay": false, 54 | "month": 4, 55 | "monthObj": TibetanMonth { 56 | "days": Array [], 57 | "endDateStr": "2019-07-02", 58 | "isDoubledMonth": false, 59 | "isLeapMonth": false, 60 | "month": 4, 61 | "startDateStr": "2019-06-04", 62 | "year": 2146, 63 | }, 64 | "westernDate": 2019-06-07T10:00:00.000Z, 65 | "year": 2146, 66 | } 67 | `; 68 | 69 | exports[`TibetanDate should return year object correctly 1`] = ` 70 | TibetanYear { 71 | "animal": "Pig", 72 | "element": "Earth", 73 | "gender": "Female", 74 | "months": Array [], 75 | "rabjungCycle": 17, 76 | "rabjungYear": 33, 77 | "tibYearNum": 2146, 78 | "westernYear": 2019, 79 | } 80 | `; 81 | -------------------------------------------------------------------------------- /src/classes/__tests__/tibetan-date.spec.ts: -------------------------------------------------------------------------------- 1 | import MockDate from 'mockdate'; 2 | import TibetanDate from '../tibetan-date'; 3 | 4 | describe('TibetanDate', () => { 5 | const mockToday = '2019-06-07'; 6 | let tDate; 7 | 8 | beforeEach(() => { 9 | MockDate.set(`${mockToday} 12:00`); 10 | tDate = new TibetanDate(); 11 | }); 12 | afterEach(() => { 13 | MockDate.reset(); 14 | }); 15 | 16 | it('should create a correct class when invoked without arg', () => { 17 | expect(new TibetanDate()).toMatchSnapshot(); 18 | }); 19 | it('should create a correct class when invoked with string arg', () => { 20 | expect(new TibetanDate('2019/06/22 12:00')).toMatchSnapshot(); 21 | }); 22 | it('should create a correct class when invoked with object arg', () => { 23 | expect(new TibetanDate({ 24 | year: 2146, month: 3, day: 10, isLeapMonth: false, isLeapDay: false 25 | })).toMatchSnapshot(); 26 | }); 27 | it('should get day correctly', () => { 28 | expect(tDate.day).toEqual(5); 29 | }); 30 | it('should return year object correctly', () => { 31 | expect(tDate.yearObj).toMatchSnapshot(); 32 | }); 33 | it('should get western date string correctly', () => { 34 | expect(tDate.westernDateStr).toEqual(mockToday); 35 | }); 36 | describe('JS Date like getters', () => { 37 | it('should return getWesternDate correctly', () => { 38 | expect(tDate.getWesternDate()).toEqual(tDate.westernDate); 39 | }); 40 | it('should return getDate correctly', () => { 41 | expect(tDate.getDate()).toEqual(tDate.date); 42 | }); 43 | it('should return getDay correctly', () => { 44 | expect(tDate.getDay()).toEqual(tDate.day); 45 | }); 46 | it('should return getMonth correctly', () => { 47 | expect(tDate.getMonth()).toEqual(tDate.month); 48 | }); 49 | it('should return getMonthObj correctly', () => { 50 | expect(tDate.getMonthObj()).toEqual(tDate.monthObj); 51 | }); 52 | it('should return getYear correctly', () => { 53 | expect(tDate.getYear()).toEqual(tDate.year); 54 | }); 55 | it('should return getYearObj correctly', () => { 56 | expect(tDate.getYearObj()).toEqual(tDate.yearObj); 57 | }); 58 | }); 59 | it('should return the correct string when `toString` is called in not double day', () => { 60 | expect(new TibetanDate().toString()).toEqual('2146-4-4'); 61 | }); 62 | it('should return the correct string when `toString` is called in leap of double month', () => { 63 | expect(new TibetanDate('2020/04/24').toString()).toEqual('2147-3-2-leap'); 64 | }); 65 | it('should return the correct string when `toString` is called in main of double month', () => { 66 | expect(new TibetanDate('2020/04/25').toString()).toEqual('2147-3-2-main'); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/generators/get-day-from-western.ts: -------------------------------------------------------------------------------- 1 | import { YEAR_DIFF } from '../constants'; 2 | import { julianFromTrueDate, julianFromUnix, monthCountFromTibetan } from '../conversions'; 3 | import getMonthFromMonthCount from './get-month-from-month-count'; 4 | import getDayFromTibetan from './get-day-from-tibetan'; 5 | import { Day } from '../types' 6 | 7 | /** 8 | * Calculates a Tibetan date for a given western date. This does a binary search, and is therefore 9 | * much slower than tibToWestern(). 10 | * 11 | * The algorithm could be much improved by using the reverse of meanDate() to start with, 12 | * and then using the fact that julian dates and "tibetan day numbers" have a quasi-linear relation. 13 | * 14 | * @param {Date} date - the western date 15 | * @return {Day} 16 | */ 17 | const getDayFromWestern = (date: Date): Day => { 18 | // const date = new Date(wYear, wMonth - 1, wDay); 19 | const wYear = date.getFullYear(); 20 | const jd = julianFromUnix(date); 21 | const tibYears = [wYear + YEAR_DIFF - 1, wYear + YEAR_DIFF + 1]; 22 | const monthCounts = tibYears.map(y => monthCountFromTibetan({ year: y, month: 1, isLeapMonth: true })); 23 | const trueDate = monthCounts.map(m => 1 + 30 * m); 24 | const jds = trueDate.map(n => julianFromTrueDate(n)); 25 | // croak "Binary search algorithm is wrong" unless $jd1 <= $jd && $jd <= $jd2; 26 | 27 | while (trueDate[0] < trueDate[1] - 1 && jds[0] < jds[1]) { 28 | const nTrueDate = Math.floor((trueDate[0] + trueDate[1]) / 2); 29 | const njd = julianFromTrueDate(nTrueDate); 30 | 31 | if (njd < jd) { 32 | trueDate[0] = nTrueDate; 33 | jds[0] = njd; 34 | } else { 35 | trueDate[1] = nTrueDate; 36 | jds[1] = njd; 37 | } 38 | } 39 | 40 | // so we found it; 41 | let winnerJd; 42 | let winnerTrueDate; 43 | // if the western date is the 1st of a doubled tib. day, then jd[0] == jd - 1 and 44 | // jd[1] == jd + 1, and the corresponding tib. day number is the one from jd[1]. 45 | if (jds[0] === jd) { 46 | winnerJd = jds[0]; // eslint-disable-line prefer-destructuring 47 | winnerTrueDate = trueDate[0]; // eslint-disable-line prefer-destructuring 48 | } else { 49 | winnerJd = jds[1]; // eslint-disable-line prefer-destructuring 50 | winnerTrueDate = trueDate[1]; // eslint-disable-line prefer-destructuring 51 | } 52 | 53 | // figure out the real tib. date: year, month, leap month, day number, leap day. 54 | const isLeapDay = winnerJd > jd; 55 | const monthCount = Math.floor((winnerTrueDate - 1) / 30); 56 | const day = (winnerTrueDate % 30) || 30; 57 | const { year, month, isLeapMonth } = getMonthFromMonthCount(monthCount); 58 | 59 | return getDayFromTibetan({ 60 | year, month, isLeapMonth, day, isLeapDay 61 | }); 62 | }; 63 | 64 | export default getDayFromWestern; 65 | -------------------------------------------------------------------------------- /src/generators/get-day-from-tibetan.ts: -------------------------------------------------------------------------------- 1 | import { 2 | julianFromTibetan, monthCountFromTibetan, trueDateFromMonthCountDay, unixFromJulian 3 | } from '../conversions'; 4 | import { getDayBefore, isDoubledMonth } from '../helpers'; 5 | import { Day } from '../types'; 6 | 7 | /** 8 | * Calculates full information for a given Tibetan date 9 | * 10 | * For doubled days, just as with doubled months, the "main" day or month is 11 | * the second, and the "leap" day or month is the first. 12 | * 13 | * @param {object} arg 14 | * @param {number} arg.year - Tibetan year number (ex. 2135) 15 | * @param {number} arg.month - month number (1 to 12) 16 | * @param {boolean} [arg.isLeapMonth=false] - is this month a leap month 17 | * @param {number} arg.day - day number (1 to 30) 18 | * @param {boolean} [arg.isLeapDay=false] - is this day a leap day 19 | * 20 | * @returns {Day} day - with all its attributes. isLeapMonth and isLeapDay are checked and corrected compared to input 21 | */ 22 | const getDayFromTibetan = ({ 23 | year, 24 | month, 25 | isLeapMonth = false, 26 | day, 27 | isLeapDay = false 28 | }: { 29 | year: number, 30 | month: number, 31 | isLeapMonth?: boolean, 32 | day: number, 33 | isLeapDay?: boolean 34 | }): Day => { 35 | let julianDate = julianFromTibetan(year, month, isLeapMonth, day); 36 | 37 | // also calculate the Julian date of the previous Tib. day 38 | const monthCount = monthCountFromTibetan({ year, month, isLeapMonth }); 39 | const dayBefore = getDayBefore(day, monthCount); 40 | const julianDatePrevious = Math.floor(trueDateFromMonthCountDay(dayBefore.day, dayBefore.monthCount)); 41 | const twoDaysBefore = getDayBefore(dayBefore.day, dayBefore.monthCount); 42 | const julianDate2DaysBefore = Math.floor(trueDateFromMonthCountDay(twoDaysBefore.day, twoDaysBefore.monthCount)); 43 | 44 | // figure out leap months, leap days & skipped days 45 | const isDoubledMonthThis = isDoubledMonth(year, month); 46 | const hasLeapDayThis = julianDate === julianDatePrevious + 2; 47 | const skippedDay = julianDate === julianDatePrevious; 48 | const isPreviousSkipped = julianDatePrevious === julianDate2DaysBefore; 49 | const isLeapDayChecked = isLeapDay && hasLeapDayThis; 50 | 51 | // figure out western date info for the main or leap day 52 | if (isLeapDayChecked) { 53 | julianDate--; 54 | } 55 | const westernDate = unixFromJulian(julianDate); 56 | 57 | return ({ 58 | year, 59 | month: { 60 | month, 61 | isLeapMonth: isLeapMonth && isDoubledMonthThis, 62 | isDoubledMonth: isDoubledMonthThis, 63 | }, 64 | day, 65 | skippedDay, 66 | isPreviousSkipped, 67 | isLeapDay: isLeapDayChecked, 68 | isDoubledDay: hasLeapDayThis, 69 | westernDate 70 | }); 71 | }; 72 | 73 | export default getDayFromTibetan; 74 | -------------------------------------------------------------------------------- /src/generators/__tests__/__snapshots__/get-day-from-tibetan.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`getDayFromTibetan() should return the correct western date for day marked as leap even though it isn't 1`] = ` 4 | Object { 5 | "day": 4, 6 | "isDoubledDay": false, 7 | "isLeapDay": false, 8 | "isPreviousSkipped": false, 9 | "month": Object { 10 | "isDoubledMonth": true, 11 | "isLeapMonth": false, 12 | "month": 8, 13 | }, 14 | "skippedDay": false, 15 | "westernDate": 2013-10-08T10:00:00.000Z, 16 | "year": 2140, 17 | } 18 | `; 19 | 20 | exports[`getDayFromTibetan() should return the correct western date for leap day 1`] = ` 21 | Object { 22 | "day": 8, 23 | "isDoubledDay": true, 24 | "isLeapDay": true, 25 | "isPreviousSkipped": false, 26 | "month": Object { 27 | "isDoubledMonth": false, 28 | "isLeapMonth": false, 29 | "month": 2, 30 | }, 31 | "skippedDay": false, 32 | "westernDate": 2013-03-19T10:00:00.000Z, 33 | "year": 2140, 34 | } 35 | `; 36 | 37 | exports[`getDayFromTibetan() should return the correct western date for main day that has leap day 1`] = ` 38 | Object { 39 | "day": 8, 40 | "isDoubledDay": true, 41 | "isLeapDay": false, 42 | "isPreviousSkipped": false, 43 | "month": Object { 44 | "isDoubledMonth": false, 45 | "isLeapMonth": false, 46 | "month": 2, 47 | }, 48 | "skippedDay": false, 49 | "westernDate": 2013-03-20T10:00:00.000Z, 50 | "year": 2140, 51 | } 52 | `; 53 | 54 | exports[`getDayFromTibetan() should return the correct western date for skipped day 1`] = ` 55 | Object { 56 | "day": 20, 57 | "isDoubledDay": false, 58 | "isLeapDay": false, 59 | "isPreviousSkipped": false, 60 | "month": Object { 61 | "isDoubledMonth": false, 62 | "isLeapMonth": false, 63 | "month": 2, 64 | }, 65 | "skippedDay": true, 66 | "westernDate": 2013-03-31T10:00:00.000Z, 67 | "year": 2140, 68 | } 69 | `; 70 | 71 | exports[`getDayFromTibetan() should return the correct western date in leap month 1`] = ` 72 | Object { 73 | "day": 4, 74 | "isDoubledDay": false, 75 | "isLeapDay": false, 76 | "isPreviousSkipped": false, 77 | "month": Object { 78 | "isDoubledMonth": true, 79 | "isLeapMonth": true, 80 | "month": 8, 81 | }, 82 | "skippedDay": false, 83 | "westernDate": 2013-09-09T10:00:00.000Z, 84 | "year": 2140, 85 | } 86 | `; 87 | 88 | exports[`getDayFromTibetan() should return the correct western date in non-leap month 1`] = ` 89 | Object { 90 | "day": 4, 91 | "isDoubledDay": false, 92 | "isLeapDay": false, 93 | "isPreviousSkipped": false, 94 | "month": Object { 95 | "isDoubledMonth": true, 96 | "isLeapMonth": false, 97 | "month": 8, 98 | }, 99 | "skippedDay": false, 100 | "westernDate": 2013-10-08T10:00:00.000Z, 101 | "year": 2140, 102 | } 103 | `; 104 | -------------------------------------------------------------------------------- /src/classes/tibetan-year.ts: -------------------------------------------------------------------------------- 1 | import TibetanDate from './tibetan-date'; // eslint-disable-line import/no-cycle 2 | import TibetanMonth from './tibetan-month'; // eslint-disable-line import/no-cycle 3 | import getYearFromTibetan from '../generators/get-year-from-tibetan'; 4 | import getYearFromWestern from '../generators/get-year-from-western'; 5 | import getYearFromRabjung from '../generators/get-year-from-rabjung'; 6 | import { isDoubledMonth } from '../helpers'; 7 | import { YEAR_ANIMALS, YEAR_ELEMENTS, YEAR_GENDER } from '../constants'; 8 | 9 | 10 | type Arg = (number | { 11 | rabjungCycle: number; 12 | rabjungYear: number; 13 | }) 14 | 15 | /** 16 | * A TibetanYear class 17 | * @param {...(object,number)} [arg] undefined will return tibetan year for 18 | * current year | number will return tibetan year unless isWestern is true | 19 | * object will return tibetan year according to rabjung cycle 20 | * @param {number} arg.rabjungCycle number of the cycle 21 | * @param {number} arg.rabjungYear number of the year within the cycle, 22 | * from 1 to 60. 23 | * @param {bool} [isWestern=false] optional second argument, if set to true 24 | * and fist arg is a number it will be treated as western year date 25 | */ 26 | class TibetanYear { 27 | rabjungCycle: number; 28 | 29 | rabjungYear: number; 30 | 31 | tibYearNum: number; 32 | 33 | westernYear: number; 34 | 35 | animal: typeof YEAR_ANIMALS[number]; 36 | 37 | element: typeof YEAR_ELEMENTS[number]; 38 | 39 | gender: typeof YEAR_GENDER[number]; 40 | 41 | months: TibetanMonth[]; 42 | 43 | constructor(arg?: Arg, isWestern = false) { 44 | let yearInit; 45 | 46 | if (!arg) { 47 | const westernDate = new Date(); 48 | const tibDate = new TibetanDate(westernDate.toISOString()); 49 | yearInit = getYearFromTibetan(tibDate.year); 50 | } else if (typeof arg === 'number') { 51 | if (isWestern) { 52 | yearInit = getYearFromWestern(arg); 53 | } else { 54 | yearInit = getYearFromTibetan(arg); 55 | } 56 | } else { 57 | yearInit = getYearFromRabjung(arg); 58 | } 59 | 60 | this.rabjungCycle = yearInit.rabjungCycle; 61 | this.rabjungYear = yearInit.rabjungYear; 62 | this.tibYearNum = yearInit.tibYear; 63 | this.westernYear = yearInit.westernYear; 64 | this.animal = yearInit.animal; 65 | this.element = yearInit.element; 66 | this.gender = yearInit.gender; 67 | 68 | this.months = []; 69 | } 70 | 71 | // Need to call this method at least once in order to calculate the month. 72 | // This is in order to reduce initial object size. 73 | getMonths(): TibetanMonth[] { 74 | if (this.months.length) { 75 | return this.months; 76 | } 77 | // loop over the month, taking care of duplicates 78 | for (let m = 1; m <= 12; m++) { 79 | const month = new TibetanMonth({ year: this.tibYearNum, month: m }); 80 | this.months.push(month); 81 | if (isDoubledMonth(this.tibYearNum, m)) { 82 | const leapMonth = new TibetanMonth({ year: this.tibYearNum, month: m, isLeapMonth: true }); 83 | this.months.push(leapMonth); 84 | } 85 | } 86 | return this.months; 87 | } 88 | 89 | toString(): string { 90 | return `${this.tibYearNum}`; 91 | } 92 | 93 | toRabjungString(): string { 94 | return `The ${this.rabjungYear}. year of the ${this.rabjungCycle}. rabjung cycle`; 95 | } 96 | } 97 | 98 | export default TibetanYear; 99 | -------------------------------------------------------------------------------- /src/classes/tibetan-month.ts: -------------------------------------------------------------------------------- 1 | import TibetanDate from './tibetan-date'; // eslint-disable-line import/no-cycle 2 | import TibetanYear from './tibetan-year'; // eslint-disable-line import/no-cycle 3 | import getMonthFromTibetan from '../generators/get-month-from-tibetan'; 4 | 5 | type Arg = (string | { 6 | year: number; 7 | month: number; 8 | isLeapMonth?: boolean; 9 | }) 10 | 11 | /** 12 | * A TibetanMonth class 13 | * @param {...(object,string)} [arg] undefined will return tibetan month for 14 | * current month | string will return tibetan day for month of `new Date(arg)` | 15 | * object will return tibetan month according to object definition 16 | * @param {number} arg.year - Tibetan year number (ex. 2135) 17 | * @param {number} arg.month - month number (1 to 12) 18 | * @param {boolean} [arg.isLeapMonth=false] - is this month a leap month 19 | */ 20 | class TibetanMonth { 21 | year: number; 22 | 23 | month: number; 24 | 25 | isLeapMonth: boolean; 26 | 27 | isDoubledMonth: boolean; 28 | 29 | startDateStr: string; 30 | 31 | endDateStr: string; 32 | 33 | days: TibetanDate[]; 34 | 35 | constructor(arg?: Arg) { 36 | let westernDate; 37 | let tibDate; 38 | 39 | if (!arg) { 40 | westernDate = new Date(); 41 | tibDate = new TibetanDate(westernDate.toISOString()); 42 | } else if (typeof arg === 'string') { 43 | westernDate = new Date(arg); 44 | tibDate = new TibetanDate(westernDate.toISOString()); 45 | } else { 46 | this.year = arg.year; 47 | this.month = arg.month; 48 | this.isLeapMonth = arg.isLeapMonth || false; 49 | } 50 | 51 | if (tibDate) { 52 | this.year = tibDate.year; 53 | this.month = tibDate.month; 54 | this.isLeapMonth = tibDate.monthObj.isLeapMonth; 55 | } 56 | 57 | const monthObj = getMonthFromTibetan({ 58 | year: this.year, 59 | month: this.month, 60 | isLeapMonth: this.isLeapMonth 61 | }); 62 | 63 | this.isDoubledMonth = monthObj.isDoubledMonth; 64 | this.startDateStr = monthObj.startDate; 65 | this.endDateStr = monthObj.endDate; 66 | 67 | this.days = []; 68 | } 69 | 70 | get yearObj(): TibetanYear { 71 | return new TibetanYear(this.year); 72 | } 73 | 74 | // Need to call this method at least once in order to calculate the dates. 75 | // This is in order to reduce initial object size. 76 | getDays(): TibetanDate[] { 77 | if (this.days.length) { 78 | return this.days; 79 | } 80 | // loop over the days, taking care of duplicate and missing days 81 | for (let d = 1; d <= 30; d++) { 82 | const day = new TibetanDate({ 83 | year: this.year, 84 | month: this.month, 85 | isLeapMonth: this.isLeapMonth, 86 | day: d, 87 | isLeapDay: false 88 | }); 89 | 90 | if (day.isDoubledDay) { 91 | const main = new TibetanDate({ 92 | year: this.year, 93 | month: this.month, 94 | isLeapMonth: this.isLeapMonth, 95 | day: d, 96 | isLeapDay: true 97 | }); 98 | this.days.push(main); 99 | } 100 | 101 | if (!day.isSkippedDay) { 102 | this.days.push(day); 103 | } 104 | } 105 | return this.days; 106 | } 107 | 108 | toString(): string { 109 | let double = ''; 110 | if (this.isDoubledMonth) { 111 | double = this.isLeapMonth ? '-leap' : '-main'; 112 | } 113 | 114 | return `${this.yearObj.toString()}-${this.month}${double}`; 115 | } 116 | } 117 | 118 | export default TibetanMonth; 119 | -------------------------------------------------------------------------------- /src/classes/__tests__/tibetan-month.spec.ts: -------------------------------------------------------------------------------- 1 | import MockDate from 'mockdate'; 2 | import TibetanMonth from '../tibetan-month'; 3 | 4 | describe('TibetanMonth', () => { 5 | beforeAll(() => { 6 | MockDate.set('2019/06/07'); 7 | }); 8 | afterAll(() => { 9 | MockDate.reset(); 10 | }); 11 | 12 | it('should create a correct class when invoked without arg', () => { 13 | expect(new TibetanMonth()).toMatchSnapshot(); 14 | }); 15 | it('should create a correct class when invoked with string arg', () => { 16 | expect(new TibetanMonth('2019/06/22')).toMatchSnapshot(); 17 | }); 18 | it('should create a correct class when invoked with object arg', () => { 19 | expect(new TibetanMonth({ 20 | year: 2146, month: 3, isLeapMonth: false 21 | })).toMatchSnapshot(); 22 | }); 23 | it('should set isLeapMonth to true for first of doubled months', () => { 24 | const month = new TibetanMonth('2019/02/10'); 25 | expect(month.isDoubledMonth).toBeTruthy(); 26 | expect(month.isLeapMonth).toBeTruthy(); 27 | }); 28 | it('should set isLeapMonth to true for second of doubled months', () => { 29 | const month = new TibetanMonth('2019/03/10'); 30 | expect(month.isDoubledMonth).toBeTruthy(); 31 | expect(month.isLeapMonth).toBeFalsy(); 32 | }); 33 | it('should not calculate days when initially incoved', () => { 34 | const month = new TibetanMonth(); 35 | expect(month.days.length).toEqual(0); 36 | }); 37 | it('should calculate days when requested (includes skipped and doubled days)', () => { 38 | const month = new TibetanMonth({ year: 2146, month: 5 }); 39 | const days = month.getDays(); 40 | 41 | expect(month.days.length).not.toEqual(0); 42 | expect(days).toMatchSnapshot(); 43 | }); 44 | it('should not include skipped days', () => { 45 | const month = new TibetanMonth({ year: 2146, month: 5 }); 46 | const days = month.getDays(); 47 | 48 | expect(days.some(day => day.isPreviousSkipped)).toBeTruthy(); 49 | expect(days.every(day => !day.isSkippedDay)).toBeTruthy(); 50 | }); 51 | it('should list days in the correct order according to tibetan date', () => { 52 | const month = new TibetanMonth({ year: 2146, month: 5 }); 53 | const days = month.getDays(); 54 | const isDateAfterPrev = (day, i, arr): boolean => { 55 | if (i === 0 && day.date === 1) { 56 | return true; 57 | } 58 | 59 | return day.date >= arr[i - 1].date; 60 | }; 61 | 62 | expect(days.every(isDateAfterPrev)).toBeTruthy(); 63 | }); 64 | it('should list days in the correct order according to western date', () => { 65 | const month = new TibetanMonth({ year: 2146, month: 5 }); 66 | const days = month.getDays(); 67 | const isDateAfterPrev = (day, i, arr): boolean => { 68 | if (i === 0 && day.date === 1) { 69 | return true; 70 | } 71 | 72 | return day.westernDate > arr[i - 1].westernDate; 73 | }; 74 | 75 | expect(days.every(isDateAfterPrev)).toBeTruthy(); 76 | }); 77 | it('should return year object correctly)', () => { 78 | const month = new TibetanMonth(); 79 | const { yearObj } = month; 80 | 81 | expect(yearObj).toMatchSnapshot(); 82 | }); 83 | it('should return the correct string when `toString` is called in not double month', () => { 84 | expect(new TibetanMonth().toString()).toEqual('2146-4'); 85 | }); 86 | it('should return the correct string when `toString` is called in leap of double month', () => { 87 | expect(new TibetanMonth('2019/02/10').toString()).toEqual('2146-1-leap'); 88 | }); 89 | it('should return the correct string when `toString` is called in main of double month', () => { 90 | expect(new TibetanMonth('2019/03/10').toString()).toEqual('2146-1-main'); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /src/classes/tibetan-date.ts: -------------------------------------------------------------------------------- 1 | import getDayFromWestern from '../generators/get-day-from-western'; 2 | import getDayFromTibetan from '../generators/get-day-from-tibetan'; 3 | import { getDateStr } from '../helpers'; 4 | import TibetanMonth from './tibetan-month'; // eslint-disable-line import/no-cycle 5 | import TibetanYear from './tibetan-year'; // eslint-disable-line import/no-cycle 6 | 7 | type Arg = (string | { 8 | year: number; 9 | month: number; 10 | isLeapMonth?: boolean; 11 | day: number; 12 | isLeapDay?: boolean; 13 | }) 14 | 15 | type SimpleMonth = { 16 | month: number; 17 | isLeapMonth: boolean; 18 | isDoubledMonth: boolean; 19 | } 20 | 21 | /** 22 | * A TibetanDate class 23 | * @param {...(object,string)} [arg] undefined will return tibetan date 24 | * for today | string will return tibetan day for `new Date(arg)` | object 25 | * will return tibetan day according to object definition 26 | * @param {number} arg.year - Tibetan year number (ex. 2135) 27 | * @param {number} arg.month - month number (1 to 12) 28 | * @param {boolean} [arg.isLeapMonth=false] - is this month a leap month 29 | * @param {number} arg.day - day number (1 to 30) 30 | * @param {boolean} [arg.isLeapDay=false] - is this day a leap day 31 | */ 32 | class TibetanDate { 33 | year: number; 34 | 35 | month: SimpleMonth; 36 | 37 | skippedDay: boolean; 38 | 39 | isPreviousSkipped: boolean; 40 | 41 | isLeapDay: boolean; 42 | 43 | isDoubledDay: boolean; 44 | 45 | westernDate: Date; 46 | 47 | isSkippedDay: boolean; 48 | 49 | date: number; 50 | 51 | monthObj: TibetanMonth; 52 | 53 | constructor(arg?: Arg) { 54 | let tibDate; 55 | if (!arg) { 56 | this.westernDate = new Date(); 57 | tibDate = getDayFromWestern(this.westernDate); 58 | } else if (typeof arg === 'string') { 59 | this.westernDate = new Date(arg); 60 | tibDate = getDayFromWestern(this.westernDate); 61 | } else { 62 | tibDate = getDayFromTibetan(arg); 63 | this.westernDate = tibDate.westernDate; 64 | } 65 | 66 | this.isSkippedDay = tibDate.skippedDay; 67 | this.isPreviousSkipped = tibDate.isPreviousSkipped; 68 | // the first of doubled days 69 | this.isLeapDay = tibDate.isLeapDay; 70 | this.isDoubledDay = tibDate.isDoubledDay; 71 | this.date = tibDate.day; 72 | this.year = tibDate.year; 73 | this.month = tibDate.month.month; 74 | this.monthObj = new TibetanMonth({ 75 | year: tibDate.year, 76 | month: tibDate.month.month, 77 | isLeapMonth: tibDate.month.isLeapMonth 78 | }); 79 | } 80 | 81 | /** GETTERS */ 82 | get day(): number { 83 | return this.westernDate.getDay(); 84 | } 85 | 86 | get yearObj(): TibetanYear { 87 | return new TibetanYear(this.year); 88 | } 89 | 90 | get westernDateStr(): string { 91 | return getDateStr(this.westernDate); 92 | } 93 | 94 | /** METHODS */ 95 | getWesternDate(): Date { 96 | return this.westernDate; 97 | } 98 | 99 | getDate(): number { 100 | return this.date; 101 | } 102 | 103 | getDay(): number { 104 | return this.westernDate.getDay(); 105 | } 106 | 107 | getMonth(): SimpleMonth { 108 | return this.month; 109 | } 110 | 111 | getMonthObj(): TibetanMonth { 112 | return this.monthObj; 113 | } 114 | 115 | getYear(): number { 116 | return this.year; 117 | } 118 | 119 | getYearObj(): TibetanYear { 120 | return this.yearObj; 121 | } 122 | 123 | toString(): string { 124 | let double = ''; 125 | if (this.isDoubledDay) { 126 | double = this.isLeapDay ? '-leap' : '-main'; 127 | } 128 | 129 | return `${this.monthObj.toString()}-${this.date}${double}`; 130 | } 131 | } 132 | 133 | export default TibetanDate; 134 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "ES2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./dist/types.d.ts", /* Concatenate and emit output to single file. */ 15 | // "outDir": "./dist/dts/", /* Redirect output structure to the directory. */ 16 | "declarationDir": "./dist/dts/", 17 | // "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 18 | // "composite": true, /* Enable project compilation */ 19 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 20 | // "removeComments": true, /* Do not emit comments to output. */ 21 | // "noEmit": true, /* Do not emit outputs. */ 22 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 23 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 24 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 25 | 26 | /* Strict Type-Checking Options */ 27 | "strict": false, /* Enable all strict type-checking options. */ 28 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 29 | // "strictNullChecks": true, /* Enable strict null checks. */ 30 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 31 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 32 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 33 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 34 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 35 | 36 | /* Additional Checks */ 37 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 38 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 39 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 40 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 41 | 42 | /* Module Resolution Options */ 43 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 44 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 45 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 46 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 47 | // "typeRoots": [], /* List of folders to include type definitions from. */ 48 | // "types": [], /* Type declaration files to be included in compilation. */ 49 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 50 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 51 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 52 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 53 | 54 | /* Source Map Options */ 55 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 56 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 57 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 58 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 59 | 60 | /* Experimental Options */ 61 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 62 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 63 | 64 | /* Advanced Options */ 65 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 66 | }, 67 | "include": [ 68 | "src/**/*", 69 | ], 70 | "exclude": [ 71 | "node_modules", 72 | "src/__mocks__", 73 | "**/*.spec.ts", 74 | ] 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Javascript library to calculate Tibetan calendrical dates according 2 | to the Phugpa tradition. 3 | 4 | The calculations are basically implementation the formulas in Svante Janson, 5 | ["Tibetan Calendar Mathematics"](http://www2.math.uu.se/~svante/papers/calendars/tibet.pdf). We are using year 806 as the epoch for all calculations. See the paper for details. 6 | 7 | # Installation 8 | npm install tibetan-date-calculator 9 | 10 | # Usage 11 | ```javascript 12 | import { TibetanDate, TibetanMonth, TibetanYear } from 'tibetan-date-calculator'; 13 | ``` 14 | 15 | # API 16 | The API exposes 3 classes: `TibetanDate`, `TibetanMonth`, `TibetanMonth` 17 | 18 | ## TibetanDate 19 | 20 | ```javascript 21 | tibDate = new TibetanDate(arg) 22 | ``` 23 | 24 | __arg__: __`undefined`__ 25 | 26 | This will return tibeatan date object for today. 27 | 28 | __arg__: __`String`__ 29 | 30 | This will return tibetan date object for the _wester date_ `new Date(arg)`. 31 | 32 | __Note__: using date string without time stamp (e.g. '2019-06-21') can create bugs when run in different timezones due to the way native Date object handles it. See [Date documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date) 33 | 34 | __arg__: __`Object`__ 35 | 36 | This will return tibetan day according to object definition: 37 | 38 | | keys | type | default | description | 39 | | -------------- | --------- | ------- | ---------------------------------------------------- | 40 | | `year` | `number` | | Tibetan year number (ex. 2135) | 41 | | `month` | `number` | | Tibetan month number (1 to 12) | 42 | | `isLeapMonth` | `boolean` | false | is this month a leap month (first of repeated months)| 43 | | `day` | `number` | | Tibetan day number (1 to 30) | 44 | | `isLeapDay` | `boolean` | false | is this day a leap day (first of repeated day) | 45 | 46 | #### Properties 47 | 48 | These can be accessed directly `tibDate.property` 49 | 50 | | property | type | description | 51 | | ------------------- | -------------- | ---------------------------------------------------- | 52 | | `westernDate` | `Date` | a JS Date object corresponding to this date | 53 | | `year` | `number` | Tibetan year number (ex. 2135) | 54 | | `month` | `number` | Tibetan month number (1 to 12) | 55 | | `date` | `number` | Tibetan date number (1 to 30) | 56 | | `day` | `number` | day of the week, same as calling `westernDate.getDay()` | 57 | | `yearObj` | `TibetanYear` | | 58 | | `monthObj` | `TibetanMonth` | | 59 | | `isDoubledDay` | `boolean` | | 60 | | `isLeapDay` | `boolean` | | 61 | | `isSkippedDay` | `boolean` | is this date skipped in the Tibetan calendar | 62 | | `isPreviousSkipped` | `boolean` | is the previous date skipped (e.g. date=3 is 2 skipped) | 63 | | `westernDateStr` | `string` | YYYY-MM-DD | 64 | 65 | #### Methods 66 | 67 | `TibetanDate` exposes a set of methods to mimic the native `Date` behaviour. 68 | 69 | | method | returns | description | 70 | | ------------------- | -------------- | ----------------------------------- | 71 | | `getWesternDate()` | `Date` | same as the property `westernDate` | 72 | | `getYear()` | `number` | same as the property `year` | 73 | | `getMonth()` | `number` | same as the property `month` | 74 | | `getDate()` | `number` | same as the property `date` | 75 | | `getDay()` | `number` | same as the property `day` | 76 | | `getMonthObj()` | `TibetanMonth` | same as the property `monthObj` | 77 | | `getYearObj()` | `TibeatnYear` | same as the property `yearObj` | 78 | | `toString()` | `string` | returns 'year-month(-leap/-main)-date(-leap/-main)' | 79 | 80 | ## TibetanMonth 81 | 82 | ```javascript 83 | tibMonth = new TibetanMonth(arg) 84 | ``` 85 | 86 | __arg__: __`undefined`__ 87 | 88 | This will return tibeatan month object that includes today. 89 | 90 | __arg__: __`String`__ 91 | 92 | This will return tibetan month object that includes the _wester date_ `new Date(arg)`. 93 | 94 | __arg__: __`Object`__ 95 | 96 | This will return tibetan month according to object definition: 97 | 98 | | keys | type | default | description | 99 | | -------------- | --------- | ------- | ---------------------------------------------------- | 100 | | `year` | `number` | | Tibetan year number (ex. 2135) | 101 | | `month` | `number` | | Tibetan month number (1 to 12) | 102 | | `isLeapMonth` | `boolean` | false | is this month a leap month (first of repeated months)| 103 | 104 | #### Properties 105 | 106 | These can be accessed directly `tibDate.property` 107 | 108 | | property | type | description | 109 | | ------------------- | -------------- | ---------------------------------------------------- | 110 | | `year` | `number` | Tibetan year number (ex. 2135) | 111 | | `month` | `number` | Tibetan month number (1 to 12) | 112 | | `isLeapMonth` | `boolean` | is this month a leap month (first of repeated months)| 113 | | `isDoubledMonth` | `boolean` | is this month doubled (either leap or main) | 114 | | `startDateStr` | `string` | western date string for the first date of the month | 115 | | `endDateStr` | `string` | western date string for the last date of the month | 116 | | `yearObj` | `TibetanYear` | TibetanYear object | 117 | | `days` | `TibetanDate[]`| list of days in the month, skipped days are not included. Need to call `getDays()` on the instance at least once to calculate it | 118 | | `toString()` | `string` | returns 'year-month(-leap/-main)' | 119 | 120 | #### Methods 121 | 122 | | method | returns | description | 123 | | ------------------- | -------------- | ---------------------------------------------------- | 124 | | `getDays()` | `TibetanDate[]`| generates an array of `TibetanDays` within this month, excluding skipped days. Once called, the `days` property will be populated too. | 125 | 126 | ## TibetanYear 127 | 128 | ```javascript 129 | tibYear = new TibetanYear(arg, isWestern = false) 130 | ``` 131 | 132 | __arg__: __`undefined`__ 133 | 134 | This will return tibeatan year object that includes today. 135 | 136 | __arg__: __`Number`__ 137 | 138 | This will return tibetan year passed, unless `isWestern` is set to `true`. In that case the `arg` will be interpreted as a western year number. 139 | 140 | __arg__: __`Object`__ 141 | 142 | This will return tibetan year according to rabjung year definition: 143 | 144 | | keys | type | description | 145 | | -------------- | --------- | ------------------------------------------ | 146 | | `rabjungCycle` | `number` | The rabjung cycle number | 147 | | `rabjungYear` | `number` | The year within the cycle (1 - 60) | 148 | 149 | #### Properties 150 | 151 | These can be accessed directly `tibDate.property` 152 | 153 | | property | type | description | 154 | | --------------- | --------- | ---------------------------------------------------- | 155 | | `rabjungCycle` | `number` | | 156 | | `rabjungYear` | `number` | | 157 | | `tibYearNum` | `number` | | 158 | | `westernYear` | `number` | | 159 | | `animal` | `string` | | 160 | | `element` | `string` | | 161 | | `gender` | `string` | | 162 | | `months` | `TibetanMonth[]`| need to call `getMonth()` on the instance at least once to calculate it | 163 | 164 | #### Methods 165 | 166 | | method | returns | description | 167 | | ------------------- | --------------- | ---------------------------------------------------- | 168 | | `getMonths()` | `TibetanMonth[]`| generates an array of `TibetanMonth` within this year. Once called, the `months` property will be populated too. | 169 | | `toRabjungString()` | `string` | returns 'The X. year of the Y. rabjung cycle' | 170 | | `toString()` | `string` | returns the Tibetan year number as string | 171 | 172 | ## Examples ## 173 | -------------------------------------------------------------------------------- /src/__mocks__/214008leap.ts: -------------------------------------------------------------------------------- 1 | export default `{ 2 | "year": 2140, 3 | "month": 8, 4 | "isLeapMonth": true, 5 | "isDoubledMonth": true, 6 | "startDate": "2013-09-06", 7 | "endDate": "2013-10-04", 8 | "days": { 9 | "2140-8-leap-1": { 10 | "year": 2140, 11 | "month": { 12 | "month": 8, 13 | "isLeapMonth": true, 14 | "isDoubledMonth": true 15 | }, 16 | "day": 1, 17 | "skippedDay": false, 18 | "isLeapDay": false, 19 | "isDoubledDay": false, 20 | "westernDate": "2013-09-06" 21 | }, 22 | "2140-8-leap-2": { 23 | "year": 2140, 24 | "month": { 25 | "month": 8, 26 | "isLeapMonth": true, 27 | "isDoubledMonth": true 28 | }, 29 | "day": 2, 30 | "skippedDay": false, 31 | "isLeapDay": false, 32 | "isDoubledDay": false, 33 | "westernDate": "2013-09-07" 34 | }, 35 | "2140-8-leap-3": { 36 | "year": 2140, 37 | "month": { 38 | "month": 8, 39 | "isLeapMonth": true, 40 | "isDoubledMonth": true 41 | }, 42 | "day": 3, 43 | "skippedDay": false, 44 | "isLeapDay": false, 45 | "isDoubledDay": false, 46 | "westernDate": "2013-09-08" 47 | }, 48 | "2140-8-leap-4": { 49 | "year": 2140, 50 | "month": { 51 | "month": 8, 52 | "isLeapMonth": true, 53 | "isDoubledMonth": true 54 | }, 55 | "day": 4, 56 | "skippedDay": false, 57 | "isLeapDay": false, 58 | "isDoubledDay": false, 59 | "westernDate": "2013-09-09" 60 | }, 61 | "2140-8-leap-5": { 62 | "year": 2140, 63 | "month": { 64 | "month": 8, 65 | "isLeapMonth": true, 66 | "isDoubledMonth": true 67 | }, 68 | "day": 5, 69 | "skippedDay": false, 70 | "isLeapDay": false, 71 | "isDoubledDay": false, 72 | "westernDate": "2013-09-10" 73 | }, 74 | "2140-8-leap-6": { 75 | "year": 2140, 76 | "month": { 77 | "month": 8, 78 | "isLeapMonth": true, 79 | "isDoubledMonth": true 80 | }, 81 | "day": 6, 82 | "skippedDay": false, 83 | "isLeapDay": false, 84 | "isDoubledDay": false, 85 | "westernDate": "2013-09-11" 86 | }, 87 | "2140-8-leap-7": { 88 | "year": 2140, 89 | "month": { 90 | "month": 8, 91 | "isLeapMonth": true, 92 | "isDoubledMonth": true 93 | }, 94 | "day": 7, 95 | "skippedDay": false, 96 | "isLeapDay": false, 97 | "isDoubledDay": false, 98 | "westernDate": "2013-09-12" 99 | }, 100 | "2140-8-leap-8": { 101 | "year": 2140, 102 | "month": { 103 | "month": 8, 104 | "isLeapMonth": true, 105 | "isDoubledMonth": true 106 | }, 107 | "day": 8, 108 | "skippedDay": false, 109 | "isLeapDay": false, 110 | "isDoubledDay": false, 111 | "westernDate": "2013-09-13" 112 | }, 113 | "2140-8-leap-9": { 114 | "year": 2140, 115 | "month": { 116 | "month": 8, 117 | "isLeapMonth": true, 118 | "isDoubledMonth": true 119 | }, 120 | "day": 9, 121 | "skippedDay": true, 122 | "isLeapDay": false, 123 | "isDoubledDay": false, 124 | "westernDate": "2013-09-13" 125 | }, 126 | "2140-8-leap-10": { 127 | "year": 2140, 128 | "month": { 129 | "month": 8, 130 | "isLeapMonth": true, 131 | "isDoubledMonth": true 132 | }, 133 | "day": 10, 134 | "skippedDay": false, 135 | "isLeapDay": false, 136 | "isDoubledDay": false, 137 | "westernDate": "2013-09-14" 138 | }, 139 | "2140-8-leap-11": { 140 | "year": 2140, 141 | "month": { 142 | "month": 8, 143 | "isLeapMonth": true, 144 | "isDoubledMonth": true 145 | }, 146 | "day": 11, 147 | "skippedDay": false, 148 | "isLeapDay": false, 149 | "isDoubledDay": false, 150 | "westernDate": "2013-09-15" 151 | }, 152 | "2140-8-leap-12": { 153 | "year": 2140, 154 | "month": { 155 | "month": 8, 156 | "isLeapMonth": true, 157 | "isDoubledMonth": true 158 | }, 159 | "day": 12, 160 | "skippedDay": false, 161 | "isLeapDay": false, 162 | "isDoubledDay": false, 163 | "westernDate": "2013-09-16" 164 | }, 165 | "2140-8-leap-13": { 166 | "year": 2140, 167 | "month": { 168 | "month": 8, 169 | "isLeapMonth": true, 170 | "isDoubledMonth": true 171 | }, 172 | "day": 13, 173 | "skippedDay": false, 174 | "isLeapDay": false, 175 | "isDoubledDay": false, 176 | "westernDate": "2013-09-17" 177 | }, 178 | "2140-8-leap-14": { 179 | "year": 2140, 180 | "month": { 181 | "month": 8, 182 | "isLeapMonth": true, 183 | "isDoubledMonth": true 184 | }, 185 | "day": 14, 186 | "skippedDay": false, 187 | "isLeapDay": false, 188 | "isDoubledDay": false, 189 | "westernDate": "2013-09-18" 190 | }, 191 | "2140-8-leap-15": { 192 | "year": 2140, 193 | "month": { 194 | "month": 8, 195 | "isLeapMonth": true, 196 | "isDoubledMonth": true 197 | }, 198 | "day": 15, 199 | "skippedDay": false, 200 | "isLeapDay": false, 201 | "isDoubledDay": false, 202 | "westernDate": "2013-09-19" 203 | }, 204 | "2140-8-leap-16": { 205 | "year": 2140, 206 | "month": { 207 | "month": 8, 208 | "isLeapMonth": true, 209 | "isDoubledMonth": true 210 | }, 211 | "day": 16, 212 | "skippedDay": false, 213 | "isLeapDay": false, 214 | "isDoubledDay": false, 215 | "westernDate": "2013-09-20" 216 | }, 217 | "2140-8-leap-17": { 218 | "year": 2140, 219 | "month": { 220 | "month": 8, 221 | "isLeapMonth": true, 222 | "isDoubledMonth": true 223 | }, 224 | "day": 17, 225 | "skippedDay": false, 226 | "isLeapDay": false, 227 | "isDoubledDay": false, 228 | "westernDate": "2013-09-21" 229 | }, 230 | "2140-8-leap-18": { 231 | "year": 2140, 232 | "month": { 233 | "month": 8, 234 | "isLeapMonth": true, 235 | "isDoubledMonth": true 236 | }, 237 | "day": 18, 238 | "skippedDay": false, 239 | "isLeapDay": false, 240 | "isDoubledDay": false, 241 | "westernDate": "2013-09-22" 242 | }, 243 | "2140-8-leap-19": { 244 | "year": 2140, 245 | "month": { 246 | "month": 8, 247 | "isLeapMonth": true, 248 | "isDoubledMonth": true 249 | }, 250 | "day": 19, 251 | "skippedDay": false, 252 | "isLeapDay": false, 253 | "isDoubledDay": false, 254 | "westernDate": "2013-09-23" 255 | }, 256 | "2140-8-leap-20": { 257 | "year": 2140, 258 | "month": { 259 | "month": 8, 260 | "isLeapMonth": true, 261 | "isDoubledMonth": true 262 | }, 263 | "day": 20, 264 | "skippedDay": false, 265 | "isLeapDay": false, 266 | "isDoubledDay": false, 267 | "westernDate": "2013-09-24" 268 | }, 269 | "2140-8-leap-21": { 270 | "year": 2140, 271 | "month": { 272 | "month": 8, 273 | "isLeapMonth": true, 274 | "isDoubledMonth": true 275 | }, 276 | "day": 21, 277 | "skippedDay": false, 278 | "isLeapDay": false, 279 | "isDoubledDay": false, 280 | "westernDate": "2013-09-25" 281 | }, 282 | "2140-8-leap-22": { 283 | "year": 2140, 284 | "month": { 285 | "month": 8, 286 | "isLeapMonth": true, 287 | "isDoubledMonth": true 288 | }, 289 | "day": 22, 290 | "skippedDay": false, 291 | "isLeapDay": false, 292 | "isDoubledDay": false, 293 | "westernDate": "2013-09-26" 294 | }, 295 | "2140-8-leap-23": { 296 | "year": 2140, 297 | "month": { 298 | "month": 8, 299 | "isLeapMonth": true, 300 | "isDoubledMonth": true 301 | }, 302 | "day": 23, 303 | "skippedDay": false, 304 | "isLeapDay": false, 305 | "isDoubledDay": false, 306 | "westernDate": "2013-09-27" 307 | }, 308 | "2140-8-leap-24": { 309 | "year": 2140, 310 | "month": { 311 | "month": 8, 312 | "isLeapMonth": true, 313 | "isDoubledMonth": true 314 | }, 315 | "day": 24, 316 | "skippedDay": false, 317 | "isLeapDay": false, 318 | "isDoubledDay": false, 319 | "westernDate": "2013-09-28" 320 | }, 321 | "2140-8-leap-25": { 322 | "year": 2140, 323 | "month": { 324 | "month": 8, 325 | "isLeapMonth": true, 326 | "isDoubledMonth": true 327 | }, 328 | "day": 25, 329 | "skippedDay": false, 330 | "isLeapDay": false, 331 | "isDoubledDay": false, 332 | "westernDate": "2013-09-29" 333 | }, 334 | "2140-8-leap-26": { 335 | "year": 2140, 336 | "month": { 337 | "month": 8, 338 | "isLeapMonth": true, 339 | "isDoubledMonth": true 340 | }, 341 | "day": 26, 342 | "skippedDay": false, 343 | "isLeapDay": false, 344 | "isDoubledDay": false, 345 | "westernDate": "2013-09-30" 346 | }, 347 | "2140-8-leap-27": { 348 | "year": 2140, 349 | "month": { 350 | "month": 8, 351 | "isLeapMonth": true, 352 | "isDoubledMonth": true 353 | }, 354 | "day": 27, 355 | "skippedDay": false, 356 | "isLeapDay": false, 357 | "isDoubledDay": false, 358 | "westernDate": "2013-10-01" 359 | }, 360 | "2140-8-leap-28": { 361 | "year": 2140, 362 | "month": { 363 | "month": 8, 364 | "isLeapMonth": true, 365 | "isDoubledMonth": true 366 | }, 367 | "day": 28, 368 | "skippedDay": false, 369 | "isLeapDay": false, 370 | "isDoubledDay": false, 371 | "westernDate": "2013-10-02" 372 | }, 373 | "2140-8-leap-29": { 374 | "year": 2140, 375 | "month": { 376 | "month": 8, 377 | "isLeapMonth": true, 378 | "isDoubledMonth": true 379 | }, 380 | "day": 29, 381 | "skippedDay": false, 382 | "isLeapDay": false, 383 | "isDoubledDay": false, 384 | "westernDate": "2013-10-03" 385 | }, 386 | "2140-8-leap-30": { 387 | "year": 2140, 388 | "month": { 389 | "month": 8, 390 | "isLeapMonth": true, 391 | "isDoubledMonth": true 392 | }, 393 | "day": 30, 394 | "skippedDay": false, 395 | "isLeapDay": false, 396 | "isDoubledDay": false, 397 | "westernDate": "2013-10-04" 398 | } 399 | }, 400 | "westernIndex": { 401 | "2013-09-06": "2140-8-leap-1", 402 | "2013-09-07": "2140-8-leap-2", 403 | "2013-09-08": "2140-8-leap-3", 404 | "2013-09-09": "2140-8-leap-4", 405 | "2013-09-10": "2140-8-leap-5", 406 | "2013-09-11": "2140-8-leap-6", 407 | "2013-09-12": "2140-8-leap-7", 408 | "2013-09-13": "2140-8-leap-8", 409 | "2013-09-14": "2140-8-leap-10", 410 | "2013-09-15": "2140-8-leap-11", 411 | "2013-09-16": "2140-8-leap-12", 412 | "2013-09-17": "2140-8-leap-13", 413 | "2013-09-18": "2140-8-leap-14", 414 | "2013-09-19": "2140-8-leap-15", 415 | "2013-09-20": "2140-8-leap-16", 416 | "2013-09-21": "2140-8-leap-17", 417 | "2013-09-22": "2140-8-leap-18", 418 | "2013-09-23": "2140-8-leap-19", 419 | "2013-09-24": "2140-8-leap-20", 420 | "2013-09-25": "2140-8-leap-21", 421 | "2013-09-26": "2140-8-leap-22", 422 | "2013-09-27": "2140-8-leap-23", 423 | "2013-09-28": "2140-8-leap-24", 424 | "2013-09-29": "2140-8-leap-25", 425 | "2013-09-30": "2140-8-leap-26", 426 | "2013-10-01": "2140-8-leap-27", 427 | "2013-10-02": "2140-8-leap-28", 428 | "2013-10-03": "2140-8-leap-29", 429 | "2013-10-04": "2140-8-leap-30" 430 | } 431 | }`; 432 | -------------------------------------------------------------------------------- /src/__mocks__/214502.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | { 3 | "year": 2145, 4 | "month": 3, 5 | "isLeapMonth": false, 6 | "isDoubledMonth": false, 7 | "startDate": "2018-04-16", 8 | "endDate": "2018-05-15", 9 | "days": { 10 | "2145-3-1": { 11 | "year": 2145, 12 | "month": { 13 | "month": 3, 14 | "isLeapMonth": false, 15 | "isDoubledMonth": false 16 | }, 17 | "day": 1, 18 | "skippedDay": false, 19 | "isLeapDay": false, 20 | "isDoubledDay": false, 21 | "westernDate": "2018-04-16" 22 | }, 23 | "2145-3-2": { 24 | "year": 2145, 25 | "month": { 26 | "month": 3, 27 | "isLeapMonth": false, 28 | "isDoubledMonth": false 29 | }, 30 | "day": 2, 31 | "skippedDay": false, 32 | "isLeapDay": false, 33 | "isDoubledDay": false, 34 | "westernDate": "2018-04-17" 35 | }, 36 | "2145-3-3": { 37 | "year": 2145, 38 | "month": { 39 | "month": 3, 40 | "isLeapMonth": false, 41 | "isDoubledMonth": false 42 | }, 43 | "day": 3, 44 | "skippedDay": false, 45 | "isLeapDay": false, 46 | "isDoubledDay": false, 47 | "westernDate": "2018-04-18" 48 | }, 49 | "2145-3-4": { 50 | "year": 2145, 51 | "month": { 52 | "month": 3, 53 | "isLeapMonth": false, 54 | "isDoubledMonth": false 55 | }, 56 | "day": 4, 57 | "skippedDay": false, 58 | "isLeapDay": false, 59 | "isDoubledDay": false, 60 | "westernDate": "2018-04-19" 61 | }, 62 | "2145-3-5": { 63 | "year": 2145, 64 | "month": { 65 | "month": 3, 66 | "isLeapMonth": false, 67 | "isDoubledMonth": false 68 | }, 69 | "day": 5, 70 | "skippedDay": false, 71 | "isLeapDay": false, 72 | "isDoubledDay": false, 73 | "westernDate": "2018-04-20" 74 | }, 75 | "2145-3-6": { 76 | "year": 2145, 77 | "month": { 78 | "month": 3, 79 | "isLeapMonth": false, 80 | "isDoubledMonth": false 81 | }, 82 | "day": 6, 83 | "skippedDay": false, 84 | "isLeapDay": false, 85 | "isDoubledDay": false, 86 | "westernDate": "2018-04-21" 87 | }, 88 | "2145-3-7": { 89 | "year": 2145, 90 | "month": { 91 | "month": 3, 92 | "isLeapMonth": false, 93 | "isDoubledMonth": false 94 | }, 95 | "day": 7, 96 | "skippedDay": false, 97 | "isLeapDay": false, 98 | "isDoubledDay": false, 99 | "westernDate": "2018-04-22" 100 | }, 101 | "2145-3-8": { 102 | "year": 2145, 103 | "month": { 104 | "month": 3, 105 | "isLeapMonth": false, 106 | "isDoubledMonth": false 107 | }, 108 | "day": 8, 109 | "skippedDay": false, 110 | "isLeapDay": false, 111 | "isDoubledDay": false, 112 | "westernDate": "2018-04-23" 113 | }, 114 | "2145-3-9": { 115 | "year": 2145, 116 | "month": { 117 | "month": 3, 118 | "isLeapMonth": false, 119 | "isDoubledMonth": false 120 | }, 121 | "day": 9, 122 | "skippedDay": false, 123 | "isLeapDay": false, 124 | "isDoubledDay": false, 125 | "westernDate": "2018-04-24" 126 | }, 127 | "2145-3-10": { 128 | "year": 2145, 129 | "month": { 130 | "month": 3, 131 | "isLeapMonth": false, 132 | "isDoubledMonth": false 133 | }, 134 | "day": 10, 135 | "skippedDay": false, 136 | "isLeapDay": false, 137 | "isDoubledDay": false, 138 | "westernDate": "2018-04-25" 139 | }, 140 | "2145-3-11": { 141 | "year": 2145, 142 | "month": { 143 | "month": 3, 144 | "isLeapMonth": false, 145 | "isDoubledMonth": false 146 | }, 147 | "day": 11, 148 | "skippedDay": false, 149 | "isLeapDay": false, 150 | "isDoubledDay": false, 151 | "westernDate": "2018-04-26" 152 | }, 153 | "2145-3-12": { 154 | "year": 2145, 155 | "month": { 156 | "month": 3, 157 | "isLeapMonth": false, 158 | "isDoubledMonth": false 159 | }, 160 | "day": 12, 161 | "skippedDay": false, 162 | "isLeapDay": false, 163 | "isDoubledDay": false, 164 | "westernDate": "2018-04-27" 165 | }, 166 | "2145-3-13": { 167 | "year": 2145, 168 | "month": { 169 | "month": 3, 170 | "isLeapMonth": false, 171 | "isDoubledMonth": false 172 | }, 173 | "day": 13, 174 | "skippedDay": true, 175 | "isLeapDay": false, 176 | "isDoubledDay": false, 177 | "westernDate": "2018-04-27" 178 | }, 179 | "2145-3-14": { 180 | "year": 2145, 181 | "month": { 182 | "month": 3, 183 | "isLeapMonth": false, 184 | "isDoubledMonth": false 185 | }, 186 | "day": 14, 187 | "skippedDay": false, 188 | "isLeapDay": false, 189 | "isDoubledDay": false, 190 | "westernDate": "2018-04-28" 191 | }, 192 | "2145-3-15": { 193 | "year": 2145, 194 | "month": { 195 | "month": 3, 196 | "isLeapMonth": false, 197 | "isDoubledMonth": false 198 | }, 199 | "day": 15, 200 | "skippedDay": false, 201 | "isLeapDay": false, 202 | "isDoubledDay": false, 203 | "westernDate": "2018-04-29" 204 | }, 205 | "2145-3-16": { 206 | "year": 2145, 207 | "month": { 208 | "month": 3, 209 | "isLeapMonth": false, 210 | "isDoubledMonth": false 211 | }, 212 | "day": 16, 213 | "skippedDay": false, 214 | "isLeapDay": false, 215 | "isDoubledDay": false, 216 | "westernDate": "2018-04-30" 217 | }, 218 | "2145-3-17": { 219 | "year": 2145, 220 | "month": { 221 | "month": 3, 222 | "isLeapMonth": false, 223 | "isDoubledMonth": false 224 | }, 225 | "day": 17, 226 | "skippedDay": false, 227 | "isLeapDay": false, 228 | "isDoubledDay": false, 229 | "westernDate": "2018-05-01" 230 | }, 231 | "2145-3-18": { 232 | "doubled": true 233 | }, 234 | "2145-3-18-main": { 235 | "year": 2145, 236 | "month": { 237 | "month": 3, 238 | "isLeapMonth": false, 239 | "isDoubledMonth": false 240 | }, 241 | "day": 18, 242 | "skippedDay": false, 243 | "isLeapDay": true, 244 | "isDoubledDay": true, 245 | "westernDate": "2018-05-02" 246 | }, 247 | "2145-3-18-leap": { 248 | "year": 2145, 249 | "month": { 250 | "month": 3, 251 | "isLeapMonth": false, 252 | "isDoubledMonth": false 253 | }, 254 | "day": 18, 255 | "skippedDay": false, 256 | "isLeapDay": false, 257 | "isDoubledDay": true, 258 | "westernDate": "2018-05-03" 259 | }, 260 | "2145-3-19": { 261 | "year": 2145, 262 | "month": { 263 | "month": 3, 264 | "isLeapMonth": false, 265 | "isDoubledMonth": false 266 | }, 267 | "day": 19, 268 | "skippedDay": false, 269 | "isLeapDay": false, 270 | "isDoubledDay": false, 271 | "westernDate": "2018-05-04" 272 | }, 273 | "2145-3-20": { 274 | "year": 2145, 275 | "month": { 276 | "month": 3, 277 | "isLeapMonth": false, 278 | "isDoubledMonth": false 279 | }, 280 | "day": 20, 281 | "skippedDay": false, 282 | "isLeapDay": false, 283 | "isDoubledDay": false, 284 | "westernDate": "2018-05-05" 285 | }, 286 | "2145-3-21": { 287 | "year": 2145, 288 | "month": { 289 | "month": 3, 290 | "isLeapMonth": false, 291 | "isDoubledMonth": false 292 | }, 293 | "day": 21, 294 | "skippedDay": false, 295 | "isLeapDay": false, 296 | "isDoubledDay": false, 297 | "westernDate": "2018-05-06" 298 | }, 299 | "2145-3-22": { 300 | "year": 2145, 301 | "month": { 302 | "month": 3, 303 | "isLeapMonth": false, 304 | "isDoubledMonth": false 305 | }, 306 | "day": 22, 307 | "skippedDay": false, 308 | "isLeapDay": false, 309 | "isDoubledDay": false, 310 | "westernDate": "2018-05-07" 311 | }, 312 | "2145-3-23": { 313 | "year": 2145, 314 | "month": { 315 | "month": 3, 316 | "isLeapMonth": false, 317 | "isDoubledMonth": false 318 | }, 319 | "day": 23, 320 | "skippedDay": false, 321 | "isLeapDay": false, 322 | "isDoubledDay": false, 323 | "westernDate": "2018-05-08" 324 | }, 325 | "2145-3-24": { 326 | "year": 2145, 327 | "month": { 328 | "month": 3, 329 | "isLeapMonth": false, 330 | "isDoubledMonth": false 331 | }, 332 | "day": 24, 333 | "skippedDay": false, 334 | "isLeapDay": false, 335 | "isDoubledDay": false, 336 | "westernDate": "2018-05-09" 337 | }, 338 | "2145-3-25": { 339 | "year": 2145, 340 | "month": { 341 | "month": 3, 342 | "isLeapMonth": false, 343 | "isDoubledMonth": false 344 | }, 345 | "day": 25, 346 | "skippedDay": false, 347 | "isLeapDay": false, 348 | "isDoubledDay": false, 349 | "westernDate": "2018-05-10" 350 | }, 351 | "2145-3-26": { 352 | "year": 2145, 353 | "month": { 354 | "month": 3, 355 | "isLeapMonth": false, 356 | "isDoubledMonth": false 357 | }, 358 | "day": 26, 359 | "skippedDay": false, 360 | "isLeapDay": false, 361 | "isDoubledDay": false, 362 | "westernDate": "2018-05-11" 363 | }, 364 | "2145-3-27": { 365 | "year": 2145, 366 | "month": { 367 | "month": 3, 368 | "isLeapMonth": false, 369 | "isDoubledMonth": false 370 | }, 371 | "day": 27, 372 | "skippedDay": false, 373 | "isLeapDay": false, 374 | "isDoubledDay": false, 375 | "westernDate": "2018-05-12" 376 | }, 377 | "2145-3-28": { 378 | "year": 2145, 379 | "month": { 380 | "month": 3, 381 | "isLeapMonth": false, 382 | "isDoubledMonth": false 383 | }, 384 | "day": 28, 385 | "skippedDay": false, 386 | "isLeapDay": false, 387 | "isDoubledDay": false, 388 | "westernDate": "2018-05-13" 389 | }, 390 | "2145-3-29": { 391 | "year": 2145, 392 | "month": { 393 | "month": 3, 394 | "isLeapMonth": false, 395 | "isDoubledMonth": false 396 | }, 397 | "day": 29, 398 | "skippedDay": false, 399 | "isLeapDay": false, 400 | "isDoubledDay": false, 401 | "westernDate": "2018-05-14" 402 | }, 403 | "2145-3-30": { 404 | "year": 2145, 405 | "month": { 406 | "month": 3, 407 | "isLeapMonth": false, 408 | "isDoubledMonth": false 409 | }, 410 | "day": 30, 411 | "skippedDay": false, 412 | "isLeapDay": false, 413 | "isDoubledDay": false, 414 | "westernDate": "2018-05-15" 415 | } 416 | }, 417 | "westernIndex": { 418 | "2018-04-16": "2145-3-1", 419 | "2018-04-17": "2145-3-2", 420 | "2018-04-18": "2145-3-3", 421 | "2018-04-19": "2145-3-4", 422 | "2018-04-20": "2145-3-5", 423 | "2018-04-21": "2145-3-6", 424 | "2018-04-22": "2145-3-7", 425 | "2018-04-23": "2145-3-8", 426 | "2018-04-24": "2145-3-9", 427 | "2018-04-25": "2145-3-10", 428 | "2018-04-26": "2145-3-11", 429 | "2018-04-27": "2145-3-12", 430 | "2018-04-28": "2145-3-14", 431 | "2018-04-29": "2145-3-15", 432 | "2018-04-30": "2145-3-16", 433 | "2018-05-01": "2145-3-17", 434 | "2018-05-02": "2145-3-18-main", 435 | "2018-05-03": "2145-3-18-leap", 436 | "2018-05-04": "2145-3-19", 437 | "2018-05-05": "2145-3-20", 438 | "2018-05-06": "2145-3-21", 439 | "2018-05-07": "2145-3-22", 440 | "2018-05-08": "2145-3-23", 441 | "2018-05-09": "2145-3-24", 442 | "2018-05-10": "2145-3-25", 443 | "2018-05-11": "2145-3-26", 444 | "2018-05-12": "2145-3-27", 445 | "2018-05-13": "2145-3-28", 446 | "2018-05-14": "2145-3-29", 447 | "2018-05-15": "2145-3-30" 448 | } 449 | }`; 450 | -------------------------------------------------------------------------------- /src/__mocks__/214008main.ts: -------------------------------------------------------------------------------- 1 | export default `{ 2 | "year": 2140, 3 | "month": 8, 4 | "isLeapMonth": false, 5 | "isDoubledMonth": true, 6 | "startDate": "2013-10-05", 7 | "endDate": "2013-11-03", 8 | "days": { 9 | "2140-8-main-1": { 10 | "year": 2140, 11 | "month": { 12 | "month": 8, 13 | "isLeapMonth": false, 14 | "isDoubledMonth": true 15 | }, 16 | "day": 1, 17 | "skippedDay": false, 18 | "isLeapDay": false, 19 | "isDoubledDay": false, 20 | "westernDate": "2013-10-05" 21 | }, 22 | "2140-8-main-2": { 23 | "year": 2140, 24 | "month": { 25 | "month": 8, 26 | "isLeapMonth": false, 27 | "isDoubledMonth": true 28 | }, 29 | "day": 2, 30 | "skippedDay": false, 31 | "isLeapDay": false, 32 | "isDoubledDay": false, 33 | "westernDate": "2013-10-06" 34 | }, 35 | "2140-8-main-3": { 36 | "year": 2140, 37 | "month": { 38 | "month": 8, 39 | "isLeapMonth": false, 40 | "isDoubledMonth": true 41 | }, 42 | "day": 3, 43 | "skippedDay": false, 44 | "isLeapDay": false, 45 | "isDoubledDay": false, 46 | "westernDate": "2013-10-07" 47 | }, 48 | "2140-8-main-4": { 49 | "year": 2140, 50 | "month": { 51 | "month": 8, 52 | "isLeapMonth": false, 53 | "isDoubledMonth": true 54 | }, 55 | "day": 4, 56 | "skippedDay": false, 57 | "isLeapDay": false, 58 | "isDoubledDay": false, 59 | "westernDate": "2013-10-08" 60 | }, 61 | "2140-8-main-5": { 62 | "year": 2140, 63 | "month": { 64 | "month": 8, 65 | "isLeapMonth": false, 66 | "isDoubledMonth": true 67 | }, 68 | "day": 5, 69 | "skippedDay": false, 70 | "isLeapDay": false, 71 | "isDoubledDay": false, 72 | "westernDate": "2013-10-09" 73 | }, 74 | "2140-8-main-6": { 75 | "year": 2140, 76 | "month": { 77 | "month": 8, 78 | "isLeapMonth": false, 79 | "isDoubledMonth": true 80 | }, 81 | "day": 6, 82 | "skippedDay": false, 83 | "isLeapDay": false, 84 | "isDoubledDay": false, 85 | "westernDate": "2013-10-10" 86 | }, 87 | "2140-8-main-7": { 88 | "year": 2140, 89 | "month": { 90 | "month": 8, 91 | "isLeapMonth": false, 92 | "isDoubledMonth": true 93 | }, 94 | "day": 7, 95 | "skippedDay": false, 96 | "isLeapDay": false, 97 | "isDoubledDay": false, 98 | "westernDate": "2013-10-11" 99 | }, 100 | "2140-8-main-8": { 101 | "year": 2140, 102 | "month": { 103 | "month": 8, 104 | "isLeapMonth": false, 105 | "isDoubledMonth": true 106 | }, 107 | "day": 8, 108 | "skippedDay": false, 109 | "isLeapDay": false, 110 | "isDoubledDay": false, 111 | "westernDate": "2013-10-12" 112 | }, 113 | "2140-8-main-9": { 114 | "year": 2140, 115 | "month": { 116 | "month": 8, 117 | "isLeapMonth": false, 118 | "isDoubledMonth": true 119 | }, 120 | "day": 9, 121 | "skippedDay": false, 122 | "isLeapDay": false, 123 | "isDoubledDay": false, 124 | "westernDate": "2013-10-13" 125 | }, 126 | "2140-8-main-10": { 127 | "year": 2140, 128 | "month": { 129 | "month": 8, 130 | "isLeapMonth": false, 131 | "isDoubledMonth": true 132 | }, 133 | "day": 10, 134 | "skippedDay": false, 135 | "isLeapDay": false, 136 | "isDoubledDay": false, 137 | "westernDate": "2013-10-14" 138 | }, 139 | "2140-8-main-11": { 140 | "year": 2140, 141 | "month": { 142 | "month": 8, 143 | "isLeapMonth": false, 144 | "isDoubledMonth": true 145 | }, 146 | "day": 11, 147 | "skippedDay": false, 148 | "isLeapDay": false, 149 | "isDoubledDay": false, 150 | "westernDate": "2013-10-15" 151 | }, 152 | "2140-8-main-12": { 153 | "year": 2140, 154 | "month": { 155 | "month": 8, 156 | "isLeapMonth": false, 157 | "isDoubledMonth": true 158 | }, 159 | "day": 12, 160 | "skippedDay": true, 161 | "isLeapDay": false, 162 | "isDoubledDay": false, 163 | "westernDate": "2013-10-15" 164 | }, 165 | "2140-8-main-13": { 166 | "year": 2140, 167 | "month": { 168 | "month": 8, 169 | "isLeapMonth": false, 170 | "isDoubledMonth": true 171 | }, 172 | "day": 13, 173 | "skippedDay": false, 174 | "isLeapDay": false, 175 | "isDoubledDay": false, 176 | "westernDate": "2013-10-16" 177 | }, 178 | "2140-8-main-14": { 179 | "year": 2140, 180 | "month": { 181 | "month": 8, 182 | "isLeapMonth": false, 183 | "isDoubledMonth": true 184 | }, 185 | "day": 14, 186 | "skippedDay": false, 187 | "isLeapDay": false, 188 | "isDoubledDay": false, 189 | "westernDate": "2013-10-17" 190 | }, 191 | "2140-8-main-15": { 192 | "year": 2140, 193 | "month": { 194 | "month": 8, 195 | "isLeapMonth": false, 196 | "isDoubledMonth": true 197 | }, 198 | "day": 15, 199 | "skippedDay": false, 200 | "isLeapDay": false, 201 | "isDoubledDay": false, 202 | "westernDate": "2013-10-18" 203 | }, 204 | "2140-8-main-16": { 205 | "year": 2140, 206 | "month": { 207 | "month": 8, 208 | "isLeapMonth": false, 209 | "isDoubledMonth": true 210 | }, 211 | "day": 16, 212 | "skippedDay": false, 213 | "isLeapDay": false, 214 | "isDoubledDay": false, 215 | "westernDate": "2013-10-19" 216 | }, 217 | "2140-8-main-17": { 218 | "year": 2140, 219 | "month": { 220 | "month": 8, 221 | "isLeapMonth": false, 222 | "isDoubledMonth": true 223 | }, 224 | "day": 17, 225 | "skippedDay": false, 226 | "isLeapDay": false, 227 | "isDoubledDay": false, 228 | "westernDate": "2013-10-20" 229 | }, 230 | "2140-8-main-18": { 231 | "year": 2140, 232 | "month": { 233 | "month": 8, 234 | "isLeapMonth": false, 235 | "isDoubledMonth": true 236 | }, 237 | "day": 18, 238 | "skippedDay": false, 239 | "isLeapDay": false, 240 | "isDoubledDay": false, 241 | "westernDate": "2013-10-21" 242 | }, 243 | "2140-8-main-19": { 244 | "year": 2140, 245 | "month": { 246 | "month": 8, 247 | "isLeapMonth": false, 248 | "isDoubledMonth": true 249 | }, 250 | "day": 19, 251 | "skippedDay": false, 252 | "isLeapDay": false, 253 | "isDoubledDay": false, 254 | "westernDate": "2013-10-22" 255 | }, 256 | "2140-8-main-20": { 257 | "doubled": true 258 | }, 259 | "2140-8-main-20-main": { 260 | "year": 2140, 261 | "month": { 262 | "month": 8, 263 | "isLeapMonth": false, 264 | "isDoubledMonth": true 265 | }, 266 | "day": 20, 267 | "skippedDay": false, 268 | "isLeapDay": true, 269 | "isDoubledDay": true, 270 | "westernDate": "2013-10-23" 271 | }, 272 | "2140-8-main-20-leap": { 273 | "year": 2140, 274 | "month": { 275 | "month": 8, 276 | "isLeapMonth": false, 277 | "isDoubledMonth": true 278 | }, 279 | "day": 20, 280 | "skippedDay": false, 281 | "isLeapDay": false, 282 | "isDoubledDay": true, 283 | "westernDate": "2013-10-24" 284 | }, 285 | "2140-8-main-21": { 286 | "year": 2140, 287 | "month": { 288 | "month": 8, 289 | "isLeapMonth": false, 290 | "isDoubledMonth": true 291 | }, 292 | "day": 21, 293 | "skippedDay": false, 294 | "isLeapDay": false, 295 | "isDoubledDay": false, 296 | "westernDate": "2013-10-25" 297 | }, 298 | "2140-8-main-22": { 299 | "year": 2140, 300 | "month": { 301 | "month": 8, 302 | "isLeapMonth": false, 303 | "isDoubledMonth": true 304 | }, 305 | "day": 22, 306 | "skippedDay": false, 307 | "isLeapDay": false, 308 | "isDoubledDay": false, 309 | "westernDate": "2013-10-26" 310 | }, 311 | "2140-8-main-23": { 312 | "year": 2140, 313 | "month": { 314 | "month": 8, 315 | "isLeapMonth": false, 316 | "isDoubledMonth": true 317 | }, 318 | "day": 23, 319 | "skippedDay": false, 320 | "isLeapDay": false, 321 | "isDoubledDay": false, 322 | "westernDate": "2013-10-27" 323 | }, 324 | "2140-8-main-24": { 325 | "year": 2140, 326 | "month": { 327 | "month": 8, 328 | "isLeapMonth": false, 329 | "isDoubledMonth": true 330 | }, 331 | "day": 24, 332 | "skippedDay": false, 333 | "isLeapDay": false, 334 | "isDoubledDay": false, 335 | "westernDate": "2013-10-28" 336 | }, 337 | "2140-8-main-25": { 338 | "year": 2140, 339 | "month": { 340 | "month": 8, 341 | "isLeapMonth": false, 342 | "isDoubledMonth": true 343 | }, 344 | "day": 25, 345 | "skippedDay": false, 346 | "isLeapDay": false, 347 | "isDoubledDay": false, 348 | "westernDate": "2013-10-29" 349 | }, 350 | "2140-8-main-26": { 351 | "year": 2140, 352 | "month": { 353 | "month": 8, 354 | "isLeapMonth": false, 355 | "isDoubledMonth": true 356 | }, 357 | "day": 26, 358 | "skippedDay": false, 359 | "isLeapDay": false, 360 | "isDoubledDay": false, 361 | "westernDate": "2013-10-30" 362 | }, 363 | "2140-8-main-27": { 364 | "year": 2140, 365 | "month": { 366 | "month": 8, 367 | "isLeapMonth": false, 368 | "isDoubledMonth": true 369 | }, 370 | "day": 27, 371 | "skippedDay": false, 372 | "isLeapDay": false, 373 | "isDoubledDay": false, 374 | "westernDate": "2013-10-31" 375 | }, 376 | "2140-8-main-28": { 377 | "year": 2140, 378 | "month": { 379 | "month": 8, 380 | "isLeapMonth": false, 381 | "isDoubledMonth": true 382 | }, 383 | "day": 28, 384 | "skippedDay": false, 385 | "isLeapDay": false, 386 | "isDoubledDay": false, 387 | "westernDate": "2013-11-01" 388 | }, 389 | "2140-8-main-29": { 390 | "year": 2140, 391 | "month": { 392 | "month": 8, 393 | "isLeapMonth": false, 394 | "isDoubledMonth": true 395 | }, 396 | "day": 29, 397 | "skippedDay": false, 398 | "isLeapDay": false, 399 | "isDoubledDay": false, 400 | "westernDate": "2013-11-02" 401 | }, 402 | "2140-8-main-30": { 403 | "year": 2140, 404 | "month": { 405 | "month": 8, 406 | "isLeapMonth": false, 407 | "isDoubledMonth": true 408 | }, 409 | "day": 30, 410 | "skippedDay": false, 411 | "isLeapDay": false, 412 | "isDoubledDay": false, 413 | "westernDate": "2013-11-03" 414 | } 415 | }, 416 | "westernIndex": { 417 | "2013-10-05": "2140-8-main-1", 418 | "2013-10-06": "2140-8-main-2", 419 | "2013-10-07": "2140-8-main-3", 420 | "2013-10-08": "2140-8-main-4", 421 | "2013-10-09": "2140-8-main-5", 422 | "2013-10-10": "2140-8-main-6", 423 | "2013-10-11": "2140-8-main-7", 424 | "2013-10-12": "2140-8-main-8", 425 | "2013-10-13": "2140-8-main-9", 426 | "2013-10-14": "2140-8-main-10", 427 | "2013-10-15": "2140-8-main-11", 428 | "2013-10-16": "2140-8-main-13", 429 | "2013-10-17": "2140-8-main-14", 430 | "2013-10-18": "2140-8-main-15", 431 | "2013-10-19": "2140-8-main-16", 432 | "2013-10-20": "2140-8-main-17", 433 | "2013-10-21": "2140-8-main-18", 434 | "2013-10-22": "2140-8-main-19", 435 | "2013-10-23": "2140-8-main-20-main", 436 | "2013-10-24": "2140-8-main-20-leap", 437 | "2013-10-25": "2140-8-main-21", 438 | "2013-10-26": "2140-8-main-22", 439 | "2013-10-27": "2140-8-main-23", 440 | "2013-10-28": "2140-8-main-24", 441 | "2013-10-29": "2140-8-main-25", 442 | "2013-10-30": "2140-8-main-26", 443 | "2013-10-31": "2140-8-main-27", 444 | "2013-11-01": "2140-8-main-28", 445 | "2013-11-02": "2140-8-main-29", 446 | "2013-11-03": "2140-8-main-30" 447 | } 448 | }`; 449 | -------------------------------------------------------------------------------- /src/classes/__tests__/__snapshots__/tibetan-month.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TibetanMonth should calculate days when requested (includes skipped and doubled days) 1`] = ` 4 | Array [ 5 | TibetanDate { 6 | "date": 1, 7 | "isDoubledDay": false, 8 | "isLeapDay": false, 9 | "isPreviousSkipped": false, 10 | "isSkippedDay": false, 11 | "month": 5, 12 | "monthObj": TibetanMonth { 13 | "days": Array [], 14 | "endDateStr": "2019-08-01", 15 | "isDoubledMonth": false, 16 | "isLeapMonth": false, 17 | "month": 5, 18 | "startDateStr": "2019-07-03", 19 | "year": 2146, 20 | }, 21 | "westernDate": 2019-07-03T10:00:00.000Z, 22 | "year": 2146, 23 | }, 24 | TibetanDate { 25 | "date": 2, 26 | "isDoubledDay": false, 27 | "isLeapDay": false, 28 | "isPreviousSkipped": false, 29 | "isSkippedDay": false, 30 | "month": 5, 31 | "monthObj": TibetanMonth { 32 | "days": Array [], 33 | "endDateStr": "2019-08-01", 34 | "isDoubledMonth": false, 35 | "isLeapMonth": false, 36 | "month": 5, 37 | "startDateStr": "2019-07-03", 38 | "year": 2146, 39 | }, 40 | "westernDate": 2019-07-04T10:00:00.000Z, 41 | "year": 2146, 42 | }, 43 | TibetanDate { 44 | "date": 3, 45 | "isDoubledDay": false, 46 | "isLeapDay": false, 47 | "isPreviousSkipped": false, 48 | "isSkippedDay": false, 49 | "month": 5, 50 | "monthObj": TibetanMonth { 51 | "days": Array [], 52 | "endDateStr": "2019-08-01", 53 | "isDoubledMonth": false, 54 | "isLeapMonth": false, 55 | "month": 5, 56 | "startDateStr": "2019-07-03", 57 | "year": 2146, 58 | }, 59 | "westernDate": 2019-07-05T10:00:00.000Z, 60 | "year": 2146, 61 | }, 62 | TibetanDate { 63 | "date": 4, 64 | "isDoubledDay": false, 65 | "isLeapDay": false, 66 | "isPreviousSkipped": false, 67 | "isSkippedDay": false, 68 | "month": 5, 69 | "monthObj": TibetanMonth { 70 | "days": Array [], 71 | "endDateStr": "2019-08-01", 72 | "isDoubledMonth": false, 73 | "isLeapMonth": false, 74 | "month": 5, 75 | "startDateStr": "2019-07-03", 76 | "year": 2146, 77 | }, 78 | "westernDate": 2019-07-06T10:00:00.000Z, 79 | "year": 2146, 80 | }, 81 | TibetanDate { 82 | "date": 5, 83 | "isDoubledDay": false, 84 | "isLeapDay": false, 85 | "isPreviousSkipped": false, 86 | "isSkippedDay": false, 87 | "month": 5, 88 | "monthObj": TibetanMonth { 89 | "days": Array [], 90 | "endDateStr": "2019-08-01", 91 | "isDoubledMonth": false, 92 | "isLeapMonth": false, 93 | "month": 5, 94 | "startDateStr": "2019-07-03", 95 | "year": 2146, 96 | }, 97 | "westernDate": 2019-07-07T10:00:00.000Z, 98 | "year": 2146, 99 | }, 100 | TibetanDate { 101 | "date": 6, 102 | "isDoubledDay": false, 103 | "isLeapDay": false, 104 | "isPreviousSkipped": false, 105 | "isSkippedDay": false, 106 | "month": 5, 107 | "monthObj": TibetanMonth { 108 | "days": Array [], 109 | "endDateStr": "2019-08-01", 110 | "isDoubledMonth": false, 111 | "isLeapMonth": false, 112 | "month": 5, 113 | "startDateStr": "2019-07-03", 114 | "year": 2146, 115 | }, 116 | "westernDate": 2019-07-08T10:00:00.000Z, 117 | "year": 2146, 118 | }, 119 | TibetanDate { 120 | "date": 7, 121 | "isDoubledDay": false, 122 | "isLeapDay": false, 123 | "isPreviousSkipped": false, 124 | "isSkippedDay": false, 125 | "month": 5, 126 | "monthObj": TibetanMonth { 127 | "days": Array [], 128 | "endDateStr": "2019-08-01", 129 | "isDoubledMonth": false, 130 | "isLeapMonth": false, 131 | "month": 5, 132 | "startDateStr": "2019-07-03", 133 | "year": 2146, 134 | }, 135 | "westernDate": 2019-07-09T10:00:00.000Z, 136 | "year": 2146, 137 | }, 138 | TibetanDate { 139 | "date": 8, 140 | "isDoubledDay": false, 141 | "isLeapDay": false, 142 | "isPreviousSkipped": false, 143 | "isSkippedDay": false, 144 | "month": 5, 145 | "monthObj": TibetanMonth { 146 | "days": Array [], 147 | "endDateStr": "2019-08-01", 148 | "isDoubledMonth": false, 149 | "isLeapMonth": false, 150 | "month": 5, 151 | "startDateStr": "2019-07-03", 152 | "year": 2146, 153 | }, 154 | "westernDate": 2019-07-10T10:00:00.000Z, 155 | "year": 2146, 156 | }, 157 | TibetanDate { 158 | "date": 10, 159 | "isDoubledDay": false, 160 | "isLeapDay": false, 161 | "isPreviousSkipped": true, 162 | "isSkippedDay": false, 163 | "month": 5, 164 | "monthObj": TibetanMonth { 165 | "days": Array [], 166 | "endDateStr": "2019-08-01", 167 | "isDoubledMonth": false, 168 | "isLeapMonth": false, 169 | "month": 5, 170 | "startDateStr": "2019-07-03", 171 | "year": 2146, 172 | }, 173 | "westernDate": 2019-07-11T10:00:00.000Z, 174 | "year": 2146, 175 | }, 176 | TibetanDate { 177 | "date": 11, 178 | "isDoubledDay": false, 179 | "isLeapDay": false, 180 | "isPreviousSkipped": false, 181 | "isSkippedDay": false, 182 | "month": 5, 183 | "monthObj": TibetanMonth { 184 | "days": Array [], 185 | "endDateStr": "2019-08-01", 186 | "isDoubledMonth": false, 187 | "isLeapMonth": false, 188 | "month": 5, 189 | "startDateStr": "2019-07-03", 190 | "year": 2146, 191 | }, 192 | "westernDate": 2019-07-12T10:00:00.000Z, 193 | "year": 2146, 194 | }, 195 | TibetanDate { 196 | "date": 12, 197 | "isDoubledDay": false, 198 | "isLeapDay": false, 199 | "isPreviousSkipped": false, 200 | "isSkippedDay": false, 201 | "month": 5, 202 | "monthObj": TibetanMonth { 203 | "days": Array [], 204 | "endDateStr": "2019-08-01", 205 | "isDoubledMonth": false, 206 | "isLeapMonth": false, 207 | "month": 5, 208 | "startDateStr": "2019-07-03", 209 | "year": 2146, 210 | }, 211 | "westernDate": 2019-07-13T10:00:00.000Z, 212 | "year": 2146, 213 | }, 214 | TibetanDate { 215 | "date": 13, 216 | "isDoubledDay": false, 217 | "isLeapDay": false, 218 | "isPreviousSkipped": false, 219 | "isSkippedDay": false, 220 | "month": 5, 221 | "monthObj": TibetanMonth { 222 | "days": Array [], 223 | "endDateStr": "2019-08-01", 224 | "isDoubledMonth": false, 225 | "isLeapMonth": false, 226 | "month": 5, 227 | "startDateStr": "2019-07-03", 228 | "year": 2146, 229 | }, 230 | "westernDate": 2019-07-14T10:00:00.000Z, 231 | "year": 2146, 232 | }, 233 | TibetanDate { 234 | "date": 14, 235 | "isDoubledDay": false, 236 | "isLeapDay": false, 237 | "isPreviousSkipped": false, 238 | "isSkippedDay": false, 239 | "month": 5, 240 | "monthObj": TibetanMonth { 241 | "days": Array [], 242 | "endDateStr": "2019-08-01", 243 | "isDoubledMonth": false, 244 | "isLeapMonth": false, 245 | "month": 5, 246 | "startDateStr": "2019-07-03", 247 | "year": 2146, 248 | }, 249 | "westernDate": 2019-07-15T10:00:00.000Z, 250 | "year": 2146, 251 | }, 252 | TibetanDate { 253 | "date": 15, 254 | "isDoubledDay": false, 255 | "isLeapDay": false, 256 | "isPreviousSkipped": false, 257 | "isSkippedDay": false, 258 | "month": 5, 259 | "monthObj": TibetanMonth { 260 | "days": Array [], 261 | "endDateStr": "2019-08-01", 262 | "isDoubledMonth": false, 263 | "isLeapMonth": false, 264 | "month": 5, 265 | "startDateStr": "2019-07-03", 266 | "year": 2146, 267 | }, 268 | "westernDate": 2019-07-16T10:00:00.000Z, 269 | "year": 2146, 270 | }, 271 | TibetanDate { 272 | "date": 16, 273 | "isDoubledDay": false, 274 | "isLeapDay": false, 275 | "isPreviousSkipped": false, 276 | "isSkippedDay": false, 277 | "month": 5, 278 | "monthObj": TibetanMonth { 279 | "days": Array [], 280 | "endDateStr": "2019-08-01", 281 | "isDoubledMonth": false, 282 | "isLeapMonth": false, 283 | "month": 5, 284 | "startDateStr": "2019-07-03", 285 | "year": 2146, 286 | }, 287 | "westernDate": 2019-07-17T10:00:00.000Z, 288 | "year": 2146, 289 | }, 290 | TibetanDate { 291 | "date": 17, 292 | "isDoubledDay": false, 293 | "isLeapDay": false, 294 | "isPreviousSkipped": false, 295 | "isSkippedDay": false, 296 | "month": 5, 297 | "monthObj": TibetanMonth { 298 | "days": Array [], 299 | "endDateStr": "2019-08-01", 300 | "isDoubledMonth": false, 301 | "isLeapMonth": false, 302 | "month": 5, 303 | "startDateStr": "2019-07-03", 304 | "year": 2146, 305 | }, 306 | "westernDate": 2019-07-18T10:00:00.000Z, 307 | "year": 2146, 308 | }, 309 | TibetanDate { 310 | "date": 18, 311 | "isDoubledDay": true, 312 | "isLeapDay": true, 313 | "isPreviousSkipped": false, 314 | "isSkippedDay": false, 315 | "month": 5, 316 | "monthObj": TibetanMonth { 317 | "days": Array [], 318 | "endDateStr": "2019-08-01", 319 | "isDoubledMonth": false, 320 | "isLeapMonth": false, 321 | "month": 5, 322 | "startDateStr": "2019-07-03", 323 | "year": 2146, 324 | }, 325 | "westernDate": 2019-07-19T10:00:00.000Z, 326 | "year": 2146, 327 | }, 328 | TibetanDate { 329 | "date": 18, 330 | "isDoubledDay": true, 331 | "isLeapDay": false, 332 | "isPreviousSkipped": false, 333 | "isSkippedDay": false, 334 | "month": 5, 335 | "monthObj": TibetanMonth { 336 | "days": Array [], 337 | "endDateStr": "2019-08-01", 338 | "isDoubledMonth": false, 339 | "isLeapMonth": false, 340 | "month": 5, 341 | "startDateStr": "2019-07-03", 342 | "year": 2146, 343 | }, 344 | "westernDate": 2019-07-20T10:00:00.000Z, 345 | "year": 2146, 346 | }, 347 | TibetanDate { 348 | "date": 19, 349 | "isDoubledDay": false, 350 | "isLeapDay": false, 351 | "isPreviousSkipped": false, 352 | "isSkippedDay": false, 353 | "month": 5, 354 | "monthObj": TibetanMonth { 355 | "days": Array [], 356 | "endDateStr": "2019-08-01", 357 | "isDoubledMonth": false, 358 | "isLeapMonth": false, 359 | "month": 5, 360 | "startDateStr": "2019-07-03", 361 | "year": 2146, 362 | }, 363 | "westernDate": 2019-07-21T10:00:00.000Z, 364 | "year": 2146, 365 | }, 366 | TibetanDate { 367 | "date": 20, 368 | "isDoubledDay": false, 369 | "isLeapDay": false, 370 | "isPreviousSkipped": false, 371 | "isSkippedDay": false, 372 | "month": 5, 373 | "monthObj": TibetanMonth { 374 | "days": Array [], 375 | "endDateStr": "2019-08-01", 376 | "isDoubledMonth": false, 377 | "isLeapMonth": false, 378 | "month": 5, 379 | "startDateStr": "2019-07-03", 380 | "year": 2146, 381 | }, 382 | "westernDate": 2019-07-22T10:00:00.000Z, 383 | "year": 2146, 384 | }, 385 | TibetanDate { 386 | "date": 21, 387 | "isDoubledDay": false, 388 | "isLeapDay": false, 389 | "isPreviousSkipped": false, 390 | "isSkippedDay": false, 391 | "month": 5, 392 | "monthObj": TibetanMonth { 393 | "days": Array [], 394 | "endDateStr": "2019-08-01", 395 | "isDoubledMonth": false, 396 | "isLeapMonth": false, 397 | "month": 5, 398 | "startDateStr": "2019-07-03", 399 | "year": 2146, 400 | }, 401 | "westernDate": 2019-07-23T10:00:00.000Z, 402 | "year": 2146, 403 | }, 404 | TibetanDate { 405 | "date": 22, 406 | "isDoubledDay": false, 407 | "isLeapDay": false, 408 | "isPreviousSkipped": false, 409 | "isSkippedDay": false, 410 | "month": 5, 411 | "monthObj": TibetanMonth { 412 | "days": Array [], 413 | "endDateStr": "2019-08-01", 414 | "isDoubledMonth": false, 415 | "isLeapMonth": false, 416 | "month": 5, 417 | "startDateStr": "2019-07-03", 418 | "year": 2146, 419 | }, 420 | "westernDate": 2019-07-24T10:00:00.000Z, 421 | "year": 2146, 422 | }, 423 | TibetanDate { 424 | "date": 23, 425 | "isDoubledDay": false, 426 | "isLeapDay": false, 427 | "isPreviousSkipped": false, 428 | "isSkippedDay": false, 429 | "month": 5, 430 | "monthObj": TibetanMonth { 431 | "days": Array [], 432 | "endDateStr": "2019-08-01", 433 | "isDoubledMonth": false, 434 | "isLeapMonth": false, 435 | "month": 5, 436 | "startDateStr": "2019-07-03", 437 | "year": 2146, 438 | }, 439 | "westernDate": 2019-07-25T10:00:00.000Z, 440 | "year": 2146, 441 | }, 442 | TibetanDate { 443 | "date": 24, 444 | "isDoubledDay": false, 445 | "isLeapDay": false, 446 | "isPreviousSkipped": false, 447 | "isSkippedDay": false, 448 | "month": 5, 449 | "monthObj": TibetanMonth { 450 | "days": Array [], 451 | "endDateStr": "2019-08-01", 452 | "isDoubledMonth": false, 453 | "isLeapMonth": false, 454 | "month": 5, 455 | "startDateStr": "2019-07-03", 456 | "year": 2146, 457 | }, 458 | "westernDate": 2019-07-26T10:00:00.000Z, 459 | "year": 2146, 460 | }, 461 | TibetanDate { 462 | "date": 25, 463 | "isDoubledDay": false, 464 | "isLeapDay": false, 465 | "isPreviousSkipped": false, 466 | "isSkippedDay": false, 467 | "month": 5, 468 | "monthObj": TibetanMonth { 469 | "days": Array [], 470 | "endDateStr": "2019-08-01", 471 | "isDoubledMonth": false, 472 | "isLeapMonth": false, 473 | "month": 5, 474 | "startDateStr": "2019-07-03", 475 | "year": 2146, 476 | }, 477 | "westernDate": 2019-07-27T10:00:00.000Z, 478 | "year": 2146, 479 | }, 480 | TibetanDate { 481 | "date": 26, 482 | "isDoubledDay": false, 483 | "isLeapDay": false, 484 | "isPreviousSkipped": false, 485 | "isSkippedDay": false, 486 | "month": 5, 487 | "monthObj": TibetanMonth { 488 | "days": Array [], 489 | "endDateStr": "2019-08-01", 490 | "isDoubledMonth": false, 491 | "isLeapMonth": false, 492 | "month": 5, 493 | "startDateStr": "2019-07-03", 494 | "year": 2146, 495 | }, 496 | "westernDate": 2019-07-28T10:00:00.000Z, 497 | "year": 2146, 498 | }, 499 | TibetanDate { 500 | "date": 27, 501 | "isDoubledDay": false, 502 | "isLeapDay": false, 503 | "isPreviousSkipped": false, 504 | "isSkippedDay": false, 505 | "month": 5, 506 | "monthObj": TibetanMonth { 507 | "days": Array [], 508 | "endDateStr": "2019-08-01", 509 | "isDoubledMonth": false, 510 | "isLeapMonth": false, 511 | "month": 5, 512 | "startDateStr": "2019-07-03", 513 | "year": 2146, 514 | }, 515 | "westernDate": 2019-07-29T10:00:00.000Z, 516 | "year": 2146, 517 | }, 518 | TibetanDate { 519 | "date": 28, 520 | "isDoubledDay": false, 521 | "isLeapDay": false, 522 | "isPreviousSkipped": false, 523 | "isSkippedDay": false, 524 | "month": 5, 525 | "monthObj": TibetanMonth { 526 | "days": Array [], 527 | "endDateStr": "2019-08-01", 528 | "isDoubledMonth": false, 529 | "isLeapMonth": false, 530 | "month": 5, 531 | "startDateStr": "2019-07-03", 532 | "year": 2146, 533 | }, 534 | "westernDate": 2019-07-30T10:00:00.000Z, 535 | "year": 2146, 536 | }, 537 | TibetanDate { 538 | "date": 29, 539 | "isDoubledDay": false, 540 | "isLeapDay": false, 541 | "isPreviousSkipped": false, 542 | "isSkippedDay": false, 543 | "month": 5, 544 | "monthObj": TibetanMonth { 545 | "days": Array [], 546 | "endDateStr": "2019-08-01", 547 | "isDoubledMonth": false, 548 | "isLeapMonth": false, 549 | "month": 5, 550 | "startDateStr": "2019-07-03", 551 | "year": 2146, 552 | }, 553 | "westernDate": 2019-07-31T10:00:00.000Z, 554 | "year": 2146, 555 | }, 556 | TibetanDate { 557 | "date": 30, 558 | "isDoubledDay": false, 559 | "isLeapDay": false, 560 | "isPreviousSkipped": false, 561 | "isSkippedDay": false, 562 | "month": 5, 563 | "monthObj": TibetanMonth { 564 | "days": Array [], 565 | "endDateStr": "2019-08-01", 566 | "isDoubledMonth": false, 567 | "isLeapMonth": false, 568 | "month": 5, 569 | "startDateStr": "2019-07-03", 570 | "year": 2146, 571 | }, 572 | "westernDate": 2019-08-01T10:00:00.000Z, 573 | "year": 2146, 574 | }, 575 | ] 576 | `; 577 | 578 | exports[`TibetanMonth should create a correct class when invoked with object arg 1`] = ` 579 | TibetanMonth { 580 | "days": Array [], 581 | "endDateStr": "2019-06-03", 582 | "isDoubledMonth": false, 583 | "isLeapMonth": false, 584 | "month": 3, 585 | "startDateStr": "2019-05-05", 586 | "year": 2146, 587 | } 588 | `; 589 | 590 | exports[`TibetanMonth should create a correct class when invoked with string arg 1`] = ` 591 | TibetanMonth { 592 | "days": Array [], 593 | "endDateStr": "2019-07-02", 594 | "isDoubledMonth": false, 595 | "isLeapMonth": false, 596 | "month": 4, 597 | "startDateStr": "2019-06-04", 598 | "year": 2146, 599 | } 600 | `; 601 | 602 | exports[`TibetanMonth should create a correct class when invoked without arg 1`] = ` 603 | TibetanMonth { 604 | "days": Array [], 605 | "endDateStr": "2019-07-02", 606 | "isDoubledMonth": false, 607 | "isLeapMonth": false, 608 | "month": 4, 609 | "startDateStr": "2019-06-04", 610 | "year": 2146, 611 | } 612 | `; 613 | 614 | exports[`TibetanMonth should return year object correctly) 1`] = ` 615 | TibetanYear { 616 | "animal": "Pig", 617 | "element": "Earth", 618 | "gender": "Female", 619 | "months": Array [], 620 | "rabjungCycle": 17, 621 | "rabjungYear": 33, 622 | "tibYearNum": 2146, 623 | "westernYear": 2019, 624 | } 625 | `; 626 | -------------------------------------------------------------------------------- /dist/index.es.js: -------------------------------------------------------------------------------- 1 | // CONSTANTS 2 | // conversion constants 3 | // The beginning of the Rabjung count according to Western calendar, A.D. 1027 4 | var RABJUNG_BEGINNING = 1027; 5 | // length of rabjung cycle in years 6 | var RABJUNG_CYCLE_LENGTH = 60; 7 | // difference between Western and Tibetan year count 8 | var YEAR_DIFF = 127; 9 | // difference between Unix and Julian date starts 10 | var JULIAN_TO_UNIX = 2440587.5; 11 | // number of milliseconds in a year 12 | var MS_IN_YEAR = 86400000; 13 | // number of minutes in day 14 | var MIN_IN_DAY = 1440; 15 | // calendrical constants: month calculations 16 | // beginning of epoch based on Kalachakra. Used as 0 for month counts since this time 17 | var YEAR0 = 806; 18 | var MONTH0 = 3; 19 | // constants given in Svante's article 20 | var BETA_STAR = 61; 21 | var BETA = 123; 22 | // const P1 = 77 / 90; 23 | // const P0 = 139 / 180; 24 | // const ALPHA = 1 + 827 / 1005; 25 | // calendrical constants: day calculations 26 | // mean date 27 | var M0 = 2015501 + 4783 / 5656; 28 | var M1 = 167025 / 5656; 29 | var M2 = M1 / 30; 30 | // mean sun 31 | var S0 = 743 / 804; 32 | var S1 = 65 / 804; 33 | var S2 = S1 / 30; 34 | // anomaly moon 35 | var A0 = 475 / 3528; 36 | var A1 = 253 / 3528; 37 | var A2 = 1 / 28; 38 | // fixed tables 39 | var MOON_TAB = [0, 5, 10, 15, 19, 22, 24, 25]; 40 | var SUN_TAB = [0, 6, 10, 11]; 41 | // year elements & animals 42 | var YEAR_ELEMENTS = ['Wood', 'Fire', 'Earth', 'Iron', 'Water']; 43 | var YEAR_ANIMALS = ['Mouse', 'Ox', 'Tiger', 'Rabbit', 44 | 'Dragon', 'Snake', 'Horse', 'Sheep', 'Monkey', 'Bird', 'Dog', 'Pig']; 45 | var YEAR_GENDER = ['Male', 'Female']; 46 | 47 | /** 48 | * fraction part a number 49 | * 50 | * @param {number} a - a number to check 51 | * @returns {number} the fractional part of a number 52 | */ 53 | var frac = function (a) { return a % 1; }; 54 | /** 55 | * Modulo of a number (a % b), but from 1..b instead of 0..b-1. 56 | * This means that 57 | * amod(a, b) = a % b 58 | * unless a is a multiple of b. In that case: 59 | * a % b = 0 but amod(a, b) = b. 60 | * 61 | * @param {number} a - number to be divided 62 | * @param {number} b - the number to be divided with 63 | * @return {number} 64 | */ 65 | // TODO: add control for b <= 0. Not urgent as this is not what we expect in calendar calculations (?) 66 | var amod = function (a, b) { return (a % b) || b; }; 67 | 68 | var getDateStr = function (date) { 69 | var year = date.getFullYear(); 70 | var month = date.getMonth() + 1; 71 | var dayNr = date.getDate(); 72 | return year + "-" + ("0" + month).slice(-2) + "-" + ("0" + dayNr).slice(-2); 73 | }; 74 | 75 | /** 76 | * Subtract 1 day from a date 77 | * @param {number} day - the tibetan day 78 | * @param {number} monthCount - month count since beginning of epoch 79 | * @returns {{day, monthCount}} 80 | */ 81 | var getDayBefore = function (day, monthCount) { return (day === 1 82 | ? { day: 30, monthCount: monthCount - 1 } 83 | : { day: day - 1, monthCount: monthCount }); }; 84 | 85 | /** 86 | * Calculates whether a given Tibetan year and month number is doubled, i.e 87 | * is preceded by a leap month. 88 | * 89 | * @param {number} tYear - tibetan year 90 | * @param {number} month - month number 91 | * @returns {boolean} 92 | */ 93 | var isDoubledMonth = function (tYear, month) { 94 | var mp = 12 * (tYear - YEAR_DIFF - YEAR0) + month; 95 | return ((2 * mp) % 65 === BETA % 65) || ((2 * mp) % 65 === (BETA + 1) % 65); 96 | }; 97 | 98 | // Implement partial formulas in Svante's paper. 99 | /** 100 | * meanDate(day, monthCount) - corresponding to the linear mean motion of the moon 101 | * @param {number} day - the tibetan day 102 | * @param {number} monthCount - month count since beginning of epoch 103 | * @returns {number} 104 | */ 105 | var meanDate = function (day, monthCount) { return monthCount * M1 + day * M2 + M0; }; 106 | 107 | /** 108 | * the anomaly of the moon 109 | * @param {number} day - the tibetan day 110 | * @param {number} monthCount - month count since beginning of epoch 111 | * @returns {number} 112 | */ 113 | var moonAnomaly = function (day, monthCount) { return monthCount * A1 + day * A2 + A0; }; 114 | /** 115 | * Moon tab for integer values 116 | * @param {number} i 117 | * @returns {number} 118 | */ 119 | var moonTabInt = function (i) { 120 | var iMod = i % 28; 121 | if (iMod <= 7) { 122 | return MOON_TAB[iMod]; 123 | } 124 | if (iMod <= 14) { 125 | return MOON_TAB[14 - iMod]; 126 | } 127 | if (iMod <= 21) { 128 | return -MOON_TAB[iMod - 14]; 129 | } 130 | return -MOON_TAB[28 - iMod]; 131 | }; 132 | /** 133 | * Moon tab, with linear interpolation 134 | * @param {number} i 135 | * @returns {number} 136 | */ 137 | var moonTab = function (i) { 138 | var a = moonTabInt(Math.floor(i)); 139 | var x = frac(i); 140 | if (x) { 141 | var b = moonTabInt(Math.floor(i) + 1); 142 | a += (b - a) * x; 143 | } 144 | return a; 145 | }; 146 | /** 147 | * Equation of the moon. 148 | * @param {number} day - the tibetan day 149 | * @param {number} monthCount - month count since beginning of epoch 150 | * @returns {number} 151 | */ 152 | var moonEqu = function (day, monthCount) { return moonTab(28 * moonAnomaly(day, monthCount)); }; 153 | 154 | /** 155 | * the mean longitude of the sun 156 | * @param {number} day - the tibetan day 157 | * @param {number} monthCount - month count since beginning of epoch 158 | * @returns {number} 159 | */ 160 | var meanSun = function (day, monthCount) { return monthCount * S1 + day * S2 + S0; }; 161 | /** 162 | * sunAnomaly(day, monthCount) 163 | * @param {number} day - the tibetan day 164 | * @param {number} monthCount - month count since beginning of epoch 165 | * @returns {number} 166 | */ 167 | var sunAnomaly = function (day, monthCount) { return meanSun(day, monthCount) - 1 / 4; }; 168 | /** 169 | * sun tab for integer values 170 | * @param {number} i 171 | * @returns {number} 172 | */ 173 | var sunTabInt = function (i) { 174 | var iMod = i % 12; 175 | if (iMod <= 3) { 176 | return SUN_TAB[iMod]; 177 | } 178 | if (iMod <= 6) { 179 | return SUN_TAB[6 - iMod]; 180 | } 181 | if (iMod <= 9) { 182 | return -SUN_TAB[iMod - 6]; 183 | } 184 | return -SUN_TAB[12 - iMod]; 185 | }; 186 | /** 187 | * sun tab, with linear interpolation 188 | * @param {number} i 189 | * @returns {number} 190 | */ 191 | var sunTab = function (i) { 192 | var a = sunTabInt(Math.floor(i)); 193 | var x = frac(i); 194 | if (x) { 195 | var b = sunTabInt(Math.floor(i) + 1); 196 | a += (b - a) * x; 197 | } 198 | return a; 199 | }; 200 | /** 201 | * Equation of the sun. 202 | * @param {number} day - the tibetan day 203 | * @param {number} monthCount - month count since beginning of epoch 204 | * @returns {number} 205 | */ 206 | var sunEqu = function (day, monthCount) { return sunTab(12 * sunAnomaly(day, monthCount)); }; 207 | 208 | /*! ***************************************************************************** 209 | Copyright (c) Microsoft Corporation. All rights reserved. 210 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 211 | this file except in compliance with the License. You may obtain a copy of the 212 | License at http://www.apache.org/licenses/LICENSE-2.0 213 | 214 | THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 215 | KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 216 | WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 217 | MERCHANTABLITY OR NON-INFRINGEMENT. 218 | 219 | See the Apache Version 2.0 License for specific language governing permissions 220 | and limitations under the License. 221 | ***************************************************************************** */ 222 | 223 | var __assign = function() { 224 | __assign = Object.assign || function __assign(t) { 225 | for (var s, i = 1, n = arguments.length; i < n; i++) { 226 | s = arguments[i]; 227 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; 228 | } 229 | return t; 230 | }; 231 | return __assign.apply(this, arguments); 232 | }; 233 | 234 | /** 235 | * figure out the animal and element for a tibetan year 236 | * 237 | * @param {Year} year 238 | * @return {Year} with additional attributes 239 | */ 240 | var yearAttributes = function (year) { 241 | var thisYear = __assign({}, year); 242 | var y = thisYear.tibYear; 243 | thisYear.animal = YEAR_ANIMALS[(y + 1) % 12]; 244 | thisYear.element = YEAR_ELEMENTS[Math.floor(((y - 1) / 2) % 5)]; 245 | thisYear.gender = YEAR_GENDER[(y + 1) % 2]; 246 | return thisYear; 247 | }; 248 | 249 | /** 250 | * get Julian date from UNIX date 251 | * see explanation: 252 | * https://stackoverflow.com/questions/11759992/calculating-jdayjulian-day-in-javascript 253 | * 254 | * @property {Date} unixTime - the date object to be converted 255 | * @return {number} - julian date 256 | */ 257 | var julianFromUnix = function (unixTime) { 258 | var dateOnly = getDateStr(unixTime); 259 | var timeAfterNoon = new Date(dateOnly + "T18:00:00"); 260 | return Math.floor(+timeAfterNoon / MS_IN_YEAR 261 | - unixTime.getTimezoneOffset() / MIN_IN_DAY 262 | + JULIAN_TO_UNIX); 263 | }; 264 | 265 | /** 266 | * This is the reverse of fromMonthCount(n): from a Tibetan year, month number 267 | * and leap month indicator, calculates the "month count" based on the epoch. 268 | * 269 | * @param {Month} monthObject 270 | * @returns {number} month count since epoch 271 | */ 272 | var monthCountFromTibetan = function (_a) { 273 | var year = _a.year, month = _a.month, isLeapMonth = _a.isLeapMonth; 274 | // the formulas on Svante's paper use western year numbers 275 | var wYear = year - YEAR_DIFF; 276 | var solarMonth = 12 * (wYear - YEAR0) + month - MONTH0; 277 | var hasLeap = isDoubledMonth(year, month); 278 | var isLeap = hasLeap && isLeapMonth ? 1 : 0; 279 | return Math.floor((67 * solarMonth + BETA_STAR + 17) / 65) - isLeap; 280 | // return Math.floor((12 * (year - Y0) + monthObject.month - ALPHA - (1 - 12 * S1) * isLeap) / (12 * S1)); 281 | }; 282 | 283 | /** 284 | * The date at the end of the lunar day (similar to month count since beginning of epoch, but for days) 285 | * It is calculated by first calculating a simpler mean date, corresponding to the linear mean motion of 286 | * the moon, and then adjusting it by the equations of the moon and sun, which are determined by the 287 | * anomalies of the moon and sun together with tables. 288 | * @param {number} day - the tibetan day 289 | * @param {number} monthCount - month count since beginning of epoch 290 | * @returns {number} 291 | */ 292 | var trueDateFromMonthCountDay = function (day, monthCount) { return (meanDate(day, monthCount) 293 | + moonEqu(day, monthCount) / 60 294 | - sunEqu(day, monthCount) / 60); }; 295 | 296 | /** 297 | * Gives the Julian date for a Tibetan year, month number (leap or not) and Tibetan day. 298 | * 299 | * Does not check that the tibetan day actually exists: 300 | * - If given the date of a skipped day, will return the same Julian date as the day before. 301 | * - If given the date of a duplicate day, returns the Julian date of the second of the two. 302 | * 303 | * @param {number} year - Tibetan year 304 | * @param {number} month - Tibetan month 305 | * @param {boolean} isLeapMonth - true if leap month 306 | * @param {number} day - Tibetan day 307 | * @returns {number} - Julian date 308 | */ 309 | var julianFromTibetan = function (year, month, isLeapMonth, day) { 310 | var monthCount = monthCountFromTibetan({ year: year, month: month, isLeapMonth: isLeapMonth }); 311 | return Math.floor(trueDateFromMonthCountDay(day, monthCount)); 312 | }; 313 | 314 | /** 315 | * calculates the julian date from the day count a variant of true_date 316 | * @param {number} dayCount - the day count since beginning of epoch defined as dayNo + 30 * monthCount 317 | * @return {number} - julian date 318 | */ 319 | var julianFromTrueDate = function (dayCount) { 320 | var monthCount = Math.floor((dayCount - 1) / 30); 321 | var calculatedDay = dayCount % 30 || 30; 322 | return Math.floor(trueDateFromMonthCountDay(calculatedDay, monthCount)); 323 | }; 324 | 325 | /** 326 | * get Unix date from Julian date 327 | * since we use only date calculations here, there is no need to correct for timezone differences 328 | * see explanation: 329 | * https://stackoverflow.com/questions/11759992/calculating-jdayjulian-day-in-javascript 330 | * 331 | * @param {number} julianDate - the julian date 332 | * @return {Date} 333 | */ 334 | var unixFromJulian = function (julianDate) { 335 | var localTimezoneOffset = new Date().getTimezoneOffset(); 336 | var unixDate = (julianDate - JULIAN_TO_UNIX + localTimezoneOffset / MIN_IN_DAY) * MS_IN_YEAR; 337 | var unix = new Date(unixDate); 338 | return unix; 339 | }; 340 | 341 | /** 342 | * Calculates the Western date for Losar (Tibetan new year) of a given Tibetan 343 | * year number (ex. 2137). 344 | * @param {number} tibYear - Tibetan year number 345 | * @returns {Date} 346 | */ 347 | var getLosarForYear = function (year, isTibetan) { 348 | if (isTibetan === void 0) { isTibetan = true; } 349 | var tibYear = isTibetan ? year : year + YEAR_DIFF; 350 | var julianDay = 1 + julianFromTibetan(tibYear - 1, 12, false, 30); 351 | return getDateStr(unixFromJulian(julianDay)); 352 | }; 353 | 354 | /** 355 | * Figures out the Tibetan year number, month number within the year, and whether 356 | * this is a leap month, from a "month count" number. See Svante Janson, 357 | * "Tibetan Calendar Mathematics", p.8 ff. 358 | * 359 | * @param {number} monthCount: the "month count" since beginning of epoch 360 | * @returns {Month} 361 | */ 362 | var getMonthFromMonthCount = function (monthCount) { 363 | // const x = ceil(12 * S1 * n + ALPHA); 364 | var x = Math.ceil((65 * monthCount + BETA) / 67); 365 | var tMonth = amod(x, 12); 366 | var tYear = Math.ceil(x / 12) - 1 + YEAR0 + YEAR_DIFF; 367 | var isLeapMonth = Math.ceil((65 * (monthCount + 1) + BETA) / 67) === x; 368 | return { year: tYear, month: tMonth, isLeapMonth: isLeapMonth }; 369 | }; 370 | 371 | /** 372 | * Calculates full information for a given Tibetan date 373 | * 374 | * For doubled days, just as with doubled months, the "main" day or month is 375 | * the second, and the "leap" day or month is the first. 376 | * 377 | * @param {object} arg 378 | * @param {number} arg.year - Tibetan year number (ex. 2135) 379 | * @param {number} arg.month - month number (1 to 12) 380 | * @param {boolean} [arg.isLeapMonth=false] - is this month a leap month 381 | * @param {number} arg.day - day number (1 to 30) 382 | * @param {boolean} [arg.isLeapDay=false] - is this day a leap day 383 | * 384 | * @returns {Day} day - with all its attributes. isLeapMonth and isLeapDay are checked and corrected compared to input 385 | */ 386 | var getDayFromTibetan = function (_a) { 387 | var year = _a.year, month = _a.month, _b = _a.isLeapMonth, isLeapMonth = _b === void 0 ? false : _b, day = _a.day, _c = _a.isLeapDay, isLeapDay = _c === void 0 ? false : _c; 388 | var julianDate = julianFromTibetan(year, month, isLeapMonth, day); 389 | // also calculate the Julian date of the previous Tib. day 390 | var monthCount = monthCountFromTibetan({ year: year, month: month, isLeapMonth: isLeapMonth }); 391 | var dayBefore = getDayBefore(day, monthCount); 392 | var julianDatePrevious = Math.floor(trueDateFromMonthCountDay(dayBefore.day, dayBefore.monthCount)); 393 | var twoDaysBefore = getDayBefore(dayBefore.day, dayBefore.monthCount); 394 | var julianDate2DaysBefore = Math.floor(trueDateFromMonthCountDay(twoDaysBefore.day, twoDaysBefore.monthCount)); 395 | // figure out leap months, leap days & skipped days 396 | var isDoubledMonthThis = isDoubledMonth(year, month); 397 | var hasLeapDayThis = julianDate === julianDatePrevious + 2; 398 | var skippedDay = julianDate === julianDatePrevious; 399 | var isPreviousSkipped = julianDatePrevious === julianDate2DaysBefore; 400 | var isLeapDayChecked = isLeapDay && hasLeapDayThis; 401 | // figure out western date info for the main or leap day 402 | if (isLeapDayChecked) { 403 | julianDate--; 404 | } 405 | var westernDate = unixFromJulian(julianDate); 406 | return ({ 407 | year: year, 408 | month: { 409 | month: month, 410 | isLeapMonth: isLeapMonth && isDoubledMonthThis, 411 | isDoubledMonth: isDoubledMonthThis, 412 | }, 413 | day: day, 414 | skippedDay: skippedDay, 415 | isPreviousSkipped: isPreviousSkipped, 416 | isLeapDay: isLeapDayChecked, 417 | isDoubledDay: hasLeapDayThis, 418 | westernDate: westernDate 419 | }); 420 | }; 421 | 422 | /** 423 | * Calculates a Tibetan date for a given western date. This does a binary search, and is therefore 424 | * much slower than tibToWestern(). 425 | * 426 | * The algorithm could be much improved by using the reverse of meanDate() to start with, 427 | * and then using the fact that julian dates and "tibetan day numbers" have a quasi-linear relation. 428 | * 429 | * @param {Date} date - the western date 430 | * @return {Day} 431 | */ 432 | var getDayFromWestern = function (date) { 433 | // const date = new Date(wYear, wMonth - 1, wDay); 434 | var wYear = date.getFullYear(); 435 | var jd = julianFromUnix(date); 436 | var tibYears = [wYear + YEAR_DIFF - 1, wYear + YEAR_DIFF + 1]; 437 | var monthCounts = tibYears.map(function (y) { return monthCountFromTibetan({ year: y, month: 1, isLeapMonth: true }); }); 438 | var trueDate = monthCounts.map(function (m) { return 1 + 30 * m; }); 439 | var jds = trueDate.map(function (n) { return julianFromTrueDate(n); }); 440 | // croak "Binary search algorithm is wrong" unless $jd1 <= $jd && $jd <= $jd2; 441 | while (trueDate[0] < trueDate[1] - 1 && jds[0] < jds[1]) { 442 | var nTrueDate = Math.floor((trueDate[0] + trueDate[1]) / 2); 443 | var njd = julianFromTrueDate(nTrueDate); 444 | if (njd < jd) { 445 | trueDate[0] = nTrueDate; 446 | jds[0] = njd; 447 | } 448 | else { 449 | trueDate[1] = nTrueDate; 450 | jds[1] = njd; 451 | } 452 | } 453 | // so we found it; 454 | var winnerJd; 455 | var winnerTrueDate; 456 | // if the western date is the 1st of a doubled tib. day, then jd[0] == jd - 1 and 457 | // jd[1] == jd + 1, and the corresponding tib. day number is the one from jd[1]. 458 | if (jds[0] === jd) { 459 | winnerJd = jds[0]; // eslint-disable-line prefer-destructuring 460 | winnerTrueDate = trueDate[0]; // eslint-disable-line prefer-destructuring 461 | } 462 | else { 463 | winnerJd = jds[1]; // eslint-disable-line prefer-destructuring 464 | winnerTrueDate = trueDate[1]; // eslint-disable-line prefer-destructuring 465 | } 466 | // figure out the real tib. date: year, month, leap month, day number, leap day. 467 | var isLeapDay = winnerJd > jd; 468 | var monthCount = Math.floor((winnerTrueDate - 1) / 30); 469 | var day = (winnerTrueDate % 30) || 30; 470 | var _a = getMonthFromMonthCount(monthCount), year = _a.year, month = _a.month, isLeapMonth = _a.isLeapMonth; 471 | return getDayFromTibetan({ 472 | year: year, month: month, isLeapMonth: isLeapMonth, day: day, isLeapDay: isLeapDay 473 | }); 474 | }; 475 | 476 | /** 477 | * Figures out a year's info from a Western calendar year number, ex. 2008. 478 | * 479 | * @param {number} wYear: Western calendar year number, ex. 2008 480 | * @returns {Year} 481 | */ 482 | var getYearFromWestern = function (wYear) { return (yearAttributes({ 483 | rabjungCycle: Math.ceil((wYear - RABJUNG_BEGINNING + 1) / RABJUNG_CYCLE_LENGTH), 484 | rabjungYear: amod(wYear - RABJUNG_BEGINNING + 1, RABJUNG_CYCLE_LENGTH), 485 | tibYear: wYear + YEAR_DIFF, 486 | westernYear: wYear, 487 | })); }; 488 | 489 | /** 490 | * Figures out a year's info from a Tibetan calendar year number, ex. 2135. 491 | * 492 | * @param {number} tYear - Tibetan calendar year number, ex. 2135. 493 | * @returns {Year} 494 | */ 495 | var getYearFromTibetan = function (tYear) { return getYearFromWestern(tYear - YEAR_DIFF); }; 496 | 497 | /** 498 | * Figures out a year's info based on the Tibetan calendar, ex. the 3rd year of the 15th Rabjung calendrical cycle. 499 | * @param {object} arg 500 | * @param {number} arg.rabjungCycle : number of the cycle 501 | * @param {number} arg.rabjungYear : number of the year within the cycle, from 1 to 60. 502 | * @returns {null | Year} 503 | */ 504 | var getYearFromRabjung = function (_a) { 505 | var rabjungCycle = _a.rabjungCycle, rabjungYear = _a.rabjungYear; 506 | if (rabjungCycle < 1 || rabjungYear > RABJUNG_CYCLE_LENGTH) { 507 | throw new Error("Year number must be between 1 and " + RABJUNG_CYCLE_LENGTH); 508 | } 509 | var wYear = RABJUNG_BEGINNING + (rabjungCycle - 1) * RABJUNG_CYCLE_LENGTH + (rabjungYear - 1); 510 | var year = yearAttributes({ 511 | rabjungCycle: rabjungCycle, 512 | rabjungYear: rabjungYear, 513 | tibYear: wYear + YEAR_DIFF, 514 | westernYear: wYear, 515 | }); 516 | return year; 517 | }; 518 | 519 | /** 520 | * A TibetanYear class 521 | * @param {...(object,number)} [arg] undefined will return tibetan year for 522 | * current year | number will return tibetan year unless isWestern is true | 523 | * object will return tibetan year according to rabjung cycle 524 | * @param {number} arg.rabjungCycle number of the cycle 525 | * @param {number} arg.rabjungYear number of the year within the cycle, 526 | * from 1 to 60. 527 | * @param {bool} [isWestern=false] optional second argument, if set to true 528 | * and fist arg is a number it will be treated as western year date 529 | */ 530 | var TibetanYear = /** @class */ (function () { 531 | function TibetanYear(arg, isWestern) { 532 | if (isWestern === void 0) { isWestern = false; } 533 | var yearInit; 534 | if (!arg) { 535 | var westernDate = new Date(); 536 | var tibDate = new TibetanDate(westernDate.toISOString()); 537 | yearInit = getYearFromTibetan(tibDate.year); 538 | } 539 | else if (typeof arg === 'number') { 540 | if (isWestern) { 541 | yearInit = getYearFromWestern(arg); 542 | } 543 | else { 544 | yearInit = getYearFromTibetan(arg); 545 | } 546 | } 547 | else { 548 | yearInit = getYearFromRabjung(arg); 549 | } 550 | this.rabjungCycle = yearInit.rabjungCycle; 551 | this.rabjungYear = yearInit.rabjungYear; 552 | this.tibYearNum = yearInit.tibYear; 553 | this.westernYear = yearInit.westernYear; 554 | this.animal = yearInit.animal; 555 | this.element = yearInit.element; 556 | this.gender = yearInit.gender; 557 | this.months = []; 558 | } 559 | // Need to call this method at least once in order to calculate the month. 560 | // This is in order to reduce initial object size. 561 | TibetanYear.prototype.getMonths = function () { 562 | if (this.months.length) { 563 | return this.months; 564 | } 565 | // loop over the month, taking care of duplicates 566 | for (var m = 1; m <= 12; m++) { 567 | var month = new TibetanMonth({ year: this.tibYearNum, month: m }); 568 | this.months.push(month); 569 | if (isDoubledMonth(this.tibYearNum, m)) { 570 | var leapMonth = new TibetanMonth({ year: this.tibYearNum, month: m, isLeapMonth: true }); 571 | this.months.push(leapMonth); 572 | } 573 | } 574 | return this.months; 575 | }; 576 | TibetanYear.prototype.toString = function () { 577 | return "" + this.tibYearNum; 578 | }; 579 | TibetanYear.prototype.toRabjungString = function () { 580 | return "The " + this.rabjungYear + ". year of the " + this.rabjungCycle + ". rabjung cycle"; 581 | }; 582 | return TibetanYear; 583 | }()); 584 | 585 | /** 586 | * Calculates full information about a Tibetan month: whether it is doubled or not, 587 | * and the western start and end date for it. 588 | * The start_date and end_date correspond to the leap month if isLeapMonth is true, 589 | * otherwise to the main month (i.e the second of the two). 590 | * 591 | * @param {object} arg 592 | * @param {number} arg.year - the Tibetan year 593 | * @param {number} arg.month - the Tibetan month number (1 to 12) 594 | * @param {boolean} [arg.isLeapMonth=false] - if leap month or not 595 | * @returns {Month} 596 | */ 597 | var getMonthFromTibetan = function (_a) { 598 | var year = _a.year, month = _a.month, _b = _a.isLeapMonth, isLeapMonth = _b === void 0 ? false : _b; 599 | var hasLeap = isDoubledMonth(year, month); 600 | var isLeap = isLeapMonth && hasLeap; 601 | // calculate the Julian date 1st and last of the month 602 | var monthCount = monthCountFromTibetan({ year: year, month: month, isLeapMonth: isLeap }); 603 | var jdFirst = 1 + Math.floor(trueDateFromMonthCountDay(30, monthCount - 1)); 604 | var jdLast = Math.floor(trueDateFromMonthCountDay(30, monthCount)); 605 | var startDate = getDateStr(unixFromJulian(jdFirst)); 606 | var endDate = getDateStr(unixFromJulian(jdLast)); 607 | return { 608 | year: year, month: month, isLeapMonth: isLeap, isDoubledMonth: hasLeap, startDate: startDate, endDate: endDate, 609 | }; 610 | }; 611 | 612 | /** 613 | * A TibetanMonth class 614 | * @param {...(object,string)} [arg] undefined will return tibetan month for 615 | * current month | string will return tibetan day for month of `new Date(arg)` | 616 | * object will return tibetan month according to object definition 617 | * @param {number} arg.year - Tibetan year number (ex. 2135) 618 | * @param {number} arg.month - month number (1 to 12) 619 | * @param {boolean} [arg.isLeapMonth=false] - is this month a leap month 620 | */ 621 | var TibetanMonth = /** @class */ (function () { 622 | function TibetanMonth(arg) { 623 | var westernDate; 624 | var tibDate; 625 | if (!arg) { 626 | westernDate = new Date(); 627 | tibDate = new TibetanDate(westernDate.toISOString()); 628 | } 629 | else if (typeof arg === 'string') { 630 | westernDate = new Date(arg); 631 | tibDate = new TibetanDate(westernDate.toISOString()); 632 | } 633 | else { 634 | this.year = arg.year; 635 | this.month = arg.month; 636 | this.isLeapMonth = arg.isLeapMonth || false; 637 | } 638 | if (tibDate) { 639 | this.year = tibDate.year; 640 | this.month = tibDate.month; 641 | this.isLeapMonth = tibDate.monthObj.isLeapMonth; 642 | } 643 | var monthObj = getMonthFromTibetan({ 644 | year: this.year, 645 | month: this.month, 646 | isLeapMonth: this.isLeapMonth 647 | }); 648 | this.isDoubledMonth = monthObj.isDoubledMonth; 649 | this.startDateStr = monthObj.startDate; 650 | this.endDateStr = monthObj.endDate; 651 | this.days = []; 652 | } 653 | Object.defineProperty(TibetanMonth.prototype, "yearObj", { 654 | get: function () { 655 | return new TibetanYear(this.year); 656 | }, 657 | enumerable: true, 658 | configurable: true 659 | }); 660 | // Need to call this method at least once in order to calculate the dates. 661 | // This is in order to reduce initial object size. 662 | TibetanMonth.prototype.getDays = function () { 663 | if (this.days.length) { 664 | return this.days; 665 | } 666 | // loop over the days, taking care of duplicate and missing days 667 | for (var d = 1; d <= 30; d++) { 668 | var day = new TibetanDate({ 669 | year: this.year, 670 | month: this.month, 671 | isLeapMonth: this.isLeapMonth, 672 | day: d, 673 | isLeapDay: false 674 | }); 675 | if (day.isDoubledDay) { 676 | var main = new TibetanDate({ 677 | year: this.year, 678 | month: this.month, 679 | isLeapMonth: this.isLeapMonth, 680 | day: d, 681 | isLeapDay: true 682 | }); 683 | this.days.push(main); 684 | } 685 | if (!day.isSkippedDay) { 686 | this.days.push(day); 687 | } 688 | } 689 | return this.days; 690 | }; 691 | TibetanMonth.prototype.toString = function () { 692 | var double = ''; 693 | if (this.isDoubledMonth) { 694 | double = this.isLeapMonth ? '-leap' : '-main'; 695 | } 696 | return this.yearObj.toString() + "-" + this.month + double; 697 | }; 698 | return TibetanMonth; 699 | }()); 700 | 701 | /** 702 | * A TibetanDate class 703 | * @param {...(object,string)} [arg] undefined will return tibetan date 704 | * for today | string will return tibetan day for `new Date(arg)` | object 705 | * will return tibetan day according to object definition 706 | * @param {number} arg.year - Tibetan year number (ex. 2135) 707 | * @param {number} arg.month - month number (1 to 12) 708 | * @param {boolean} [arg.isLeapMonth=false] - is this month a leap month 709 | * @param {number} arg.day - day number (1 to 30) 710 | * @param {boolean} [arg.isLeapDay=false] - is this day a leap day 711 | */ 712 | var TibetanDate = /** @class */ (function () { 713 | function TibetanDate(arg) { 714 | var tibDate; 715 | if (!arg) { 716 | this.westernDate = new Date(); 717 | tibDate = getDayFromWestern(this.westernDate); 718 | } 719 | else if (typeof arg === 'string') { 720 | this.westernDate = new Date(arg); 721 | tibDate = getDayFromWestern(this.westernDate); 722 | } 723 | else { 724 | tibDate = getDayFromTibetan(arg); 725 | this.westernDate = tibDate.westernDate; 726 | } 727 | this.isSkippedDay = tibDate.skippedDay; 728 | this.isPreviousSkipped = tibDate.isPreviousSkipped; 729 | // the first of doubled days 730 | this.isLeapDay = tibDate.isLeapDay; 731 | this.isDoubledDay = tibDate.isDoubledDay; 732 | this.date = tibDate.day; 733 | this.year = tibDate.year; 734 | this.month = tibDate.month.month; 735 | this.monthObj = new TibetanMonth({ 736 | year: tibDate.year, 737 | month: tibDate.month.month, 738 | isLeapMonth: tibDate.month.isLeapMonth 739 | }); 740 | } 741 | Object.defineProperty(TibetanDate.prototype, "day", { 742 | /** GETTERS */ 743 | get: function () { 744 | return this.westernDate.getDay(); 745 | }, 746 | enumerable: true, 747 | configurable: true 748 | }); 749 | Object.defineProperty(TibetanDate.prototype, "yearObj", { 750 | get: function () { 751 | return new TibetanYear(this.year); 752 | }, 753 | enumerable: true, 754 | configurable: true 755 | }); 756 | Object.defineProperty(TibetanDate.prototype, "westernDateStr", { 757 | get: function () { 758 | return getDateStr(this.westernDate); 759 | }, 760 | enumerable: true, 761 | configurable: true 762 | }); 763 | /** METHODS */ 764 | TibetanDate.prototype.getWesternDate = function () { 765 | return this.westernDate; 766 | }; 767 | TibetanDate.prototype.getDate = function () { 768 | return this.date; 769 | }; 770 | TibetanDate.prototype.getDay = function () { 771 | return this.westernDate.getDay(); 772 | }; 773 | TibetanDate.prototype.getMonth = function () { 774 | return this.month; 775 | }; 776 | TibetanDate.prototype.getMonthObj = function () { 777 | return this.monthObj; 778 | }; 779 | TibetanDate.prototype.getYear = function () { 780 | return this.year; 781 | }; 782 | TibetanDate.prototype.getYearObj = function () { 783 | return this.yearObj; 784 | }; 785 | TibetanDate.prototype.toString = function () { 786 | var double = ''; 787 | if (this.isDoubledDay) { 788 | double = this.isLeapDay ? '-leap' : '-main'; 789 | } 790 | return this.monthObj.toString() + "-" + this.date + double; 791 | }; 792 | return TibetanDate; 793 | }()); 794 | 795 | export { TibetanDate, TibetanMonth, TibetanYear, getLosarForYear }; 796 | --------------------------------------------------------------------------------