/* * 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; }