cejs
Version:
A JavaScript module framework that is simple to use.
1,929 lines (1,655 loc) • 283 kB
JavaScript
/**
* @name CeL function for era calendar.
* @fileoverview 本檔案包含了東亞傳統曆法/中國傳統曆法/曆書/歷譜/帝王紀年/年號紀年,農曆、夏曆、陰曆,中西曆/信史的日期轉換功能。<br />
* 以歷史上使用過的曆數為準。用意不在推導曆法,而在對過去時間作正確轉換。因此僅用查表法,不作繁複天文計算。
*
* @since 2013/2/13 12:45:44
*
* TODO:<br />
* 檢查前後相交的記年資料 每個月日數是否相同<br />
* 歲時記事 幾龍治水、幾日得辛、幾牛耕地、幾姑把蠶、蠶食幾葉
* http://mathematicsclass.blogspot.tw/2009/06/blog-post_17.html<br />
* bug: 西漢武帝後元2年<br />
* 太複雜了,效率不高,重構。<br />
* 舊唐書/卷11 <span data-era="~">寶應元年</span>..<span data-era="~">二年</span>..<span
* data-era="~">二月甲午</span>,回紇登裏可汗辭歸蕃。<span data-era="~">三月甲辰</span>朔,襄州右兵馬使梁崇義殺大將李昭,據城自固,仍授崇義襄州刺史、山南東道節度使。<span
* data-era="~">丁未</span>,袁傪破袁晁之眾於浙東。玄宗、肅宗歸祔山陵。自<span data-era="~">三月一日</span>廢朝,至於<span
* data-era="~">晦日</span>,百僚素服詣延英門通名起居。<br />
* CeL.era('二年春正月丁亥朔',{after:'寶應元年'})<br />
* CeL.era('丁亥朔',{after:'寶應二年春正月'})<br />
* CeL.era('寶應元年',{period_end:true})<br />
* CeL.era('嘉慶十六年二月二十四日寅刻')===CeL.era('嘉慶十六年二月二十四日寅時')<br />
* Period 獨立成 class<br />
* 南明紹宗隆武1年閏6月月干支!=甲申, should be 癸未<br />
* 月令別名 http://140.112.30.230/datemap/monthalias.php<br />
* 月の異称 http://www.geocities.jp/okugesan_com/yougo.html<br />
* 西周金文紀時術語. e.g., 初吉,既生霸,既望,既死霸
* (http://wywu.pixnet.net/blog/post/22412573-%E6%9C%88%E7%9B%B8%E8%A8%98%E6%97%A5%E8%A1%A8)
*
* 未來發展:<br />
* 加入世界各國的對應機能。<br />
* 加入 國旗
*
* @example <code>
// demo / feature: 較常用、吸引人的特性。
CeL.log('公曆農曆(陽曆陰曆)日期互換:');
var 農曆, 公曆;
// 公曆←農曆特定日期。
農曆 = '農曆2014年1月1日';
公曆 = CeL.era(農曆).format('公元%Y年%m月%d日');
CeL.log(['農曆: ', 農曆, ' → 公曆: ', 公曆]);
// 農曆←公曆特定日期。
公曆 = '公元2014年1月1日';
農曆 = CeL.era({date:公曆.to_Date(), era:'農曆'}).format({parser:'CE',format:'%紀年%年年%月月%日日',locale:'cmn-Hant-TW'});
CeL.log(['公曆: ', 公曆, ' → 農曆: ', 農曆]);
// 今天的農曆日期。
var 今天的農曆日期 = (new Date).format('Chinese');
CeL.log(['今天是農曆: ', 今天的農曆日期]);
今天的農曆日期 = CeL.era({date:new Date, era:'農曆'}).format({parser:'CE',format:'農曆%年(%歲次)年%月月%日日',locale:'cmn-Hant-TW'});
CeL.log(['今天是農曆: ', 今天的農曆日期]);
// 取得公元 415年, 中曆 三月 之 CE Date。
CeL.era.中曆('415年三月');
// CeL.era('') 相當於:
// https://docs.microsoft.com/zh-tw/dotnet/api/system.datetime.parse
// DateTime.Parse("")
// https://support.office.com/en-us/article/datevalue-function-df8b07d4-7761-4a93-bc33-b7471bbff252?omkt=en-US&ui=en-US&rs=en-US&ad=US
// Excel: =DATEVALUE("")
CeL.run('data.date.era', test_era_data);
// era.onload
CeL.env.era_data_load = function(country, queue) {
if (typeof country === 'object') {
// 第一次呼叫 callback。
// 在載入era模組之前設定好,可以用來篩選需要載入的國家。
// gettext_config:{"id":"china"}
queue.truncate().push('中國');
return;
}
// assert: 已載入 {String}country
CeL.log('era data of [' + country + '] loaded.');
// 判斷是否已載入所有曆數資料。
if (!queue) {
CeL.log('All era data loaded.');
// assert: CeL.era.loaded === true
}
};
function test_era_data() {
// 判斷是否已載入所有曆數資料。
if (!CeL.era.loaded) {
setTimeout(test_era_data, 80);
return;
}
}
// More examples: see /_test suite/test.js
// should be error: 清任何一個紀年無第一八八〇年。
'清一八八〇年四月二十一日七時'.to_Date('era').format({parser:'CE',format:'%歲次年%月干支月%日干支日%時干支時',locale:'cmn-Hant-TW'})
// should be error
'元一八八〇年四月二十一日七時'.to_Date('era').format({parser:'CE',format:'%歲次年%月干支月%日干支日%時干支時',locale:'cmn-Hant-TW'})
// ---------------------------------------
廢棄:
查找:某 era name → era data:
1st: [朝代 or 朝代兼紀年] from dynasty{*}
2ed: [朝代:君主(帝王) list] from dynasty{朝代:{*}}
3ed: [朝代君主(帝王):紀年 list] from dynasty{朝代:{君主(帝王):[]}}
查找:某日期 → era data:
1. get start date: 定 era_start_UTC 所有 day 或之前的 index。
2. get end date, refrence:
遍歷 era_end_UTC,處理所有(結束)日期於 day 之後的,即所有包含此日期的 data。
</code>
*/
'use strict';
// 'use asm';
// --------------------------------------------------------------------------------------------
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
name : 'data.date.era',
// data.code.compatibility. : for String.prototype.repeat(),
// String.prototype.trim()
// includes() @ data.code.compatibility.
require : 'data.code.compatibility.'
// data.native. : for Array.prototype.search_sorted()
+ '|data.native.'
// application.locale. : 中文數字
+ '|application.locale.'
// data.date. : 干支
+ '|data.date.String_to_Date'
// Maya 需要用到 data.date.calendar。
+ '|data.date.Julian_day|data.date.calendar.',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code,
// 設定不匯出的子函式。
// this is a sub module.
// 完全不 export 至 library_namespace.
no_extend : '*'
});
function module_code(library_namespace) {
// requiring
var String_to_Date = this.r('String_to_Date'), Julian_day = this
.r('Julian_day');
// ---------------------------------------------------------------------//
// basic constants. 定義基本常數。
// 工具函數。
function generate_pattern(pattern_source, delete_干支, flag) {
if (library_namespace.is_RegExp(pattern_source)) {
if (flag === undefined && ('flags' in pattern_source))
flag = pattern_source.flags;
pattern_source = pattern_source.source;
}
pattern_source = pattern_source
// 數字
.replace(/數/g, '(?:[' + library_namespace
// "有": e.g., 十有二月。
.positional_Chinese_numerals_digits + '百千]|[十廿卅]有?)');
if (delete_干支) {
pattern_source = pattern_source.replace(/干支\|/g, '');
} else {
pattern_source = pattern_source
// 天干
.replace(/干/g, '[' + library_namespace.STEM_LIST + ']')
// 地支
.replace(/支/g, '[' + library_namespace.BRANCH_LIST + ']');
}
return new RegExp(pattern_source, flag || '');
}
function to_list(string) {
if (typeof string === 'string') {
if (string.includes('|'))
string = string.split('|');
else if (string.includes(','))
string = string.split(',');
else
string = string.chars('');
}
return string;
}
var is_Date = library_namespace.is_Date,
/**
* 把第2個引數陣列添加到第1個引數陣列後面
*
* or try Array.prototype.splice()
*/
Array_push = Array.prototype.push.apply.bind(Array.prototype.push),
Date_to_String_parser = library_namespace.Date_to_String.parser,
//
strftime = Date_to_String_parser.strftime,
// 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),
/** {Number}一分鐘的 time 值(in milliseconds)。should be 60 * 1000 = 60000. */
ONE_MINUTE_LENGTH_VALUE = new Date(0, 0, 1, 0, 2) - new Date(0, 0, 1, 0, 1),
CE_REFORM_YEAR = library_namespace.Gregorian_reform_date.getFullYear(),
CE_COMMON_YEAR_LENGTH_VALUE
//
= new Date(2, 0, 1) - new Date(1, 0, 1),
//
CE_LEAP_YEAR_LENGTH_VALUE = CE_COMMON_YEAR_LENGTH_VALUE
+ ONE_DAY_LENGTH_VALUE,
//
CE_REFORM_YEAR_LENGTH_VALUE = library_namespace
//
.is_leap_year(CE_REFORM_YEAR, true) ? CE_LEAP_YEAR_LENGTH_VALUE
: CE_COMMON_YEAR_LENGTH_VALUE,
CE_COMMON_YEAR_DATA = Object.seal(library_namespace.get_month_days()),
//
CE_LEAP_YEAR_DATA = Object.seal(library_namespace.get_month_days(true)),
//
CE_REFORM_YEAR_DATA = library_namespace.get_month_days(CE_REFORM_YEAR),
// cache
gettext_date = library_namespace.gettext.date,
// 專門供搜尋各特殊紀年使用。
// @see create_era_search_pattern()
era_search_pattern, era_key_list,
// search_index[ {String}key : 朝代、君主(帝王)、帝王紀年、年號紀年、國家 ]
// = Set(對應之 era_list index list)
// = [ Set(對應之 era_list index list), 'key of search_index',
// 'key'..
// ]
search_index = Object.create(null),
// constant 常數。
// http://zh.wikipedia.org/wiki/Talk:%E8%BE%B2%E6%9B%86
// 將公元日時換算為夏曆日時,1929年1月1日以前,應將時間換為北京紫禁城(東經116.4度)實際時間,1929年1月1日開始,則使用東八區(東經120度)的標準時間。
DEFAULT_TIMEZONE = String_to_Date.zone.CST,
// http://zh.wikipedia.org/wiki/%E7%AF%80%E6%B0%A3
// 中氣持續日期/前後範疇。
中氣日_days = 3,
// 中氣發生於每月此日起 (中氣日_days - 1) 日間。
// assert: 在整個作業年代中,此中氣日皆有效。起碼須包含
// proleptic Gregorian calendar -1500 – 2100 CE。
中氣日 = [ 19, 18, 20, 19, 20, 20, 22, 22, 22, 22, 21, 20 ],
/** {Number}未發現之index。 const: 基本上與程式碼設計合一,僅表示名義,不可更改。(=== -1) */
NOT_FOUND = ''.indexOf('_'),
// 起始年月日。年月日 starts form 1.
// 基本上與程式碼設計合一,僅表示名義,不可更改。
START_YEAR = 1, START_MONTH = 1, START_DATE = 1,
// 閏月名前綴。
// 基本上與程式碼設計合一,僅表示名義,不可更改。
// TODO: 閏或潤均可
LEAP_MONTH_PREFIX = '閏',
// (年/月分資料=[年分各月資料/月分日數])[NAME_KEY]=[年/月分名稱]
NAME_KEY = 'name', LEAP_MONTH_KEY = 'leap',
// 月次,歲次
START_KEY = 'start',
// 起始日名/起始日碼/起始日期名
START_DATE_KEY = 'start date',
//
MONTH_NAME_KEY = 'month name',
//
MINUTE_OFFSET_KEY = 'minute offset',
COUNT_KEY = 'count',
// 亦用於春秋戰國時期"周諸侯國"分類
PERIOD_KEY = '時期',
//
PERIOD_PREFIX = 'period:',
//
PERIOD_PATTERN = new RegExp('^' + PERIOD_PREFIX + '(.+)$'),
// 日期連接號。 e.g., "–".
// 減號"-"與太多符號用途重疊,因此不是個好的選擇。
PERIOD_DASH = '–',
// set normal month count of a year.
// 月數12: 每年有12個月.
LUNISOLAR_MONTH_COUNT = 12,
// 可能出現的最大日期值。
MAX_DATE_NUMBER = 1e5,
// 二進位。
// 基本上與程式碼設計合一,僅表示名義,不可更改。
RADIX_2 = 2,
/**
* parseInt( , radix) 可處理之最大 radix, 與 Number.prototype.toString ( [ radix ] )
* 可用之最大基數 (radix, base)。<br />
* 10 Arabic numerals + 26 Latin alphabet.
*
* @inner
* @see <a href="http://en.wikipedia.org/wiki/Hexadecimal"
* accessdate="2013/9/8 17:26">Hexadecimal</a>
*/
PACK_RADIX = 10 + 26,
LEAP_MONTH_PADDING = new Array(
// 閏月會有 LUNISOLAR_MONTH_COUNT 個月 + 1個閏月 筆資料。
(LUNISOLAR_MONTH_COUNT + 1).toString(RADIX_2).length + 1).join(0),
// 每年月數資料的固定長度。
// 依當前實作法,最長可能為長度 4。
YEAR_CHUNK_SIZE = parseInt(
// 為了保持應有的長度,最前面加上 1。
'1' + new Array(LUNISOLAR_MONTH_COUNT).join(
// 農曆通常是大小月相間。
'110').slice(0, LUNISOLAR_MONTH_COUNT + 1)
// 13 個月可以二進位 1101 表現。
+ (LUNISOLAR_MONTH_COUNT + 1).toString(RADIX_2), RADIX_2)
//
.toString(PACK_RADIX).length,
PACKED_YEAR_CHUNK_PADDING = new Array(
// using String.prototype.repeat
YEAR_CHUNK_SIZE + 1).join(' '),
// 篩選出每年月數資料的 pattern。
CALENDAR_DATA_SPLIT_PATTERN = new RegExp('[\\s\\S]{1,'
// 或可使用: /[\s\S]{4}/g
+ YEAR_CHUNK_SIZE + '}', 'g'),
// date_data 0/1 設定。
// 農曆一個月是29日或30日。
// long month / short month
大月 = 30, 小月 = 29,
// length of the months
// 0:30, 1:29
// 注意:會影響到 parse_era()!
// 基本上與程式碼設計合一,僅表示名義,不可更改。
MONTH_DAYS = [ 大月, 小月 ],
// month length / month days: 公曆大月為31天。
// 僅表示名義,不可更改。
CE_MONTH_DAYS = 31,
// 所有所處理的曆法中,可能出現的每月最大日數。
// now it's CE.
MAX_MONTH_DAYS = CE_MONTH_DAYS,
MONTH_DAY_INDEX = Object.create(null),
// 辨識曆數項。
// 基本上與程式碼設計合一,不可更改。
// 見 extract_calendar_data()
// [ all, front, date_name, calendar_data, back ]
// 曆數_PATTERN =
// /(?:([;\t]|^))(.*?)=?([^;\t=]+)(?:([;\t]|$))/g,
//
// [ all, date_name, calendar_data, back ]
曆數_PATTERN = /(.*?)=?([^;\t=]+)([;\t]|$)/g,
// 以最快速度測出已壓縮曆數。
// 見 initialize_era_date()
已壓縮曆數_PATTERN = /^(?:[\d\/]*=)?[\da-z]{3}[\da-z ]*$/,
// matched: [ , is_閏月, 月序/月分號碼 ]
// TODO: 11冬月, 12臘月.
// TODO: [閏後]
MONTH_NAME_PATTERN = /^([閏闰])?([正元]|[01]?\d)月?$/,
干支_PATTERN = generate_pattern(/^干支$/),
年_SOURCE =
// 年分名稱。允許"嘉慶十八年癸酉"之類表"嘉慶十八年癸酉歲"。
/([前\-−‐]?\d{1,4}|干支|前?數{1,4}|元)[\/.\-年]\s*(?:(?:歲次)?干支\s*)?/
//
.source,
// 月分名稱。
月_SOURCE = /\s*([^\s\/.\-年月日]{1,20})[\/.\-月]/.source,
// 日期名稱。
日_SOURCE = /\s*初?(\d{1,2}|數{1,3}|[^\s日朔晦望]{1,5})日?/.source,
// 四季, 四時
季_LIST = to_list('春夏秋冬'),
// ⛱️,☀️
季_Unicode = to_list('🌱,😎,🍂,⛄'),
// 季名稱。e.g., 春正月
季_SOURCE = '[' + 季_LIST + ']?王?',
孟仲季_LIST = to_list('孟仲季'),
// see: numeralize_time()
時刻_PATTERN = generate_pattern(
// '(?:[早晚夜])'+
/(支)(?:時?\s*([初正])([初一二三123])刻|時)/),
// should matched: 月|年/|/日|月/日|/月/日|年/月/|年/月/日
// ^(年?/)?月/日|年/|/日|月$
// matched: [ , 年, 月, 日 ]
// TODO: 11冬月, 12臘月.
起始日碼_PATTERN =
// [日朔晦望]
/^(-?\d+|元)?[\/.\-年]([閏闰]?(?:[正元]|[01]?\d))[\/.\-月]?(?:(初?\d{1,2}?|[正元])日?)?$/
//
,
// e.g., 滿洲帝國, 中華民國
國_PATTERN = /^(.*[^民帝])國$/,
// [ , 名稱 ]
名稱加稱號_PATTERN = /^(.{2,})(?:天皇|皇后)$/,
// 取得/保存前置資訊。
前置_SOURCE = '^(.*?)',
// 取得/保存後置資訊。
後置_SOURCE = '(.*?)$',
// NOT: 測試是否全為數字,單純只有數字用。
// 測試是否為單一中文數字字元。
單數字_PATTERN = generate_pattern(/^數$/),
// 當前的 ERA_DATE_PATTERN 必須指明所求年/月/日,無法僅省略日。
// 否則遇到'吳大帝太元元年1月1日'之類的無法處理。
// 若有非數字,干支之年分名稱,需要重新設計!
// matched: [ , prefix, year, month, date, suffix ]
ERA_DATE_PATTERN = generate_pattern(前置_SOURCE + 年_SOURCE + 季_SOURCE
+ 月_SOURCE + 日_SOURCE + 後置_SOURCE),
// 減縮版 ERA_DATE_PATTERN: 省略日期,或亦省略月分。 ERA_DATE_PATTERN_NO_DATE
ERA_DATE_PATTERN_ERA_ONLY
// matched: [ , prefix, year, numeral month, month, suffix ]
= generate_pattern(前置_SOURCE + 年_SOURCE + 季_SOURCE
// 月分名稱。參考 (月_SOURCE)。
+ /\s*(?:([01]?\d)|([^\s\/.\-年月日]{1,20})月)?/.source + 後置_SOURCE),
// 減縮版 ERA_DATE_PATTERN: parse 年分 only。
// matched: [ , prefix, year, , , suffix ]
ERA_DATE_PATTERN_YEAR = generate_pattern(前置_SOURCE
// 年分名稱。
+ /([前\-−‐]?\d{1,4}|干支|前?數{1,4})[\/.\-年]?()()/.source + 後置_SOURCE),
// 用來測試如 "一八八〇"
POSITIONAL_DATE_NAME_PATTERN = new RegExp('^['
+ library_namespace.positional_Chinese_numerals_digits + ']{1,4}$'),
ERA_PATTERN =
//
/^([東西南北前後]?\S)(.{1,3}[祖宗皇帝王君公侯伯叔主子后])(.{0,8})(?:([一二三四五六七八九十]{1,3})年)?/
//
,
持續日數_PATTERN = /^\s*\+\d+\s*$/,
// [ 紀年曆數, 起始日期名, 所參照之紀年或國家 ]
參照_PATTERN = /^(?:(.*?)=)?:(.+)$/,
// 可指示尚存疑/爭議資料,例如傳說時代/神話之資料。
// https://en.wikipedia.org/wiki/Circa
// c., ca or ca. (also circ. or cca.), means "approximately" in
// several European languages including English, usually in
// reference to a date.
//
// r. can be used to designate the ruling period of a person in
// dynastic power, to distinguish from his or her lifespan.
準確程度_ENUM = {
// 資料尚存有爭議或疑點
疑 : '尚存疑',
// 為傳說時代/神話之資料
傳說 : '傳說時代'
},
主要索引名稱 = to_list('紀年,君主,朝代,國家'),
// 配合 parse_era() 與 get_next_era()。
// 因為須從範圍小的開始搜尋,因此範圍小的得排前面!
紀年名稱索引值 = {
era : 0,
紀年 : 0,
// gettext_config:{"id":"era-date"}
"年號" : 0,
// 紀年法: 日本年號, 元号/年号
元号 : 0,
// 稱號
// 統治者, 起事者, 國家最高領導人, 國家元首, 作亂/起辜/起義領導者, 民變領導人, 領袖, 首領
君主 : 1,
// monarch, ruler
ruler : 1,
// 君主姓名
君主名 : 1,
// 君主字,小字(乳名)
表字 : 1,
帝王 : 1,
總統 : 1,
// 天皇名
天皇 : 1,
// 自唐朝以後,廟號在前、諡號在後的連稱方式,構成已死帝王的全號。
// 唐朝以前的皇帝有廟號者較少,所以對歿世的皇帝一般簡稱諡號,如漢武帝、隋明帝,不稱廟號。唐朝以後,由於皇帝有廟號者佔絕大多數,所以多稱廟號,如唐太宗、宋太宗等。
// NG: 謚號
諡 : 1,
諱 : 1,
称号 : 1,
廟號 : 1,
// 尊號: 君主、后妃在世時的稱呼。不需避諱
// 尊號 : 1,
封號 : 1,
分期 : 1,
// for 琉球國
// 童名常有重複
// 童名 : 1,
神號 : 1,
君主性別 : 1,
// dynasty
朝代 : 2,
政權 : 2,
國號 : 2,
// 王國名
國名 : 2,
// 王朝, 王家, 帝國, Empire
// state 州
// Ancient Chinese states
// https://en.wikipedia.org/wiki/Ancient_Chinese_states
//
// 諸侯國名
諸侯國 : 2,
// 歷史時期 period. e.g., 魏晉南北朝, 五代十國
// period : 2,
時期 : 2,
// country
// e.g., 中國, 日本
國家 : 3
// territory 疆域
// nation
// 民族 : 3
// 地區, 區域. e,g, 中亞, 北亞, 東北亞
},
Period_屬性歸屬 = Object.assign({
// 君主出生日期
生 : 1,
// 君主逝世/死亡日期, 天子駕崩/諸侯薨
卒 : 1,
// 君主在位期間: 上任/退位, 執政,君主統治,統治,支配
在位 : 1
}, 紀年名稱索引值),
// era data refrence 對應
// sorted by: start Date 標準時間(如UTC+8) → parse_era() 插入順序.
/** {Array}按照起始時間排列的所有紀年列表 */
era_list = [],
// era tree.
// period_root[國家]
// = 次階層 Period
period_root = new Period,
// default date parser.
// 採用 'Chinese' 可 parse 日干支。
DEFAULT_DATE_PARSER = 'Chinese',
// 不使用 parser。
PASS_PARSER = [ 'PASS_PARSER' ],
// 標準時間分析器名稱(如公元)
// gettext_config:{"id":"common-era"}
standard_time_parser_name = '公元',
// 標準時間分析器(如公元紀年日期), 標準紀年時間
standard_time_parser = 'CE',
// default date format
// 基本上與程式碼設計合一,不可更改。
DATE_NAME_FORMAT = '%Y/%m/%d',
// pass to date formatter.
standard_time_format = {
parser : standard_time_parser,
format : DATE_NAME_FORMAT
}, standard_year_format = {
parser : standard_time_parser,
format : '%Y年'
},
// @see get_era_name(type)
// 基本上僅表示名義。若欲更改,需考慮對外部程式之相容性。
SEARCH_STRING = 'dynasty',
//
WITH_PERIOD = 'period', WITH_COUNTRY = 'country',
// 年名後綴
POSTFIX_年名稱 = '年',
// 十二生肖,或屬相。
// Chinese Zodiac
十二生肖_LIST = to_list('鼠牛虎兔龍蛇馬羊猴雞狗豬'),
// Chinese Zodiac in Unicode, 表情符號/圖畫文字/象形字
十二生肖圖像文字_LIST = to_list('🐁🐄🐅🐇🐉🐍🐎🐑🐒🐓🐕🐖'),
// 陰陽五行
// The Wu Xing, (五行 wŭ xíng) also known as the Five
// Elements, Five
// Phases, the Five Agents, the Five Movements, Five
// Processes, and
// the Five Steps/Stages
陰陽五行_LIST = to_list('木火土金水'),
// @see https://zh.wikipedia.org/wiki/%E5%8D%81%E4%BA%8C%E5%BE%8B
// 十二月律
// 黃鐘之月:十一月子月
// 蕤賓 or 蕤賔 http://sidneyluo.net/a/a05/016.htm 晉書 卷十六 ‧ 志第六 律歷上
月律_LIST = to_list('太簇,夾鐘,姑洗,仲呂,蕤賓,林鐘,夷則,南呂,無射,應鐘,黃鐘,大呂'),
// 各月の別名, 日本月名
// https://ja.wikipedia.org/wiki/%E6%97%A5%E6%9C%AC%E3%81%AE%E6%9A%A6#.E5.90.84.E6.9C.88.E3.81.AE.E5.88.A5.E5.90.8D
月の別名_LIST = to_list('睦月,如月,弥生,卯月,皐月,水無月,文月,葉月,長月,神無月,霜月,師走'),
// '大安赤口先勝友引先負仏滅'.match(/../g)
六曜_LIST = to_list('大安,赤口,先勝,友引,先負,仏滅'),
// 七曜, 曜日. ㈪-㈰: ㈰㈪㈫㈬㈭㈮㈯. ㊊-㊐: ㊐㊊㊋㊌㊍㊎㊏
七曜_LIST = to_list('日月火水木金土'),
// "十二值位星"(十二值:建除十二神,十二值位:十二建星) @ 「通勝」或農民曆
// 建、除、滿、平、定、執、破、危、成、收、開、閉。
// http://jerry100630902.pixnet.net/blog/post/333011570-%E8%AA%8D%E8%AD%98%E4%BD%A0%E7%9A%84%E5%A2%83%E7%95%8C~-%E9%99%BD%E6%9B%86%E3%80%81%E9%99%B0%E6%9B%86%E3%80%81%E9%99%B0%E9%99%BD%E5%90%88%E6%9B%86---%E7%AF%80%E6%B0%A3-
// 十二建星每月兩「建」,即正月建寅、二月建卯、三月建辰……,依此類推。正月為寅月,所以六寅日(甲寅、丙寅、戊寅、庚寅、壬寅)中必須有兩個寅日和「建」遇到一起;二月為卯月,所以六卯日(乙卯、丁卯、己卯、辛卯、癸卯)中必須有兩個卯日和「建」遇到一起,否則就不對。逢節(立春、驚蜇、清明、立夏、芒種、小暑、立秋、白魯、寒露、立冬、大雪、小寒)兩個建星相重,這樣才能保證本月第一個與月支相同之日與「建」相遇。
十二直_LIST = to_list('建除満平定執破危成納開閉'),
// "廿八星宿" @ 農民曆: 東青龍7北玄武7西白虎7南朱雀7
// It will be splitted later.
// jp:角亢氐房心尾箕斗牛女虚危室壁奎婁胃昴畢觜参井鬼柳星張翼軫
// diff: 虚, 参
// 因始於中國,採中國字。
二十八宿_LIST = '角亢氐房心尾箕斗牛女虛危室壁奎婁胃昴畢觜參井鬼柳星張翼軫',
// 二十八宿にあり二十七宿にはない宿は、牛宿である。
// It will be splitted and modified later.
二十七宿_LIST = 二十八宿_LIST.replace(/牛/, ''),
// 旧暦(太陽太陰暦)における月日がわかれば、自動的に二十七宿が決定される。
// 各月の朔日の宿
二十七宿_offset = to_list('室奎胃畢參鬼張角氐心斗虛'),
// 六十甲子納音 / 納音五行
// 《三命通會》《論納音取象》
// http://ctext.org/wiki.pl?if=gb&chapter=212352
納音_LIST = to_list('海中,爐中,大林,路旁,劍鋒,山頭,澗下,城頭,白蠟,楊柳,井泉,屋上,霹靂,松柏,長流,'
// 0 – 59 干支序轉納音: 納音_LIST[index / 2 | 0]; '/2': 0,1→0; 2,3→1; ...
+ '砂中,山下,平地,壁上,金泊,覆燈,天河,大驛,釵釧,桑柘,大溪,沙中,天上,石榴,大海'),
// It will be splitted later.
九星_LIST = '一白水星,二黑土星,三碧木星,四綠木星,五黃土星,六白金星,七赤金星,八白土星,九紫火星',
// '一白水星,二黒土星,三碧木星,四緑木星,五黄土星,六白金星,七赤金星,八白土星,九紫火星'
九星_JP_LIST = to_list(九星_LIST.replace(/黑/, '黒').replace(/綠/, '緑').replace(
/黃/, '黄'));
// ---------------------------------------------------------------------//
// 初始調整並規範基本常數。
九星_LIST = to_list(九星_LIST);
(function() {
var a = [ 2, 1 ];
Array_push(a, [ 4, 3 ]);
if (a.join(',') !== "2,1,4,3")
Array_push = function(array, list) {
return Array.prototype.push.apply(array, list);
};
a = library_namespace.Gregorian_reform_date;
a = [ a.getFullYear(), a.getMonth() + 1, a.getDate() ];
if (CE_REFORM_YEAR_LENGTH_VALUE > CE_COMMON_YEAR_LENGTH_VALUE
&& a[1] < 3 && library_namespace
//
.is_different_leap_year(a[0], true))
CE_REFORM_YEAR_LENGTH_VALUE = CE_COMMON_YEAR_LENGTH_VALUE,
CE_REFORM_YEAR_DATA[1]--;
var d = library_namespace.Julian_shift_days(a);
CE_REFORM_YEAR_LENGTH_VALUE += d * ONE_DAY_LENGTH_VALUE;
CE_REFORM_YEAR_DATA[a[1] - 1] += d;
// TODO: 無法處理 1582/10/15-30!!
Object.seal(CE_REFORM_YEAR_DATA);
二十八宿_LIST = to_list(二十八宿_LIST);
to_list('蛟龍貉兔狐虎豹獬牛蝠鼠燕豬貐狼狗雉雞烏猴猿犴羊獐馬鹿蛇蚓')
// https://zh.wikisource.org/wiki/演禽通纂_(四庫全書本)/全覽
// 角木蛟〈蛇父雉母細頸上白嬰四脚〉亢金龍 氐土狢
// 房日兎 心月狐 尾火虎〈為暗禽〉
// 箕水豹〈為暗禽〉 斗木獬 牛金牛
// 女土蝠 虚日䑕 危月燕
// 室火猪 壁水㺄 奎木狼
// 婁金狗 胃土雉 昴日雞〈為明禽〉
// 畢月烏 嘴火猴 參水猿
// 井木犴 鬼金羊 柳土獐
// 星日馬 張月鹿 翼火蛇
// 軫水蚓
.forEach(function(動物, index) {
二十八宿_LIST[index]
// starts from '角木蛟'
+= 七曜_LIST[(index + 4) % 七曜_LIST.length] + 動物;
});
a = 二十七宿_offset;
// d = 二十七宿_LIST.length;
// 二十七宿_offset[m] 可得到 m月之 offset。
二十七宿_offset = new Array(START_MONTH);
a.forEach(function(first) {
二十七宿_offset.push(二十七宿_LIST.indexOf(first) - START_DATE);
});
二十七宿_LIST = to_list(二十七宿_LIST);
// 為納音配上五行。
if (false) {
'金火木土金火水土金木水土火木水金火木土金火水土金木水土火木水'
// 六十甲子納音 / 納音五行
.replace(/(.)/g, function($0, 五行) {
var index = '火木水土金'.indexOf(五行);
return index === -1 ? $0 : index;
});
// "401340234123012401340234123012"
// 401340234123012
// 401 340 234 123 012
// 456 345 234 123 012
}
a = '火木水土金';
a += a;
for (d = 納音_LIST.length; d-- > 0;)
// "% 15": 30個 → 15個 loop : 0 – 14
納音_LIST[d] += a.charAt(4 - ((d % 15) / 3 | 0) + (d % 3));
})();
if (false)
// assert: this is already done.
主要索引名稱.forEach(function(name, index) {
紀年名稱索引值[name] = index;
});
// 預設國家。
// parse_era.default_country = '中國';
// clone MONTH_DAYS
parse_era.days = [];
parse_era.chunk_size = YEAR_CHUNK_SIZE;
MONTH_DAYS.forEach(function(days, index) {
MONTH_DAY_INDEX[days] = index;
parse_era.days.push(days);
});
// ---------------------------------------------------------------------//
// private tool functions. 工具函數
// search_index 處理。search_index public interface。
// TODO: 增加效率。
// search_index 必須允許以 ({String}key in search_index)
// 的方式來偵測是否具有此 key。
function for_each_era_of_key(key, operator, queue) {
// 預防循環參照用。
function not_in_queue(key) {
if (!queue.has(key)) {
queue.add(key);
return true;
}
library_namespace.debug('skip [' + eras[i] + ']. (queue: ['
+ queue.values() + '])', 1, 'for_each_era_of_key');
}
var eras = search_index[key],
//
for_single = function(era) {
if (not_in_queue(era))
operator(era);
};
if (!library_namespace.is_Set(queue))
queue = new Set;
// assert: queue is Set.
// era: Era, Set, []
if (Array.isArray(eras)) {
eras[0].forEach(for_single);
for (var i = 1; i < eras.length; i++)
if (not_in_queue(eras[i]))
for_each_era_of_key(eras[i], operator, queue);
} else
eras.forEach(for_single);
}
// bug: 當擅自改變子紀年時,將因 cache 而無法得到正確的 size。
function era_count_of_key(key, queue) {
var eras = search_index[key],
//
size = ('size' in eras) && eras.size;
if (!size && Array.isArray(eras)) {
size = eras[0].size;
if (Array.isArray(queue)) {
if (queue.includes(key)) {
library_namespace.debug(
// 將造成之後遇到此 key 時,使 for_each_era_of_key() 不斷循環參照。
'別名設定存在循環參照!您應該改正別名設定: ' + queue.join('→') + '→' + key, 1,
'era_count_of_key');
return 0;
}
queue.push(key);
} else
queue = [ key ];
for (var i = 1; i < eras.length; i++)
size += era_count_of_key(eras[i], queue);
eras.size = size;
}
return size;
}
// 取得以 key 登錄之所有 era。
// get era Set of {String}key
function get_era_Set_of_key(key, no_expand) {
var eras = search_index[key];
if (Array.isArray(eras)) {
if (no_expand)
// eras[0]: 所有僅包含 key 的 era Set。
return eras[0];
if (eras.cache)
eras = eras.cache;
else {
var i = 1, length = eras.length,
// 不動到 search_index
set = library_namespace.Set_from_Array(eras[0]);
for (; i < length; i++)
for_each_era_of_key(eras[i], function(era) {
// console.log(String(era));
set.add(era);
});
eras.cache = set;
eras = set;
}
}
return eras;
}
// 為取得單一 era。否則應用 to_era_Date()。
function get_era(name) {
if (name instanceof Era) {
return name;
}
var list = search_index[name];
if (!list) {
return;
}
if (Array.isArray(list)) {
// assert: list = [ {Set}, {String}alias ]
if (list.length === 1) {
list = list[0];
} else if (list.length === 2 && library_namespace.is_Set(list[0])
&& list[0].size === 0) {
return get_era(list[1]);
} else {
return;
}
}
var era;
if (library_namespace.is_Set(list)) {
if (list.size !== 1)
return;
list.forEach(function(_era) {
era = _era;
});
} else {
// list is {Era}
era = list;
}
return era;
}
// 登錄 key,使 search_index[key] 可以找到 era。
// 可處理重複 key 之情況,而不覆蓋原有值。
function add_to_era_by_key(key, era) {
if (!key || !era || key === era)
return;
var eras = search_index[key];
if (!eras)
// 初始化 search_index[key]。
if (typeof era !== 'string') {
// search_index[]: Set, [原生 Set, alias String 1,
// alias
// String 2, ..]
(search_index[key] = eras = library_namespace
.Set_from_Array(Array.isArray(era)
// era: Era, string, []
? era : [ era ])).origin = true;
return;
} else
(search_index[key] = eras = new Set).origin = true;
if (era instanceof Era) {
if (Array.isArray(eras)) {
// .size, .cache 已經不準。
delete eras.size;
delete eras.cache;
// 添加在原生 Set: 名稱本身即為此 key。
eras = eras[0];
}
eras.add(era);
// else assert: typeof era==='string'
} else if (Array.isArray(eras)) {
eras.push(era);
// .size, .cache 已經不準。
delete eras.size;
delete eras.cache;
} else
(search_index[key] = eras = [ eras, era ]).origin = true;
}
function append_period(object, name) {
var start = object.start,
// 處理精度
format = object.精 === '年' ? standard_year_format : standard_time_format;
name.push(' (', (is_Date(start) ? start : new Date(start))
.format(format),
// @see CeL.date.parse_period.PATTERN
// [\-–~-—─~〜﹣至]
'~', new Date(object.end
// 向前一天以取得最後一日。
// 並非萬全之法?
- ONE_DAY_LENGTH_VALUE).format(format), ')');
}
// ---------------------------------------------------------------------//
// bar 工具函數。
// TODO: comparator()
// sorted_array: sorted by .[start_key]
function order_bar(sorted_array, start_key, end_key, comparator) {
if (sorted_array.length === 0)
return [];
if (!start_key)
// .start
start_key = 'start';
if (!end_key)
// .end
end_key = 'end';
var bars = [], all_end = -Infinity,
// 置入最後欲回傳的階層。
layer = [ bars ];
function settle(do_reset) {
// clear. 結清。
// 寫入/紀錄階層序數。
if (bars.length > 1) {
// sort 前一區間。
// TODO: 若有接續前後者的,酌加權重。
bars.sort(function(a, b) {
// 大→小。
return b.weight - a.weight;
});
if (do_reset)
layer.push(bars = []);
}
}
sorted_array.forEach(function(object) {
var bar,
//
start = object[start_key], end = object[end_key];
if (start < all_end) {
// 有重疊。
if (bars.length === 1 && bars[0].length > 1) {
// 先結清一下前面沒重疊的部分,只擠出最後一個元素。
// bars : last bar
bars = bars[0];
// bar : 最後一個元素。
bar = bars.pop();
bars.end = bars.at(-1).end;
bars.weight -= bar.end - bar.start;
// 重建新的 bar。
(bars = [ bar ]).weight -= bar.end - bar.start;
bars.end = bar.end;
// 置入最後欲回傳的階層。
layer.push(bars = [ bars ]);
// reset
bar = null;
}
// 取 bar 之 end 最接近 object.start 者。
var
// 最接近間距。
closest_gap = Infinity,
// 最接近之 bar index。
closest_index = undefined;
bars.forEach(function(bar, i) {
var gap = start - bar.end;
if (gap === 0 || 0 < gap && (
// TODO: comparator()
closest_index === undefined
//
|| gap < end - start ? gap < closest_gap
// 當 gap 極大時,取不同策略。
: bar.end - bar.start - gap
//
< bars[closest_index].end - bars[closest_index].start
//
- closest_gap)) {
closest_gap = gap;
closest_index = i;
}
});
if (closest_index !== undefined)
bar = bars[closest_index];
} else {
settle(true);
bar = bars[0];
}
// start = 本 object 之 weight。
start = end - start;
// 將本 object 加入 bars 中。
if (bar) {
bar.push(object);
bar.weight += start;
} else {
// 初始化。
bars.push(bar = [ object ]);
bar.weight = start;
}
bar.end = end;
if (all_end < end)
all_end = end;
});
settle();
layer[start_key] = sorted_array[0][start_key];
layer[end_key] = all_end;
return layer;
}
// TODO: comparator
// sorted_array: sorted by .[start_key]
function order_bar_another_type(sorted_array, start_key, end_key) {
if (sorted_array.length === 0)
return [];
if (!start_key)
start_key = 'start';
if (!end_key)
end_key = 'end';
var bars = [], all_end = -Infinity,
// 最後欲回傳的階層。
layer = [ [] ];
function settle() {
if (bars.length > 0) {
// clear. 結清。
// 寫入/紀錄階層序數。
var layer_now;
if (bars.length === 1) {
layer_now = layer[0];
bars[0].forEach(function(object) {
layer_now.push(object);
});
} else {
// sort 前一區間。
// TODO: 若有接續前後者的,酌加權重。
bars.sort(function(a, b) {
// 大→小。
return b.weight - a.weight;
});
bars.forEach(function(bar, i) {
layer_now = layer[i];
if (!layer_now)
layer_now = layer[i] = [];
bar.forEach(function(object) {
layer_now.push(object);
});
});
}
// reset
bars = [];
}
}
sorted_array.forEach(function(object) {
var bar,
//
start = object[start_key], end = object[end_key];
if (start < all_end) {
// 有重疊。
// 取 bar 之 end 最接近 object.start 者。
var
// 最接近間距。
closest_gap = Infinity,
// 最接近之 bar index。
closest_index = undefined;
bars.forEach(function(bar, i) {
var gap = start - bar.end;
if (gap < closest_gap && 0 <= gap) {
closest_gap = gap;
closest_index = i;
}
});
if (closest_index !== undefined)
bar = bars[closest_index];
} else
settle();
// start = 本 object 之 weight。
start = end - start;
// 將本 object 加入 bars 中。
if (bar) {
bar.push(object);
bar.weight += start;
} else {
// 初始化。
bars.push(bar = [ object ]);
bar.weight = start;
}
bar.end = end;
if (all_end < end)
all_end = end;
});
settle();
return layer;
}
// ---------------------------------------------------------------------//
// 時期/時段 class。
function Period(start, end) {
// {Integer}
this.start = start;
// {Integer}
this.end = end;
// this.sub[sub Period name] = sub Period
this.sub = Object.create(null);
// 屬性值 attributes
// e.g., this.attributes[君主名] = {String}君主名
this.attributes = Object.create(null);
// .name, .parent, .level: see Period.prototype.add_sub()
// 階層序數: 0, 1, 2..
// see get_periods()
// this.bar = [ [], [], ..];
}
Period.is_Period = function(value) {
return value.constructor === Period;
};
Period.prototype.add_sub = function(start, end, name) {
var sub;
if (typeof start === 'object' && start.start) {
sub = start;
name = end;
} else
sub = new Period(start, end);
if (!name)
name = sub.name;
// 若子 period/era 之時間範圍於原 period (this) 外,
// 則擴張原 period 之時間範圍,以包含本 period/era。
if (!(this.start <= sub.start))
this.start = sub.start;
if (!(sub.end <= this.end))
this.end = sub.end;
this.sub[name] = sub;
// {String}
sub.name = name;
sub.parent = this;
sub.level = (this.level | 0) + 1;
// return this;
return sub;
};
Period.prototype.toString = function(type) {
var name = this.name;
if (!name)
name = '[class Period]';
else if (type === WITH_PERIOD) {
append_period(this, name = [ name ]);
name = name.join('');
}
return name;
};
// ---------------------------------------------------------------------//
// 處理農曆之工具函數。
/**
* 正規化名稱,盡量將中文數字、漢字數字轉為阿拉伯數字。
*
* @param {String}number_String
* 中文數字。
*
* @returns {String}數字化名稱
*/
function normalize_number(number_String) {
number_String = String(number_String).trim()
//
.replace(/([十廿卅])有/g, '$1')
// ㋀㋁㋂㋃㋄㋅㋆㋇㋈㋉㋊㋋
.replace(/[㋀-㋋]/g, function($0) {
return ($0.charCodeAt(0) - START_INDEX_0月) + '月';
})
// ㏠㏡㏢㏣㏤㏥㏦㏧㏨㏩㏪㏫㏬㏭㏮㏯㏰㏱㏲㏳㏴㏵㏶㏷㏸㏹㏺㏻㏼㏽㏾
.replace(/[㏠-㏾]/g, function($0) {
return ($0.charCodeAt(0) - START_INDEX_0日) + '日';
});
return library_namespace.Chinese_numerals_Formal_to_Normal(
// "有": e.g., 十有二月。
library_namespace.normalize_Chinese_numeral(number_String));
}
// 處理 square symbols
// http://unicode.org/cldr/utility/list-unicodeset.jsp?a=[%E3%8B%80-%E3%8B%8B%E3%8F%A0-%E3%8F%BE%E3%8D%98-%E3%8D%B0]
var START_INDEX_0月 = '㋀'.charCodeAt(0) - 1, START_INDEX_0日 = '㏠'
.charCodeAt(0) - 1, START_INDEX_0時 = '㍘'.charCodeAt(0);
/**
* 正規化日期名稱,盡量將中文數字、漢字數字轉為阿拉伯數字。
*
* @param {String}number_String
* 中文數字年/月/日。
*
* @returns {String}數字化日期名稱
*/
function numeralize_date_name(number_String, no_alias) {
if (!number_String)
return number_String === 0 ? 0 : '';
number_String = String(number_String).trim();
// 處理元年, [閏闰]?[正元]月, 初日
if (!no_alias)
number_String = number_String.replace(/^初/, '')
// 初吉即陰曆初一朔日。
.replace(/[正元吉]$/, 1)
// TODO: 統整月令別名。
.replace(/冬$/, 10).replace(/臘$/, 11)
// e.g., '前104' (年) → -104
.replace(/^前/, '-');
else if (/正$/.test(number_String))
// 最起碼得把會當作數字的處理掉。
return number_String;
return POSITIONAL_DATE_NAME_PATTERN.test(number_String)
//
? library_namespace.from_positional_Chinese_numeral(number_String)
//
: library_namespace.from_Chinese_numeral(number_String);
}
/**
* 正規化時間名稱,盡量將中文數字、漢字數字轉為阿拉伯數字。
*
* 至順治二年(公元1645年)頒行時憲曆後,改為日96刻,每時辰八刻(初初刻、初一刻、初二刻、初三刻、正初刻、正一刻、正二刻、正三刻)。自此每刻15分,無「四刻」之名。
*
* @param {String}time_String
* 中文數字時間。
*
* @returns {String}數字化時間名稱
*/
function numeralize_time(time_String) {
time_String = String(time_String).trim()
// 時刻 to hour
.replace(時刻_PATTERN, function($0, 時, 初正, 刻) {
return (2
//
* library_namespace.BRANCH_LIST.indexOf(時)
//
- (初正 === '初' ? 1 : 0)) + '時'
//
+ (刻 && (刻 = isNaN(刻)
//
? '初一二三'.indexOf(刻) : +刻) ? 15 * 刻 + '分' : '');
});
// ㍘㍙㍚㍛㍜㍝㍞㍟㍠㍡㍢㍣㍤㍥㍦㍧㍨㍩㍪㍫㍬㍭㍮㍯㍰
time_String.replace(/[㍘-㍰]/g, function($0) {
return ($0.charCodeAt(0) - START_INDEX_0時) + '時';
});
return time_String;
}
/**
* 檢查是否可能是日數。
*
* @param {String}string
* 欲檢查之字串。
*
* @returns {Boolean}可能是日數。
*/
function maybe_month_days(string) {
// 因為得考慮月中起始的情況,因此只檢查是否小於最大可能之日數。
return string <= MAX_MONTH_DAYS;
}
// 解壓縮日數 data 片段。
function extract_calendar_slice(calendar_data_String, date_name, 閏月名) {
if (maybe_month_days(calendar_data_String))
return [ date_name, calendar_data_String ];
var calendar_data = calendar_data_String
// TODO: 除此 .split() 之外,盡量不動到這些過於龐大的資料…戯言。
// http://jsperf.com/chunk-vs-slice
// JavaScript 中 split 固定長度比 .slice() 慢。
.match(CALENDAR_DATA_SPLIT_PATTERN),
//
calendar_data_Array = [], initial_month = date_name || '';
if (initial_month.includes('/')) {
initial_month = initial_month.split('/');
// 須考慮特殊情況。
if (initial_month.length === 2 && !initial_month[0])
// e.g., '/2': should be 1/1/2.
initial_month = null;
else
// 一般情況。 e.g., 2/3/4, 2/3
initial_month = initial_month[1];
}
// assert: initial_month && typeof initial_month === 'string'
if (calendar_data.length === 0) {
library_namespace.error('extract_calendar_slice: 無法辨識日數資料 ['
+ calendar_data_String + ']!');
return [ date_name, calendar_data_String ];
}
calendar_data.forEach(function(year_data) {
year_data = parseInt(year_data, PACK_RADIX).toString(RADIX_2)
.slice(1);
var year_data_Array = [], leap_month_index, leap_month_index_list;
// LUNISOLAR_MONTH_COUNT 個月 + 1個閏月 = 13。
while (year_data.length > LUNISOLAR_MONTH_COUNT + 1) {
leap_month_index = parseInt(
// 閏月的部分以 4 (LEAP_MONTH_PADDING.length) 個二進位數字指示。
year_data.slice(-LEAP_MONTH_PADDING.length), RADIX_2);
year_data = year_data.slice(0, -LEAP_MONTH_PADDING.length);
if (leap_month_index_list) {
library_namespace
.error('extract_calendar_slice: 本年有超過1個閏月!');
leap_month_index_list.unshift(leap_month_index);
} else
leap_month_index_list = [ leap_month_index ];
}
leap_month_index
// assert: 由小至大。
= leap_month_index_list
// 僅取最小的 1個閏月。
&& leap_month_index_list.sort()[0];
if (initial_month
// && initial_month != START_MONTH
) {
if (閏月名)
// 正規化閏月名。
initial_month = initial_month.replace(閏月名,
LEAP_MONTH_PREFIX);
if (initial_month === LEAP_MONTH_PREFIX)
initial_month += leap_month_index;
if (initial_month = initial_month.match(MONTH_NAME_PATTERN)) {
if (initial_month[1]
//
|| leap_month_index < initial_month[2]) {
if (initial_month[1]) {
if (initial_month[2] != leap_month_index)
library_namespace
.error('extract_calendar_slice: 起始閏月次['
+ initial_month[2]
+ '] != 日數資料定義之閏月次['
+ leap_month_index + ']!');
// 由於已經在起頭設定閏月或之後起始,
// 因此再加上閏月的指示詞,會造成重複。
leap_month_index = null;
}
// 閏月或之後起始,須多截1個。
initial_month[2]++;
}
initial_month = initial_month[2] - START_MONTH;
if (!(0 <= (leap_month_index -= initial_month)))
leap_month_index = null;
// 若有起始月分,則會 truncate 到起始月分。
// 注意:閏月之 index 是 padding 前之資料。
year_data = year_data.slice(initial_month);
// 僅能使用一次。
initial_month = null;
}
}
year_data = to_list(year_data);
year_data.forEach(function(month_days) {
year_data_Array.push(
//
(leap_month_index === year_data_Array.length
//
? LEAP_MONTH_PREFIX + '=' : '') + MONTH_DAYS[month_days]);
});
calendar_data_Array.push(year_data_Array
.join(pack_era.month_separator));
});
return [ date_name, calendar_data_Array.join(pack_era.year_separator) ];
}
// 解壓縮日數 data。
function extract_calendar_data(calendar_data, era) {
return calendar_data.replace(曆數_PATTERN,
// replace slice
function(all, date_name, calendar_data, back) {
calendar_data = extract_calendar_slice(calendar_data, date_name,
era && era.閏月名);
return (calendar_data[0] ? calendar_data.join('=')
: calendar_data[1])
+ back;
});
}
// date_Array = [ 年, 月, 日 ]
function numeralize_date_format(date_Array, numeral) {
return [ gettext_date.year(date_Array[0], numeral),
gettext_date.month(date_Array[1], numeral),
gettext_date.date(date_Array[2], numeral) ];
}
function split_era_name(name) {
if (name = name.trim().match(ERA_PATTERN))
return {
朝代 : name[1],
君主 : name[2],
// 紀年/其他
紀年 : name[3],
// 日期名稱
日期 : name[4]
};
}
// ---------------------------------------------------------------------//
// 紀年 class。
function Era(properties, previous) {
for ( var property in properties)
this[property] = properties[property];
}
// 當紀年名稱以這些字元結尾時,接上日期(年月日)時就會多添加上空格。
// ": "Casper", include [[en:Thai (Unicode block)]]
var NEED_SPLIT_CHARS = /a-zA-Z\d\-,'"\u0E00-\u0E7F/.source,
//
NEED_SPLIT_PREFIX = new RegExp(
//
'^[' + NEED_SPLIT_CHARS + ']'),
//
NEED_SPLIT_POSTFIX = new RegExp(
//
'[' + NEED_SPLIT_CHARS.replace('\\d', '') + ']$'),
//
REDUCE_PATTERN = new RegExp('([^' + NEED_SPLIT_CHARS + ']) ([^'
+ NEED_SPLIT_CHARS.replace('\\d', '') + '])', 'g');
// 把紀年名稱與日期連接起來,並且在有需要的時候添加上空格。
// 警告: 會改變 name_with_date_Array!
// @return {String}
function concat_era_name(name_with_date_Array) {
name_with_date_Array.forEach(function(slice, index) {
var _slice = String(slice).trim();
if (index > 0 && NEED_SPLIT_PREFIX.test(_slice)
//
&& NEED_SPLIT_POSTFIX.test(name_with_date_Array[index - 1])) {
// 為需要以 space 間隔之紀元名添加 space。
_slice = ' ' + _slice;
}
if (_slice !== slice)
name_with_date_Array[index] = _slice;
});
return name_with_date_Array.join('');
}
// remove needless space in the era name
function reduce_era_name(name) {
return name.trim()
// 去除不需要以 space 間隔之紀元名中之 space。
.replace(REDUCE_PATTERN, '$1$2');
}
// <a
// href="http://big5.huaxia.com/zhwh/wszs/2009/12/1670026.html"
// accessdate="2013/5/2 19:46">《中國歷史紀年表》解惑</a>
// 謚號紀年的方法是:國號——帝王謚號——年號(無年號者不用)——年序號,如漢惠帝五年,梁武帝大同八年。
// 自唐朝開始,改紀年方式為國號——帝王廟號——年號——年序號。如唐高宗永徽四年,清世宗雍正八年等。
function get_era_name(type) {
var name = this.name;
if (type === SEARCH_STRING)
// 搜尋時,紀年顯示方法:"紀年 (朝代君主(帝王), 國家)"
// e.g., "元始 (西漢平帝劉衍, 中國)"
return name[紀年名稱索引值.紀年] + ' (' + (name[紀年名稱索引值.朝代] || '')
+ (name[紀年名稱索引值.君主] || '') + ', ' + name[紀年名稱索引值.國家] + ')';
if (!name.cache) {
// 基本上不加國家名稱。
// name → [ 朝代, 君主, 紀年 ]
name = name.slice(0, 3).reverse();
// 對重複的名稱作適當簡略調整。
if (name[0] && name[0].includes(name[2])
//
|| name[1] && name[1].includes(name[2]))
name[2] = '';
if (name[1]) {
// 處理如周諸侯國之類。
// 例如 魯國/魯昭公 → 魯昭公
var matched = name[0].match(國_PATTERN);
if (name[1].startsWith(matched ? matched[1] : name[0]))
name[0] = '';
}
if (type === WITH_PERIOD)
append_period(this, name);
this.name.cache = reduce_era_name(name.join(' '));
name = this.name;
}
return type === WITH_COUNTRY ? [ this.name[紀年名稱索引值.國家], name.cache ]
: name.cache;
}
// ---------------------------------------
// 月次,歲次或名稱與序號 (index) 之互換。
// 歲序(index: start from 0)
// →歲次(ordinal/serial/NO № #序數: start with START_YEAR)
// →歲名(name)
function year_index_to_name(歲序) {
var 歲名 = this.calendar[NAME_KEY];
if (!歲名 || !(歲名 = 歲名[歲序])) {
歲名 = 歲序 + (START_KEY in this.calendar
//
? this.calendar[START_KEY] : START_YEAR);
if (this.skip_year_0 && 歲名 >= 0)
歲名++;
}
return 歲名;
}
// (歲名 name→)
// 歲次(ordinal/serial/NO: start with START_YEAR)
// →歲序(index of year[]: start from 0)
function year_name_to_index(歲名) {
if (!歲名)
return;
var 歲序 = this.calendar[NAME_KEY];
if (!歲序 || (歲序 = 歲序.indexOf(歲名)) === NOT_FOUND) {
歲名 = numeralize_date_name(歲名);
if (isNaN(歲名)) {
// 可能只是 to_era_Date() 在作測試,看是否能成功解析。
if (library_namespace.is_debug())
library_namespace.error(
//
'year_name_to_index: 紀年 [' + this + '] '
//
+ (歲序 ? '沒有[' + 歲名 + ']年!' : '不具有特殊名稱設定!'));
return;
}
if (this.skip_year_0 && 歲名 > 0)
歲名--;
歲序 = 歲名 - (START_KEY in this.calendar
//
? this.calendar[START_KEY] : START_YEAR);
}
return 歲序;
}
// 月序(index: start from 0)
// →月次(ordinal/serial/NO: start with START_MONTH)
// →月名(name)
function month_index_to_name(月序, 歲序) {
歲序 = this.calendar[歲序];
var 月名 = 歲序[NAME_KEY];
// 以個別命名的月名為第一優先。
if (!月名 || !(月名 = 月名[月序])) {
月名 = 月序 + (START_KEY in 歲序 ? 歲序[START_KEY] : START_MONTH);
if (this.歲首序 && (月名 += this.歲首序) > LUNISOLAR_MONTH_COUNT)
月名 -= LUNISOLAR_MONTH_COUNT;
}
// 依 month_index_to_name() 之演算法,
// 若為閏月起首,則 [START_KEY] 須設定為下一月名!
// e.g., 閏3月起首,則 [START_KEY] = 4。
if (月序 >= 歲序[LEAP_MONTH_KEY]) {
if (!isNaN(月名) && --月名 < START_MONTH)
// 確保月數為正。
月名 += LUNISOLAR_MONTH_COUNT;
if (月序 === 歲序[LEAP_MONTH_KEY]) {
// 是為閏月。
月名 = (this.閏月名 || LEAP_MONTH_PREFIX) + 月名;
}
}
return 月名;
}
// (月名 name→)
// 月次(ordinal/serial/NO: start with START_MONTH)
// →月序(index of month[]: start from 0)
function month_name_to_index(月名, 歲序) {
if (!月名 || !(歲序 in this.calendar))
return;
var is_閏月, 歲_data = this.calendar[歲序], 月序 = 歲_data[NAME_KEY],
// (閏月序) 與 [LEAP_MONTH_KEY] 皆為 (index of month[])!
// 若當年 .start = 3,並閏4月,則 (閏月序 = 2)。
閏月序 = 歲_data[LEAP_MONTH_KEY];
if (!月序 || (月序
// 以個別命名的月名為第一優先。
= 月序.indexOf(numeralize_date_name(月名, true))) === NOT_FOUND) {
月名 = String(numeralize_date_name(月名));
if (this.閏月名)
// 正規化閏月名。
月名 = 月名.replace(this.閏月名, LEAP_MONTH_PREFIX);
if (!isNaN(is_閏月 = this.歲首序))
月名 = 月名.replace(/\d+/, function(month) {
if ((month -= is_閏月) < 1)
month += LUNISOLAR_MONTH_COUNT;
return month;
});
if (月名 === LEAP_MONTH_PREFIX) {
if (isNaN(月序 = 閏月序)) {
// 可能只是 to_era_Date() 在作測試,看是否能成功解析。
if (library_namespace.is_debug())
library_namespace.warn(
//
'month_name_to_index: 紀年 [' + this + '] 之 ['
+ this.歲名(歲序) + ']年沒有閏月!');
return;
}
} else if ((月序 = String(numeralize_date_name(月名)))
// 直接用 String(numeralize_date_name(月名)).match()
// 在 Chrome 中可能造成值為 null。
// e.g., 北魏孝武帝永興1年12月 曆譜
&& (月序 = 月序.match(MONTH_NAME_PATTERN))) {
is_閏月 = 月序[1];
月序 = 月序[2] - (START_KEY in 歲_data
//
? 歲_data[START_KEY] : START_MONTH);
// 閏月或之後,月序++。
if (is_閏月 || 月序 >= 閏月序)
月序++;
if (is_閏月 && 月序 !== 閏月序) {
// 可能只是 to_era_Date() 在作測試,看是否能成功解析。
if (library_namespace.is_debug())
library_namespace.warn(
//
'month_name_to_index: 紀年 [' + this + '] 之 ['
+ this.歲名(歲序) + ']年沒有 [' + 月名 + ']月'
+ (閏月序 ? ',只有' + this.月名(閏月序, 歲序) + '月' : '')
+ '!');
return;
}
} else {
// 可能只是 to_era_Date() 在作測試,看是否能成功解析。
if (library_namespace.is_debug())
library_namespace.warn('month_name_to_index: 紀年 ['
+ this
+ '] 之 ['
+ this.歲名(歲序)
+ ']年'
+ (歲_data[NAME_KEY] ? '不具有特殊月分名稱設定!' : '沒有月分名稱['
+ 月名 + ']!'));
return;
}
}
return 月序;
}
// 日序轉成日名。
// [ 日名, 月名, 歲名 ]
function date_index_to_name(日序, 月序, 歲序, 日序_only) {
if (月序 < 0 || this.calendar[歲序].length <= 月序)
if (月序 = this.shift_month(月序, 歲序)) {
歲序 = 月序[1];
月序 = 月序[0];
} else
return;
日序 += 月序 === 0 && (START_DATE_KEY in this.calendar[歲序])
// 若當年首月有設定起始日名/起始日碼,則使用之。
? this.calendar[歲序][START_DATE_KEY]
// 不採 this.calendar[START_DATE_KEY]
// : 月序 === 0 && 歲序 === 0 && (START_DATE_KEY in this.calendar)
//
// ? this.calendar[START_DATE_KEY]
//
: START_DATE;
return 日序_only ? 日序 : [ 日序, this.月名(月序, 歲序), this.歲名(歲序) ];
}
// 日名轉成日序。
function date_name_to_index(日名, 首月採用年序) {
if (!isNaN(日名
//
= numeralize_date_name(日名))) {
// 不採 this.calendar[START_DATE_KEY]
日名 -= ((首月採用年序 in this.calendar)
//
&& (START_DATE_KEY in (首月採用年序 = this.calendar[首月採用年序]))
//
? 首月採用年序[START_DATE_KEY] : START_DATE);
}
return 日名;
}
// 取得 (歲序)年,與 (月數) 個月之後的月序與歲序。
function shift_month(月數, 歲數, 基準月) {
if (Array.isArray(月數))
基準月 = 月數, 月數 = 歲數 = 0;
else {
if (isNaN(月數 |= 0))
月數 = 0;
if (Array.isArray(歲數))
基準月 = 歲數, 歲數 = 0;
else {
if (isNaN(歲數 |= 0))
歲數 = 0;
if (!Array.isArray(基準月))
基準月 = [ 0, 0 ];
}
}
// 基準月: [ 月序, 歲序, 差距月數 ]
var 月序 = (基準月[0] | 0) + 月數,
//
歲序 = 基準月[1] | 0,
//
差距月數 = (基準月[2] | 0) + 月數;
if (歲數 > 0)
while (歲數 > 0 && 歲序 < this.calendar.length)
歲數--, 差距月數 += this.calendar[歲序++].length;
else
while (歲數 < 0 && 歲序 > 0)
歲數++, 差距月數 -= this.calendar[歲序--].length;
if (月序 > 0)
while (true) {
if (歲序 >= this.calendar.length) {
if (library_namespace.is_debug())
// 可能是孝徳天皇之類,期間過短,又嘗試
// get_month_branch_index()
// 的。
library_namespace.error('shift_month: 已至 [' + this
+ '] 曆數結尾,無可資利用之月分資料!');
差距月數 = NaN;
歲序--;
break;
}
月數 = this.calendar[歲序].length;
if (月序 < 月數)
break;
歲序++;
月序 -= 月數;
}
else
while (月序 < 0) {
if (--歲序 < 0) {
if (library_namespace.is_debug())
library_namespace.error('shift_month: 已至 [' + this
+ '] 曆數起頭,無可資利用之月分資料!');
差距月數 = NaN;
歲序 = 0;
break;
}
月序 += this.calendar[歲序].length;
}
基準月[0] = 月序;
基準月[1] = 歲序;
基準月[2] = 差距月數;
return !isNaN(差距月數) && 基準月;
}
// date index of era → Date
function date_index_to_Date(歲序, 月序, 日序, strict) {
if (!this.shift_month(歲序 = [ 月序, 歲序 ]))
return;
// 差距日數
月序 = 歲序[0];
歲序 = 歲序[1];
日序 |= 0;
var date = this.year_start[歲序],
//
i = 0, calendar = this.calendar[歲序];
// TODO: use Array.prototype.reduce() or other method
for (; i < 月序; i++)
日序 += calendar[i];
date += 日序 * ONE_DAY_LENGTH_VALUE;
if (strict && this.end - date < 0)
// 作邊界檢查。
return;
return new Date(date);
}
/**
* parse date name of calendar data.
*
* @param {String}date_name
* date name
* @returns [ 年名, 月名, 起始日碼 ]
*/
function parse_calendar_date_name(date_name) {
if (!date_name)
return [];
// matched: [ , 年, 月, 日 ]
var matched = date_name.match(/^\/(\d+)$/);
date_name = matched ? [ , , matched[1] ]
//
: (matched = date_name.match(起始日碼_PATTERN)) ? matched.slice(1)
: date_name.split('/');
// 得考慮有特殊月名的情況,因此不可採
// (name === LEAP_MONTH_PREFIX ||
// MONTH_NAME_PATTERN.test(name))
// 之類的測試方式。
if (date_name.length === 1)
// 月名
date_name = [ , date_name[0] ];
if (date_name.length > 3)
library_namespace.warn('parse_calendar_date_name: 日碼 ['
+ date_name.join('/') + '].length = ' + date_name.length
+ ',已過長!');
date_name.forEach(function(name, index) {
date_name[index] = numeralize_date_name(name);
});
// 正規化月名。
if ((matched = date_name[1]) && typeof matched === 'string')
if (matched = matched.match(MONTH_NAME_PATTERN))
// 去空白與"月"字。
date_name[1] = (matched[1] || '') + matched[2];
else if (library_namespace.is_debug()
&& date_name[1] !== LEAP_MONTH_PREFIX)
library_namespace.warn(
//
'parse_calendar_date_name: 特殊月名: [' + date_name[1] + ']');
return date_name;
}
function clone_year_data(year_data, clone_to) {
if (!clone_to)