/*
* World Calendars
* https://github.com/alexcjohnson/world-calendars
*
* Batch-converted from kbwood/calendars
* Many thanks to Keith Wood and all of the contributors to the original project!
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/* http://keith-wood.name/calendars.html
Traditional Chinese calendar for jQuery v2.0.2.
Written by Nicolas Riesco (enquiries@nicolasriesco.net) December 2016.
Available under the MIT (http://keith-wood.name/licence.html) license.
Please attribute the author if you use it. */
var main = require('../main');
var assign = require('object-assign');
var gregorianCalendar = main.instance();
/** Implementation of the traditional Chinese calendar.
Source of calendar tables https://github.com/isee15/Lunar-Solar-Calendar-Converter .
@class ChineseCalendar
@param [language=''] {string} The language code (default English) for localisation. */
function ChineseCalendar(language) {
this.local = this.regionalOptions[language || ''] || this.regionalOptions[''];
}
ChineseCalendar.prototype = new main.baseCalendar;
assign(ChineseCalendar.prototype, {
/** The calendar name.
@memberof ChineseCalendar */
name: 'Chinese',
/** Julian date of start of Gregorian epoch: 1 January 0001 CE.
@memberof GregorianCalendar */
jdEpoch: 1721425.5,
/** true
if has a year zero, false
if not.
@memberof ChineseCalendar */
hasYearZero: false,
/** The minimum month number.
This calendar uses month indices to account for intercalary months.
@memberof ChineseCalendar */
minMonth: 0,
/** The first month in the year.
This calendar uses month indices to account for intercalary months.
@memberof ChineseCalendar */
firstMonth: 0,
/** The minimum day number.
@memberof ChineseCalendar */
minDay: 1,
/** Localisations for the plugin.
Entries are objects indexed by the language code ('' being the default US/English).
Each object has the following attributes.
@memberof ChineseCalendar
@property name {string} The calendar name.
@property epochs {string[]} The epoch names.
@property monthNames {string[]} The long names of the months of the year.
@property monthNamesShort {string[]} The short names of the months of the year.
@property dayNames {string[]} The long names of the days of the week.
@property dayNamesShort {string[]} The short names of the days of the week.
@property dayNamesMin {string[]} The minimal names of the days of the week.
@property dateFormat {string} The date format for this calendar.
See the options on formatDate
for details.
@property firstDay {number} The number of the first day of the week, starting at 0.
@property isRTL {number} true
if this localisation reads right-to-left. */
regionalOptions: { // Localisations
'': {
name: 'Chinese',
epochs: ['BEC', 'EC'],
monthNumbers: function(date, padded) {
if (typeof date === 'string') {
var match = date.match(MONTH_NUMBER_REGEXP);
return (match) ? match[0] : '';
}
var year = this._validateYear(date);
var monthIndex = date.month();
var month = '' + this.toChineseMonth(year, monthIndex);
if (padded && month.length < 2) {
month = "0" + month;
}
if (this.isIntercalaryMonth(year, monthIndex)) {
month += 'i';
}
return month;
},
monthNames: function(date) {
if (typeof date === 'string') {
var match = date.match(MONTH_NAME_REGEXP);
return (match) ? match[0] : '';
}
var year = this._validateYear(date);
var monthIndex = date.month();
var month = this.toChineseMonth(year, monthIndex);
var monthName = ['一月','二月','三月','四月','五月','六月',
'七月','八月','九月','十月','十一月','十二月'][month - 1];
if (this.isIntercalaryMonth(year, monthIndex)) {
monthName = '闰' + monthName;
}
return monthName;
},
monthNamesShort: function(date) {
if (typeof date === 'string') {
var match = date.match(MONTH_SHORT_NAME_REGEXP);
return (match) ? match[0] : '';
}
var year = this._validateYear(date);
var monthIndex = date.month();
var month = this.toChineseMonth(year, monthIndex);
var monthName = ['一','二','三','四','五','六',
'七','八','九','十','十一','十二'][month - 1];
if (this.isIntercalaryMonth(year, monthIndex)) {
monthName = '闰' + monthName;
}
return monthName;
},
parseMonth: function(year, monthString) {
year = this._validateYear(year);
var month = parseInt(monthString);
var isIntercalary;
if (!isNaN(month)) {
var i = monthString[monthString.length - 1];
isIntercalary = (i === 'i' || i === 'I');
} else {
if (monthString[0] === '闰') {
isIntercalary = true;
monthString = monthString.substring(1);
}
if (monthString[monthString.length - 1] === '月') {
monthString = monthString.substring(0, monthString.length - 1);
}
month = 1 +
['一','二','三','四','五','六',
'七','八','九','十','十一','十二'].indexOf(monthString);
}
var monthIndex = this.toMonthIndex(year, month, isIntercalary);
return monthIndex;
},
dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
digits: null,
dateFormat: 'yyyy/mm/dd',
firstDay: 1,
isRTL: false
}
},
/** Check that a candidate date is from the same calendar and is valid.
@memberof BaseCalendar
@private
@param year {CDate|number} The date or the year to validate.
@param error {string} Error message if invalid.
@return {number} The year.
@throws Error if year out of range. */
_validateYear: function(year, error) {
if (year.year) {
year = year.year();
}
if (typeof year !== 'number' || year < 1888 || year > 2111) {
throw error.replace(/\{0\}/, this.local.name);
}
return year;
},
/** Retrieve the month index (i.e. accounting for intercalary months).
@memberof ChineseCalendar
@param year {number} The year.
@param month {number} The month (1 for first month).
@param [isIntercalary=false] {boolean} If month is intercalary.
@return {number} The month index (0 for first month).
@throws Error if an invalid month/year or a different calendar used. */
toMonthIndex: function(year, month, isIntercalary) {
// compute intercalary month in the year (0 if none)
var intercalaryMonth = this.intercalaryMonth(year);
// validate month
var invalidIntercalaryMonth =
(isIntercalary && month !== intercalaryMonth);
if (invalidIntercalaryMonth || month < 1 || month > 12) {
throw main.local.invalidMonth
.replace(/\{0\}/, this.local.name);
}
// compute month index
var monthIndex;
if (!intercalaryMonth) {
monthIndex = month - 1;
} else if(!isIntercalary && month <= intercalaryMonth) {
monthIndex = month - 1;
} else {
monthIndex = month;
}
return monthIndex;
},
/** Retrieve the month (i.e. accounting for intercalary months).
@memberof ChineseCalendar
@param year {CDate|number} The date or the year to examine.
@param monthIndex {number} The month index (0 for first month).
@return {number} The month (1 for first month).
@throws Error if an invalid month/year or a different calendar used. */
toChineseMonth: function(year, monthIndex) {
if (year.year) {
year = year.year();
monthIndex = year.month();
}
// compute intercalary month in the year (0 if none)
var intercalaryMonth = this.intercalaryMonth(year);
// validate month
var maxMonthIndex = (intercalaryMonth) ? 12 : 11;
if (monthIndex < 0 || monthIndex > maxMonthIndex) {
throw main.local.invalidMonth
.replace(/\{0\}/, this.local.name);
}
// compute Chinese month
var month;
if (!intercalaryMonth) {
month = monthIndex + 1;
} else if(monthIndex < intercalaryMonth) {
month = monthIndex + 1;
} else {
month = monthIndex;
}
return month;
},
/** Determine the intercalary month of a year (if any).
@memberof ChineseCalendar
@param year {CDate|number} The date to examine or the year to examine.
@return {number} The intercalary month number, or 0 if none.
@throws Error if an invalid year or a different calendar used. */
intercalaryMonth: function(year) {
year = this._validateYear(year);
var monthDaysTable = LUNAR_MONTH_DAYS[year - LUNAR_MONTH_DAYS[0]];
var intercalaryMonth = monthDaysTable >> 13;
return intercalaryMonth;
},
/** Determine whether this date is an intercalary month.
@memberof ChineseCalendar
@param year {CDate|number} The date to examine or the year to examine.
@param [monthIndex] {number} The month index to examine.
@return {boolean} true
if this is an intercalary month, false
if not.
@throws Error if an invalid year or a different calendar used. */
isIntercalaryMonth: function(year, monthIndex) {
if (year.year) {
year = year.year();
monthIndex = year.month();
}
var intercalaryMonth = this.intercalaryMonth(year);
return !!intercalaryMonth && intercalaryMonth === monthIndex;
},
/** Determine whether this date is in a leap year.
@memberof ChineseCalendar
@param year {CDate|number} The date to examine or the year to examine.
@return {boolean} true
if this is a leap year, false
if not.
@throws Error if an invalid year or a different calendar used. */
leapYear: function(year) {
return (this.intercalaryMonth(year) !== 0);
},
/** Determine the week of the year for a date - ISO 8601.
@memberof ChineseCalendar
@param year {CDate|number} The date to examine or the year to examine.
@param [monthIndex] {number} The month index to examine.
@param [day] {number} The day to examine.
@return {number} The week of the year.
@throws Error if an invalid date or a different calendar used. */
weekOfYear: function(year, monthIndex, day) {
// compute Chinese new year
var validatedYear =
this._validateYear(year, main.local.invalidyear);
var packedDate =
CHINESE_NEW_YEAR[validatedYear - CHINESE_NEW_YEAR[0]];
var y = (packedDate >> 9) & 0xFFF;
var m = (packedDate >> 5) & 0x0F;
var d = packedDate & 0x1F;
// find first Thrusday of the year
var firstThursday;
firstThursday = gregorianCalendar.newDate(y, m, d);
firstThursday.add(4 - (firstThursday.dayOfWeek() || 7), 'd');
// compute days from first Thursday
var offset =
this.toJD(year, monthIndex, day) - firstThursday.toJD();
return 1 + Math.floor(offset / 7);
},
/** Retrieve the number of months in a year.
@memberof ChineseCalendar
@param year {CDate|number} The date to examine or the year to examine.
@return {number} The number of months.
@throws Error if an invalid year or a different calendar used. */
monthsInYear: function(year) {
return (this.leapYear(year)) ? 13 : 12;
},
/** Retrieve the number of days in a month.
@memberof ChineseCalendar
@param year {CDate|number} The date to examine or the year of the month.
@param [monthIndex] {number} The month index.
@return {number} The number of days in this month.
@throws Error if an invalid month/year or a different calendar used. */
daysInMonth: function(year, monthIndex) {
if (year.year) {
monthIndex = year.month();
year = year.year();
}
year = this._validateYear(year);
var monthDaysTable = LUNAR_MONTH_DAYS[year - LUNAR_MONTH_DAYS[0]];
var intercalaryMonth = monthDaysTable >> 13;
var maxMonthIndex = (intercalaryMonth) ? 12 : 11;
if (monthIndex > maxMonthIndex) {
throw main.local.invalidMonth
.replace(/\{0\}/, this.local.name);
}
var daysInMonth = (monthDaysTable & (1 << (12 - monthIndex))) ?
30 : 29;
return daysInMonth;
},
/** Determine whether this date is a week day.
@memberof ChineseCalendar
@param year {CDate|number} The date to examine or the year to examine.
@param [monthIndex] {number} The month index to examine.
@param [day] {number} The day to examine.
@return {boolean} true
if a week day, false
if not.
@throws Error if an invalid date or a different calendar used. */
weekDay: function(year, monthIndex, day) {
return (this.dayOfWeek(year, monthIndex, day) || 7) < 6;
},
/** Retrieve the Julian date equivalent for this date,
i.e. days since January 1, 4713 BCE Greenwich noon.
@memberof ChineseCalendar
@param year {CDate|number} The date to convert or the year to convert.
@param [monthIndex] {number} The month index to convert.
@param [day] {number} The day to convert.
@return {number} The equivalent Julian date.
@throws Error if an invalid date or a different calendar used. */
toJD: function(year, monthIndex, day) {
var date = this._validate(year, month, day, main.local.invalidDate);
year = this._validateYear(date.year());
monthIndex = date.month();
day = date.day();
var isIntercalary = this.isIntercalaryMonth(year, monthIndex);
var month = this.toChineseMonth(year, monthIndex);
var solar = toSolar(year, month, day, isIntercalary);
return gregorianCalendar.toJD(solar.year, solar.month, solar.day);
},
/** Create a new date from a Julian date.
@memberof ChineseCalendar
@param jd {number} The Julian date to convert.
@return {CDate} The equivalent date. */
fromJD: function(jd) {
var date = gregorianCalendar.fromJD(jd);
var lunar = toLunar(date.year(), date.month(), date.day());
var monthIndex = this.toMonthIndex(
lunar.year, lunar.month, lunar.isIntercalary);
return this.newDate(lunar.year, monthIndex, lunar.day);
},
/** Create a new date from a string.
@memberof ChineseCalendar
@param dateString {string} String representing a Chinese date
@return {CDate} The new date.
@throws Error if an invalid date. */
fromString: function(dateString) {
var match = dateString.match(DATE_REGEXP);
var year = this._validateYear(+match[1]);
var month = +match[2];
var isIntercalary = !!match[3];
var monthIndex = this.toMonthIndex(year, month, isIntercalary);
var day = +match[4];
return this.newDate(year, monthIndex, day);
},
/** Add period(s) to a date.
Cater for no year zero.
@memberof ChineseCalendar
@param date {CDate} The starting date.
@param offset {number} The number of periods to adjust by.
@param period {string} One of 'y' for year, 'm' for month, 'w' for week, 'd' for day.
@return {CDate} The updated date.
@throws Error if a different calendar used. */
add: function(date, offset, period) {
var year = date.year();
var monthIndex = date.month();
var isIntercalary = this.isIntercalaryMonth(year, monthIndex);
var month = this.toChineseMonth(year, monthIndex);
var cdate = Object.getPrototypeOf(ChineseCalendar.prototype)
.add.call(this, date, offset, period);
if (period === 'y') {
// Resync month
var resultYear = cdate.year();
var resultMonthIndex = cdate.month();
// Using the fact the month index of an intercalary month
// equals its month number:
var resultCanBeIntercalaryMonth =
this.isIntercalaryMonth(resultYear, month);
var correctedMonthIndex =
(isIntercalary && resultCanBeIntercalaryMonth) ?
this.toMonthIndex(resultYear, month, true) :
this.toMonthIndex(resultYear, month, false);
if (correctedMonthIndex !== resultMonthIndex) {
cdate.month(correctedMonthIndex);
}
}
return cdate;
},
});
// Used by ChineseCalendar.prototype.fromString
var DATE_REGEXP = /^\s*(-?\d\d\d\d|\d\d)[-/](\d?\d)([iI]?)[-/](\d?\d)/m;
var MONTH_NUMBER_REGEXP = /^\d?\d[iI]?/m;
var MONTH_NAME_REGEXP = /^闰?十?[一二三四五六七八九]?月/m;
var MONTH_SHORT_NAME_REGEXP = /^闰?十?[一二三四五六七八九]?/m;
// Chinese calendar implementation
main.calendars.chinese = ChineseCalendar;
// Chinese calendar tables from year 1888 to 2111
//
// Source:
// https://github.com/isee15/Lunar-Solar-Calendar-Converter.git
// Table of intercalary months and days per month from year 1888 to 2111
//
// bit (12 - i): days in the i^th month
// (= 0 if i^th lunar month has 29 days)
// (= 1 if i^th lunar month has 30 days)
// (first month in lunar year is i = 0)
// bits (13,14,15,16): intercalary month
// (= 0 if lunar year has no intercalary month)
var LUNAR_MONTH_DAYS = [1887, 0x1694, 0x16aa, 0x4ad5,
0xab6, 0xc4b7, 0x4ae, 0xa56, 0xb52a, 0x1d2a, 0xd54, 0x75aa, 0x156a,
0x1096d, 0x95c, 0x14ae, 0xaa4d, 0x1a4c, 0x1b2a, 0x8d55, 0xad4,
0x135a, 0x495d, 0x95c, 0xd49b, 0x149a, 0x1a4a, 0xbaa5, 0x16a8,
0x1ad4, 0x52da, 0x12b6, 0xe937, 0x92e, 0x1496, 0xb64b, 0xd4a,
0xda8, 0x95b5, 0x56c, 0x12ae, 0x492f, 0x92e, 0xcc96, 0x1a94,
0x1d4a, 0xada9, 0xb5a, 0x56c, 0x726e, 0x125c, 0xf92d, 0x192a,
0x1a94, 0xdb4a, 0x16aa, 0xad4, 0x955b, 0x4ba, 0x125a, 0x592b,
0x152a, 0xf695, 0xd94, 0x16aa, 0xaab5, 0x9b4, 0x14b6, 0x6a57,
0xa56, 0x1152a, 0x1d2a, 0xd54, 0xd5aa, 0x156a, 0x96c, 0x94ae,
0x14ae, 0xa4c, 0x7d26, 0x1b2a, 0xeb55, 0xad4, 0x12da, 0xa95d,
0x95a, 0x149a, 0x9a4d, 0x1a4a, 0x11aa5, 0x16a8, 0x16d4, 0xd2da,
0x12b6, 0x936, 0x9497, 0x1496, 0x1564b, 0xd4a, 0xda8, 0xd5b4,
0x156c, 0x12ae, 0xa92f, 0x92e, 0xc96, 0x6d4a, 0x1d4a, 0x10d65,
0xb58, 0x156c, 0xb26d, 0x125c, 0x192c, 0x9a95, 0x1a94, 0x1b4a,
0x4b55, 0xad4, 0xf55b, 0x4ba, 0x125a, 0xb92b, 0x152a, 0x1694,
0x96aa, 0x15aa, 0x12ab5, 0x974, 0x14b6, 0xca57, 0xa56, 0x1526,
0x8e95, 0xd54, 0x15aa, 0x49b5, 0x96c, 0xd4ae, 0x149c, 0x1a4c,
0xbd26, 0x1aa6, 0xb54, 0x6d6a, 0x12da, 0x1695d, 0x95a, 0x149a,
0xda4b, 0x1a4a, 0x1aa4, 0xbb54, 0x16b4, 0xada, 0x495b, 0x936,
0xf497, 0x1496, 0x154a, 0xb6a5, 0xda4, 0x15b4, 0x6ab6, 0x126e,
0x1092f, 0x92e, 0xc96, 0xcd4a, 0x1d4a, 0xd64, 0x956c, 0x155c,
0x125c, 0x792e, 0x192c, 0xfa95, 0x1a94, 0x1b4a, 0xab55, 0xad4,
0x14da, 0x8a5d, 0xa5a, 0x1152b, 0x152a, 0x1694, 0xd6aa, 0x15aa,
0xab4, 0x94ba, 0x14b6, 0xa56, 0x7527, 0xd26, 0xee53, 0xd54, 0x15aa,
0xa9b5, 0x96c, 0x14ae, 0x8a4e, 0x1a4c, 0x11d26, 0x1aa4, 0x1b54,
0xcd6a, 0xada, 0x95c, 0x949d, 0x149a, 0x1a2a, 0x5b25, 0x1aa4,
0xfb52, 0x16b4, 0xaba, 0xa95b, 0x936, 0x1496, 0x9a4b, 0x154a,
0x136a5, 0xda4, 0x15ac];
// Table of Chinese New Years from year 1888 to 2111
//
// bits (0 to 4): solar day
// bits (5 to 8): solar month
// bits (9 to 20): solar year
var CHINESE_NEW_YEAR = [1887, 0xec04c, 0xec23f, 0xec435, 0xec649,
0xec83e, 0xeca51, 0xecc46, 0xece3a, 0xed04d, 0xed242, 0xed436,
0xed64a, 0xed83f, 0xeda53, 0xedc48, 0xede3d, 0xee050, 0xee244,
0xee439, 0xee64d, 0xee842, 0xeea36, 0xeec4a, 0xeee3e, 0xef052,
0xef246, 0xef43a, 0xef64e, 0xef843, 0xefa37, 0xefc4b, 0xefe41,
0xf0054, 0xf0248, 0xf043c, 0xf0650, 0xf0845, 0xf0a38, 0xf0c4d,
0xf0e42, 0xf1037, 0xf124a, 0xf143e, 0xf1651, 0xf1846, 0xf1a3a,
0xf1c4e, 0xf1e44, 0xf2038, 0xf224b, 0xf243f, 0xf2653, 0xf2848,
0xf2a3b, 0xf2c4f, 0xf2e45, 0xf3039, 0xf324d, 0xf3442, 0xf3636,
0xf384a, 0xf3a3d, 0xf3c51, 0xf3e46, 0xf403b, 0xf424e, 0xf4443,
0xf4638, 0xf484c, 0xf4a3f, 0xf4c52, 0xf4e48, 0xf503c, 0xf524f,
0xf5445, 0xf5639, 0xf584d, 0xf5a42, 0xf5c35, 0xf5e49, 0xf603e,
0xf6251, 0xf6446, 0xf663b, 0xf684f, 0xf6a43, 0xf6c37, 0xf6e4b,
0xf703f, 0xf7252, 0xf7447, 0xf763c, 0xf7850, 0xf7a45, 0xf7c39,
0xf7e4d, 0xf8042, 0xf8254, 0xf8449, 0xf863d, 0xf8851, 0xf8a46,
0xf8c3b, 0xf8e4f, 0xf9044, 0xf9237, 0xf944a, 0xf963f, 0xf9853,
0xf9a47, 0xf9c3c, 0xf9e50, 0xfa045, 0xfa238, 0xfa44c, 0xfa641,
0xfa836, 0xfaa49, 0xfac3d, 0xfae52, 0xfb047, 0xfb23a, 0xfb44e,
0xfb643, 0xfb837, 0xfba4a, 0xfbc3f, 0xfbe53, 0xfc048, 0xfc23c,
0xfc450, 0xfc645, 0xfc839, 0xfca4c, 0xfcc41, 0xfce36, 0xfd04a,
0xfd23d, 0xfd451, 0xfd646, 0xfd83a, 0xfda4d, 0xfdc43, 0xfde37,
0xfe04b, 0xfe23f, 0xfe453, 0xfe648, 0xfe83c, 0xfea4f, 0xfec44,
0xfee38, 0xff04c, 0xff241, 0xff436, 0xff64a, 0xff83e, 0xffa51,
0xffc46, 0xffe3a, 0x10004e, 0x100242, 0x100437, 0x10064b, 0x100841,
0x100a53, 0x100c48, 0x100e3c, 0x10104f, 0x101244, 0x101438,
0x10164c, 0x101842, 0x101a35, 0x101c49, 0x101e3d, 0x102051,
0x102245, 0x10243a, 0x10264e, 0x102843, 0x102a37, 0x102c4b,
0x102e3f, 0x103053, 0x103247, 0x10343b, 0x10364f, 0x103845,
0x103a38, 0x103c4c, 0x103e42, 0x104036, 0x104249, 0x10443d,
0x104651, 0x104846, 0x104a3a, 0x104c4e, 0x104e43, 0x105038,
0x10524a, 0x10543e, 0x105652, 0x105847, 0x105a3b, 0x105c4f,
0x105e45, 0x106039, 0x10624c, 0x106441, 0x106635, 0x106849,
0x106a3d, 0x106c51, 0x106e47, 0x10703c, 0x10724f, 0x107444,
0x107638, 0x10784c, 0x107a3f, 0x107c53, 0x107e48];
function toLunar(yearOrDate, monthOrResult, day, result) {
var solarDate;
var lunarDate;
if(typeof yearOrDate === 'object') {
solarDate = yearOrDate;
lunarDate = monthOrResult || {};
} else {
var isValidYear = (typeof yearOrDate === 'number') &&
(yearOrDate >= 1888) && (yearOrDate <= 2111);
if(!isValidYear)
throw new Error("Solar year outside range 1888-2111");
var isValidMonth = (typeof monthOrResult === 'number') &&
(monthOrResult >= 1) && (monthOrResult <= 12);
if(!isValidMonth)
throw new Error("Solar month outside range 1 - 12");
var isValidDay = (typeof day === 'number') && (day >= 1) && (day <= 31);
if(!isValidDay)
throw new Error("Solar day outside range 1 - 31");
solarDate = {
year: yearOrDate,
month: monthOrResult,
day: day,
};
lunarDate = result || {};
}
// Compute Chinese new year and lunar year
var chineseNewYearPackedDate =
CHINESE_NEW_YEAR[solarDate.year - CHINESE_NEW_YEAR[0]];
var packedDate = (solarDate.year << 9) | (solarDate.month << 5)
| solarDate.day;
lunarDate.year = (packedDate >= chineseNewYearPackedDate) ?
solarDate.year :
solarDate.year - 1;
chineseNewYearPackedDate =
CHINESE_NEW_YEAR[lunarDate.year - CHINESE_NEW_YEAR[0]];
var y = (chineseNewYearPackedDate >> 9) & 0xFFF;
var m = (chineseNewYearPackedDate >> 5) & 0x0F;
var d = chineseNewYearPackedDate & 0x1F;
// Compute days from new year
var daysFromNewYear;
var chineseNewYearJSDate = new Date(y, m -1, d);
var jsDate = new Date(solarDate.year, solarDate.month - 1, solarDate.day);
daysFromNewYear = Math.round(
(jsDate - chineseNewYearJSDate) / (24 * 3600 * 1000));
// Compute lunar month and day
var monthDaysTable = LUNAR_MONTH_DAYS[lunarDate.year - LUNAR_MONTH_DAYS[0]];
var i;
for(i = 0; i < 13; i++) {
var daysInMonth = (monthDaysTable & (1 << (12 - i))) ? 30 : 29;
if (daysFromNewYear < daysInMonth) {
break;
}
daysFromNewYear -= daysInMonth;
}
var intercalaryMonth = monthDaysTable >> 13;
if (!intercalaryMonth || i < intercalaryMonth) {
lunarDate.isIntercalary = false;
lunarDate.month = 1 + i;
} else if (i === intercalaryMonth) {
lunarDate.isIntercalary = true;
lunarDate.month = i;
} else {
lunarDate.isIntercalary = false;
lunarDate.month = i;
}
lunarDate.day = 1 + daysFromNewYear;
return lunarDate;
}
function toSolar(yearOrDate, monthOrResult, day, isIntercalaryOrResult, result) {
var solarDate;
var lunarDate;
if(typeof yearOrDate === 'object') {
lunarDate = yearOrDate;
solarDate = monthOrResult || {};
} else {
var isValidYear = (typeof yearOrDate === 'number') &&
(yearOrDate >= 1888) && (yearOrDate <= 2111);
if(!isValidYear)
throw new Error("Lunar year outside range 1888-2111");
var isValidMonth = (typeof monthOrResult === 'number') &&
(monthOrResult >= 1) && (monthOrResult <= 12);
if(!isValidMonth)
throw new Error("Lunar month outside range 1 - 12");
var isValidDay = (typeof day === 'number') && (day >= 1) && (day <= 30);
if(!isValidDay)
throw new Error("Lunar day outside range 1 - 30");
var isIntercalary;
if(typeof isIntercalaryOrResult === 'object') {
isIntercalary = false;
solarDate = isIntercalaryOrResult;
} else {
isIntercalary = !!isIntercalaryOrResult;
solarDate = result || {};
}
lunarDate = {
year: yearOrDate,
month: monthOrResult,
day: day,
isIntercalary: isIntercalary,
};
}
// Compute days from new year
var daysFromNewYear;
daysFromNewYear = lunarDate.day - 1;
var monthDaysTable = LUNAR_MONTH_DAYS[lunarDate.year - LUNAR_MONTH_DAYS[0]];
var intercalaryMonth = monthDaysTable >> 13;
var monthsFromNewYear;
if (!intercalaryMonth) {
monthsFromNewYear = lunarDate.month - 1;
} else if (lunarDate.month > intercalaryMonth) {
monthsFromNewYear = lunarDate.month;
} else if (lunarDate.isIntercalary) {
monthsFromNewYear = lunarDate.month;
} else {
monthsFromNewYear = lunarDate.month - 1;
}
for(var i = 0; i < monthsFromNewYear; i++) {
var daysInMonth = (monthDaysTable & (1 << (12 - i))) ? 30 : 29;
daysFromNewYear += daysInMonth;
}
// Compute Chinese new year
var packedDate = CHINESE_NEW_YEAR[lunarDate.year - CHINESE_NEW_YEAR[0]];
var y = (packedDate >> 9) & 0xFFF;
var m = (packedDate >> 5) & 0x0F;
var d = packedDate & 0x1F;
// Compute solar date
var jsDate = new Date(y, m - 1, d + daysFromNewYear);
solarDate.year = jsDate.getFullYear();
solarDate.month = 1 + jsDate.getMonth();
solarDate.day = jsDate.getDate();
return solarDate;
}