UNPKG

cejs

Version:

A JavaScript module framework that is simple to use.

1,737 lines (1,453 loc) 261 kB
/** * @name CeL function for calendrical calculations. * * If you need a online demo of these calendars, please visit: * http://lyrics.meicho.com.tw/lib/JS/_test%20suite/era.htm * * @fileoverview * 本檔案包含了曆法轉換的功能。 * * @since 2014/4/12 15:37:56 */ // https://www.funaba.org/cc // http://www.fourmilab.ch/documents/calendar/ // http://the-light.com/cal/converter/ // http://keith-wood.name/calendars.html // http://www.cc.kyoto-su.ac.jp/~yanom/pancanga/index.html /* TODO: https://en.wikipedia.org/wiki/Vikram_Samvat the official calendar of Nepal */ 'use strict'; if (typeof CeL === 'function') CeL.run( { name : 'data.date.calendar', // |application.astronomy. // data.math.find_root require : 'data.code.compatibility.|data.native.set_bind|data.date.String_to_Date|data.date.is_leap_year|data.date.Julian_day|data.math.', code : function(library_namespace) { // requiring var set_bind = this.r('set_bind'), String_to_Date = this.r('String_to_Date'), is_leap_year = this.r('is_leap_year'), Julian_day = this.r('Julian_day'); /** * null module constructor * @class calendars 的 functions */ var _// JSDT:_module_ = function() { // null module constructor }; /** * for JSDT: 有 prototype 才會將之當作 Class */ _// JSDT:_module_ .prototype = { }; //----------------------------------------------------------------------------------------------------------------------------------------------------------// // 工具函數。 var // copy from data.date. /** {Number}一整天的 time 值。should be 24 * 60 * 60 * 1000 = 86400000. */ ONE_DAY_LENGTH_VALUE = new Date(0, 0, 2) - new Date(0, 0, 1); var // 24 hours ONE_DAY_HOURS = (new Date(1, 1, 1, -1)).getHours() | 0, // set weekday name converter. KEY_WEEK = 'week'; function _Date(year, month, date) { if (year < 100) { // 僅使用 new Date(0) 的話,會含入 timezone offset (.getTimezoneOffset)。 // 因此得使用 new Date(0, 0)。 var d = new Date(0, 0); d.setFullYear(year, month, date); return d; } return new Date(year, month, date); } function _time(year, month, date, hour) { if (year < 100) { // 僅使用 new Date(0) 的話,會含入 timezone offset (.getTimezoneOffset)。 // 因此得使用 new Date(0, 0)。 var d = new Date(0, 0); d.setFullYear(year, month, date, hour); return d; } return new Date(year, month, date, hour); } /** * format 回傳處理。 * * <code> * API: Date_to_Calendar({Date}, {Object}options) options.format = 'serial': return 數字序號 (numerical serial №) [ {Integer}year, {Natural}month, {Natural}date ] options.format = 'item': 一般會: return [ {Integer}year, {String}month name, {Natural}date ] return [ {Integer}year, {String}month name, {Natural}date, {小數}餘下不到一日之時間值 remainder (單位:日) ] options.format = 'text': return {String} 當地語言之表現法。常是 "weekday, date month year"。 others: default: text * </code> * * @param {Array}date [ * {Integer}year, {Natural}month, {Natural}date ],<br /> * date[KEY_WEEK] = {Integer}weekday * @param {Object}[options] * options that called * @param {Array|Function}to_name [ * {Function}year serial to name, {Function}month serial to name, * {Function}date serial to name, {Function}weekday serial to name ] * @returns */ function _format(date, options, to_name, is_leap, combine) { var format = options && options.format; if (format === 'serial') return date; if (typeof to_name === 'function') // 當作 month_to_name。 date[1] = to_name(date[1], is_leap, options); else if (Array.isArray(to_name)) to_name.forEach(function(func, index) { date[ index === 3 ? KEY_WEEK : index ] // = func(date[index], is_leap, index); }); else library_namespace.warn('_format: 無法辨識之 to_name: ' + to_name); if (format === 'item') return date; if (options && typeof options.numeral === 'function') { date[0] = options.numeral(date[0]); date[2] = options.numeral(date[2]); } if (typeof combine === 'function') { format = combine(date); } else { format = date.slice(0, 3); // direction if (combine !== true) format = format.reverse(); format = format.join(' '); } if (options) { if (options.postfix) format += options.postfix; if (options.prefix) format = options.prefix + format; } // add weekday name if (date[KEY_WEEK]) format = date[KEY_WEEK] + ', ' + format; return format; } /** * 創建測試器。<br /> * test: 經過正反轉換運算,應該回到相同的日子。 * * @param {Function}to_Calendar * @param {Function}to_Date * @param {Object}[options] * * @returns {Function}測試器。 */ function new_tester(to_Calendar, to_Date, options) { options = Object.assign(Object.create(null), new_tester.default_options, options || {}); var epoch = (options.epoch || to_Date.epoch) - 0 || 0, // month_days = options.month_days, CE_format = options.CE_format, // continued_month = options.continued_month, // get_month_serial = options.month_serial; return function(begin_Date, end_Date, error_limit) { begin_Date = typeof begin_Date === 'number' ? epoch + (begin_Date | 0) * ONE_DAY_LENGTH_VALUE : begin_Date - 0; var tmp = typeof end_Date === 'string' && end_Date.trim().match(/^\+(\d+)$/); end_Date = tmp || typeof end_Date === 'number' ? (tmp ? begin_Date : epoch) + end_Date * ONE_DAY_LENGTH_VALUE : end_Date - 0; if (isNaN(begin_Date) || isNaN(end_Date)) return; var begin = Date.now(), last_show = begin, date_name, old_date_name, error = []; if (!(0 < error_limit && error_limit < 1e9)) error_limit = new_tester.default_options.error_limit; for (; begin_Date <= end_Date && error.length < error_limit; begin_Date += ONE_DAY_LENGTH_VALUE) { // 正解: Date → calendar date date_name = to_Calendar(new Date(begin_Date), options); if (old_date_name // && (date_name[2] - old_date_name[2] !== 1 || old_date_name[1] !== date_name[1])) { if (date_name[0] !== old_date_name[0] // 每世紀記錄一次使用時間。 && date_name[0] % 100 === 0 && Date.now() - last_show > 20000) { console.log((begin_Date - epoch) / ONE_DAY_LENGTH_VALUE + ' days: ' + date_name.join() + ' (' + (new Date(begin_Date)).format(CE_format) + ')' + ', 使用時間 ' + ((last_show = Date.now()) - begin) + ' ms.'); } // 確定 old_date_name 的下一個天為 date_name。 // 月差距 tmp = get_month_serial(date_name) - get_month_serial(old_date_name); if (date_name[2] - old_date_name[2] === 1) tmp = tmp !== 0 && !continued_month(date_name[1], old_date_name[1]) && '隔日(日期名接續),但月 serial 差距 !== 0'; else if (date_name[2] !== 1) tmp = '日期名未接續: 隔月/隔年,但日期非以 1 起始'; else if (!(old_date_name[2] in month_days)) tmp = '日期名未接續: 前一月末日數 ' + old_date_name[2] + '未設定於 month_days 中'; else if (tmp !== 1 && (tmp !== 0 // 這邊不再檢查年份是否差一,因為可能是閏月。 // || date_name[0] - old_date_name[0] !== 1 ) && !continued_month(date_name[1], old_date_name[1])) tmp = '月名未接續 (' + old_date_name[1] + '→' + date_name[1] + ': 相差' + tmp + ')'; else if (date_name[2] === old_date_name[2]) tmp = '前後日期名相同'; else if (date_name[0] !== old_date_name[0] && date_name[0] - old_date_name[0] !== 1 // Skip last day of -1 → first day of 1 && date_name[0] !== 1 && old_date_name[0] !== -1) tmp = '前後年份不同: ' + old_date_name[0] + '→' + date_name[0]; else // 若 OK,必得設定 tmp! tmp = false; if (tmp) { error.push(tmp + ': 前一天 ' + old_date_name.join('/') + ' ⇨ 隔天 ' + date_name.join('/') + ' (' + (new Date(begin_Date)).format(CE_format) + ', ' + begin_Date + ')'); } } old_date_name = date_name; // 反解: calendar date → Date tmp = to_Date(date_name[0], date_name[1], date_name[2]); if (begin_Date - tmp !== 0) { tmp = '正反解到了不同日期: ' + (new Date(begin_Date)).format(CE_format) + ', ' + (begin_Date - epoch) / ONE_DAY_LENGTH_VALUE + ' days → ' + date_name.join(',') + ' → ' + (tmp ? tmp.format(CE_format) : tmp); error.push(tmp); if (error.length < 9) console.error(tmp); } } library_namespace.info((new Date - begin) + ' ms, error ' + error.length + '/' + error_limit); if (true || error.length) return error; }; } new_tester.default_options = { // length of the months month_days : { 29 : '陰陽曆大月', 30 : '陰陽曆小月' }, CE_format : { parser : 'CE', format : '%Y/%m/%d %HH CE' }, // 延續的月序,月序未中斷。continued/non-interrupted month serial. continued_month : function(month, old_month) { return month === 1 && (old_month === 12 || old_month === 13); }, // get month serial // 其他方法: 見 Hindu_Date.test month_serial : function(date_name) { var month = date_name[1]; if (isNaN(month)) { var matched = month.match(/^\D?(\d{1,2})$/); if (!matched) throw 'tester: Illegal month name: ' + month; month = matched[1] | 0; } return month; }, // get 數字序號 (numerical serial). format : 'serial', error_limit : 20 }; function continued_month_中曆(month, old_month) { var matched; if (typeof old_month === 'string') return (matched = old_month.match(/^閏(\d{1,2})$/)) && (matched[1] - month === 1 || month === 1 && matched[1] == 12); if (typeof month === 'string') return (matched = month.match(/^閏(\d{1,2})$/)) && (matched[1] - old_month === 0 || matched[1] == 1 && old_month === 12); return month === 1 && old_month === 12; } /** * 提供 to calendar date 之 front-end (wrapper) * * @param {Function}calendar_Date * to calendar date * @param {Function}[new_year_Date] * to calendar new year's day * * @returns {Function} parser */ function _parser(calendar_Date, new_year_Date) { return function(date, minute_offset, options) { var period_end = options && options.period_end; if (!isNaN(date)) { if (new_year_Date) // use the new year's day return new_year_Date(date); // use year/1/1 // String → Number date |= 0; return calendar_Date(period_end ? 1 + date : date, 1, 1); } if (date = date.match(/(-?\d{1,4})[\/\-](\d{1,2})(?:[\/\-](\d{1,2}))?/)) { if (period_end) date[date[3] ? 3 : 2]++; return calendar_Date(date[1] | 0, date[2] | 0, date[3] && (date[3] | 0) || 1); } }; } //----------------------------------------------------------------------------------------------------------------------------------------------------------// // 長曆: 伊斯蘭曆 // گاه‌شماری هجری قمری // https://fa.wikipedia.org/wiki/%DA%AF%D8%A7%D9%87%E2%80%8C%D8%B4%D9%85%D8%A7%D8%B1%DB%8C_%D9%87%D8%AC%D8%B1%DB%8C_%D9%82%D9%85%D8%B1%DB%8C // تقويم هجري // https://ar.wikipedia.org/wiki/%D8%AA%D9%82%D9%88%D9%8A%D9%85_%D9%87%D8%AC%D8%B1%D9%8A // Tabular Islamic calendar / lunar Hijri calendar (AH, A.H. = anno hegirae), lunar Hejrī calendar / التقويم الهجري المجدول / // http://en.wikipedia.org/wiki/Tabular_Islamic_calendar // 伊斯蘭曆(回回曆) // 陳垣編的《中西回史日曆》(中華書局1962年修訂重印)。 // 陈氏中西回史日历冬至订误,鲁实先 // There are 11 leap years in a 30 year cycle. var Tabular_cycle_years = 30 | 0, Tabular_half_cycle = 15 | 0, // 平年日數。6=(12個月 / 2) // 每年有12個月。奇數個月有30天,偶數個月有29天,除第12/最後一個月在閏年有30天。 Tabular_common_year_days = (30 + 29) * 6 | 0, // 每一30年周期內設11閏年。 Tabular_leaps_in_cycle = 11 | 0, // Tabular_cycle_days = Tabular_common_year_days * Tabular_cycle_years + Tabular_leaps_in_cycle, // Tabular_leap_count[shift + Tabular_cycle_years] // = new Array( 30 : [ 各年於30年周期內已累積 intercalary days ] ) Tabular_leap_count = [], // 各月1日累積日數。 // = [0, 30, 30+29, 30+29+30, ..] // Tabular_month_days.length = 12 Tabular_month_days = [ 0 ]; (function() { for (var month = 0, count = 0; month < 12;) Tabular_month_days.push(count += (month++ % 2 === 0 ? 30 : 29)); // assert: Tabular_common_year_days === Tabular_month_days.pop(); })(); function Tabular_list_leap() { for (var s = -Tabular_cycle_years; s <= Tabular_cycle_years; s++) { for (var year = 1, shift = s, leap = []; year <= Tabular_cycle_years; year++) if ((shift += Tabular_leaps_in_cycle) > Tabular_half_cycle) shift -= Tabular_cycle_years, leap.push(year); library_namespace.log(s + ': ' + leap); } } // 0: 2,5,7,10,13,16,18,21,24,26,29 // -3: 2,5,8,10,13,16,19,21,24,27,29 // 1: 2,5,7,10,13,15,18,21,24,26,29 // -5: 2,5,8,11,13,16,19,21,24,27,30 // shift: 小餘, -30–30. function get_Tabular_leap_count(shift, year_serial) { if (0 < (shift |= 0)) shift %= Tabular_cycle_years; else shift = 0; // + Tabular_cycle_years: 預防有負數。 if (!((shift + Tabular_cycle_years) in Tabular_leap_count)) // 計算各年於30年周期內已累積 intercalary days。 for (var year = 0, count = 0, // new Array(Tabular_cycle_years) intercalary_days_count = Tabular_leap_count[shift + Tabular_cycle_years] = [ 0 ]; // year < Tabular_cycle_years; year++) { if ((shift += Tabular_leaps_in_cycle) > Tabular_half_cycle) shift -= Tabular_cycle_years, count++; intercalary_days_count.push(count); } return Tabular_leap_count[shift + Tabular_cycle_years][year_serial]; } // Tabular date to Date. function Tabular_Date(year, month, date, shift) { return new Date(Tabular_Date.epoch + // 計算距離 Tabular_Date.epoch 日數。 (Math.floor((year = year < 0 ? year | 0 : year > 0 ? year - 1 : 0) // ↑ No year 0. / Tabular_cycle_years) * Tabular_cycle_days // 添上閏年數。 + get_Tabular_leap_count(shift, // 確認 year >=0。 (year %= Tabular_cycle_years) < 0 ? (year += Tabular_cycle_years) : year) // 添上整年之日數。 + year * Tabular_common_year_days // 添上整月之日數。 + Tabular_month_days[(month || 1) - 1] // 添上月初至 date 日數。 + (date || 1) - 1) * ONE_DAY_LENGTH_VALUE); } // 622/7/15 18:0 Tabular begin offset // 伊斯蘭曆每日以日落時分日。例如 AH 1/1/1 可與公元 622/7/16 互換, // 但 AH 1/1/1 事實上是從 622/7/15 的日落時算起,一直到 622/7/16 的日落前為止。 // '622/7/16'.to_Date('CE').format(): '622/7/19' === new Date(622, 7 - 1, 19) Tabular_Date.epoch = String_to_Date('622/7/16', { parser : 'CE' }).getTime(); var Tabular_month_name_of_serial = '|محرم|صفر|ربيع الأول|ربيع الثاني|جمادى الأول|جمادى الآخر|رجب |شعبان|رمضان|شوال|ذو القعدة|ذو الحجة'.split('|'); Tabular_Date.month_name = function(month_serial) { return Tabular_month_name_of_serial[month_serial]; }; Tabular_Date.is_leap = function(year, shift) { // 轉正。保證變數值非負數。 if ((year %= Tabular_cycle_years) < 1) year += Tabular_cycle_years; return get_Tabular_leap_count(shift, year) - get_Tabular_leap_count(shift, year - 1); }; // 有更快的方法。 function Date_to_Tabular(date, options) { var month, // 距離 Tabular_Date.epoch 的日數。 tmp = (date - Tabular_Date.epoch) / ONE_DAY_LENGTH_VALUE, // delta = tmp - (date = Math.floor(tmp)), // 距離 Tabular_Date.epoch 的30年周期之年數。 year = Math.floor(date / Tabular_cycle_days) * Tabular_cycle_years; // 本30年周期內之日數。 date %= Tabular_cycle_days; // 保證 date >=0。 if (date < 0) date += Tabular_cycle_days; // month: 本30年周期內之積年數: 0–30。 // 30: 第29年年底。 month = (date / Tabular_common_year_days) | 0; year += month; date %= Tabular_common_year_days; // 不動到原 options。 options = Object.assign({ postfix:' هـ' },options); // 求出為本年第幾天之序數。 // 減去累積到第 month 年首日,應該有幾個閏日。 tmp = get_Tabular_leap_count(options.shift, month); if (date < tmp) // 退位。 year--, date += Tabular_common_year_days // - get_Tabular_leap_count(options.shift, month - 1); else date -= tmp; // 至此確定年序數與求出本年第幾天之序數。 // 這邊的計算法為 Tabular Islamic calendar 特殊設計過,並不普適。 // 理據: 每月日數 >=29 && 末月累積日數 - 29*月數 < 29 (不會 overflow) // tmp 可能是本月,或是下月累積日數。 tmp = Tabular_month_days[month = (date / 29) | 0]; if (date < tmp // assert: month === 12: 年內最後一天。 || month === 12) // tmp 是下月累積日數。 tmp = Tabular_month_days[--month]; // 本月日數。 date -= tmp; // 日期序數→日期名。year/month/date index to serial. // There is no year 0. if (year >= 0) year++; // [ year, month, date, 餘下時間值(單位:日) ] return _format([ year, month + 1, date + 1, delta ], options, Tabular_Date.month_name); } /* CeL.run('data.date.calendar'); CeL.Tabular_Date.test(-2e4, 4e6, 4).join('\n') || 'OK'; // "OK" '624/6/23'.to_Date('CE').to_Tabular({format : 'serial'}) // [2, 12, 30, 0] CeL.Tabular_Date(3, 7, 1).format('CE') */ Tabular_Date.test = new_tester(Date_to_Tabular, Tabular_Date); _.Tabular_Date = Tabular_Date; //----------------------------------------------------------------------------------------------------------------------------------------------------------// // 長曆: הַלּוּחַ הָעִבְרִי / Hebrew calendar / Jewish Calendar / 希伯來曆 / 猶太曆計算 // https://en.wikipedia.org/wiki/Hebrew_calendar // http://www.stevemorse.org/jcal/rules.htm // http://www.jewishgen.org/infofiles/m_calint.htm // Hebrew_month_serial[month_name] = month serial (1–12 or 13) var Hebrew_month_serial = Object.create(null), // Hour is divided into 1080 parts called haliq (singular of halaqim) Hebrew_1_HOUR = 1080 | 0, // hour length in halaqim Hebrew_1_DAY = 24 * Hebrew_1_HOUR | 0, // month length in halaqim // The Jewish month is defined to be 29 days, 12 hours, 793 halaqim. Hebrew_1_MONTH = 29 * Hebrew_1_DAY + 12 * Hebrew_1_HOUR + 793 | 0, // Metonic cycle length in halaqim // Metonic cycle = 235 months (about 19 years): Hebrew calendar 採十九年七閏法 //Hebrew_1_cycle = (19 * 12 + 7) * Hebrew_1_MONTH | 0, ONE_HOUR_LENGTH_VALUE = Date.parse('1/1/1 2:0') - Date.parse('1/1/1 1:0'), // 1 hour of Date / 1 hour of halaqim (Hebrew calendar). // (length of halaqim) * halaqim_to_Date_ratio = length value of Date halaqim_to_Date_ratio = ONE_HOUR_LENGTH_VALUE / Hebrew_1_HOUR, // http://www.stevemorse.org/jcal/rules.htm // Molad of Tishri in year 1 occurred on Monday at 5hr, 204hq (5hr, 11mn, 20 sc) // i.e., evening before Monday daytime at 11 min and 20 sec after 11 PM Hebrew_epoch_halaqim = 5 * Hebrew_1_HOUR + 204 | 0, // https://en.wikipedia.org/wiki/Molad // The traditional epoch of the cycle was 5 hours 11 minutes and 20 seconds // after the mean sunset (considered to be 6 hours before midnight) at the epoch // of the Hebrew calendar (first eve of Tishrei of Hebrew year 1). Hebrew_epoch_shift_halaqim = -6 * Hebrew_1_HOUR | 0, // for reduce error. Hebrew_epoch_shift = Math.round(Hebrew_epoch_shift_halaqim * halaqim_to_Date_ratio), // 1/Tishri/1: Julian -3761/10/7 // // https://en.wikipedia.org/wiki/Hebrew_calendar // The Jewish calendar's epoch (reference date), 1 Tishrei AM 1, is equivalent // to Monday, 7 October 3761 BC/BCE in the proleptic Julian calendar, the // equivalent tabular date (same daylight period) and is about one year before // the traditional Jewish date of Creation on 25 Elul AM 1, based upon the Seder // Olam Rabbah. // // http://www.stevemorse.org/jcal/jcal.html // http://www.fourmilab.ch/documents/calendar/ Hebrew_epoch = String_to_Date('-3761/10/7', { parser : 'Julian' }).getTime(); // ---------------------------------------------------------------------------// // Hebrew to Date // Hebrew year, month, date // get_days: get days of year function Hebrew_Date(year, month, date, get_days) { // no year 0. year: -1 → 0 if (year < 0 && !Hebrew_Date.year_0) year++; var is_leap = Hebrew_Date.is_leap(year), // days = isNaN(date) ? 0 : date - 1 | 0, // days diff (year type) // add_days = -1 (defective) / 0 (normal) / 1 (complete) add_days = Hebrew_Date.days_of_year(year) - 354 | 0; if (add_days > 1) add_days -= 30; if (!month) // month index 0 month = 0; else if (isNaN(month = Hebrew_Date.month_index(month, is_leap))) return; // month: month index (0–11 or 12) if (month > 2 || month === 2 && add_days > 0) { // 所有後面的月份皆須加上此 add_days。 days += add_days; if (is_leap && month > 5) // subtract the 30 days of leap month Adar I. month--, days += 30; } days += (month >> 1) * (30 + 29); if (month % 2 === 1) days += 30; // days: days from new year day return get_days ? days : Hebrew_Date.new_year_days(year, days, true); } // Are there year 0? // 警告:除了 Hebrew_Date(), Date_to_Hebrew() 之外,其他函數皆假定有 year 0! Hebrew_Date.year_0 = false; //---------------------------------------------------------------------------// // tools for month name // https://en.wikipedia.org/wiki/Hebrew_Academy // Academy name of specified month serial. // common year: Nisan: 1, Iyyar: 2, .. Adar: 12 // leap year: Nisan: 1, Iyyar: 2, .. (Adar I): 12, (Adar II/Adar/Ve'Adar): 13 // Tishri: 下半年的開始。 http://en.wikipedia.org/wiki/Babylonian_calendar (Hebrew_Date.month_name_of_serial = "|Nisan|Iyyar|Sivan|Tammuz|Av|Elul|Tishri|Marẖeshvan|Kislev|Tevet|Shvat|Adar" .split('|')) // .forEach(function(month_name, month_serial) { if (month_serial > 0) Hebrew_month_serial[month_name.toLowerCase()] = month_serial; }); // other common names. // all should be in lower case! Object.assign(Hebrew_month_serial, { nissan : 1, iyar : 2, siwan : 3, tamuz : 4, ab : 5, tishrei : 7, heshvan : 8, marcheshvan : 8, cheshvan : 8, 'marẖeshwan' : 8, chisleu : 9, chislev : 9, tebeth : 10, shevat : 11, shebat : 11, sebat : 11, // 'adar 1':12, // 'adar 2':12, // Occasionally instead of Adar I and Adar II, "Adar" and "Ve'Adar" are used // (Ve means 'and' thus: And Adar). veadar : 13, "ve'adar" : 13 }); // return Academy name of specified month serial. // common year: 1: Nisan, 2: Iyyar, .. 12: Adar // leap year: 1: Nisan, 2: Iyyar, .. 12: Adar, 13: Adar II Hebrew_Date.month_name = function(month_serial, is_leap_year) { if (month_serial >= 12 && (month_serial === 13 || is_leap_year)) return month_serial === 12 ? 'Adar I' : 'Adar II'; return Hebrew_Date.month_name_of_serial[month_serial]; }; // return month serial. // common year: Nisan: 1, Iyyar: 2, .. Adar: 12 // leap year: Nisan: 1, Iyyar: 2, .. (Adar I): 12, (Adar II/Adar/Ve'Adar): 13 Hebrew_Date.month_serial = function(month_name, is_leap_year) { if (typeof month_name === 'string') { // normalize month name. month_name = month_name.trim().toLowerCase(); if (/^adar\s*[1i]$/i.test(month_name)) // Only in Leap years. return 12; if (/^adar\s*(2|ii)$/i.test(month_name)) // Only in Leap years. return 13; if (month_name === 'adar') if (is_leap_year) return 13; else if (is_leap_year === undefined) { if (library_namespace.is_debug(2)) library_namespace .warn('May be 12, but will return 13 for Adar.'); return 13; } if (month_name in Hebrew_month_serial) return Hebrew_month_serial[month_name]; } library_namespace.error('Hebrew_Date.month_serial: Unknown month name: ' + month_name); return month_name; }; // month: month name or serial // // return 0: Tishri, 1: Heshvan, .. // // common year: 0–11 // leap year: 0–12 // // for numeral month name (i.e. month serial): // Hebrew year begins on 7/1, then month 8, 9, .. 12, 1, 2, .. 6. // // common year: 7→0 (Tishri), 8→1, .. 12→5 (Adar), // 1→6 (Nisan), 2→7, .. 6→11 (Elul) // // leap year: 7→0 (Tishri), 8→1, .. 12→5 (Adar I), 13→6 (Adar II), // 1→7 (Nisan), 2→8, .. 6→12 (Elul) Hebrew_Date.month_index = function(month, is_leap_year) { if (isNaN(month)) // month name to serial month = Hebrew_Date.month_serial(month, is_leap_year); if (month === (month | 0)) if (month === 13) // Adar II return 6; else if (1 <= month && month <= 12 && (month -= 7) < 0) // leap 1→-6→7, .. // common: 1→-6→6, .. month += is_leap_year ? 13 : 12; if (Number.isNaN(month)) library_namespace.error('Hebrew_Date.month_index: Unknown month: ' + month); return month; }; //---------------------------------------------------------------------------// /* for (y = 0; y < 19; y++) if (Hebrew_Date.is_leap(y)) console.log(y); */ // the years 3, 6, 8, 11, 14, 17, and 19 // are the long (13-month) years of the Metonic cycle Hebrew_Date.is_leap = function(year) { year = (7 * (year | 0) + 1) % 19; // 轉正。保證變數值非負數。 if (year < 0) year += 19; return year < 7; }; /* 累積 leap: (7 * year - 6) / 19 | 0 Simplify[12*(year - 1) + (7*year - 6)/19] 1/19 (-234 + 235 year) */ // 累積 months of new year begins (7/1) Hebrew_Date.month_count = function(year, month_index) { return Math.floor((235 * year - 234 | 0) / 19) + (month_index | 0); }; // halaqim of molad from Hebrew_epoch // month_index 0: Tishri // CeL.Hebrew_Date.molad(1,0,true).format('CE')==="-3761/10/6 23:11:20.000" Hebrew_Date.molad = function(year, month_index, get_Date) { year = Hebrew_Date.month_count(year, month_index) * Hebrew_1_MONTH + Hebrew_epoch_halaqim; return get_Date ? new Date(Hebrew_epoch + Hebrew_epoch_shift + year * halaqim_to_Date_ratio) : year; }; // return [ week_day (0:Sunday, 1:Monday, .. 6), // hour (0–23 from sunset 18:0 of previous day), halaqim (0–) ] // @see // http://www.stevemorse.org/jcal/molad.htm?year=1 Hebrew_Date.molad_date = function(year, month_index) { var halaqim = Hebrew_Date.molad(year, month_index), // the first day of 1/1/1 is Monday, index 1. week_day = (Math.floor(halaqim / Hebrew_1_DAY) + 1) % 7 | 0; // 轉正。保證變數值非負數。 if (week_day < 0) week_day += 7; halaqim %= Hebrew_1_DAY; if (halaqim < 0) halaqim += Hebrew_1_DAY; return [ week_day, halaqim / Hebrew_1_HOUR | 0, halaqim % Hebrew_1_HOUR | 0 ]; }; // cache var Hebrew_delay_days = [], Hebrew_new_year_days = []; /* test: for (year = 0; year < 1e4; year++) if (CeL.Hebrew_Date.delay_days(year) === 2 && (!CeL.Hebrew_Date.delay_days(year - 1) || !CeL.Hebrew_Date .delay_days(year + 1))) throw year; */ // return 0, 1, 2 Hebrew_Date.delay_days = function(year) { if ((year |= 0) in Hebrew_delay_days) return Hebrew_delay_days[year]; var delay_days = Hebrew_Date.molad_date(year), // week_day = delay_days[0] | 0, hour = delay_days[1] | 0, halaqim = delay_days[2] | 0; // phase 1 // http://www.stevemorse.org/jcal/rules.htm // Computing the beginning of year (Rosh Hashanah): if (delay_days = // (2) If molad Tishri occurs at 18 hr (i.e., noon) or later, Tishri 1 must // be delayed by one day. hour >= 18 // (3) If molad Tishri in a common year falls on Tuesday at 9 hr 204 hq // (i.e., 3:11:20 AM) or later, then Tishri 1 is delayed by one day || week_day === 2 && (hour > 9 || hour === 9 && halaqim >= 204) && !Hebrew_Date.is_leap(year) // (4) If molad Tishri following a leap year falls on Monday at 15 // hr 589 hq (9:32:43 1/3 AM) or later, Tishri 1 is delayed by one // day || week_day === 1 && (hour > 15 || hour === 15 && halaqim >= 589) && Hebrew_Date.is_leap(year - 1) // default: no delayed ? 1 : 0) week_day++; // phase 2 // (1) If molad Tishri occurs on Sunday, Wednesday, or Friday, Tishri 1 must // be delayed by one day // // since the molad Tishri of year 2 falls on a Friday, Tishri 1 of that year // should have been delayed by rule 1 so that Yom Kippor wouldn't be on the // day after the Sabbath. However Adam and Eve would not yet have sinned as // of the start of that year, so there was no predetermined need for them to // fast on that first Yom Kippor, and the delay rule would not have been // needed. And if year 2 was not delayed, the Sunday to Friday of creation // would not have been from 24-29 Elul but rather from 25 Elul to 1 Tishri. // In other words, Tishri 1 in the year 2 is not the first Sabbath, but // rather it is the day that Adam and Eve were created. // // http://www.stevemorse.org/jcal/jcal.js // year 3 wants to start on Wed according to its molad, so delaying year // 3 by the WFS rule would require too many days in year 2, therefore // WFS must be suspended for year 3 as well if (3 * week_day % 7 < 3 && 3 < year) delay_days++; return Hebrew_delay_days[year] = delay_days | 0; }; // return days of year's first day. Hebrew_Date.new_year_Date_original = function(year, days) { days = new Date(Hebrew_epoch + Hebrew_Date.molad(year) * halaqim_to_Date_ratio + (Hebrew_Date.delay_days(year) + (days | 0)) * ONE_DAY_LENGTH_VALUE); // set to 0:0 of the day days.setHours(0, 0, 0, 0); return days; }; // calculate days from 1/1/1. // simplify from Hebrew_Date.new_year_Date_original() // new_year_days(1) = 0 // new_year_days(2) = 354 // new_year_days(3) = 709 // CeL.Hebrew_Date.new_year_days(1,0,true).format('CE') Hebrew_Date.new_year_days = function(year, days, get_Date) { if (!(year in Hebrew_new_year_days)) Hebrew_new_year_days[year] = Math.floor(Hebrew_Date.molad(year) / Hebrew_1_DAY) // + Hebrew_Date.delay_days(year) | 0; year = Hebrew_new_year_days[year] + (days | 0); return get_Date ? new Date(Hebrew_epoch + year * ONE_DAY_LENGTH_VALUE) : year; }; // return days of year's first day. // Please use Hebrew_Date.new_year_days(year, days, true) instead. if (false) Hebrew_Date.new_year_Date = function(year, days) { return Hebrew_Date.new_year_days(year, days, true); }; /* test: for (var year = 0, d, d2; year <= 1e5; year++) { d = CeL.Hebrew_Date.days_of_year_original(year); d2 = CeL.Hebrew_Date.days_of_year(year); if (d !== d2) throw year + ': ' + d + '!==' + d2; // common year has 353 (defective), 354 (normal), or 355 (complete) days d -= 354; if (d > 1) d -= 30; if (d !== Math.sign(d)) throw year + ': ' + d2 + ' days'; } console.log('OK'); */ // day count of year. Hebrew_Date.days_of_year_original = function(year) { // common year has 353 (defective), 354 (normal), or 355 (complete) days // leap year has 383 (defective), 384 (normal), or 385 (complete) days return (Hebrew_Date.new_year_Date_original(year + 1) - Hebrew_Date .new_year_Date_original(year)) / ONE_DAY_LENGTH_VALUE | 0; }; // day count of year. // days_of_year(1) = 354 // days_of_year(2) = 354 // days_of_year(3) = 384 // common year has 353 (defective), 354 (normal), or 355 (complete) days // leap year has 383 (defective), 384 (normal), or 385 (complete) days Hebrew_Date.days_of_year = function(year) { return Hebrew_Date.new_year_days(year + 1) - Hebrew_Date.new_year_days(year); }; // month days of normal common year var Hebrew_normal_month_days = []; (function() { for (var m = 0; m < 12; m++) Hebrew_normal_month_days.push(m % 2 === 0 ? 30 : 29); })(); Hebrew_Date.year_data = function(year) { var days = Hebrew_Date.days_of_year(year) | 0, // copy from normal data = Hebrew_normal_month_days.slice(); data.days = days; days -= 354; if (days > 1) days -= 30, data.leap = true, data.splice(5, 0, 30); // assert: days = -1 (defective) / 0 (normal) / 1 (complete) data.add_days = days; if (days > 0) data[1]++; else if (days < 0) data[2]--; return Object.assign(data, { delay_days : Hebrew_Date.delay_days(year), new_year_days : Hebrew_Date.new_year_days(year) }); }; //---------------------------------------------------------------------------// // Date to Hebrew /* days = new_year_days + Δ # 0 <= Δ < 385 new_year_days = days - Δ Math.floor(Hebrew_Date.molad(year) / Hebrew_1_DAY) + Hebrew_Date.delay_days(year) = new_year_days → Math.floor((Hebrew_Date.month_count(year, month_index) * Hebrew_1_MONTH + Hebrew_epoch_halaqim) / Hebrew_1_DAY) + Hebrew_Date.delay_days(year) = new_year_days → Math.floor((Math.floor((235 * year - 234 | 0) / 19) * Hebrew_1_MONTH + Hebrew_epoch_halaqim) / Hebrew_1_DAY) + Hebrew_Date.delay_days(year) = new_year_days → (((235 * year - 234) / 19 + Δ2) * Hebrew_1_MONTH + Hebrew_epoch_halaqim) / Hebrew_1_DAY + Δ1 + delay_days = new_year_days # 0 <= (Δ1, Δ2) <1 # delay_days = 0, 1, 2 → year = ((((days - Δ - Δ1 - delay_days) * Hebrew_1_DAY - Hebrew_epoch_halaqim) / Hebrew_1_MONTH - Δ2) * 19 + 234) / 235 <= ((days * Hebrew_1_DAY - Hebrew_epoch_halaqim) / Hebrew_1_MONTH * 19 + 234) / 235 < (days * Hebrew_1_DAY - Hebrew_epoch_halaqim) / Hebrew_1_MONTH * 19 / 235 + 1 test: var begin = new Date; for (var year = 0, new_year_days, days = 0; year <= 1e5; year++) for (new_year_days = CeL.Hebrew_Date.new_year_days(year + 1); days < new_year_days; days++) if (CeL.Hebrew_Date.year_of_days(days) !== year) throw 'CeL.Hebrew_Date.year_of_days(' + days + ') = ' + CeL.Hebrew_Date.year_of_days(days) + ' != ' + year; console.log('CeL.Hebrew_Date.year_of_days() 使用時間: ' + (new Date - begin) / 1000); //CeL.Hebrew_Date.year_of_days() 使用時間: 154.131 */ // return year of the day; Hebrew_Date.year_of_days = function(days) { // 即使預先計算參數(coefficient),以加快速度,也不會顯著加快。@ Chrome/36 var year = Math.ceil((days * Hebrew_1_DAY - Hebrew_epoch_halaqim) / Hebrew_1_MONTH * 19 / 235) + 1 | 0; // assert: 最多減兩次。 // 經測試 0–4e6,96% 皆為減一次。 // [ 139779, 3859350, 871 ] while (days < Hebrew_Date.new_year_days(year)) year--; return year; }; /* d = '-3761/10/7'.to_Date('CE').to_Hebrew(); */ function Date_to_Hebrew(date, options) { var tmp, month, days = date - Hebrew_epoch - Hebrew_epoch_shift, // hour = days % ONE_DAY_LENGTH_VALUE, // year = Hebrew_Date.year_of_days(days = Math.floor(days / ONE_DAY_LENGTH_VALUE) | 0), // is_leap = Hebrew_Date.is_leap(year), // add_days = Hebrew_Date.days_of_year(year) - 354 | 0; // 轉正。保證變數值非負數。 if (hour < 0) hour += ONE_DAY_LENGTH_VALUE; days -= Hebrew_Date.new_year_days(year); // assert: days: days from new year day if (add_days > 1) // assert: is_leap === true add_days -= 30; // assert: add_days = -1 (defective) / 0 (normal) / 1 (complete) // 將 days 模擬成 normal common year. // 因此需要作相應的處理: // 從前面的日期處理到後面的, // 自開始被影響,與 normal common year 不同的那天起將之改成與 normal common year 相同。 // days → month index / days index of month if (add_days !== 0) if (add_days === 1) { // 30 + 29: complete year 開始被影響的一日。 if (days === 30 + 29) // 因為 normal common year 沒有辦法表現 8/30,須特別處理 8/30。 month = 1, days = 29, tmp = true; else if (days > 30 + 29) days--; } else if (days >= 30 + 29 + 29) // 30 + 29 + 29: defective year 開始被影響的一日。 // assert: add_days === -1 days++; if (!tmp) { // is_leap 還會用到,因此將 tmp 當作暫用值。 // 3 * (30 + 29): leap year 開始被影響的一日。 if (tmp = is_leap && days >= 3 * (30 + 29)) days -= 30; // 計算有幾組 (30 + 29) 月份。 month = days / (30 + 29) | 0; // 計算 date in month。 days -= month * (30 + 29); // 每組 (30 + 29) 月份有 2個月。 month <<= 1; // normal common year 每組 (30 + 29) 月份,首月 30日。 if (days >= 30) month++, days -= 30; if (tmp) // 加上 leap month. month++; } // 日期序數→日期名。year/month/date index to serial. // index 0 → serial 7 month += 7; // add_days: months of the year. tmp = is_leap ? 13 : 12; if (month > tmp) month -= tmp; if (year <= 0 && !Hebrew_Date.year_0) // year: 0 → -1 --year; // 前置處理。 if (!library_namespace.is_Object(options)) if (options === true) options = { // month serial to name month_name : true }; else options = Object.create(null); return _format([ year, month, days + 1, // hour Math.floor(hour / ONE_HOUR_LENGTH_VALUE) | 0, // halaqim (hour % ONE_HOUR_LENGTH_VALUE) / halaqim_to_Date_ratio ], options, Hebrew_Date.month_name, is_leap); } /* CeL.Hebrew_Date.test(-2e4, 4e6, 4).join('\n') || 'OK'; // "OK" '-3762/9/18'.to_Date('CE').to_Hebrew({format : 'serial'}) // -1/6/29 CeL.Hebrew_Date(3, 7, 1).format('CE') */ Hebrew_Date.test = new_tester(Date_to_Hebrew, Hebrew_Date, { epoch : Hebrew_epoch }); _.Hebrew_Date = Hebrew_Date; //----------------------------------------------------------------------------------------------------------------------------------------------------------// // 長曆: Mesoamerican Long Count calendar / 馬雅長紀曆 // <a href="https://en.wikipedia.org/wiki/Mesoamerican_Long_Count_calendar" accessdate="2014/4/28 22:15" title="Mesoamerican Long Count calendar">中美洲長紀曆</a> // GMT correlation: starting-point is equivalent to August 11, 3114 BCE in the proleptic Gregorian calendar // https://en.wikipedia.org/wiki/Template:Maya_Calendar // GMT 584283 // GMT+2 584285 // Thompson (Lounsbury) 584,285 // 注意:據 mayaman@ptt 言,<q>目前比較流行的是GMT+2 如果你要統治者紀年的話</q>。 Maya_Date.epoch = (new Date(-3114 + 1, 8 - 1, 11/* + 2*/)).getTime(); // Era Base date, the date of creation is expressed as 13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.0.0.0.0 4 Ajaw 8 Kumk'u /* // get offset: // 4 Ajaw → 3/13, 19/20 for (i = 0, d = 3, l = 20; i < l; i++, d += 13) if (d % l === 19) throw d; // 159 */ var Tzolkin_day_offset = 159, Tzolkin_day_period = 13 * 20, /* // get offset: // 8 Kumk'u → 348/(20 * 18 + 5) 365 - 17 = 348 */ Haab_day_offset = 348, Haab_day_period = 20 * 18 + 5; function Maya_Date(date, minute_offset, options) { if (typeof date === 'string') date = date.split(/[,.]/); else if (!Array.isArray(date)) return new Date(NaN); var days = 0, length = date.length - 1, i = 0, // e.g., 8.19.15.3.4 1 K'an 2 K'ayab' matched = date[length].match(/^(\d{1,2})\s/); if (matched) date[length] = matched[1] | 0; if (matched = date[0].match(/\s(\d{1,2})$/)) date[0] = matched[1] | 0; length--; while (i < length) days = days * 20 + (date[i++] | 0); days = (days * 18 + (date[i] | 0)) * 20 + (date[++i] | 0); if (options && options.period_end) days++; return new Date(days * ONE_DAY_LENGTH_VALUE + Maya_Date.epoch); } Maya_Date.days = function(date) { return Math.floor((date - Maya_Date.epoch) / ONE_DAY_LENGTH_VALUE); }; Maya_Date.to_Long_Count = function(date, get_Array) { var days = Maya_Date.days(date), // Mesoamerican Long Count calendar. Long_Count; if (!Number.isFinite(days)) // NaN return; if (days <= 0) // give a 13.0.0.0.0 // but it should be: // 13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.13.0.0.0.0 days += 13 * 20 * 20 * 18 * 20; Long_Count = [ days % 20 ]; days = Math.floor(days / 20); Long_Count.unshift(days % 18); days = Math.floor(days / 18) | 0; while (days > 0 || Long_Count.length < 5) { Long_Count.unshift(days % 20); days = days / 20 | 0; } return get_Array ? Long_Count : Long_Count.join('.'); }; Maya_Date.Tzolkin_day_name = "Imix'|Ik'|Ak'b'al|K'an|Chikchan|Kimi|Manik'|Lamat|Muluk|Ok|Chuwen|Eb'|B'en|Ix|Men|Kib'|Kab'an|Etz'nab'|Kawak|Ajaw" .split('|'); // <a href="https://en.wikipedia.org/wiki/Tzolk%27in" accessdate="2014/4/30 // 18:56">Tzolk'in</a> // type = 1: {Array} [ 13-day cycle index, 20-day cycle index ] (index: start // from 0), // // 2: {Array} [ 13-day cycle name, 20-day cycle ordinal/serial: start from 1, // 20-day cycle name ], // // 3: {Array} [ 13-day cycle name, 20-day cycle name ], // others (default): {String} (13-day cycle name) (20-day cycle name) Maya_Date.to_Tzolkin = function(date, type) { var days = Maya_Date.days(date) + Tzolkin_day_offset; // 轉正。保證變數值非負數。 if (days < 0) days = days % Tzolkin_day_period + Tzolkin_day_period; // 20: Maya_Date.Tzolkin_day_name.length days = [ days % 13, days % 20 ]; if (type === 1) return days; days[0]++; var day_name = Maya_Date.Tzolkin_day_name[days[1]]; if (type === 2) { days[1]++; days[2] = day_name; } else if (type === 3) days[1] = day_name; else // 先日子,再月份。 days = days[0] + ' ' + day_name; return days; }; Maya_Date.Haab_month_name = "Pop|Wo'|Sip|Sotz'|Sek|Xul|Yaxk'in'|Mol|Ch'en|Yax|Sak'|Keh|Mak|K'ank'in|Muwan'|Pax|K'ayab|Kumk'u|Wayeb'" .split('|'); // <a href="https://en.wikipedia.org/wiki/Haab%27" accessdate="2014/4/30 // 18:56">Haab'</a> // type = 1: {Array} [ date index, month index ] (index: start from 0), // // 2: {Array} [ date ordinal/serial: start from 1, month ordinal/serial: start // from 1, date name, month name ], // // 3: {Array} [ date name, month name ], // others (default): {String} (date name) (month name) Maya_Date.to_Haab = function(date, type) { var days = (Maya_Date.days(date) + Haab_day_offset) % Haab_day_period; // 轉正。保證變數值非負數。 if (days < 0) days += Haab_day_period; // 20 days in a month. days = [ days % 20, days / 20 | 0 ]; if (type === 1) return days; // Day numbers began with a glyph translated as the "seating of" a named // month, which is usually regarded as day 0 of that month, although a // minority treat it as day 20 of the month preceding the named month. In // the latter case, the seating of Pop is day 5 of Wayeb'. For the majority, // the first day of the year was Seating Pop. This was followed by 1 Pop, 2 // Pop ... 19 Pop, Seating Wo, 1 Wo and so on. days[2] = days[0] === 0 ? 'Seating' : days[0]; days[3] = Maya_Date.Haab_month_name[days[1]]; if (type === 2) days[1]++; else { days.splice(0, 2); if (type !== 3) // 先日子,再月份。 days = days.join(' '); } return days; }; _.Maya_Date = Maya_Date; //----------------------------------------------------------------------------------------------------------------------------------------------------------// // 長曆: 西雙版納傣曆計算。傣历 / Dai Calendar // 適用範圍: 傣曆 714年(1352/3/28–)至 3190年期間內。 /* 基本上按张公瑾:《西双版纳傣文〈历法星卜要略〉历法部分译注》、《傣历中的纪元纪时法》計算公式推算,加上過去暦書多有出入,因此與實暦恐有一兩天差距。 《中国天文学史文集 第三集》 http://blog.sina.com.cn/s/blog_4131e58f0101fikx.html 傣曆和農曆一樣,用干支紀年和紀日。傣曆干支約於東漢時由漢地傳入,使用年代早於紀元紀時的方法。不過傣族十二地支所代表的對象和漢族不完全相同,如「子」不以表鼠而代表大象,「辰」不代表龍,而代表蛟或大蛇。 [张公瑾,陈久金] 傣历中的干支及其与汉历的关系 (傣曆中的干支及其與漢曆的關係, 《中央民族学院学报》1977年第04期) 值得注意的是, 傣历中称干支日为“腕乃” 或‘婉傣” , 意思是“ 里面的日子” 或“傣族的日子” , 而一周一匕日的周日, 明显地是从外面传进来的, 则称为“腕诺” 或,’m 命” , 即“外面的日子· 或“ 你的日子’, , 两者你我相对, 内外有8lJ, 是很清楚的。很明显, 傣历甲的干支纪年与纪日是从汉历中吸收过来的, 而且已经成了傣历中不可分害少的组成部分。在傣文的两本最基本的推算历法书‘苏定》和《苏力牙》中, 干支纪年与纪日的名称冠全书之首, 可见汉历成份在傣历中的重要性。 《中央民族學院學報》 1979年03期 傣曆中的紀元紀時法 張公瑾 傣曆中的紀元紀時法,與公曆的紀時法相近似,即以某一個時間為傣曆紀元開始累計的時間,以後就順此按年月日往下記,至今年(1979年)10月1日(農曆己未年八月十一)為傣曆1341年12月月出11日,這是一種情況。 還有一種情況是:公元1979年10月1日又是傣曆紀元的第1341年、傣曆紀元的第16592月,並是傣曆紀元的第489982日。對這種年月日的累計數,現譯稱為傣曆紀元年數、紀元積月數和紀元積日數。 TODO: 有極少例外,如1190年未閏(八月滿月),而1191年閏。 */ /* year: 傣曆紀元年數。 應可處理元旦,空日,除夕,閏月,後六月,後七月等。 Dai_Date(紀元積日數) Dai_Date(紀元年數, 特殊日期) 特殊日期: 元旦/除夕/空1/空2 Dai_Date(紀元年數, 0, 當年日序數) Dai_Date(紀元年數, 月, 日) 月: 1–12/閏/後6/後7 元旦: Dai_Date(year, 0) Dai_Date(year, '元旦') 當年日序 n: Dai_Date(year, 0, n) 空日(當年元旦之前的): Dai_Date(year, '空1日') Dai_Date(year, '空2日') Dai_Date(year, 0, -1) Dai_Date(year, 0, -2) 除夕(當年元旦之前的): Dai_Date(year, '除夕') 閏月: Dai_Date(year, '閏9', date) Dai_Date(year, '雙9', date) Dai_Date(year, '閏', date) 後六月: Dai_Date(year, '後6', date) 後七月: Dai_Date(year, '後7', date) 注意:由於傣曆元旦不固定在某月某日,因此同一年可能出現相同月分與日期的日子。例如傣曆1376年(公元2014年)就有兩個六月下五日。 為了維持獨一性,此處以"後六月"稱第二次出現的六月同日。 */ function Dai_Date(year, month, date, get_days) { if (isNaN(year = Dai_Date.to_valid_year(year))) return get_days ? NaN : new Date(NaN); var days = typeof date === 'string' && (date = date.trim()).match(/^(\D*)(\d+)/), is_leap; // 處理如「六月下一日」或「六月月下一日」即傣曆6月16日。 if (days) { date = days[2] | 0; if (/月?上/.test(days[1])) date += 15; } else date |= 0; if (typeof month === 'string') if (/^[閏雙後][9九]?月?$/.test(month)) month = 9, is_leap = true; else if (days = month.match(/^後([67])/)) month = days[1]; if (isNaN(month) || month < 1 || 12 < month) { // 確定元旦之前的空日數目。 days = Dai_Date.vacant_days(year - 1); switch (month) { case '空2日': // 若有空2日,其必為元旦前一日。 date--; case '空日': case '空1日': date -= days; case '除夕': date -= days + 1; } // 當作當年日序。 days = Dai_Date.new_year_days(year) + date | 0; } else { // 將 (month) 轉成月序: // 6月 → 0 // 7月 → 1 // ... // 12月 → 6 // 1月 → 7 if ((month -= 6) < 0 // 後6月, 後7月 || days) month += 12; // 處理應為年末之6月, 7月的情況。 if (month < 2 && 0 < date // 七月: 7/1 → 6/30, 7/2 → 6/31.. && (month === 0 ? date : 29 + date) < // Dai_Date.new_year_date_serial(year)) month += 12; days = Dai_Date.days_6_1(year) + date - 1 // + (month >> 1) * (29 + 30) | 0; if (month % 2 === 1) days += 29; if ((month > 3 || month === 3 && is_leap) // 處理閏月。 && Dai_Date.is_leap(year)) days += 30; if (month > 2 && Dai_Date.is_full8(year)) days++; } return get_days ? days : new Date(Dai_Date.epoch + days * ONE_DAY_LENGTH_VALUE); } // 適用範圍: 傣曆 0–103295 年 Dai_Date.to_valid_year = function(year, ignore_range) { if (false && year < 0) library_namespace.warn('Dai_Date.to_valid_year: 公式不適用於過小之年分:' + year); return !isNaN(year) && (ignore_range || // 一般情況 // -1e2 < year && year < 103296 // from new_year_date_serial() 0 <= year && (year < 2 || 714 <= year && year <= 3190) // ) && year == (year | 0) ? year | 0 : NaN; }; // 傣曆採十九年七閏法,平年有12個月,閏年有13個月。閏月固定在9月,所以閏年又稱為「雙九月」年 // 閏9月, 閏九月。 // 適用範圍: 傣曆 0– 年 Dai_Date.is_leap = function(year) { // 傣曆零年當年九月置閏月。 return year == 0 || // 攝 = (year + 1) % 19; (((7 * year | 0) - 6) % 19 + 19) % 19 < 7; }; // 當年日數。365 or 366. Dai_Date.year_days = function(year) { return Dai_Date.new_year_days(year + 1) - Dai_Date.new_year_days(year); }; // 當年空日數目。1 or 2. // 注意:這邊之年分,指的是當年除夕後,即明年(隔年)元旦之前的空日數目。與 Dai_Date() 不同! // e.g., Dai_Date.vacant_days(100) 指的是傣曆100年除夕後,即傣曆101年元旦之前的空日數目。 // 依 Dai_Date.date_of_days() 的做法,空日本身會被算在前一年內。 Dai_Date.vacant_days = function(year) { // 傣曆潑水節末日之元旦(新年的第一天)與隔年元旦間,一般為365日(有「宛腦」一天)或366日(有「宛腦」兩天)。 return Dai_Date.year_days(year) - 364; }; /* 傣历算法剖析 原法@曆法星卜要略, 傣曆中的紀元紀時法: x := year + 1 y := Floor[(year + 4)/9] z := Floor[(year - y)/3] r := Floor[(x - z)/2] R := year - r + 49049 S := Floor[(36525875 year + R)/100000] d := S + 1 Simplify[d] 1 + Floor[( 49049 + 36525876 year - Floor[1/2 (1 + year - Floor[1/3 (year - Floor[(4 + year)/9])])])/ 100000] 簡化法: x := year + 1 y := ((year + 4)/9) z := ((year - y)/3) r := ((x - z)/2) R := year - r + 49049 S := ((36525875 year + R)/100000) d := S + 1 Simplify[d] (1609723 + 394479457 year)/1080000 // test 簡化法 @ Javascript: for (var year = -1000000, days; year <= 1000000; year++) { if (CeL.Dai_Date.new_year_days(year) !== CeL.Dai_Date .new_year_days_original(year)) console.error('new_year_days: ' + year); var days = CeL.Dai_Date.new_year_days(year); if (CeL.Dai_Date.year_of_days(days) !== year || CeL.Dai_Date.year_of_days(days - 1) !== year - 1) console.error('year_of_days: ' + year); } // get: -976704 -803518 -630332 -523297 -350111 -176925 -69890 103296 276482 449668 556703 729889 903075 */ // 元旦紀元積日數, accumulated days // 原法@曆法星卜要略: Dai_Date.new_year_days_original = function(year) { return 1 + Math .floor((49049 + 36525876 * year - Math.floor((1 + year - Math .floor((year - Math.floor((4 + year) / 9)) / 3)) / 2)) / 100000); }; // 元旦紀元積日數, accumulated days // 簡化法:適用於 -69889–103295 年 Dai_Date.new_year_days = function(year, get_remainder) { // 防止 overflow。但效果相同。 // var v = 365 * year + 1 + (279457 * year + 529723) / 1080000, var v = (394479457 * year + 1609723) / 1080000 | 0, // f = Math.floor(v); // 餘數 return get_remainder ? v - f : f; }; // 簡化法:適用於 -3738–1000000 年 Dai_Date.year_of_days = function(days) { return Math.floor((1080000 * (days + 1) - 1609723) / 394479457) | 0; }; // 紀元積月數, accumulated month /* 原法@傣曆中的紀元紀時法: day = 元旦紀元積日數 b := 11 day + 633 c := Floor[(day + 7368)/8878] d := Floor[(b - c)/692] dd := day + d e := Floor[dd/30] f := Mod[dd, 30] Simplify[e] Simplify[f] e: Floor[1/30 (day + Floor[1/692 (633 + 11 day - Floor[(7368 + day)/8878])])] f: Mod[day + Floor[1/692 (633 + 11 day - Floor[(7368 + day)/8878])], 30] */ // cache var new_year_date_serial = [ 30 ]; // 元旦之當月日序基數 // d = 30–35: 7/(d-29) // others: 6/d Dai_Date.new_year_date_serial = function(year, days, ignore_year_limit) { if (year in new_year_date_serial) return new_year_date_serial[year]; if (isNaN(year = Dai_Date.to_valid_year(year, ignore_year_limit))) return NaN; // days: 元旦紀元積日數。 if (isNaN(days)) days = Dai_Date.new_year_days(year) | 0; else if (days < 0) library_namespace.warn('Dai_Date.new_year_date_serial: 輸入負數日數 [' + days + ']!'); // 參考用元旦之當月日序基數:常常須作調整。 var date = (days + // 小月補足日數 Math.floor((633 + 11 * days - Math.floor((7368 + days) / 8878)) / 692) // (date / 30 | 0) 是元旦所在月的紀元積月數 ) % 30 | 0; // 年初之6/1累積日數 var days_diff // 平年年初累積日數 = year * 354 // 閏月年初累積日數 = 30 * (年初累積閏月數 (7r-6)/19+1=(7r+13)/19) + 30 * (((7 * (year - 1) - 6) / 19) + 2 | 0) // 八月滿月年初累積日數。.194: 經手動測試,誤差=0 or 1日@部分0–1400年 + (.194 * year | 0) // 為傣曆紀元始於 7/1,而非 6/1;以及 date 由 6/1 而非 6/0 起始而調整。 - 30 // 至上方為年初之6/1累積日數,因此需要再加上元旦之當月日序基數,才是元旦紀元積日數。 + date // 計算兩者差距。 - days | 0; // assert: -31 < days_diff < 2 // for (var i = 0, j, y; i < 1200; i++) if ((j = CeL.Dai_Date.new_year_date_serial(i)) > 1 || j < -31) y = i; // 599 // for (var i = 1200, j, y; i < 103296; i++) if ((j = CeL.Dai_Date.new_year_date_serial(i)) > 1 || j < -31) throw i; // 3191 // return days_diff; if (false && library_namespace.is_debug(3) && !(-31 < days_diff && days_diff < 2)) library_namespace.warn('days_diff of ' + year + ': ' + days_diff); // 判斷 date 在 6月 或 7月:選擇與應有日數差距較小的。 if (Math.abs(days_diff) > Math.abs(days_diff + 30)) // 七月. 7/date0 → 6/30, 7/date1 → 6/31.. date += 30; // 微調:當前後年 6/1 間不是指定的日數時,應當前後移動一兩日。但據調查發現,除前一年是雙九月暨八月滿月外,毋須微調。 // 六月出一日與隔年六月出一日間,平年354天(八月小月)或355天(八月滿月),雙九月之年384天。 if (Dai_Date.is_leap(year - 1)) { var last_days = D