/**
* @name CeL function for era calendar.
* @fileoverview 本檔案包含了東亞傳統曆法/中國傳統曆法/曆書/歷譜/帝王紀年/年號紀年,農曆、夏曆、陰曆,中西曆/信史的日期轉換功能。
* 以歷史上使用過的曆數為準。用意不在推導曆法,而在對過去時間作正確轉換。因此僅用查表法,不作繁複天文計算。
*
* @since 2013/2/13 12:45:44
*
* TODO:
* 檢查前後相交的記年資料 每個月日數是否相同
* 歲時記事 幾龍治水、幾日得辛、幾牛耕地、幾姑把蠶、蠶食幾葉
* http://mathematicsclass.blogspot.tw/2009/06/blog-post_17.html
* bug: 西漢武帝後元2年
* 太複雜了,效率不高,重構。
* 舊唐書/卷11 寶應元年..二年..二月甲午,回紇登裏可汗辭歸蕃。三月甲辰朔,襄州右兵馬使梁崇義殺大將李昭,據城自固,仍授崇義襄州刺史、山南東道節度使。丁未,袁傪破袁晁之眾於浙東。玄宗、肅宗歸祔山陵。自三月一日廢朝,至於晦日,百僚素服詣延英門通名起居。
* CeL.era('二年春正月丁亥朔',{after:'寶應元年'})
* CeL.era('丁亥朔',{after:'寶應二年春正月'})
* CeL.era('寶應元年',{period_end:true})
* CeL.era('嘉慶十六年二月二十四日寅刻')===CeL.era('嘉慶十六年二月二十四日寅時')
* Period 獨立成 class
* 南明紹宗隆武1年閏6月月干支!=甲申, should be 癸未
* 月令別名 http://140.112.30.230/datemap/monthalias.php
* 月の異称 http://www.geocities.jp/okugesan_com/yougo.html
* 西周金文紀時術語. e.g., 初吉,既生霸,既望,既死霸
* (http://wywu.pixnet.net/blog/post/22412573-%E6%9C%88%E7%9B%B8%E8%A8%98%E6%97%A5%E8%A1%A8)
*
* 未來發展:
* 加入世界各國的對應機能。
* 加入 國旗
*
* @example
// 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。
*/
'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)。
* 10 Arabic numerals + 26 Latin alphabet.
*
* @inner
* @see Hexadecimal
*/
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');
}
// 《中國歷史紀年表》解惑
// 謚號紀年的方法是:國號——帝王謚號——年號(無年號者不用)——年序號,如漢惠帝五年,梁武帝大同八年。
// 自唐朝開始,改紀年方式為國號——帝王廟號——年號——年序號。如唐高宗永徽四年,清世宗雍正八年等。
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)
clone_to = year_data.slice();
[ START_KEY, LEAP_MONTH_KEY
// , NAME_KEY
]
//
.forEach(
// 複製本年之月 START_KEY, LEAP_MONTH_KEY 等。
function(key) {
if (key in year_data) {
var value = year_data[key];
clone_to[key] = Array.isArray(value) ? value.slice() : value;
}
});
return clone_to;
}
// 需在設定完個別 this_year_data 之月名後,才作本紀年泛用設定。
function add_month_name(月名_Array, this_year_data) {
var name_Array = this_year_data[NAME_KEY],
//
leap = this_year_data[LEAP_MONTH_KEY], start;
if (start = this_year_data[START_KEY])
start -= START_MONTH;
else
start = 0;
if (!Array.isArray(name_Array))
if (isNaN(leap)) {
if (Array.isArray(月名_Array)) {
// this_year_data = clone_year_data(this_year_data);
this_year_data[NAME_KEY] = start ? 月名_Array.slice(start)
: 月名_Array;
}
return;
} else {
// this_year_data = clone_year_data(this_year_data);
name_Array = this_year_data[NAME_KEY] = [];
}
月名_Array.forEach(function(名, index) {
if (0 <= (index -= start)) {
if (leap <= index) {
if (leap === index && !(index in name_Array)
&& 月名_Array[index + start - 1])
name_Array[index]
// 閏月使用上一 index 月名。
= 月名_Array[index + start - 1];
// index 為閏月或之後,則使用在下一 index 之月名。
index++;
}
// 不作覆蓋。
if (名 && !(index in name_Array))
name_Array[index] = 名;
}
});
}
function is_正統(era, key) {
// assert: era.正統 === undefined || typeof era.正統 === 'string' ||
// Array.isArray(era.正統)
return era.正統 && (era.正統 === true
// 採用"正統"方法,可避免某些情況下因「挑選最後結束之紀年」之演算法,造成最後無可供參照之紀年。
// 但這需要手動測試每一種參照 key,並依測試結果添加。非萬全之道。
|| key && era.正統.includes(key));
}
var important_properties = {
精 : true,
準 : true,
曆法 : true
};
// 複製當前參照紀年之重要屬性至本紀年。
// 注意: 由於這會在 initialize_era_date(),產生曆譜時才會執行,
// 因此像是展示線圖時並不具有這些屬性。若紀年本身沒設定非準確屬性,則會當作準確紀年來顯示。
function copy_important_properties(from_era, to_era) {
for ( var property in important_properties) {
if (!(property in from_era)) {
continue;
}
var value = from_era[property];
if (property in to_era) {
// 可能是本紀年自己的,可能是從其他參照紀年獲得的。
if (to_era[property] !== value
&& important_properties[property]) {
library_namespace.warn('copy_important_properties: '
+ '紀年 [' + to_era + '] 原有重要屬性 [' + property
+ '] 為"' + to_era[property] + '",與所參照紀年 ['
+ from_era + '] 之屬性值"' + value + '" 不同!');
}
continue;
}
library_namespace.debug('複製當前參照紀年之重要屬性 [' + property + '] = '
+ value, 1, 'copy_important_properties');
to_era[property] = value;
}
}
// parse_era() 之後,初始化/parse 紀年之月分日數 data。
// initialize era date.
function initialize_era_date() {
// IE 需要 .getTime():IE8 以 new Date(Date object) 會得到 NaN!
var days,
/**
* {Date}本紀年的起始時間。
*/
start_time = this.start.getTime(),
// 當前年分之各月資料 cache。calendar_data[this year]。
this_year_data,
//
紀年曆數 = this.calendar, this_end = this.end.getTime(),
// 最後將作為 this.year_start 之資料。
year_start_time = [ start_time ],
// 最後將作為 this.calendar 之資料。
// (年/月分資料=[年分各月資料/月分日數])[NAME_KEY]=[年/月分名稱],
// [START_KEY] = start ordinal,
// [LEAP_MONTH_KEY] = leap month index.
calendar_data = [],
//
年序, 月序;
start_time = new Date(start_time);
// ---------------------------------------
if (!紀年曆數 || typeof 紀年曆數 !== 'string') {
library_namespace.error('initialize_era_date: 無法辨識曆數資料!');
return;
}
if ((月序 = 紀年曆數.match(參照_PATTERN))
// [ 紀年曆數, 起始日期名, 所參照之紀年或國家 ]
&& ((年序 = 月序[2]) in search_index
//
|| (年序 in String_to_Date.parser
//
&& 年序 in Date_to_String_parser))) {
var 曆法 = 年序,
// [ 年名, 月名, 起始日碼 ]
date_name = parse_calendar_date_name(月序[1]);
library_namespace.debug(this + ': 參照紀年或國家 [' + 曆法 + '] 之曆數。', 2);
// 處理紀年曆數所設定之起始年名:基本上僅允許年分不同。
// 其他月名,日數皆得與起訖時間所設定的相同。
// 年名應可允許 '0' 與負數。
if (date_name[0] !== '' && !isNaN(date_name[0])
//
&& (date_name[0] |= 0) !== START_YEAR)
// 複製本年之 START_KEY。
calendar_data[START_KEY] = date_name[0];
if (曆法 in search_index) {
// ---------------------------------------
/**
* e.g., test:
CeL.era.set('古曆|-60~1230|-61/=:中國');
CeL.era('古曆9年');
*
*
*
CeL.set_debug(6);
CeL.era.set('古曆|Egyptian:-571/11~-570|:Egyptian|準=年');
CeL.Log.clear();
CeL.era('古曆2年1月').format({parser:'CE',format:'%Y/%m/%d'});
*
*/
// CeL.era.set('古曆|-60~80|-60/=:中國');CeL.era('古曆1年');
// CeL.era.set('古曆|25/2/17~27|:中國');CeL.era('古曆1年');
// CeL.era.set('古曆|-60~1230|-61/=:中國');CeL.era('古曆249年');
// CeL.era.set('古曆|-57~-48|-58/=:中國');//CeL.era('古曆-58年');
// CeL.era.set('古曆|-54~-48|-55/=:中國');//CeL.era('古曆-55年');
// n='古曆',sy=-55;CeL.era.set(n+'|'+(sy+1)+'~'+(sy+10)+'|'+sy+'/=:中國');//CeL.era(n+sy+'年');
// CeL.era.set('古曆|901~1820|900/=:中國');
// CeL.era('古曆54年1月').format({parser:'CE',format:'%Y/%m/%d'});
// CeL.era.dates('古曆901年',{year_limit:2000,date_only:true});
this.參照紀年 = 曆法;
var tmp,
// 所有候選紀年。
// assert: 不會更動到候選紀年之資料。
era_Array = [],
// 當前參照之紀年。
era,
// 當前參照紀年之 date 指標。
date_index,
// era_year_data: 當前參照紀年之當前年分各月資料。
era_year_data,
// for lazy evaluation.
correct_month_count,
// 校正 this_year_data 之月份數量:
// 參照紀元引入的可能只能用到 10月,但卻具足了到 12月的資料。
// 此時需要先將後兩個月的資料剔除,再行 push()。
// tested:
// 百濟汾西王1年
// 成漢太宗建興1年
// 新羅儒禮尼師今7年
correct_month = function(月中交接) {
// 參照紀元 era 之參照月序。
var era_month =
// 參考 month_name_to_index()
(era_year_data[START_KEY] || START_MONTH) + 月序;
if ((LEAP_MONTH_KEY in era_year_data)
//
&& era_year_data[LEAP_MONTH_KEY] < 月序)
era_month--;
var month_diff
//
= (this_year_data[START_KEY] || START_MONTH)
// 當前月序
+ this_year_data.length;
if ((LEAP_MONTH_KEY in this_year_data)
//
&& this_year_data[LEAP_MONTH_KEY] < month_diff)
month_diff--;
if (月中交接
// 之前已經有東西,並非處在第一個月,「月中交接」才有意義。
&& this_year_data.length > 0)
month_diff--;
// 減去參照紀元 era 之參照月序
month_diff -= era_month;
if (!library_namespace.is_debug())
return;
if (month_diff > 0) {
library_namespace.debug('引入 [' + era + '] 前,先刪掉年序 '
+ (calendar_data.length - 1) + ' 之 '
+ month_diff + ' 個月份。 ('
+ this_year_data.length + ' - ' + month_diff
+ ')', 1, 'initialize_era_date.correct_month');
this_year_data.length -= month_diff;
} else if (month_diff === -1 ?
// 若是當前 this_year_data 的下一個月為閏月,
// 亦可能出現 (month_diff = -1) 的情況。
// 這時得避免亂噴警告。
era_year_data[LEAP_MONTH_KEY] !== 月序
//
|| this_year_data[LEAP_MONTH_KEY] >= 0
//
&& this_year_data[LEAP_MONTH_KEY]
//
!== this_year_data.length :
// 差太多了。
month_diff < -1)
library_namespace.warn(
//
'initialize_era_date.correct_month: 建構途中之曆數資料,與當前正欲參照之紀元 ['
+ era + '] 間,中斷了 ' + (-month_diff) + ' 個月份!');
},
// 當無須改變最後一年曆數,例如已在年尾時,不再複製。
clone_last_year = function(月中交接) {
if (!correct_month_count) {
correct_month(月中交接);
return;
}
var tmp = calendar_data.length - 1;
if (calendar_data[tmp][COUNT_KEY]) {
this_year_data = calendar_data[tmp];
this_year_data.length
//
= this_year_data[COUNT_KEY]
//
+= correct_month_count;
correct_month(月中交接);
return;
}
tmp = calendar_data.pop();
calendar_data.push(this_year_data
// 初始化本年曆數。
= tmp.slice(0, correct_month_count));
this_year_data[COUNT_KEY] = correct_month_count;
if (tmp.月名)
this_year_data.月名 = tmp.月名
.slice(0, correct_month_count);
clone_year_data(tmp, this_year_data);
// 複製 era 之[NAME_KEY]。
if (NAME_KEY in tmp)
this_year_data[NAME_KEY] = tmp[NAME_KEY].slice(0,
correct_month_count);
// 去除標記。
correct_month_count = null;
correct_month(月中交接);
},
// 處理月中換日的情況,複製本年本月之月分名稱與本月日數。
copy_date = function(本月日數) {
var 月名 = era_year_data[NAME_KEY];
if (月名 && (月名 = 月名[月序])) {
if (!(NAME_KEY in this_year_data))
// 初始化本年之月分名稱。
this_year_data[NAME_KEY] = [];
library_namespace.debug(
//
'複製本年本月之月分名稱。月序' + 月序 + ',本月現有 '
//
+ this_year_data.length + '個月。', 3);
this_year_data[NAME_KEY][
//
this_year_data.length] = 月名;
}
this_year_data.push(本月日數);
// 本月已處理完,將月序指向下一個月。
月序++;
};
// 初始化:取得所有候選紀年列表。
tmp = start_time.getTime();
for_each_era_of_key(曆法, function(era) {
if (// era !== this &&
era.start - this_end < 0
// 有交集(重疊)才納入。
&& tmp - era.end < 0 && (era.year_start
// 篩選合宜的紀年。
|| !參照_PATTERN.test(era.calendar)))
era_Array.push(era);
}
// .bind(this)
);
// sort by era start time.
era_Array.sort(compare_start_date);
library_namespace.debug('[' + this + '] 參照紀年 key [' + 曆法
+ ']: 共有 ' + era_Array.length + ' 個候選紀年。', 1,
'initialize_era_date');
library_namespace.debug('候選紀年列表: [' + era_Array
//
.join('|') + ']。', 2,
'initialize_era_date');
// 因為 parse_calendar_date_name() 與 .日名()
// 得到相反次序的資料,因此需要轉回來。因為有些項目可能未指定,因此不能用 .reverse()。
tmp = date_name[0];
date_name[0] = date_name[2];
date_name[2] = tmp;
/**
* 參照紀年之演算機制:定 this.year_start 與 this.calendar 之過程。
*
CeL.era.pack('/文宗/天曆|1329/8/25~|2/8=30;29;29;30;30\t29;30;30;29');
*
*
* @param {Array|Object|String}plain_era_data
* 紀年資料。
*
* @returns {String}壓縮後的紀年資料。
*/
function pack_era(plain_era_data) {
// 單筆/多筆共用函數。
function pre_parse(era) {
if (typeof era === 'string')
era = era.split(pack_era.field_separator);
if (Array.isArray(era) && era.length === 1 && era[0].includes(
//
pack_era.month_separator))
// gettext_config:{"id":"era-name"}
era.unshift('紀年', '');
if (Array.isArray(era) && 1 < era.length) {
// 使 pack_era() 可採用 Era / 壓縮過的日期資料 為 input。
// TODO: 尚未完善。應直接採用 parse_era 解析。
era[0] = era[0].split(pack_era.era_name_classifier);
(era[2] = era[2].split(pack_era.year_separator))
.forEach(function(date, index) {
era[2][index] = date
.split(pack_era.month_separator);
});
era = {
紀年 : era[0],
起訖 : parse_duration(era[1], era[0])
// @see CeL.date.parse_period.PATTERN
// Must include PERIOD_DASH
// assert: 已經警示過了。
|| era[1].split(/[–~-—─~〜﹣至]/),
曆數 : era[2]
};
}
return era;
}
// -----------------------------
// 處理多筆紀年。
if (Array.isArray(plain_era_data)) {
var last_era = [],
/** {Date}上一紀年結束日期。 */
last_end_date, era_list = [];
plain_era_data.forEach(function(era) {
if (!library_namespace.is_Object(
//
era = pre_parse(era))) {
library_namespace.error('pack_era: 跳過資料結構錯誤的紀年資料!');
return;
}
// 簡併紀年名稱。
var i = 0, this_era = era.紀年, no_inherit;
if (!Array.isArray(this_era))
this_era = [ this_era ];
for (; i < this_era.length; i++)
if (!no_inherit && this_era[i] === last_era[i])
this_era[i] = '';
else {
no_inherit = true;
if (this_era[i] !== parse_era.inherit)
last_era[i] = this_era[i] || '';
}
era.紀年 = this_era;
// 簡併起訖日期。
// 起訖 : [ 起, 訖, parser ]
if (!(this_era = parse_duration(era.起訖, this_era))) {
library_namespace.error('pack_era(Array): 跳過起訖日期錯誤的紀年資料!');
return;
}
// 回存。
era.起訖 = this_era;
// 正規化日期。
// assert: 整個 era Array 都使用相同 parser。
// 若上一紀年結束日期 == 本紀年開始日期,
// 則除去上一紀年結束日期。
if (
// cache 計算過的值。
(this_era[0] = normalize_date(this_era[0], this_era[2]
|| PASS_PARSER))
&& this_era[0] === last_end_date) {
library_namespace.debug('接續上一個紀年的日期 [' + last_end_date
+ ']。除去上一紀年結束日期。', 2);
last_era.date[1] = '';
// 這是採除去本紀年開始日期時的方法。
// this_era[0] = '';
// 之所以不採除去本紀年的方法,是因為:
// 史書通常紀載的是紀年開始的日期,而非何時結束。
} else
library_namespace.debug('未接續上一個紀年的日期: [' + last_end_date
+ ']→[' + this_era[0] + ']。', 2);
if (持續日數_PATTERN.test((last_era.date = this_era)[1])) {
(last_end_date = normalize_date(this_era[0], this_era[2]
|| PASS_PARSER, true, true)).setDate(+this_era[1]);
last_end_date = normalize_date(last_end_date);
library_namespace.debug('訖時間 "+d" [' + this_era[1]
+ '] : 持續日數 [' + last_end_date + ']。', 2);
} else {
last_end_date = normalize_date(this_era[1].trim(),
this_era[2] || PASS_PARSER, true);
library_namespace.debug('訖時間 "-y/m/d" [' + this_era[1]
+ '] : 指定 end date [' + last_end_date + ']。', 2);
}
era_list.push(era);
});
// 因為可能動到前一筆資料,只好在最後才從頭再跑一次。
library_namespace.debug('開始 pack data。', 2);
last_era = [];
era_list.forEach(function(era) {
last_era.push(pack_era(era));
});
library_namespace.debug('共轉換 ' + last_era.length + '/'
+ era_list.length + '/' + plain_era_data.length + ' 筆紀錄。');
return last_era;
}
// -----------------------------
// 處理單筆紀年。
if (!library_namespace.is_Object(
//
plain_era_data = pre_parse(plain_era_data))) {
library_namespace.error('pack_era: 無法判別紀年資料!');
return plain_era_data;
}
// 至此 plain_era_data = {
// 紀年 : [ 朝代, 君主(帝王), 紀年名稱 ],
// 起訖 : [ 起, 訖, parser ],
// 曆數 : [ [1年之月分資料], [2年之月分資料], .. ],
// 其他附加屬性 : ..
// }
var i = 0, j,
//
year_data,
// 當前年度
year_now = START_YEAR,
// 當前月分
month_now,
// 壓縮用月分資料
month_data,
//
month_name,
//
前項已壓縮,
// {String} 二進位閏月 index
leap_month_index_base_2, 日數,
//
年名, 月名, 起始日碼,
//
to_skip = {
紀年 : 0,
起訖 : 1,
曆數 : 2
}, packed_era_data,
//
紀年名稱 = plain_era_data.紀年,
//
起訖時間 = parse_duration(plain_era_data.起訖, 紀年名稱),
// calendar_data
年度月分資料 = plain_era_data.曆數;
if (!起訖時間) {
起訖時間 = [];
// return;
}
if (!Array.isArray(年度月分資料) || !年度月分資料[0]) {
library_namespace.error('pack_era: 未設定年度月分資料!');
return;
}
if (Array.isArray(紀年名稱))
紀年名稱 = 紀年名稱.join(pack_era.era_name_classifier)
//
.replace(pack_era.era_name_重複起頭, pack_era.era_name_classifier)
//
.replace(pack_era.era_name_符號結尾, '');
if (!紀年名稱 || typeof 紀年名稱 !== 'string') {
library_namespace.error(
//
'pack_era: 無法判別紀年名稱: [' + 紀年名稱 + ']');
return;
}
// 簡併月分資料。
for (; i < 年度月分資料.length; i++, year_now++) {
year_data = 年度月分資料[i];
// 每年自一月開始。
month_now = START_MONTH;
month_data = [];
leap_month_index_base_2 = '';
for (j = 0; j < year_data.length; j++, month_now++) {
// 允許之日數格式:
// 日數
// '起始日碼=日數'
// [ 起始日碼, 日數 ]
if (isNaN(日數 = year_data[j])) {
if (typeof 日數 === 'string')
日數 = 日數.split('=');
if (!Array.isArray(日數) || 日數.length !== 2) {
library_namespace.error(
//
'pack_era: 無法辨識日數資料 [' + year_data[j] + ']!');
month_data = null;
} else {
if (起始日碼 = parse_calendar_date_name(
//
月名 = String(日數[0])))
// [ 年名, 月名, 起始日碼 ]
年名 = 起始日碼[0], 月名 = 起始日碼[1], 起始日碼 = 起始日碼[2];
else {
library_namespace.warn(
//
'pack_era: 無法辨識紀年 [' + 紀年名稱 + '] ' + year_now
+ '年之年度月分資料 ' + j + '/' + year_data.length
+ ':起始日碼 [' + 月名 + '],將之逕作為月分名!');
年名 = 起始日碼 = '';
}
// assert: 至此 (年名), (月名), (起始日碼) 皆已設定。
日數 = 日數[1];
if (year_now == 年名)
年名 = '';
if (month_now == 月名)
月名 = '';
if (START_DATE == 起始日碼)
起始日碼 = '';
if ((month_name = 月名) || 年名 || 起始日碼) {
// 可能為: [閏闰]?\d+, illegal.
if (i === 0 && j === 0 && !起始日碼
&& (month_name = month_name.match(
//
MONTH_NAME_PATTERN))) {
library_namespace.info(
//
'pack_era: 紀年 [' + 紀年名稱 + '] '
//
+ (年名 || year_now) + '年:起始的年月分並非 ' + year_now
+ '/' + month_now + ',而為 ' + 年名 + '/'
+ 月名);
// 將元年前面不足的填滿。
// 為了增高壓縮率,對元年即使給了整年的資料,也僅取從指定之日期開始之資料。
month_data = to_list(new Array(
// reset
month_now = +month_name[2]
+ (month_name[1] ? 1 : 0)).join('0'));
}
// 處理簡略表示法: '閏=\d+'
if (月名 === LEAP_MONTH_PREFIX)
月名 += month_now - 1;
// 可壓縮: 必須為閏(month_now - 1)
if ((month_name = 月名) !== LEAP_MONTH_PREFIX
+ (month_now - 1)
|| 年名 || 起始日碼) {
if ((month_name = 月名)
//
!== LEAP_MONTH_PREFIX + (month_now - 1)
&& (i > 0 || j > 0)) {
library_namespace.warn(
//
'pack_era: 紀年 [' + 紀年名稱 + '] '
//
+ year_now + '年:日期非序號或未按照順序。月分資料 '
+ (j + START_MONTH) + '/'
+ year_data.length + ' ['
+ year_now + '/' + month_now + '/'
+ START_DATE + '] → [' + (年名 || '')
+ '/' + (月名 || '') + '/'
+ (起始日碼 || '') + ']');
month_data = null;
}
} else if (leap_month_index_base_2) {
library_namespace.error(
//
'pack_era: 本年有超過1個閏月!');
month_data = null;
} else {
// 處理正常閏月。
if (month_data) {
leap_month_index_base_2 =
// 二進位
month_data.length
//
.toString(RADIX_2);
// 預防
// leap_month_index_base_2
// 過短。
leap_month_index_base_2
//
= LEAP_MONTH_PADDING
//
.slice(0, LEAP_MONTH_PADDING.length
//
- leap_month_index_base_2.length)
+ leap_month_index_base_2;
} else
leap_month_index_base_2
//
= month_now;
month_now--;
}
if (month_name = (年名 ? 年名 + '/' : '') + (月名 || '')
+ (起始日碼 ? '/' + 起始日碼 : ''))
month_name += '=';
if (year_data[j] != (month_name += 日數))
year_data[j] = month_name;
if (年名 !== '' && !isNaN(年名)) {
library_namespace.debug('year: ' + year_now
+ ' → ' + 年名, 2);
year_now = 年名;
}
if (月名 !== ''
&& typeof 月名 === 'string'
&& !isNaN(月名 = 月名.replace(
MONTH_NAME_PATTERN, '$2'))
&& month_now != 月名) {
library_namespace.debug('month: ' + month_now
+ ' → ' + 月名, 2);
month_now = 月名;
}
} else if (year_data[j] != 日數)
// 可省略起始日碼的情況。
year_data[j] = 日數;
}
}
if (month_data)
if (日數 in MONTH_DAY_INDEX) {
month_data.push(MONTH_DAY_INDEX[日數]);
} else {
library_namespace.warn(
//
'pack_era: 錯誤的日數?[' + 日數 + ']日。');
month_data = null;
}
}
if (month_data) {
j = LUNISOLAR_MONTH_COUNT + (leap_month_index_base_2 ? 1 : 0);
if (month_data.length < j) {
// padding
Array_push(
//
month_data, to_list(new Array(j + 1 - month_data.length)
.join(0)));
} else if (month_data.length > j) {
library_namespace.warn('pack_era: 紀年 [' + 紀年名稱 + '] '
+ year_now + '年:月分資料過長! (' + month_data.length
+ '>' + j + ') month_data: ' + month_data);
}
if (library_namespace.is_debug(2))
j = '] ← ['
+ month_data.join('')
+ (leap_month_index_base_2 ? ' '
+ leap_month_index_base_2 : '') + '] ← ['
+ year_data.join(pack_era.month_separator) + ']';
month_data = parseInt(
// 為了保持應有的長度,最前面加上 1。
'1' + month_data.join('') + leap_month_index_base_2, RADIX_2)
//
.toString(PACK_RADIX);
if (month_data.length > YEAR_CHUNK_SIZE)
library_namespace.warn('pack_era: 紀年 [' + 紀年名稱 + '] '
+ year_now + '年:月分資料過長! (' + month_data.length
+ '>' + YEAR_CHUNK_SIZE + ') month_data: '
+ month_data);
else if (month_data.length < YEAR_CHUNK_SIZE
// 非尾
&& i < 年度月分資料.length - 1) {
if (month_data.length < YEAR_CHUNK_SIZE - 1
// 非首
&& i > 0)
// 非首非尾
library_namespace.warn('pack_era:紀年 [' + 紀年名稱 + '] '
+ year_now + '年:月分資料過短! (' + month_data.length
+ '<' + YEAR_CHUNK_SIZE + ') month_data: '
+ month_data);
// 注意:閏月之 index 是 padding 前之資料。
month_data += PACKED_YEAR_CHUNK_PADDING.slice(0,
YEAR_CHUNK_SIZE - month_data.length);
}
library_namespace.debug('[' + month_data + j, 2);
if (i === 0 && /\=./.test(year_data[0]))
month_data = year_data[0].replace(/[^=]+$/, '')
+ month_data;
年度月分資料[i] = month_data;
} else {
// 可能只是 to_era_Date() 在作測試,看是否能成功解析。
if (library_namespace.is_debug())
library_namespace.warn(
//
'pack_era: 無法壓縮紀年 [' + 紀年名稱 + '] ' + year_now + '年資料 ['
+ year_data.join(pack_era.month_separator) + ']');
// 年與年以 pack_era.year_separator 分隔。
// 月與月以 pack_era.month_separator 分隔。
年度月分資料[i] = (前項已壓縮 ? pack_era.year_separator : '')
+ year_data.join(pack_era.month_separator)
+ pack_era.year_separator;
}
前項已壓縮 = !!month_data;
}
年度月分資料[i - 1] = 前項已壓縮 ? 年度月分資料[i - 1].replace(/\s+$/, '')
: 年度月分資料[i - 1].slice(0, -1);
起訖時間[0] = normalize_date(起訖時間[0], 起訖時間[2] || PASS_PARSER);
if (!持續日數_PATTERN.test(起訖時間[1]))
// assert: isNaN(起訖時間[1])
起訖時間[1] = normalize_date(起訖時間[1], 起訖時間[2] || PASS_PARSER);
// 去掉相同年分。
// 800/1/1-800/2/1 → 800/1/1–2/1
if ((i = 起訖時間[0].match(/^[^\/]+\//)) && 起訖時間[1].indexOf(i = i[0]) === 0)
起訖時間[1] = 起訖時間[1].slice(i.length);
packed_era_data = [ 紀年名稱, (起訖時間[2] ? 起訖時間[2] + ':' : '')
//
+ 起訖時間[0] + PERIOD_DASH + 起訖時間[1], 年度月分資料.join('') ];
// 添加其他附加屬性名稱。
for (i in plain_era_data)
if (!(i in to_skip))
// TODO: 檢查屬性是否有特殊字元。
packed_era_data.push(i + '=' + plain_era_data[i]);
return packed_era_data.join(pack_era.field_separator);
}
parse_era.inherit = '=';
pack_era.field_separator = '|';
// assert: .length === 1
pack_era.year_separator = '\t';
// assert: .length === 1
pack_era.month_separator = ';';
pack_era.era_name_separator = pack_era.month_separator;
pack_era.era_name_classifier = '/';
pack_era.era_name_重複起頭 = new RegExp('^\\' + pack_era.era_name_classifier
+ '{2,}');
// 應當用在 "朝代//" 的情況,而非 "/君主/"。
pack_era.era_name_符號結尾 = new RegExp('\\' + pack_era.era_name_classifier
+ '+$');
// ---------------------------------------------------------------------//
// private 工具函數。
// set time zone / time offset (UTC offset by minutes)
function set_minute_offset(date, minute_offset, detect_if_configured) {
// 偵測/預防重複設定。
if (detect_if_configured)
if ('minute_offset' in date) {
// 已設定過。
if (date.minute_offset !== minute_offset)
library_namespace.error('set_minute_offset: 之前已將 ' + date
+ ' 設定成 ' + date.minute_offset + ' 分鐘,現在又欲設定成 '
+ minute_offset + ' 分鐘!');
return;
} else
date.minute_offset = minute_offset;
date.setMinutes(date.getMinutes() - minute_offset);
}
function create_era_search_pattern(get_pattern) {
if (!era_search_pattern) {
era_key_list = [];
for ( var key in search_index)
era_key_list.push(key);
library_namespace.debug(
//
'初始化 search pattern: ' + era_key_list.length + ' era keys', 3);
// 排序:長的 key 排前面。
era_key_list.sort(function(key_1, key_2) {
return key_2.length - key_1.length || era_count_of_key(key_2)
- era_count_of_key(key_1);
});
// 從最後搜尋起。
// 從後端開始搜尋較容易一開始就取得最少的候選者(越後面特異度越高),能少做點處理,較有效率。
// 且可能較正確。 e.g., "他國王+紀年+年",應優先選用 紀年+年 而非 他國王+年。
// 因為採用 /().*?$/ 的方法不一定能 match 到所需(按順序)的 key,只好放棄
// /().*?$/。
era_search_pattern = new RegExp('(?:' + era_key_list.join('|')
// escape.
.replace(/([()])/g, '\\$1')
// 處理 space。
.replace(/\s+/g, '\\s*') + ')$',
// 對分大小寫之名稱,應允許混用。
'i');
}
return get_pattern ? era_search_pattern : era_key_list;
}
// private 工具函數。
function compare_start_date(era_1, era_2) {
return era_1.start - era_2.start;
}
// 避免重複設定或覆蓋原有值。 set_attribute()
// object[key] = value
// TODO: {Array}value
function add_attribute(object, key, value, prepend) {
if (key in object) {
// 有衝突。
var values = object[key];
if (Array.isArray(values)) {
// 不重複設定。
if (!values.includes(value))
// prepend or append
if (prepend)
values.unshift(value);
else
values.push(value);
} else if (values !== value)
object[key] = prepend ? [ value, values ] : [ values, value ];
} else {
// 一般情況。
object[key] = value;
}
}
function parse_month_name(月名, 月名_Array) {
月名 = 月名.split(pack_era.month_separator);
if (月名.length > 0) {
if (!Array.isArray(月名_Array))
月名_Array = [];
var index = 0, matched;
月名.forEach(function(名) {
名 = 名.trim();
if ((matched = 名.match(/^(\d+)\s*:\s*(.+)$/))
&& START_MONTH <= matched[1])
index = matched[1] - START_MONTH, 名 = matched[2];
if (名)
月名_Array[index++] = 名;
});
}
return 月名_Array;
}
function get_closed_year_start(date) {
var year = date.getFullYear(), 前 = new Date(0, 0),
// 僅使用 new Date(0) 的話,會含入 timezone offset (.getTimezoneOffset)。
// 因此得使用 new Date(0, 0)。
後 = new Date(0, 0);
// incase year 0–99
前.setFullYear(year, 0, 1);
後.setFullYear(year + 1, 0, 1);
return date - 前 < 後 - date ? 前 : 後;
}
// 處理朝代紀年之 main functions。
// build data (using insertion):
// parse era data
function parse_era(era_data_array, options) {
if (!era_data_array) {
// Invalid input.
if (options && options.國家) {
// 可能由CeL.env.era_data_load()篩選過。
library_namespace.error('Unknown country: ' + options.國家);
}
return;
}
function pre_parse_紀年資料(index) {
var i, j, 附加屬性, era_data = era_data_array[index];
if (typeof era_data === 'string')
era_data = era_data.split(pack_era.field_separator);
else if (library_namespace.is_Object(era_data)) {
附加屬性 = era_data;
if (era_data.data) {
era_data = era_data.data.split(pack_era.field_separator);
delete 附加屬性.data;
} else
era_data = [];
for (i in 紀年名稱索引值)
// 當正式名稱闕如時,改附加屬性作為正式名稱。
if (!era_data[j = 紀年名稱索引值[i]] && (i in 附加屬性)) {
era_data[j] = 附加屬性[i];
delete 附加屬性[i];
}
}
if (!Array.isArray(era_data) || era_data.length < 2) {
library_namespace.error('parse_era.pre_parse_紀年資料: 無法判別紀年 ['
+ index + '] 之資料!');
return;
}
if (!era_data.parsed) {
if (era_data.length < 3) {
if (library_namespace.is_Object(i = era_data[1]))
附加屬性 = i;
else
i = [ i ];
era_data = era_data[0].split(pack_era.field_separator);
} else
i = era_data.slice(3);
if (!附加屬性)
附加屬性 = Object.create(null);
i.forEach(function(pair) {
pair = pair.trim();
if (j = pair.match(
// 允許 "\n"
/^([^=]+)=([\s\S]+)$/))
add_attribute(附加屬性, j[1].trim(), j[2].trim());
else if (/^[^\s,.;]+$/.test(pair))
// 當作屬性名稱,預設將屬性定為 true。
add_attribute(附加屬性, pair, true);
else
library_namespace.warn(
//
'pre_parse_紀年資料: 無法解析屬性值[' + pair + ']!');
});
era_data.length = 3;
era_data[3] = 附加屬性;
era_data.parsed = true;
// 回存。
era_data_array[index] = era_data;
}
return era_data;
}
// 前置處理。
if (!library_namespace.is_Object(options))
options = Object.create(null);
if (!Array.isArray(era_data_array))
era_data_array = [ era_data_array ];
// 主要功能。
var 前一紀年名稱 = [],
//
國家 = options.國家 || parse_era.default_country,
/** {Era}上一紀年資料 @ era_list。 */
last_era_data,
// 紀元所使用的當地之 time zone / time offset (UTC offset by minutes)。
// e.g., UTC+8: 8 * 60 = 480
// e.g., UTC-5: -5 * 60
minute_offset = era_data_array.minute_offset
// 直接將時間設定成「紀元使用地真正之時間」使用。
// (era_data_array.minute_offset || 0) +
// String_to_Date.default_offset
;
function for_era_data(era_data, index) {
if (!(era_data = pre_parse_紀年資料(index)))
return;
var tmp, i, j, k,
// 紀年:紀年名稱
紀年 = era_data[0],
/** {Array}起訖日期 [ {Date}起, {Date}訖, parser ] */
起訖 = era_data[1],
//
曆數 = era_data[2], 附加屬性 = era_data[3];
// 至此已定出 (紀年), (起訖), (曆數), (其他附加屬性),接下來作進一步解析。
if (紀年 && !Array.isArray(紀年))
紀年 = String(紀年).split(pack_era.era_name_classifier);
if (!紀年 || 紀年.length === 0) {
library_namespace.error('parse_era: 無法判別紀年 [' + index
+ '] 之名稱資訊!');
return;
}
library_namespace.debug(
//
'前期準備:正規化紀年 [' + 紀年 + '] 之名稱資訊。', 2);
// 紀年 = [ 朝代, 君主(帝王), 紀年 ]
// 配合 (紀年名稱索引值)
if (紀年.length === 1 && 紀年[0]) {
// 朝代兼紀年:紀年=朝代
前一紀年名稱 = [ 紀年[2] = 紀年[0] ];
} else {
if (!紀年[0] && (tmp = 前一紀年名稱.length) > 0) {
// 填補 inherited 繼承值/預設值。
// 得允許前一位有紀年,後一位無;以及相反的情況。
紀年.shift();
tmp -= 紀年.length;
// 3 = 最大紀年名稱資料長度 = 紀年名稱索引值.國家
Array.prototype.unshift.apply(紀年, 前一紀年名稱.slice(0,
tmp > 1 ? tmp : 1));
}
紀年.forEach(function(name, index) {
if (name === parse_era.inherit) {
if (!前一紀年名稱[index])
library_namespace.error('parse_era: 前一紀年 ['
//
+ 前一紀年名稱 + '] 並未設定 index [' + index + ']!');
紀年[index] = 前一紀年名稱[index] || '';
}
});
// do clone
前一紀年名稱 = 紀年.slice();
if (紀年[1] && !紀年[2])
// 朝代/君主(帝王):紀年=君主(帝王)
紀年[2] = 紀年[1];
}
// 處理如周諸侯國之類。
tmp = 紀年[0].match(國_PATTERN);
// 例如:
// 魯國/昭公 → 魯國/魯昭公
// 秦國/秦王政 → 秦國/秦王政 (no change)
if (tmp && !紀年[1].includes('國') && !紀年[1].includes(tmp = tmp[1])) {
// add_attribute(附加屬性, '君主', tmp[1] + 紀年[1]);
// 直接改才能得到效果。
紀年[1] = tmp + 紀年[1];
}
紀年.reverse();
if (國家) {
if (!紀年[3])
紀年[3] = 國家;
tmp = 紀年[0].match(名稱加稱號_PATTERN);
if (tmp) {
// 為了parse不包括"天皇",如 "推古9年" 的情況。
紀年.push(tmp[1]);
}
}
// assert: 至此
// 前一紀年名稱 = [ 朝代, 君主(帝王), 紀年 ]
// 紀年 = [ 紀年, 君主(帝王), 朝代, 國家 ]
tmp = false;
if (/\d$/.test(紀年[0])) {
tmp = '紀年名稱 [' + 紀年[0] + ']';
} else if (/\d$/.test(紀年[1])) {
tmp = '君主名稱 [' + 紀年[1] + ']';
}
if (tmp) {
tmp = 'parse_era: ' + tmp
//
+ ' 以阿拉伯數字做結尾,請改成原生語言之數字表示法,或如羅馬數字之結尾。'
//
+ '本函式庫以阿拉伯數字標示年分,因此阿拉伯數字結尾之名稱將與年分混淆。';
// 注意: 這邊的警告在載入後會被清空。
library_namespace.warn(tmp);
// throw new Error(tmp);
}
library_namespace.debug(
//
'前期準備:正規化紀年 [' + 紀年 + '] 起訖日期。', 2);
if (!(起訖 = parse_duration(起訖, 紀年)))
if (options.extract_only)
起訖 = [ new Date(0), new Date(0) ];
else {
library_namespace.error('parse_era: 跳過起訖日期錯誤的紀年資料!');
return;
}
if (!起訖[0])
if (index > 0)
// 本一個紀年的起始日期接續上一個紀年。
起訖[0] = era_data_array[index - 1].end;
else if (options.extract_only)
起訖[0] = new Date(0);
else {
library_namespace.error('parse_era: 沒有上一紀年以資參考!');
return;
}
起訖[0] = normalize_date(起訖[0], 起訖[2], false, true);
if (!起訖[0])
throw new Error('parse_era: 未能 parse 起始日期: [' + 紀年 + ']!');
if (起訖[1])
// tmp 於此將設成是否取終點。
tmp = true;
else if ((tmp = pre_parse_紀年資料(index + 1))
// 下一個紀年的起始日期接續本紀年,因此先分解下一個紀年。
// assert: tmp[1](起訖) is String
&& (tmp = parse_duration(tmp[1], tmp[0])) && tmp[0]) {
起訖[1] = tmp[0];
起訖[2] = tmp[2];
// 既然直接採下一個紀年的起始日期,就不需要取終點了。
tmp = false;
} else if (options.extract_only)
起訖[1] = new Date(0);
else {
library_namespace.error('parse_era: 無法求得紀年[' + 紀年.toString()
+ ']之結束時間!');
return;
}
if (持續日數_PATTERN.test(起訖[1])) {
// 訖時間 "+d" : 持續日數
tmp = +起訖[1];
(起訖[1] = normalize_date(起訖[0], 起訖[2], true, true)).setDate(tmp);
} else
// 訖時間 "–y/m/d"
起訖[1] = normalize_date(起訖[1], 起訖[2], tmp, true);
last_era_data = {
// 紀年名稱資訊(範疇小→大)
// [ 紀年, 君主(帝王), 朝代, 國家, 其他搜尋 keys ]
name : 紀年,
// {Date}起 標準時間(如UTC+8),開始時間.
start : 起訖[0],
start_JDN : library_namespace.date.Date_to_JDN(起訖[0]),
// {Date}訖 標準時間(如UTC+8), 結束時間.
end : 起訖[1],
end_JDN : library_namespace.date.Date_to_JDN(起訖[1]),
// 共存紀年/同時存在紀年 []:
// 在本紀年開始時尚未結束的紀年 list,
contemporary : [],
// 年分起始 Date value (搜尋用) [ 1年, 2年, .. ],
// year_tart:[],
// 曆數/歷譜資料:
// 各月分資料 [ [1年之月分資料], [2年之月分資料], .. ],
// 這邊還不先作處理。
calendar : 曆數
// { 其他附加屬性 : .. }
};
// 處理 time zone / time offset (UTC offset by minutes)
if (!isNaN(minute_offset)) {
// 注意:這邊不設定真正的 date value,使得所得出的值為「把本地當作紀元所使用的當地」所得出之值。
last_era_data[MINUTE_OFFSET_KEY] = minute_offset;
// set_minute_offset(起訖[0], minute_offset, true);
// set_minute_offset(起訖[1], minute_offset, true);
}
// assert: 至此
// 起訖 = [ 起 Date, 訖 Date, parser ]
last_era_data = new Era(last_era_data);
library_namespace.debug('add period [' + 紀年 + ']。', 2);
i = 紀年名稱索引值.國家;
k = undefined;
tmp = period_root;
// [ , 君主, 朝代, 國家 ]
var period_attribute_hierarchy = [];
for (var start = 起訖[0].getTime(),
//
end = 起訖[1].getTime();;) {
// 若本 era 之時間範圍於原 period 外,
// 則擴張 period 之時間範圍以包含本 era。
if (!(tmp.start <= start))
tmp.start = start;
if (!(end <= tmp.end))
tmp.end = end;
if (!(j = 紀年[i]) || i <= 0) {
if (j || (j = k)) {
if (!tmp.era)
tmp.era = Object.create(null);
add_attribute(tmp.era, j, last_era_data);
if (library_namespace.is_debug()
&& Array.isArray(tmp.era[j]))
library_namespace.warn(
//
'add_attribute: 存在相同朝代、名稱重複之紀年 '
+ tmp.era[j].length + ' 個: '
+ last_era_data);
}
break;
}
k = j;
if (!(j in tmp.sub))
tmp.add_sub(start, end, j);
period_attribute_hierarchy[i--]
// move to sub-period.
= (tmp = tmp.sub[j]).attributes;
}
library_namespace.debug('設定紀年[' + 紀年 + ']之搜尋用 index。', 2);
紀年.forEach(function(era_token) {
add_to_era_by_key(era_token, last_era_data);
});
library_namespace.debug(
//
'正規化紀年 [' + 紀年 + '] 之其他屬性。', 2);
for (i in 附加屬性) {
j = 附加屬性[i];
if (i in Period_屬性歸屬) {
i = Period_屬性歸屬[tmp = i];
// now: tmp = name,
// i = Period_屬性歸屬 index of name
// e.g., tmp = 君主名, i = 1
// 解開屬性值。
// j = 'a;b' → k = [ 'a', 'b' ]
if (Array.isArray(j)) {
k = [];
j.forEach(function(name) {
Array_push(k, name
//
.split(pack_era.era_name_separator));
});
} else
k = j.split(pack_era.era_name_separator);
// 將屬性值搬移至 period_root 之 tree 中。
// i === 0,即紀元本身時,毋須搬移。
// 使用者測試資料時,可能導致 j 為 undefined。
if (0 < i && (j = period_attribute_hierarchy[i])) {
// j: attributes of hierarchy[i]
// assert: Object.isObject(j)
if (tmp in j)
// 解決重複設定、多重設定問題。
// assert: Array.isArray(j[tmp])
Array_push(j[tmp], k);
else
j[tmp] = k;
// 僅將(留下)君主、紀元年號相關的附加屬性供查閱,其他較高階的朝代、國家等則省略之。
// 恐還需要更改 ((sign_note.copy_attributes))!
if (Period_屬性歸屬[tmp] <= Period_屬性歸屬.君主)
add_attribute(last_era_data, tmp, j[tmp]);
// 實際效用:將此屬性搬移、設定到 period_root 之 tree 中。
delete 附加屬性[tmp];
}
if (tmp in 紀年名稱索引值) {
library_namespace.debug(
// 設定所有屬性值之 search index。
'設定紀年[' + 紀年 + ']之次要搜尋用 index ['
// 例如: 元太祖→大蒙古國太祖
+ tmp + '] (level ' + i + ')。', 2);
k.forEach(function(name) {
if (name
//
&& !紀年.includes(name)) {
add_to_era_by_key(name,
// 對 i 不為 0–2 的情況,將 last_era_data 直接加進去。
i >= 0 ? 紀年[i] : last_era_data);
// 實際效用:除了既定的((紀年名稱索引值))外,
// ((紀年)) 都被拿來放屬性索引值。
// TODO:
// 對其他同性質的亦能加入此屬性。
// 例如設定
// "朝代=曹魏"
// 則所有曹魏紀年皆能加入此屬性,
// 如此則不須每個紀年皆個別設定。
if (i === 0)
// ((紀年)) === last_era_data.name
紀年.push(name);
}
});
}
} else if (i === '月名' || i === MONTH_NAME_KEY) {
if (j = parse_month_name(j, last_era_data[MONTH_NAME_KEY]))
last_era_data[MONTH_NAME_KEY] = j;
} else
add_attribute(last_era_data, i, j);
}
// era.精=:歷史上這個時期曆法與公元的對照本來就無法追溯得精準至日,甚至曆法本身就不夠精準。
// era.準=:歷史上這個時期曆法與公元的對照應該非常精準,但是本數據庫的資料準確程度不足。
// era.疑=:歷史上這個時期曆法與公元的對照應該非常精準,本數據庫的資料尺度標示也很精準,但是本數據庫的資料實際上存在疑問、可能不準確。
// era.傳說=:為傳說時代/神話之資料
// 處理 accuracy/準度/誤差/正確度。
if (!last_era_data.準) {
for (i in 準確程度_ENUM) {
// 這裡會設定如 era.準 = "疑"
if (last_era_data[i]) {
last_era_data.準 = i;
break;
}
}
}
// check 準度。
if (i = last_era_data.準) {
if (!/^\d*[年月日]$/.test(i) && !(i in 準確程度_ENUM))
library_namespace.warn('parse_era: 未支援紀年[' + 紀年
+ ']所指定之準確度:[' + i + ']');
if (!last_era_data.calendar && !last_era_data.精)
last_era_data.精 = '年';
}
// 處理 precision/精度準度/精密度準確度。
// cf. https://en.wikipedia.org/wiki/Module:Wikidata
i = last_era_data.精;
if (i === '年') {
if (!last_era_data.calendar)
last_era_data.calendar
// 自動指定個常用的曆法。
= ':' + standard_time_parser;
last_era_data.大月 = CE_MONTH_DAYS;
} else {
if (i && i !== '月' && i !== '日')
library_namespace.warn('parse_era: 未支援紀年[' + 紀年
+ ']所指定之精密度:[' + i + ']');
if (('歲首' in last_era_data)
// 此處之"歲首"指每年開始之月序數,當前農曆為1。秦曆始於10。
// 惟曆法上之"歲首"指每歲起算點(之月序數)。當前農曆之"歲"指冬至月首至冬至月首之間,"年"指正月首(1月1日)至正月首之間,故歲首為11月1日夜半(子夜時刻)。
&& (i = last_era_data.歲首 | 0) !== START_MONTH
//
&& 0 < i && i <= LUNISOLAR_MONTH_COUNT)
last_era_data.歲首序 = i - START_MONTH;
if (!(0 < (last_era_data.大月 |= 0)) || last_era_data.大月 === 大月)
delete last_era_data.大月;
}
if (last_era_data.參照用) {
library_namespace.debug(
//
'為使後來的操作能利用此新加入紀年 [' + last_era_data
//
+ '],重新設定 era_search_pattern。', 3);
era_search_pattern = null;
}
if (options.extract_only)
return;
i = era_list.length;
if (i === 0) {
era_list.push(last_era_data);
return;
}
if (起訖[0] - era_list[i - 1].end === 0) {
// assert: 本紀年接續著上一個紀年。
if (紀年[1] !== era_list[i - 1].name[1]) {
last_era_data.name.前任 = era_list[i - 1].name;
var _i = i, _前任 = era_list[i - 1].name[1];
while (_i-- > 0 && _前任 === era_list[_i].name[1]) {
era_list[_i].name.繼任 = 紀年;
}
} else if (era_list[i - 1].name.前任) {
last_era_data.name.前任 = era_list[i - 1].name.前任;
}
}
var start = 起訖[0], start_JDN = last_era_data.start_JDN,
//
contemporary = last_era_data.contemporary;
// 紀年E 插入演算:
// 依紀年開始時間,以 binary search 找到插入點 index。
i -= 4;
// 因為輸入資料通常按照時間順序,
// 因此可以先檢查最後幾筆資料,以加快速度。
if (i < 9) {
i = 0;
} else if (0 < era_list[i].start - start) {
i = era_list.search_sorted(last_era_data, {
comparator : compare_start_date,
found : true,
start : 0
});
}
// 這一段其實可以不要。下一段while()可以補充這一段的功能。但是使用`.start_JDN`應該會比`.start`快一點點。
while (i < era_list.length && era_list[i].start_JDN < start_JDN) {
i++;
}
// assert: era_list[i].start_JDN >= start_JDN
while (i < era_list.length && era_list[i].start - start <= 0) {
// 預防本紀年實為開始時間最早者,
// 因此在這邊才處理是否該插入在下一 index。
// 因為 .search_sorted(, {found : true})
// 會回傳 <= 的值,
// 因此應插入在下一 index。
// 這方法還會跳過相同時間的紀年,將本紀年插入在相同時間的紀念群最後面,成為最後一個。
// 需要注意: [MINUTE_OFFSET_KEY]將會有作用,會按照時區排列。
i++;
}
// 以 Array.prototype.splice(插入點 index, 0, 紀年) 插入紀年E,
// 使本紀年E 之 index 為 (插入點 index)。
era_list.splice(i, 0, last_era_data);
// 向後處理"共存紀年" list:
// 依紀年開始時間,
// 將所有紀年E 之後(其開始時間 >= 紀年E 開始時間),
// 所有開始時間在其結束時間前的紀年,
// 插入紀年E 於"共存紀年" list。
for (k = last_era_data.end_JDN,
// 從本紀年E 之下個紀年起。
j = i + 1; j < era_list.length; j++) {
// next {Era}
tmp = era_list[j];
if (tmp.start_JDN < k) {
tmp = tmp.contemporary;
tmp.push(last_era_data);
if (tmp.length > 1) {
// 不能保證依照 紀年開始時間 時序,應該插入在最後。
tmp.sort(compare_start_date);
}
} else
break;
}
// 處理與`last_era_data`同時開始之`.共存紀年` list:
j = [];
while (i > 0 && (tmp = era_list[--i]).start_JDN === start_JDN) {
// tmp: 與`last_era_data`同時開始的紀年。
j.unshift(tmp);
tmp.contemporary.push(last_era_data);
}
// 向前處理"共存紀年" list:
// 檢查前一紀年,
// 與其"在本紀年開始時尚未結束的紀年 list",
// 找出所有(其結束時間 period_end > 紀年E 開始時間)之紀年,
// 將之插入紀年E 之"共存紀年" list。
tmp = era_list[i];
tmp.contemporary.concat(tmp).forEach(function(era) {
if (era.end - start > 0)
contemporary.push(era);
});
// 為了按照 紀年開始時間 順序排列。
if (j.length > 0)
Array_push(contemporary, j);
}
era_data_array.forEach(for_era_data);
if (last_era_data) {
if (options.extract_only) {
last_era_data.initialize();
return last_era_data;
}
// 當有新加入者時,原先的 pattern 已無法使用。
era_search_pattern = null;
}
}
// ---------------------------------------------------------------------//
// 工具函數。
/**
* 對於每個朝代,逐一執行 callback。
*
* @param {Function}callback
* callback(dynasty_name, dynasty);
* @param {String|RegExp}[filter]
* TODO
*/
function for_dynasty(callback, filter) {
for ( var nation_name in period_root.sub) {
var nations = period_root.sub[nation_name].sub;
for ( var dynasty_name in nations)
callback(dynasty_name, nations[dynasty_name]);
}
}
/**
* 對於每個君主,逐一執行 callback。
*
* @param {Function}callback
* callback(monarch_name, monarch);
* @param {String|RegExp}[filter]
* TODO
*/
function for_monarch(callback, filter) {
for ( var nation_name in period_root.sub) {
var nations = period_root.sub[nation_name].sub;
for ( var dynasty_name in nations) {
var dynasty = nations[dynasty_name].sub;
for ( var monarch_name in dynasty)
callback(monarch_name, nations[monarch_name]);
}
}
}
/**
* 為 era Date 添加上共存紀年。
*
* @param {Date}date
* @param {Era}[指定紀年]
* 主要紀年
* @param {Object}[options]
* 附加參數/設定特殊功能與選項
*
* @returns {Array} 共存紀年
*/
function add_contemporary(date, 指定紀年, options) {
var tmp, date_index, time_offset = date.getTimezoneOffset()
* ONE_MINUTE_LENGTH_VALUE,
// 以當日為單位而非採用精準時間
use_whole_day = options && ('use_whole_day' in options)
//
? options.use_whole_day
//
: 'precision' in date ? date.precision === 'day'
//
: date % ONE_DAY_LENGTH_VALUE === time_offset,
// 沒意外的話,共存紀年應該會照紀年初始時間排序。
// 共存紀年.start <= date < 共存紀年.end
共存紀年,
// 某時間點(時刻)搜尋演算:
era_index = (options && Array.isArray(options.list)
// 查詢某時間點(時刻)存在的所有紀年與資訊:
// 依紀年開始時間,以 binary search 找到插入點 index。
? options.list : era_list).search_sorted({
start : date
}, {
comparator : compare_start_date,
found : true
}),
//
紀年 = era_list[era_index];
if ((紀年.精 || 紀年.準
// 準確程度_ENUM
|| 紀年.疑 || 紀年.傳說) && (tmp = options && options.尋精準)) {
tmp = Math.max(era_index - Math.max(2, tmp | 0), 0);
for (date_index = era_index; date_index > tmp
// 使用這方法不能保證無漏失,應該使用 (紀年.contemporary)。
&& (共存紀年 = era_list[--date_index]).end - date > 0;)
if (!共存紀年.精 && !共存紀年.準 && !共存紀年.疑
// 盡可能向前找到精密暨準確的紀年。
&& 共存紀年.Date_to_date_index(date)) {
era_index = date_index;
紀年 = 共存紀年;
break;
}
}
if (era_index === 0 && date - 紀年.start < 0) {
if (library_namespace.is_debug())
library_namespace.warn('add_contemporary: 日期 ['
+ date.format(standard_time_format) + '] 在所有已知紀年之前!');
return;
}
// 至此 (era_list[era_index].start <= date)
// 除非 date < era_list[0].start,那麼 (era_index===0)。
共存紀年 = [];
// 向前找。
紀年.contemporary
//
.forEach(function(era) {
// 檢查其"共存紀年" list,
// 找出所有(所求時間 < 其結束時間 period_end)之紀年,即為所求紀年。
if (date - era.end < 0 && (!era.參照用 || options.含參照用))
共存紀年.push(era);
});
// 本紀年本身+向後找。
// 為了待會取未交疊的相同國家紀年作為前後紀年,這邊不改變 era_index。
for (date_index = era_index;
//
date_index < era_list.length; date_index++) {
tmp = era_list[date_index];
if (date - tmp.start < 0)
break;
else if (date - tmp.end < 0 && (!tmp.參照用 || options.含參照用))
共存紀年.push(tmp);
}
if (options.era_only)
return 共存紀年;
if (指定紀年) {
var 指定紀年名 = 指定紀年.name;
if (Array.isArray(指定紀年名))
指定紀年名 = 指定紀年名[0] || 指定紀年名[2];
tmp = 共存紀年;
共存紀年 = [];
tmp.forEach(function(era) {
// 去除指定紀年本身。
if (era === 指定紀年)
tmp = null;
// 避免循環參照。
else if (era.year_start || era.參照紀年 !== 指定紀年名)
共存紀年.push(era);
});
if (tmp)
// 不包含指定紀年本身。
指定紀年 = null;
else
// 包含指定紀年本身。
紀年 = 指定紀年;
}
// 取未交疊的相同國家紀年作為前後紀年。
tmp = era_index;
while (0 < tmp--)
if (era_list[tmp].end - 紀年.start <= 0
// 相同國家
&& era_list[tmp].name[3] === 紀年.name[3]) {
date.前紀年 = era_list[tmp].toString();
break;
}
tmp = era_index;
while (++tmp < era_list.length)
if (紀年.end - era_list[tmp].start <= 0
// 相同國家
&& era_list[tmp].name[3] === 紀年.name[3]) {
date.後紀年 = era_list[tmp].toString();
break;
}
// 作結尾檢測 (bounds check)。
if (紀年.end - date <= 0) {
if (指定紀年) {
if (library_namespace.is_debug())
library_namespace.warn(
//
'add_contemporary: 日期 ['
+ date.format(standard_time_format) + '] 在指定紀年 ['
+ 指定紀年 + '] 之後!');
return;
}
if (共存紀年.length === 0) {
if (library_namespace.is_debug())
library_namespace.warn('add_contemporary: 日期 ['
+ date.format(standard_time_format)
+ '] 在所有已知紀年之後!');
return;
}
紀年 = 共存紀年[0];
}
// 至此已確定所使用紀年。
共存紀年.紀年 = 紀年;
if (共存紀年.length > 0) {
if (typeof options.contemporary_filter === 'function')
共存紀年 = 共存紀年.filter(options.contemporary_filter);
tmp = [];
共存紀年.forEach(function(era) {
if (date_index = era.Date_to_date_index(date
// 轉成目標共存紀年的當日零時。
- time_offset + (era[MINUTE_OFFSET_KEY] || 0)
* ONE_MINUTE_LENGTH_VALUE)) {
// .日名(日序, 月序, 歲序) = [ 日名, 月名, 歲名 ]
date_index = era.日名(date_index[2], date_index[1],
date_index[0]).reverse();
if (options.numeral) {
date_index = numeralize_date_format(date_index,
options.numeral);
}
// [ era, 年, 月, 日 ]
var name = [ era ];
name.toString = function() {
return this.join('');
};
// add properties needed.
for ( var 準確程度 in 準確程度_ENUM) {
if (era[準確程度]) {
// 特別標示存在疑問、不準確的紀年。
name[準確程度] = era[準確程度];
}
}
// 為需要以 space 間隔之紀元名添加 space。
if (NEED_SPLIT_POSTFIX.test(name))
name.push(' ');
name.push(date_index[0] + (
// era.年名 ||
POSTFIX_年名稱));
if (era.精 !== '年') {
name.push(date_index[1] + '月');
if (era.精 !== '月')
name.push(date_index[2]
+ (options.numeral === 'Chinese'
//
? '' : '日'));
}
if (options.add_country)
name = [ era.name[紀年名稱索引值.國家], name ];
tmp.push(name);
}
});
if (tmp.length > 0)
date.共存紀年 = tmp;
}
return 共存紀年;
}
// e.g., UTC+8: -8 * 60 = -480
var present_local_minute_offset = (new Date).getTimezoneOffset() || 0;
function offseted_value(minute_offset) {
if (minute_offset === undefined)
minute_offset = this[MINUTE_OFFSET_KEY];
else if (minute_offset === '') {
// 可用來還原 local 之時間。
minute_offset = -this.getTimezoneOffset() || 0;
}
if (!isNaN(minute_offset)) {
if (isNaN(this.original_value))
this.original_value = this.getTime();
return this.original_value
- (minute_offset + (this.getTimezoneOffset() || 0))
* ONE_MINUTE_LENGTH_VALUE;
}
}
function adapt_minute_offset(minute_offset) {
var offseted_value = this.offseted_value(minute_offset);
if (!isNaN(offseted_value))
this.setTime(offseted_value);
return this;
}
function add_offset_function(date, 紀年) {
if (MINUTE_OFFSET_KEY in 紀年) {
date[MINUTE_OFFSET_KEY] = 紀年[MINUTE_OFFSET_KEY];
date.offseted_value = offseted_value;
// 注意:這邊不更改真正的 date value,使得所得出的值為「把本地當作紀元所使用的當地」所得出之值。
// 例如求 "東漢明帝永平1年1月1日",
// 得到的是 date = "58/2/13 0:0 (UTC-5)",
// 實際上只是把本地(例如紐約)當作中國時,所得之時間。
// 若須得到「紀元使用地真正之時間」(中國真正之時間) "58/2/13 0:0 (UTC+8)",
// 則得使用 date.adapt_offset()。
// 再使用 date.adapt_offset(''),
// 或 date.adapt_offset(-5*60),
// 或 date.adapt_offset(-date.getTimezoneOffset()),
// 可以還原 local 之時間。
if (false)
date.adapt_offset = adapt_minute_offset;
}
}
// ---------------------------------------------------------------------//
// 應用功能。
/**
* 取得 year CE 當年,特定之月日之日期。
*
* @example
// gettext_config:{"id":"china"}
CeL.era.Date_of_CE_year(1850, 1, 1, '中國');
CeL.era.Date_of_CE_year(1850);
*
* @param {Integer}year
* CE year
* @param {Integer}[月]
* month of era. default: START_MONTH = 1.
* @param {Integer}[日]
* date of era. default: START_DATE = 1.
* @param {String}[era_key] //
* gettext_config:{"id":"china"} e.g., '中國'
*
* @returns {Date}
*
* @since 2014/12/15 20:32:43
*
*/
function get_Date_of_key_by_CE(year, 月, 日, era_key) {
var 日期,
// 7: 年中, (1 + LUNISOLAR_MONTH_COUNT >> 1)
date = new Date((year < 0 ? year : '000' + year) + '/7/1'),
//
共存紀年 = add_contemporary(date, null, {
era_only : true,
尋精準 : true,
list : !era_key || !(era_key = get_Date_of_key_by_CE.default_key)
//
? era_list : get_era_Set_of_key(era_key).values()
});
共存紀年.forEach(function(紀年) {
if (!日期) {
// [ 歲序, 月序, 日序 | 0 ]
var date_index = 紀年.Date_to_date_index(date);
日期 = 紀年.date_name_to_Date(紀年.歲名(date_index[0]), 月, 日, true);
}
});
return 日期;
}
// gettext_config:{"id":"china"}
get_Date_of_key_by_CE.default_key = '中國';
// ---------------------------------------------------------------------//
// 應用功能。
/**
* date.getTimezoneOffset() 這個數字會隨著夏令時間、各個歷史時代而作調整,不一定和當前的時區相同。
[
[
[ 紀年(1年-2年), 紀年(3年-4年) ]
],
[
[ 紀年(5年-6年), 紀年(7年-8年) ],
[ 紀年(6年-7年), 紀年(8年-9年) ]
]
]
*
*
* @param {Array|String}hierarchy
* 指定層次/關鍵字。
* @param {Object}[options]
* 附加參數/設定特殊功能與選項.
*
* @returns {Array}紀年列表。(new Date).format('Chinese')
era_to_export = {
Chinese : {
era : '中曆',
format : '%歲次年%月月%日'
}
};
Date_to_String_parser.era = Date_to_era_String;
for (era_name in era_to_export) {
Date_to_String_parser[era_name] = Date_to_era_String.bind(
//
era_data = era_to_export[era_name]);
if (!era_data.numeral)
era_data.numeral = era_name;
if (!era_data.locale)
era_data.locale = library_namespace.gettext
.to_standard(era_name);
}
})();
// ---------------------------------------------------------------------//
// 網頁應用功能。
// warning: need CeL.interact.DOM
// UNDONE
function determain_node_era(node) {
var node_queue = [];
var era_data = library_namespace.DOM_data(node, 'era');
}
/**
* 計算已具紀年標記之指定 HTML node 之紀年值。
*
* @param {ELEMENT_NODE}node
* 具紀年標記之指定 node。
* @param {Boolean}[return_type]
* 回傳的型別。 'String': 僅回傳 era String。
*
* @returns [range] || {String}date
*/
function calculate_node_era(node, return_type) {
var era, date, previous_date_to_check, original_era,
// data-era: read-only
era_data = library_namespace.DOM_data(node, 'era');
if (!era_data) {
// no era data. Not a era node. Skip this node.
return;
}
// 看看是不是有之前解析、驗證過的cache。
if (return_type === 'String'
&& (era = library_namespace.DOM_data(node, 'era_refrenced'))) {
return era;
}
era = library_namespace.DOM_data(node, 'era_parsed');
// console.log(era);
if (!era) {
// determain node era
if (false) {
var node_queue = [], era_map = new Map;
// 自身不完整。溯前尋找 base。
for (var node_to_test = node;;
//
node_to_test = library_namespace.previous_node_of(node_to_test)) {
if (!node_to_test) {
break;
}
if (!node_to_test.tagName
|| node_to_test.tagName.toLowerCase() !== set_up_era_nodes.default_tag) {
continue;
}
// console.log(node_to_test);
era = library_namespace
.DOM_data(node_to_test, 'era_parsed');
if (era) {
node_queue.unshift(node_to_test);
continue;
}
var era_data = library_namespace.DOM_data(node_to_test,
'era');
era = library_namespace.set_text(node_to_test);
if (era_data !== '~') {
// '~':如英語字典之省略符號,將以本node之內含文字代替。
era = era_data.replace('~', era);
}
// console.log(era);
// 去除(干支_PATTERN): 預防"丁未"被 parse 成丁朝之類的意外。
date = !干支_PATTERN.test(era)
// 預防被解析為明朝的年份。
&& !era.startsWith('明年') && to_era_Date(era, {
parse_only : true
});
if (!date || !date[1]) {
continue;
}
// date: [ {Set}紀年_list, {Era}紀年, 年, 月, 日 ]
node_queue.unshift([ node_to_test, date, date[0].size ]);
if (node_queue.length > 3 && date[0].size === 1) {
// 找到了準確認判斷出的。
break;
}
// console.log(date[0]);
date[0].forEach(function(era) {
// console.log(era);
era_map.set(era,
era_map.has(era) ? era_map.get(era) + 1 : 1);
});
}
console.log([ node_queue, era_map ]);
return;
}
// ------------------------------------
// 解析 era。
era = library_namespace.set_text(node);
if (era_data !== '~') {
// '~':如英語字典之省略符號,將以本node之內含文字代替。
era = era_data.replace('~', ' ' + era);
}
// console.log([ 'era:', era ]);
// 去除(干支_PATTERN): 預防"丁未"被 parse 成丁朝之類的意外。
date = !干支_PATTERN.test(era)
// 預防被解析為明朝的年份。
&& !era.startsWith('明年') && to_era_Date(era, {
parse_only : true
});
var previous_date = undefined,
//
get_previous_date = function() {
var node_queue = [];
// 自身不完整。溯前尋找 base。
var node_to_test = node;
while (node_to_test = library_namespace
.previous_node_of(node_to_test)) {
// 向前取第一個可以明確找出日期的。
if (previous_date = calculate_node_era(node_to_test,
'String'))
break;
}
return previous_date;
};
// date: [ {Set}紀年_list, {Era}紀年, 年, 月, 日 ]
if (!date || !date[1]) {
if (!get_previous_date()
// && (!date || !date[1])
) {
return;
}
original_era = date = to_era_Date(era, {
parse_without_check : true
});
// console.log([ 'original_era:', original_era ]);
if (original_era && original_era[0]
&& original_era[0].size === 1) {
original_era = Array.from(original_era[0])[0];
} else {
original_era = null;
}
date = to_era_Date(era, {
parse_only : true,
base : previous_date
});
// console.log([ previous_date, date.join(', ') ]);
if (!date[1])
return;
// 檢查本節點有幾項資料。
previous_date_to_check = [];
date.slice(2).forEach(function(name, index) {
if (name)
previous_date_to_check.push(index);
});
if (previous_date_to_check.length === 1) {
previous_date_to_check.unshift(previous_date);
} else {
previous_date_to_check = null;
}
}
// assert: date: [ {Set}紀年_list, {Era}紀年, 年, 月, 日 ]
var era_list = date.shift();
if (era_list && era_list.size > 1) {
// 當存在有多個可能的紀年時,應該從前文來篩選出比較可能的一個。
if (previous_date || get_previous_date()) {
// console.log(previous_date);
// console.log(era);
era_list = to_era_Date(era, {
parse_only : true,
base : to_era_Date(previous_date, {
date_only : true
})
});
// console.log(era_list);
era_list.shift();
if (era_list[0] && era_list[1]) {
// 確定可以找到時,才採用以{Date}為準的日期。
date = era_list;
} else {
// e.g., "永曆二年" + "閏六月"
}
// assert: 必然會選出最接近的一個紀年。
era_list = null;
} else {
library_namespace.warn('calculate_node_era: [' + era
//
+ ']: 共取得 ' + era_list.size + ' 個可能的紀年名稱: '
//
+ Array.from(era_list).map(function(era) {
// 大約年份
return era + ' (' + era.start.format('%Y') + ')';
}).join(', '));
}
} else {
era_list = null;
}
era = date.shift();
if (!era) {
// e.g., 昭宗永曆 注
return;
}
if (Array.isArray(era.name)) {
// 當有多個可能的紀年名稱時,僅取紀年名,保留最大可能性。
era = era_list ? era.name[0] : era.toString();
}
if (original_era
&& era !== (original_era = original_era.toString())) {
library_namespace.debug('本節點本來就指定了紀年名稱[' + original_era
+ '],因此當作後續節點之參考時,將使用原先的紀年,而不採用解析出的紀年[' + era + ']。');
} else {
original_era = null;
}
// console.log([ 'date:', date.join(', '), 'era:', era ]);
var date_name = date.shift();
if (date_name) {
var tmp = date_name + (
// original_era && original_era.年名 ||
POSTFIX_年名稱);
era += tmp;
if (original_era)
original_era += tmp;
if (date_name = date.shift()) {
tmp = date_name + '月';
era += tmp;
if (original_era)
original_era += tmp;
if (date_name = date.shift()) {
tmp = date_name + '日';
era += tmp;
if (original_era)
original_era += tmp;
}
}
}
// assert: {String}era
// cache.
if (original_era)
library_namespace.DOM_data(node, 'era_refrenced', original_era);
library_namespace.DOM_data(node, 'era_parsed', era);
}
var era_date = to_era_Date(era);
if (era_date.error) {
node.title = era + ': ' + era_date.error;
// 假如有明確指定紀年名稱,則依然可參照之。
var tmp = to_era_Date(library_namespace.set_text(node), {
parse_only : true
});
if (false)
console.log([ node, library_namespace.set_text(node), tmp, era,
era_date ]);
if (!tmp || !tmp[1] || tmp[1].toString() !== era_date.紀年名) {
library_namespace.set_class(node, 'era_text', {
remove : true
});
// reset event / style
node.style.cursor = '';
node.onclick = node.onmouseover = node.onmouseout = null;
return;
}
}
if (return_type === 'String') {
if (previous_date_to_check) {
var error = null,
//
previous_date = to_era_Date(previous_date_to_check[0]);
// 當僅有年月日其中一項資料的時候,比較有可能是判讀、解析錯誤。因此某些情況下不拿來當作參考對象。
if (previous_date - era_date > 0) {
error = '時間更早';
} else {
var diff_in_2_months = (era_date - previous_date)
/ (2 * 大月 * ONE_DAY_LENGTH_VALUE);
if (previous_date_to_check[1] === 2 ? diff_in_2_months > 1
// ↑ 僅有日期資料。 ↓ 僅有月份資料。
: previous_date_to_check[1] === 1 ? diff_in_2_months > 12
// 當間隔過大,例如超過80年時,則跳過這一筆。
: diff_in_2_months > 40 * 12) {
error = '間距過長';
}
}
if (error) {
node.title = era + ': ' + error;
library_namespace.warn('calculate_node_era: 本節點[' + era
+ ']比起前一個節點[' + previous_date_to_check[0] + ']'
+ error + ',且只有一項資料['
+ '年月日'.charAt(previous_date_to_check[1])
+ '],因此跳過本節點而取前一個節點。');
return;
}
}
// console.log([ 'trace back to', era ]);
return /* !era_date.error && */era;
}
if (era_date.error) {
return;
}
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time
node.setAttribute('datetime', era_date.toISOString());
if (return_type === 'Date')
return era_date;
date = era_date.format(calculate_node_era.era_format);
var tmp = to_era_Date(era, {
get_range_String : calculate_node_era.format
});
if (tmp.includes(PERIOD_DASH))
date += '起';
tmp = [ era, date, tmp ];
// TODO: era_date.精;
// TODO: era_date.準;
if (era_date.共存紀年) {
// old: ☼
date = '
// test cases:
地皇三年,天鳳六年改為地皇。
改齊中興二年爲天監元年
以建平二年為太初元年
一年中地再動
大酺五日
乃元康四年嘉谷
(玄宗開元)十年
道光十九年正月廿五
未及一年
去年
明年
是[年月日]
《清華大學藏戰國竹簡(貳)·繫年》周惠王立十又七年
歲 次丙子四月丁卯
*/
史籍紀年_PATTERN = [
// 識別干支紀年「年號+干支(年)」。
'(?:' + 紀年 + ')+干支年?',
// 一般紀年. 立: 周惠王立十又七年, [)]: (玄宗開元)十年
'(?:' + 紀年 + ')*(?:)|\\)|立)?' + 年 + '(?:' + 月 + 日 + '?)?',
'(?:' + 月 + ')?' + 日, 月 ];
// console.log(史籍紀年_PATTERN);
史籍紀年_PATTERN = generate_pattern(
// 0: head 為爲乃
'(^|[^酺])'
// 1: era
+ '(' + 史籍紀年_PATTERN.join('|') + ')'
// 2: tail
+ '([^中]|$)', false, 'g');
// console.log(史籍紀年_PATTERN);
return 史籍紀年_PATTERN;
};
/**
* 標注文本: 直接處理一整個 HTML 元素,加上帝王紀年/年號紀年標示。
*
* @example
CeL.run([ 'data.date.era', 'interact.DOM' ]);
CeL.env.era_data_load = function(country, queue) {
if (!queue) {
CeL.era.note_node('#mw-content-text', { add_date : true });
}
};
*
*/
function add_era_note(node, options) {
library_namespace.for_nodes(function(node, index) {
// console.log(node);
var text;
if (node.nodeType !== document.TEXT_NODE
|| !(text = library_namespace.set_text(node)).trim()) {
return;
}
var HTML = era_text_to_HTML(text, null, options);
if (text === HTML) {
// 沒有改變。處理下一個。
return;
}
var last_node = node, parentNode = node.parentNode,
//
container = document.createElement(parentNode.tagName || 'div');
container.innerHTML = HTML;
// console.log(container);
library_namespace.get_tag_list(container.childNodes).reverse()
// node.parentNode.childNodes[index] === node;
.forEach(function(n) {
parentNode.insertBefore(n, last_node);
last_node = n;
if (false && n.tagName && n.tagName.toLowerCase()
// TODO: useless...
=== set_up_era_nodes.default_tag)
set_up_era_node(n, options);
});
// 去掉原先的文字節點。
node.parentNode.removeChild(node);
}, node, {
traversal : true
});
set_up_era_nodes(null, options);
}
// ---------------------------------------------------------------------//
// export 導出.
Object.assign(to_era_Date, {
set : parse_era,
pack : pack_era,
extract : extract_calendar_data,
periods : get_periods,
// normalize_date : normalize_date,
get_candidate : get_candidate,
dates : get_dates,
era_list : create_era_search_pattern,
for_dynasty : for_dynasty,
for_monarch : for_monarch,
numeralize : numeralize_date_name,
中曆 : 公元年_中曆月日,
NEED_SPLIT_PREFIX : NEED_SPLIT_PREFIX,
NEED_SPLIT_POSTFIX : NEED_SPLIT_POSTFIX,
concat_name : concat_era_name,
reduce_name : reduce_era_name,
compare_start : compare_start_date,
Date_of_CE_year : get_Date_of_key_by_CE,
MINUTE_OFFSET_KEY : MINUTE_OFFSET_KEY,
// 網頁應用功能。
node_era : calculate_node_era,
setup_nodes : set_up_era_nodes,
to_HTML : era_text_to_HTML,
note_node : add_era_note,
//
PERIOD_PATTERN : PERIOD_PATTERN
}, sign_note.notes);
// 加工處理。
(function() {
function note_proxy(date_value, options) {
return this(options
//
&& options.original_Date || date_value);
}
var notes = sign_note.notes;
for ( var name in notes)
notes[name]
//
= note_proxy.bind(notes[name]);
})();
Object.assign(sign_note.notes, {
// 注意:依 .format() 之設定,在未設定值時將採本處之預設。
// 因此對於可能不設定的值,預設得設定為 ''。
// 講述東周歷史的兩部典籍《春秋》和《戰國策》都是使用帝王紀年。
// 共伯和/周定公、召穆公
// 國號
朝代 : '',
// 君主(帝王)號
君主 : '',
// 共和
// 君主(帝王)/年號/民國
紀年 : '',
紀年名 : '',
// 季節:
// 立春到立夏前為春季,立夏到立秋前為夏季,立秋到立冬前為秋季,立冬到立春前為冬季。
年 : '(年名)',
月 : '(月名)',
日 : '(日名)',
// 重新定義 (override)
// 東漢四分曆以前,用歲星紀年和太歲紀年(歲星:木星)。到現在來用干支紀年。
// 干支紀年萌芽於西漢,始行於王莽,通行於東漢後期。
歲次 : function(date_value, options) {
return (options
//
&& options.original_Date || date_value).歲次
|| library_namespace.guess_year_stem_branch(date_value,
options);
},
// 重新定義 (override) alias
// gettext_config:{"id":"year-of-the-sexagenary-cycle"}
年干支 : '歲次',
// gettext_config:{"id":"year-of-the-sexagenary-cycle"}
年柱 : '歲次',
// 星座 : '',
// 佔位:會引用 Date object 本身的屬性。
// see strftime()
// gettext_config:{"id":"month-of-the-sexagenary-cycle"}
月干支 : '月干支',
// 每年正月初一即改變干支,例如錢益謙在崇禎十五年除夕作「壬午除夕」、隔日作「癸未元日」
// 日干支:'干支紀日',
// 月干支:'干支紀月',
// gettext_config:{"id":"month-of-the-sexagenary-cycle"}
月柱 : '月干支',
閏月 : '(是否為閏月)',
大小月 : '(大小月)',
// 晝夜 : '',
// 第一個時辰是子時,半夜十一點到一點。
// 時辰 : '子丑寅卯辰巳午未申酉戌亥',
// 晚上七點到第二天早上五點平均分為五更(合十個小時),每更合二個小時。
// 更 : '',
// 用四柱神算推算之時辰八字
四柱 : '%年柱%月柱%日柱%時柱',
// 生辰八字
八字 : '%年干支%月干支%日干支%時干支',
// 夏曆 : '%歲次年%月月%日日%辰時',
// 農民曆 : '',
// 授時歷即統天歷
曆法 : '',
// 注解
注 : ''
});
strftime.set_conversion(sign_note.notes,
//
library_namespace.gettext.to_standard('Chinese'));
// 已經作過改變,不再利用之。
delete sign_note.notes;
Object.assign(library_namespace, {
十二生肖_LIST : 十二生肖_LIST,
十二生肖圖像文字_LIST : 十二生肖圖像文字_LIST,
陰陽五行_LIST : 陰陽五行_LIST
});
String_to_Date.parser.era = function(date_string, minute_offset, options) {
if (false) {
// 依 String_to_Date() 當前之實作,不會出現此般差錯。
// 引數之前置處理。
if (library_namespace.is_Object(minute_offset)) {
if (!options)
options = minute_offset;
} else if (!isNaN(minute_offset)) {
if (!library_namespace.is_Object(options))
options = Object.create(null);
options.minute_offset = minute_offset;
}
}
library_namespace.debug('parse (' + typeof date_string + ') ['
+ date_string + ']', 3, 'String_to_Date.parser.era');
return to_era_Date(date_string, options);
};
// 更正 data.date .age_of.get_new_year
library_namespace.date.age_of.get_new_year = get_Date_of_key_by_CE;
// ---------------------------------------
this.finish = function(name_space, waiting) {
// 載入各紀年期間曆數資料 (era_data.js)。
var queue = [ library_namespace.get_module_path(this.id + '_data')
// .replace(/\\{2,}/g, '\\')
];
if (library_namespace.is_WWW(true))
// 載入 CSS resource(s)。
// include resources of module.
queue.unshift(library_namespace.get_module_path(this.id).replace(
/[^.]+$/, 'css'));
// library_namespace.log(queue);
library_namespace.run(queue, waiting);
return waiting;
};
return to_era_Date;
}