From 74b5d361ce0815de195265404f842006e2afae26 Mon Sep 17 00:00:00 2001 From: Yoshio HANAWA Date: Sat, 17 May 2025 17:16:39 +0900 Subject: [PATCH] feat: Add Tibetan calendar support --- README.md | 1 + docs/specification.md | 22 ++++++++++- package.json | 1 + src/CalEventFactory.js | 9 +++++ src/Parser.js | 21 +++++++++++ src/Tibetan.js | 39 ++++++++++++++++++++ test/DateFn.mocha.js | 79 +++++++++++++++++++++++++++++++++++----- test/fixtures/parser.cjs | 33 +++++++++++++++++ 8 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 src/Tibetan.js diff --git a/README.md b/README.md index 28f3f8c..e4539b0 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ The features are: not be correct as they are subject to the sighting of the moon) - support for hebrew calendar from 1970 to 2100 - support for chinese calendar +- support for tibetan calendar Happy holidays! diff --git a/docs/specification.md b/docs/specification.md index 30c1750..dee8833 100644 --- a/docs/specification.md +++ b/docs/specification.md @@ -24,6 +24,7 @@ This document describes the data contained within the files `holidays.yaml` and * [Dates in Chinese calendar (lunar)](#dates-in-chinese-calendar-lunar) * [Dates in Chinese calendar (solar)](#dates-in-chinese-calendar-solar) * [Dates in Bengali Revised calendar](#dates-in-bengali-revised-calendar) + * [Dates in Tibetan calendar](#dates-in-tibetan-calendar) * [Dates in Persian calendar](#dates-in-persian-calendar) * [Equinox, Solstice](#equinox-solstice) * [Different start-time for Fixed Date other than midnight](#different-start-time-for-fixed-date-other-than-midnight) @@ -271,7 +272,7 @@ Where: #### Dates in Chinese calendar (lunar) -Dates in the chines calendar can be attributed using the following rule: +Dates in the chinese calendar can be attributed using the following rule: Rule: `chinese ----` @@ -316,6 +317,25 @@ Where: - `bengali-revised 12-1` is the 1st day in the 12th month - `bengali-revised 1425-1-1` is the 1st day in the first month of 1425 - equals 2018-04-14 in Gregorian date +#### Dates in Tibetan calendar + +Dates in the tibetan lunar calendar can be attributed using the following rule: + +Rule: `(tibetan|mongolian|bhutanese) -----` + +Where: +- `` (optional) Tibetan cycle - current is 17 +- `` (optional) year in Tibetan cycle (1 ... 60) +- `` (mandatory) lunar month +- `` (mandatory) `0|1` - `1` means month is leap month +- `` (mandatory) day of lunar month +- `` (optional) `0|1` - `1` means day is leap day + +**Examples**: + +- `tibetan 01-0-01` is 1st day in the 1st lunar month +- `tibetan 17-33-01-1-06-1` is 6th leap day in the 1st leap lunar month in year 33 of 17th cycle + #### Dates in Persian calendar Dates in the persian calendar can be attributed using the following rule: diff --git a/package.json b/package.json index a3bf13d..a62e54e 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "colors": true }, "dependencies": { + "@hnw/date-tibetan": "^1.0.2", "astronomia": "^4.1.1", "caldate": "^2.0.5", "date-bengali-revised": "^2.0.2", diff --git a/src/CalEventFactory.js b/src/CalEventFactory.js index d968d54..780f3cb 100644 --- a/src/CalEventFactory.js +++ b/src/CalEventFactory.js @@ -25,6 +25,9 @@ import Chinese from './Chinese.js' // #ifndef nobengali import BengaliRevised from './BengaliRevised.js' // #endif +// #ifndef notibet +import Tibetan from './Tibetan.js' +// #endif export default class CalEventFactory { constructor (opts) { @@ -61,6 +64,12 @@ export default class CalEventFactory { case 'bengali-revised': return new BengaliRevised(opts) // #endif + // #ifndef notibetan + case 'tibetan': + case 'mongolian': + case 'bhutanese': + return new Tibetan(opts) + // #endif default: return new CalEvent(opts) } diff --git a/src/Parser.js b/src/Parser.js index 57ce882..a0fc2c1 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -62,6 +62,7 @@ const grammar = (function () { chineseLunar: /^(chinese|korean|vietnamese) (?:(\d+)-(\d{1,2})-)?(\d{1,2})-([01])-(\d{1,2})/, chineseSolar: /^(chinese|korean|vietnamese) (?:(\d+)-(\d{1,2})-)?(\d{1,2})-(\d{1,2}) solarterm/, bengaliRevised: /^(bengali-revised) (?:-?0*(\d{1,4})-)?0?(\d{1,2})-0?(\d{1,2})/, + tibetanLunar: /^(tibetan|mongolian|bhutanese) (?:(\d+)-(\d{1,2})-)?(\d{1,2})-([01])-(\d{1,2})(?:-([01]))?/, modifier: /^(substitutes|and|if equal|then|if)\b/, rule_year: /^(?:in (even|odd|leap|non-leap) years|every (\d+) years? since 0*(\d{1,4}))/, @@ -182,6 +183,7 @@ export default class Parser { '_chineseSolar', '_chineseLunar', '_bengaliRevised', + '_tibetanLunar', '_dateMonth', '_ruleDateIfThen', '_ruleWeekday', @@ -422,6 +424,25 @@ export default class Parser { } } + _tibetanLunar (o) { + let cap + if ((cap = grammar.tibetanLunar.exec(o.str))) { + this._shorten(o, cap[0]) + cap.shift() + const res = { + fn: cap.shift(), + cycle: toNumber(cap.shift()), + year: toNumber(cap.shift()), + month: toNumber(cap.shift()), + leapMonth: !!toNumber(cap.shift()), + day: toNumber(cap.shift()), + leapDay: !!toNumber(cap.shift()), + } + this.tokens.push(res) + return true + } + } + _dateMonth (o) { let cap if ((cap = grammar.dateMonth.exec(o.str))) { diff --git a/src/Tibetan.js b/src/Tibetan.js new file mode 100644 index 0000000..cc44991 --- /dev/null +++ b/src/Tibetan.js @@ -0,0 +1,39 @@ +import { + CalendarTibetan, + CalendarMongolian, + CalendarBhutanese +} from '@hnw/date-tibetan'; +import CalEvent from './CalEvent.js' +import CalDate from 'caldate' + +export default class Tibetan extends CalEvent { + /** + * @param {object} [opts] + */ + constructor (opts) { + opts = opts || {} + super(opts) + switch (opts.fn) { + case 'tibetan': + this.cal = new CalendarTibetan() + break + case 'mongolian': + this.cal = new CalendarMongolian() + break + case 'bhutanese': + this.cal = new CalendarBhutanese() + break + } + } + + inYear (year) { + let d + let date + const opts = this.opts + this.cal.set(opts.cycle, opts.year, opts.month, opts.leapMonth, opts.day, opts.leapDay) + date = this.cal.toGregorian(year) + d = new CalDate(date) + this.dates.push(d) + return this + } +} diff --git a/test/DateFn.mocha.js b/test/DateFn.mocha.js index 1b51994..25a988e 100644 --- a/test/DateFn.mocha.js +++ b/test/DateFn.mocha.js @@ -412,17 +412,78 @@ describe('#DateFn', function () { }] assert.deepStrictEqual(fixResult(res), exp) }) + + it('bengali-revised 12-1', function () { + const fn = new DateFn('bengali-revised 12-1') + const res = fn.inYear(2015).get() + const exp = [{ + date: '2015-03-15 00:00:00', + start: 'sun 2015-03-15 00:00', + end: 'mon 2015-03-16 00:00' + }] + assert.deepStrictEqual(fixResult(res), exp) + }) }) - it('bengali-revised 12-1', function () { - const fn = new DateFn('bengali-revised 12-1') - const res = fn.inYear(2015).get() - const exp = [{ - date: '2015-03-15 00:00:00', - start: 'sun 2015-03-15 00:00', - end: 'mon 2015-03-16 00:00' - }] - assert.deepStrictEqual(fixResult(res), exp) + describe('tibetan', function () { + it('tibetan 1-0-1', function () { + const fn = new DateFn('tibetan 1-0-1') + const res = fn.inYear(2003).get() + const exp = [{ + date: '2003-03-03 00:00:00', + start: 'mon 2003-03-03 00:00', + end: 'tue 2003-03-04 00:00' + }] + assert.deepStrictEqual(fixResult(res), exp) + }) + + it('mongolian 1-0-1', function () { + const fn = new DateFn('mongolian 1-0-1') + const res = fn.inYear(2003).get() + const exp = [{ + date: '2003-02-02 00:00:00', + start: 'sun 2003-02-02 00:00', + end: 'mon 2003-02-03 00:00' + }] + assert.deepStrictEqual(fixResult(res), exp) + }) + + it('bhutanese 1-0-1', function () { + const fn = new DateFn('bhutanese 1-0-1') + const res = fn.inYear(2003).get() + const exp = [{ + date: '2003-03-04 00:00:00', + start: 'tue 2003-03-04 00:00', + end: 'wed 2003-03-05 00:00' + }] + assert.deepStrictEqual(fixResult(res), exp) + }) + + it('tibetan 12-0-17-1', function () { + const fn = new DateFn('tibetan 12-0-17-1') + const res = fn.inYear(2013).get() + const exp = [{ + date: '2013-01-28 00:00:00', + start: 'mon 2013-01-28 00:00', + end: 'tue 2013-01-29 00:00' + }] + assert.deepStrictEqual(fixResult(res), exp) + }) + it('twice tibetan 11-0-15 in gregorian year 2012', function () { + const fn = new DateFn('tibetan 11-0-15') + const res = fn.inYear(2012).get() + const exp = [{ + date: '2012-01-09 00:00:00', + start: 'mon 2012-01-09 00:00', + end: 'tue 2012-01-10 00:00' + }, { + date: '2012-12-28 00:00:00', + start: 'fri 2012-12-28 00:00', + end: 'sat 2012-12-29 00:00' + }] + assert.deepStrictEqual(fixResult(res), exp) + }) + }) }) diff --git a/test/fixtures/parser.cjs b/test/fixtures/parser.cjs index 1908877..ba34d0f 100644 --- a/test/fixtures/parser.cjs +++ b/test/fixtures/parser.cjs @@ -405,6 +405,39 @@ module.exports = { year: undefined } ], + 'tibetan 01-0-01': [ + { + fn: 'tibetan', + day: 1, + month: 1, + leapMonth: false, + leapDay: false, + year: undefined, + cycle: undefined, + } + ], + 'mongolian 01-1-01': [ + { + fn: 'mongolian', + day: 1, + month: 1, + leapMonth: true, + leapDay: false, + year: undefined, + cycle: undefined, + } + ], + 'bhutanese 01-1-01-1': [ + { + fn: 'bhutanese', + day: 1, + month: 1, + leapMonth: true, + leapDay: true, + year: undefined, + cycle: undefined, + } + ], '1 day before vietnamese 01-0-01': [ { fn: 'vietnamese',