mirror of
				https://scm.univ-tours.fr/22107988t/rappaurio-sae501_502.git
				synced 2025-11-04 04:55:22 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			4112 lines
		
	
	
		
			132 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			4112 lines
		
	
	
		
			132 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						||
 * @name CeL function for date / time operations.
 | 
						||
 * @fileoverview 本檔案包含了 date / time 的功能。
 | 
						||
 * 
 | 
						||
 * TODO: http://momentjs.com/
 | 
						||
 * @see Moment.js https://momentjs.com/
 | 
						||
 * @since
 | 
						||
 */
 | 
						||
 | 
						||
'use strict';
 | 
						||
// 'use asm';
 | 
						||
 | 
						||
// More examples: see /_test suite/test.js
 | 
						||
 | 
						||
// --------------------------------------------------------------------------------------------
 | 
						||
 | 
						||
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
 | 
						||
typeof CeL === 'function' && CeL.run({
 | 
						||
	// module name
 | 
						||
	name : 'data.date',
 | 
						||
	// includes() @ CeL.data.code.compatibility.
 | 
						||
	require : 'data.code.compatibility.'
 | 
						||
 | 
						||
	// for gettext()
 | 
						||
	// + '|application.locale'
 | 
						||
 | 
						||
	+ '|data.native.set_bind|data.code.parse_escape|data.native.pad',
 | 
						||
 | 
						||
	// 設定不匯出的子函式。
 | 
						||
	// no_extend : '*',
 | 
						||
 | 
						||
	// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
 | 
						||
	code : module_code
 | 
						||
});
 | 
						||
 | 
						||
function module_code(library_namespace) {
 | 
						||
 | 
						||
	// requiring
 | 
						||
	var set_bind = this.r('set_bind'), parse_escape = this.r('parse_escape'), pad = this
 | 
						||
			.r('pad');
 | 
						||
 | 
						||
	/**
 | 
						||
	 * null module constructor
 | 
						||
	 * 
 | 
						||
	 * @class date objects 的 functions
 | 
						||
	 */
 | 
						||
	var _// JSDT:_module_
 | 
						||
	= function() {
 | 
						||
		// null module constructor
 | 
						||
	};
 | 
						||
 | 
						||
	/**
 | 
						||
	 * for JSDT: 有 prototype 才會將之當作 Class
 | 
						||
	 */
 | 
						||
	_// JSDT:_module_
 | 
						||
	.prototype = {};
 | 
						||
 | 
						||
	if (false) {
 | 
						||
		(function() {
 | 
						||
			/*
 | 
						||
			 * opposite of toUTCString() 尚不成熟!假如是type=='date',不如用new Date()!
 | 
						||
			 * string大部分可用new Date(Date.parse(str))代替!
 | 
						||
			 * http://www.comsharp.com/GetKnowledge/zh-CN/TeamBlogTimothyPage_K742.aspx
 | 
						||
			 */
 | 
						||
			var UTCDay, UTCMonth;
 | 
						||
			set_Object_value('UTCDay', 'Sun,Mon,Tue,Wed,Thu,Fri,Sat', 1);
 | 
						||
			set_Object_value('UTCMonth',
 | 
						||
					'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec', 1);
 | 
						||
			// 0:[Mon, 9 Aug 2004 12:05:00 GMT],1:[Thu Sep 30
 | 
						||
			// 18:12:08 UTC+0800 2004],2:[Sat Jun 26 18:19:46 2004]
 | 
						||
			var fromUTCStringFormat = [ [ 0, 3, 2, 1, 4 ], [ 0, 5, 1, 2, 3 ],
 | 
						||
					[ 0, 4, 1, 2, 3 ] ];
 | 
						||
			function fromUTCString(str, format) {
 | 
						||
				var s = '' + str, f;
 | 
						||
				if (!s)
 | 
						||
					return;
 | 
						||
				if (typeof format == 'undefined')
 | 
						||
					if (f = Date.parse(s))
 | 
						||
						return new Date(f);
 | 
						||
					else
 | 
						||
						return 'Unknown format!';// format=0;
 | 
						||
				if (!isNaN(format) && format < fromUTCStringFormat.length)
 | 
						||
					f = fromUTCStringFormat[format];
 | 
						||
				else
 | 
						||
					return 'Yet support this kind of format[' + format
 | 
						||
							+ ']!\nWe support to ' + fromUTCStringFormat.length
 | 
						||
							+ '.';
 | 
						||
				if (!f[0])
 | 
						||
					f[0] = ' ';
 | 
						||
				s = s.replace(new RegExp(f[0] + '+', 'g'), f[0]).split(f[0]);
 | 
						||
				if (s.length < f.length)
 | 
						||
					return 'The item length of data: ' + s.length
 | 
						||
							+ ' is less then format[' + format + ']: '
 | 
						||
							+ f.length + '!\n' + s.join(',');// new
 | 
						||
				// Date
 | 
						||
				if (f.length == 5)
 | 
						||
					s[f[4]] = s[f[4]].split(':');
 | 
						||
				else if (f.length == 7)
 | 
						||
					s[f[4]] = [ s[f[4]], s[f[5]], s[f[6]] ];
 | 
						||
				else
 | 
						||
					return 'Illegal date format!';
 | 
						||
				if (format == 1 && s[4].match(/([+-]\d{2})/))
 | 
						||
					s[f[4]][0] = parseInt(s[f[3]][0]) + parseInt(RegExp.$1);
 | 
						||
				library_namespace.debug(str + '\n' + s[f[1]] + ',' + s[f[2]]
 | 
						||
						+ '(' + UTCMonth[s[f[2]]] + '),' + s[f[3]] + ','
 | 
						||
						+ s[f[4]][0] + ',' + s[f[4]][1] + ',' + s[f[4]][2]);
 | 
						||
				// check, 可以包括星期
 | 
						||
				if (!(s[f[2]] = UTCMonth[s[f[2]]])
 | 
						||
						|| !(s = new Date(s[f[1]], s[f[2]], s[f[3]],
 | 
						||
								s[f[4]][0], s[f[4]][1], s[f[4]][2]))) // Date.UTC()
 | 
						||
					s = 'Input data error!';
 | 
						||
				return s;
 | 
						||
			}
 | 
						||
		});
 | 
						||
	}
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
 | 
						||
	var is_Date = library_namespace.is_Date,
 | 
						||
	//
 | 
						||
	UTC_PATTERN = /UTC(?:\s*([+-]?\d{1,2})(:\d{1,2})?)?/i,
 | 
						||
	// assert: isNaN(DEFAULT_TIME_ZONE) === true
 | 
						||
	// isNaN(Object.create(null)) will throw @ Chrome/36
 | 
						||
	// (Cannot convert object to primitive value),
 | 
						||
	// therefore we can't use Object.create(null) here.
 | 
						||
	DEFAULT_TIME_ZONE = {
 | 
						||
		timezone : 'default'
 | 
						||
	};
 | 
						||
 | 
						||
	// 嘗試 UTC+08:00 之類的標準表示法。
 | 
						||
	function get_minute_offset(date_string) {
 | 
						||
		var matched = date_string.match(UTC_PATTERN);
 | 
						||
		if (matched) {
 | 
						||
			return 60 * (matched[1] | 0) + (matched[2] | 0);
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
	// basic constants. 定義基本常數。
 | 
						||
 | 
						||
	/** {Number}一整天的 time 值。should be 24 * 60 * 60 * 1000 = 86400000. */
 | 
						||
	var ONE_DAY_LENGTH_VALUE = new Date(0, 0, 2) - new Date(0, 0, 1),
 | 
						||
	// ONE_DAY_LENGTH_VALUE = CeL.date.to_millisecond('1D')
 | 
						||
	// 3 * ONE_DAY_LENGTH_VALUE === CeL.date.to_millisecond('3D')
 | 
						||
 | 
						||
	/** {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),
 | 
						||
	/** {Number}一整時辰的 time 值。should be 2 * 60 * 60 * 1000 = 7200000. */
 | 
						||
	ONE_時辰_LENGTH_VALUE = new Date(0, 0, 0, 2) - new Date(0, 0, 0, 0),
 | 
						||
 | 
						||
	// e.g., UTC+8: -8 * 60 = -480
 | 
						||
	present_local_minute_offset = (new Date).getTimezoneOffset() || 0;
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
	// for Julian date. 期能不使用內建 Date 以快速計算日期。
 | 
						||
	// @see https://en.wikipedia.org/wiki/Julian_day#Calculation
 | 
						||
	// 適用範圍: 4717/3/1 BCE 0:0 之後。
 | 
						||
 | 
						||
	/**
 | 
						||
	 * Get Julian day number (JDN) of date.<br />
 | 
						||
	 * If type of date is Date, we'll treat date as local date.<br />
 | 
						||
	 * 因為得出的是 UTC+0 12:0 之 JDN,UTC+0 0:0 之 JD = JDN - .5。
 | 
						||
	 * 
 | 
						||
	 * JDN = Math.round(JD);
 | 
						||
	 * 
 | 
						||
	 * @param {String|Date|Number}date
 | 
						||
	 *            {Date}date or date value
 | 
						||
	 * @param {Boolean}type
 | 
						||
	 *            calendar type. true: Gregorian, false: Julian, 'CE': Common
 | 
						||
	 *            Era
 | 
						||
	 * @param {Boolean}no_year_0
 | 
						||
	 *            no year 0
 | 
						||
	 * @param {Boolean}get_remainder
 | 
						||
	 *            Will return [ {Number} Julian day number (JDN), {Number}
 | 
						||
	 *            remainder ].<br />
 | 
						||
	 *            remainder / ONE_DAY_LENGTH_VALUE = day.
 | 
						||
	 * 
 | 
						||
	 * @returns {Number} Julian day number (JDN)
 | 
						||
	 */
 | 
						||
	function Julian_day(date, type, no_year_0, get_remainder) {
 | 
						||
		if (typeof date === 'string') {
 | 
						||
			var matched = date
 | 
						||
			// parse '1/1/1'
 | 
						||
			.match(/(-?\d{1,4})[\/\-](\d{1,2})[\/\-](\d{1,2})/);
 | 
						||
			if (matched) {
 | 
						||
				return Julian_day.from_YMD(matched[1] | 0, matched[2] | 0,
 | 
						||
						matched[3] | 0, type, no_year_0);
 | 
						||
			}
 | 
						||
 | 
						||
			if (-4716 < date && date < 9999) {
 | 
						||
				// treat as year
 | 
						||
				return Julian_day.from_YMD(date | 0, 1, 1, type, no_year_0);
 | 
						||
			}
 | 
						||
 | 
						||
			if (matched = date.match(/(-?\d{1,4})[\/\-](\d{1,2})/)) {
 | 
						||
				return Julian_day.from_YMD(matched[1] | 0, matched[2] | 0, 1,
 | 
						||
						type, no_year_0);
 | 
						||
			}
 | 
						||
 | 
						||
			// throw new Error('Julian_day: Cannot parse [' + date + ']');
 | 
						||
			if (library_namespace.is_debug(2)) {
 | 
						||
				library_namespace.error('Julian_day: 無法解析 [' + date + ']!');
 | 
						||
			}
 | 
						||
			return;
 | 
						||
		}
 | 
						||
 | 
						||
		if (!isNaN(date)) {
 | 
						||
			// offset: convert local to UTC+0.
 | 
						||
			var offset;
 | 
						||
			if (is_Date(date)) {
 | 
						||
				offset = date.getTimezoneOffset() * ONE_MINUTE_LENGTH_VALUE;
 | 
						||
				date = date.getTime();
 | 
						||
			} else {
 | 
						||
				offset = Julian_day.default_offset;
 | 
						||
			}
 | 
						||
 | 
						||
			// treat ((date)) as date value. So it's Gregorian.
 | 
						||
			type = true;
 | 
						||
			date -= offset
 | 
						||
			// epoch 為 12:0,需要將之減回來以轉成 midnight (0:0)。
 | 
						||
			+ Julian_day.epoch - ONE_DAY_LENGTH_VALUE / 2;
 | 
						||
			var remainder;
 | 
						||
			if (get_remainder) {
 | 
						||
				remainder = date % ONE_DAY_LENGTH_VALUE;
 | 
						||
				if (remainder < 0) {
 | 
						||
					remainder += ONE_DAY_LENGTH_VALUE;
 | 
						||
				}
 | 
						||
			}
 | 
						||
			date = Math.floor(date / ONE_DAY_LENGTH_VALUE);
 | 
						||
			return get_remainder ? [ date, remainder ] : date;
 | 
						||
		}
 | 
						||
 | 
						||
		if (Array.isArray(date)) {
 | 
						||
			var JD = Julian_day.from_YMD(date[0], date[1], date[2], type,
 | 
						||
					no_year_0);
 | 
						||
			return get_remainder ? [ JD, date.length > 3
 | 
						||
			//
 | 
						||
			? Julian_day.from_HMS(date[3], date[4], date[5]) - .5 : 0 ] : JD;
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	/**
 | 
						||
	 * Get JDN of (year, month, date).<br />
 | 
						||
	 * input MUST latter than -4716/3/1 (4717/3/1 BCE)!!
 | 
						||
	 * 
 | 
						||
	 * <code>
 | 
						||
 | 
						||
	JDN = CeL.Julian_day.from_YMD(year, month, date, 'CE');
 | 
						||
 | 
						||
	</code>
 | 
						||
	 * 
 | 
						||
	 * @param {Integer}year
 | 
						||
	 *            year >= -4716
 | 
						||
	 * @param {Natural}month
 | 
						||
	 *            1–12
 | 
						||
	 * @param {Natural}date
 | 
						||
	 *            1–31
 | 
						||
	 * @param {Boolean}type
 | 
						||
	 *            calendar type. true: Gregorian, false: Julian, 'CE': Common
 | 
						||
	 *            Era
 | 
						||
	 * @param {Boolean}no_year_0
 | 
						||
	 *            no year 0
 | 
						||
	 * 
 | 
						||
	 * @returns {Number} JDN
 | 
						||
	 */
 | 
						||
	Julian_day.from_YMD = function(year, month, date, type, no_year_0) {
 | 
						||
		if (Array.isArray(year)) {
 | 
						||
			return Array.isArray(month)
 | 
						||
			// month = [H,M,S]
 | 
						||
			? Julian_day.from_YMD(year[0], year[1], year[2], date, type) - .5
 | 
						||
					+ Julian_day.from_HMS(month[0], month[1], month[2])
 | 
						||
			//
 | 
						||
			: Julian_day.from_YMD(year[0], year[1], year[2], month, date);
 | 
						||
		}
 | 
						||
		if (no_year_0 && year < 0) {
 | 
						||
			// no year 0. year: -1 → 0
 | 
						||
			year++;
 | 
						||
		}
 | 
						||
		if (type === 'CE') {
 | 
						||
			type = year > 1582
 | 
						||
			// Julian calendar(儒略曆)1582年10月4日的下一日為
 | 
						||
			// Gregorian calendar(格里高利曆)1582年10月15日。
 | 
						||
			|| year == 1582 && (month > 10 || month == 10 && date >= 15);
 | 
						||
		}
 | 
						||
 | 
						||
		// method: 自 3月起算。
 | 
						||
		if (false) {
 | 
						||
			if (month < 3) {
 | 
						||
				year = +year + 4800 - 1 | 0;
 | 
						||
				month = +month + 12 - 3 | 0;
 | 
						||
			} else {
 | 
						||
				year = +year + 4800 | 0;
 | 
						||
				month = +month - 3 | 0;
 | 
						||
			}
 | 
						||
		}
 | 
						||
		// a=1: 1–2月, a=0: 3–12月
 | 
						||
		// var a = (14 - month) / 12 | 0;
 | 
						||
		var a = month < 3 ? 1 : 0;
 | 
						||
		year = +year + 4800 - a | 0;
 | 
						||
		month = +month + 12 * a - 3 | 0;
 | 
						||
		// assert: year, month are integers. month >= 0
 | 
						||
 | 
						||
		// 3–7月:153日
 | 
						||
		return +date + ((153 * month + 2) / 5 | 0)
 | 
						||
		//
 | 
						||
		+ 365 * year + Math.floor(year / 4) -
 | 
						||
		// for Gregorian calendar
 | 
						||
		(type ? 32045 + Math.floor(year / 100) - Math.floor(year / 400)
 | 
						||
		// for Julian calendar
 | 
						||
		: 32083);
 | 
						||
	};
 | 
						||
 | 
						||
	/**
 | 
						||
	 * Get day value from hour, minute, second.<br />
 | 
						||
	 * TODO: microsecond µs, nanosecond ns
 | 
						||
	 * 
 | 
						||
	 * @param {Number}[hour]
 | 
						||
	 *            hour
 | 
						||
	 * @param {Number}[minute]
 | 
						||
	 *            minute
 | 
						||
	 * @param {Number}[second]
 | 
						||
	 *            second
 | 
						||
	 * @param {Number}[millisecond]
 | 
						||
	 *            millisecond
 | 
						||
	 * 
 | 
						||
	 * @returns {Number}day value
 | 
						||
	 */
 | 
						||
	Julian_day.from_HMS = function(hour, minute, second, millisecond) {
 | 
						||
		// initialization, milliseconds to seconds
 | 
						||
		var time = millisecond ? millisecond / 1000 : 0;
 | 
						||
		if (second) {
 | 
						||
			time += +second;
 | 
						||
		}
 | 
						||
		// to minutes
 | 
						||
		time /= 60;
 | 
						||
		if (minute) {
 | 
						||
			time += +minute;
 | 
						||
		}
 | 
						||
		// to hours → to days
 | 
						||
		return (time / 60 + (+hour || 0)) / 24;
 | 
						||
	};
 | 
						||
 | 
						||
	/**
 | 
						||
	 * Get (year, month, date) of JDN.
 | 
						||
	 * 
 | 
						||
	 * @param {Number}JDN
 | 
						||
	 *            Julian date number
 | 
						||
	 * @param {Boolean}type
 | 
						||
	 *            calendar type. true: Gregorian, false: Julian, 'CE': Common
 | 
						||
	 *            Era.
 | 
						||
	 * @param {Boolean}no_year_0
 | 
						||
	 *            no year 0
 | 
						||
	 * 
 | 
						||
	 * @returns {Array} [ year, month, date ]
 | 
						||
	 * 
 | 
						||
	 * @see https://en.wikipedia.org/wiki/Julian_day#Julian_or_Gregorian_calendar_from_Julian_day_number
 | 
						||
	 *      algorithm by Richards 2013
 | 
						||
	 */
 | 
						||
	Julian_day.to_YMD = function(JDN, type, no_year_0) {
 | 
						||
		var f = JDN + 1401 | 0;
 | 
						||
		if (type && (type !== 'CE' || JDN >= Gregorian_reform_JDN)) {
 | 
						||
			// to proleptic Gregorian calendar
 | 
						||
			f += ((((4 * JDN + 274277) / 146097 | 0) * 3) / 4 | 0) - 38;
 | 
						||
		} else {
 | 
						||
			// to proleptic Julian calendar with year 0
 | 
						||
		}
 | 
						||
 | 
						||
		var e = 4 * f + 3 | 0,
 | 
						||
		//
 | 
						||
		g = (e % 1461) / 4 | 0,
 | 
						||
		//
 | 
						||
		h = 5 * g + 2,
 | 
						||
		//
 | 
						||
		date = ((h % 153) / 5 | 0) + 1,
 | 
						||
		//
 | 
						||
		month = (((h / 153 | 0) + 2) % 12) + 1,
 | 
						||
		//
 | 
						||
		year = (e / 1461 | 0) - 4716 + ((12 + 2 - month) / 12 | 0);
 | 
						||
 | 
						||
		if (no_year_0 && year < 1) {
 | 
						||
			// no year 0. year: 0 → -1
 | 
						||
			year--;
 | 
						||
		}
 | 
						||
 | 
						||
		// TODO: time
 | 
						||
		return [ year, month, date ];
 | 
						||
	};
 | 
						||
 | 
						||
	/**
 | 
						||
	 * JD to YMDHMS. Get (year, month, date, hour, minute, second) of JD.
 | 
						||
	 * 
 | 
						||
	 * @param {Number}JD
 | 
						||
	 *            Julian date
 | 
						||
	 * @param {Number}zone
 | 
						||
	 *            local time zone. 0 if is UTC+0 (default), 8 if is UTC+8.
 | 
						||
	 * @param {Boolean}type
 | 
						||
	 *            calendar type. true: Gregorian, false: Julian, 'CE': Common
 | 
						||
	 *            Era.
 | 
						||
	 * @param {Boolean}no_year_0
 | 
						||
	 *            no year 0
 | 
						||
	 * 
 | 
						||
	 * @returns {Array} [ year, month, date, hour, minute, second ]
 | 
						||
	 */
 | 
						||
	Julian_day.to_YMDHMS = function(JD, zone, type, no_year_0) {
 | 
						||
		// +.5: input JD instead of JDN
 | 
						||
		// 1e-16 (days): for error. e.g., CeL.Julian_day.to_YMDHMS(.6, 8)
 | 
						||
		// 2451544.5 is 2000/1/1 0:0 UTC+12, 1999/12/31 12:0 UTC+0
 | 
						||
		// → 2451545 is 2000/1/1 12:0 UTC+0
 | 
						||
		// 0 is -4712/1/1 12:0 UTC+0, -4712/1/2 0:0 UTC+12
 | 
						||
		var JDN = Julian_day.to_YMD(JD += .5 + 1e-16 + (zone | 0) / 24, type,
 | 
						||
				no_year_0);
 | 
						||
		// to local time
 | 
						||
		JDN.push((JD = JD.mod(1) * 24) | 0, (JD = (JD % 1) * 60) | 0,
 | 
						||
				(JD = (JD % 1) * 60) | 0);
 | 
						||
		// milliseconds 去除 error。
 | 
						||
		// 4e-11:
 | 
						||
		// 1e-16*86400 ≈ 1e-11
 | 
						||
		// (-Math.log10((1/2-1/3-1/6)*86400)|0) → 1e-11
 | 
						||
		// So we use 1e-11 + 1e-11 = 2e-11.
 | 
						||
		// But for CeL.Julian_day.to_YMDHMS(.6, 8), it seems still not enough.
 | 
						||
		// We should use 4e-11 at least.
 | 
						||
		if ((JD %= 1) > 4e-11) {
 | 
						||
			// 8.64e-9 = 1e-16 * 86400000: 將之前加的 error 修正補回來。
 | 
						||
			// 約精確到 1e-7 ms
 | 
						||
			JDN.push(JD * 1000 - 8.64e-9);
 | 
						||
		} else {
 | 
						||
			// 當作 error。
 | 
						||
		}
 | 
						||
		return JDN;
 | 
						||
	};
 | 
						||
 | 
						||
	/**
 | 
						||
	 * Get the local midnight date of JDN.<br />
 | 
						||
	 * 傳回 local midnight (0:0)。
 | 
						||
	 * 
 | 
						||
	 * <code>
 | 
						||
 | 
						||
	date = CeL.Julian_day.to_Date(JDN);
 | 
						||
 | 
						||
	</code>
 | 
						||
	 * 
 | 
						||
	 * @param {Integer}JDN
 | 
						||
	 *            input {Integer}JDN or {Number}JD.
 | 
						||
	 * @param {Boolean}is_JD
 | 
						||
	 *            The JDN is JD.
 | 
						||
	 * @param {Boolean}get_value
 | 
						||
	 *            get {Number} date value instead of {Date}.
 | 
						||
	 * 
 | 
						||
	 * @returns {Date} local midnight date
 | 
						||
	 */
 | 
						||
	Julian_day.to_Date = function(JDN, is_JD, get_value, offset) {
 | 
						||
		if (!is_JD) {
 | 
						||
			// epoch 為 12:0,需要將之減回來以轉成 midnight (0:0)。
 | 
						||
			JDN -= .5;
 | 
						||
		}
 | 
						||
		JDN = JDN * ONE_DAY_LENGTH_VALUE + Julian_day.epoch
 | 
						||
		//
 | 
						||
		+ (isNaN(offset) ? Julian_day.default_offset : offset);
 | 
						||
		return get_value ? JDN : new Date(JDN);
 | 
						||
	};
 | 
						||
 | 
						||
	Julian_day.YMD_to_Date = function(year, month, date, type, get_value,
 | 
						||
			no_year_0) {
 | 
						||
		var JDN = Julian_day.from_YMD(year, month, date, type, no_year_0);
 | 
						||
		// 當作 JD 才方便 date.format() 得到正確結果。
 | 
						||
		return Julian_day.to_Date(JDN, true, get_value);
 | 
						||
	};
 | 
						||
 | 
						||
	/**
 | 
						||
	 * Get Julian date (JD) of date.
 | 
						||
	 * 
 | 
						||
	 * @param {String|Date|Number}date
 | 
						||
	 *            date or date value
 | 
						||
	 * @param {Boolean}type
 | 
						||
	 *            calendar type. true: Gregorian, false: Julian, 'CE': Common
 | 
						||
	 *            Era
 | 
						||
	 * @param {Boolean}no_year_0
 | 
						||
	 *            no year 0
 | 
						||
	 * 
 | 
						||
	 * @returns {Number} Julian date
 | 
						||
	 */
 | 
						||
	Julian_day.JD = function(date, type, no_year_0) {
 | 
						||
		if (is_Date(date))
 | 
						||
			return Date_to_JD(date);
 | 
						||
		date = Julian_day(date, type, no_year_0, true);
 | 
						||
		return date[0] + date[1] / ONE_DAY_LENGTH_VALUE;
 | 
						||
	};
 | 
						||
 | 
						||
	/**
 | 
						||
	 * default offset (time value)
 | 
						||
	 * 
 | 
						||
	 * @type {Integer}
 | 
						||
	 */
 | 
						||
	Julian_day.default_offset = present_local_minute_offset
 | 
						||
			* ONE_MINUTE_LENGTH_VALUE;
 | 
						||
 | 
						||
	// Get the epoch of Julian date, i.e., -4713/11/24 12:0
 | 
						||
	(function() {
 | 
						||
		var date = new Date(0),
 | 
						||
		// [ -4713, 11, 24 ]
 | 
						||
		JD0 = Julian_day.to_YMD(0, true);
 | 
						||
		// set the date value of Julian date 0
 | 
						||
		date.setUTCHours(12, 0, 0, 0);
 | 
						||
		date.setUTCFullYear(JD0[0] | 0, (JD0[1] | 0) - 1, JD0[2] | 0);
 | 
						||
		Julian_day.epoch = date.getTime();
 | 
						||
		// Julian_day.epoch = -210866760000000;
 | 
						||
	})();
 | 
						||
 | 
						||
	/**
 | 
						||
	 * Gregorian reform JDN.
 | 
						||
	 * 
 | 
						||
	 * @type {Integer}
 | 
						||
	 */
 | 
						||
	var Gregorian_reform_JDN = Julian_day.from_YMD(1582, 10, 15);
 | 
						||
 | 
						||
	/**
 | 
						||
	 * Get weekday index of JD.
 | 
						||
	 * 
 | 
						||
	 * @param {Number}JD
 | 
						||
	 *            Julian date
 | 
						||
	 * @param {Boolean}to_ISO
 | 
						||
	 *            to ISO type.
 | 
						||
	 * 
 | 
						||
	 * @returns {Integer} weekday index.
 | 
						||
	 * 
 | 
						||
	 * @see https://en.wikipedia.org/wiki/Zeller's_congruence
 | 
						||
	 */
 | 
						||
	Julian_day.weekday = function(JD, to_ISO) {
 | 
						||
		return to_ISO ? (Math.floor(JD) % 7) + 1
 | 
						||
		// Sunday: 0, Monday: 1, ...
 | 
						||
		: (Math.floor(JD) + 1) % 7;
 | 
						||
	};
 | 
						||
 | 
						||
	_.Julian_day = Julian_day;
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
 | 
						||
	// Unix time (a.k.a. POSIX time or Epoch time)
 | 
						||
	function Unix_time(date) {
 | 
						||
		return ((date || Date.now()) - Unix_time.epoch) / 1000;
 | 
						||
	}
 | 
						||
 | 
						||
	_.Unix_time = Unix_time;
 | 
						||
 | 
						||
	// Unix epoch '1970-01-01T00:00:00Z', 0 @ most systems
 | 
						||
	Unix_time.epoch = Date.parse('1970/1/1 UTC');
 | 
						||
 | 
						||
	Unix_time.to_Date = function(time_value) {
 | 
						||
		return new Date(1000 * time_value + Unix_time.epoch);
 | 
						||
	};
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
 | 
						||
	// The 1900 Date System
 | 
						||
	// 序列值 1 代表 1/1/1900 12:00:00 a.m。
 | 
						||
	// 數字 32331.06 代表日期和時間 7/7/1988年 1:26:24 a.m。
 | 
						||
	var Excel_1900_epoch = Date.parse('1900/1/1') - ONE_DAY_LENGTH_VALUE,
 | 
						||
	// The 1904 Date System
 | 
						||
	// 依預設,Excel for Mac 使用 1904 日期系統,而 Excel for Windows 使用 1900 日期系統。這表示當您在
 | 
						||
	// Excel for Mac 中輸入序列值 1 並將其格式化為日期,Excel 會將其顯示為 1/2/1904 12:00 a.m。Excel
 | 
						||
	// for Windows 則會將序列值 1 顯示為 1/1/1900 12:00 a.m。
 | 
						||
	// Date.parse('1904/1/2') - ONE_DAY_LENGTH_VALUE
 | 
						||
	Excel_1904_epoch = Date.parse('1904/1/1');
 | 
						||
 | 
						||
	function Excel_Date(date_value, is_Mac, get_value) {
 | 
						||
		// 0會被轉成 1900/1/0,已經不正常。
 | 
						||
		if (date_value >= 1) {
 | 
						||
			// 這邊採用與 function Date_to_Excel() 相對應的判別式。
 | 
						||
			if (!is_Mac && !(date_value < 60)) {
 | 
						||
				date_value--;
 | 
						||
			}
 | 
						||
			date_value = (is_Mac ? Excel_1904_epoch : Excel_1900_epoch)
 | 
						||
					+ ONE_DAY_LENGTH_VALUE * date_value;
 | 
						||
		} else {
 | 
						||
			date_value = NaN;
 | 
						||
		}
 | 
						||
		return get_value ? date_value : new Date(date_value);
 | 
						||
	}
 | 
						||
 | 
						||
	_.Excel_Date = Excel_Date;
 | 
						||
 | 
						||
	if (false) {
 | 
						||
		Excel_Date.error_value = {
 | 
						||
			valueOf : function() {
 | 
						||
				return NaN;
 | 
						||
			},
 | 
						||
			toString : function() {
 | 
						||
				return '#VALUE!';
 | 
						||
			}
 | 
						||
		};
 | 
						||
	}
 | 
						||
	// Excel 2010 會將錯誤值顯示為'#VALUE!',但負數或過大值則會以'#'填滿格子(e.g., "#########")。
 | 
						||
	Excel_Date.error_value = '#VALUE!';
 | 
						||
 | 
						||
	if (false) {
 | 
						||
		// to show Excel date
 | 
						||
		(date = date.to_Excel()) && date.toFixed(2)
 | 
						||
				|| CeL.Excel_Date.error_value;
 | 
						||
	}
 | 
						||
 | 
						||
	// http://support.microsoft.com/kb/214094
 | 
						||
	// Excel for Mac uses the 1904 date system and
 | 
						||
	// Excel for Windows uses the 1900 date system.
 | 
						||
	function Date_to_Excel(date, is_Mac) {
 | 
						||
		date = date.getTime() - (is_Mac ? Excel_1904_epoch : Excel_1900_epoch);
 | 
						||
		return date >= 1 ?
 | 
						||
		// Excel 有 1900/2/29 (60),但現實中沒有這天。因此一般轉換時,不應出現60之值。
 | 
						||
		// Mac 系統以 1904 CE 起始,迴避了這個問題。
 | 
						||
		// 0: 1900/1/0
 | 
						||
		// https://en.wikipedia.org/wiki/Year_1900_problem
 | 
						||
		// 60: 1900/2/29 (nonexistent date)
 | 
						||
		// 61: 1900/3/1
 | 
						||
		(date /= ONE_DAY_LENGTH_VALUE) < 60 || is_Mac ? date : date + 1
 | 
						||
		// or use Excel_Date.error_value
 | 
						||
		: NaN;
 | 
						||
	}
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------
 | 
						||
 | 
						||
	// 2016/8/22 22:1:51
 | 
						||
 | 
						||
	/**
 | 
						||
	 * <code>
 | 
						||
	 https://msdn.microsoft.com/en-us/library/system.datetime.tofiletime.aspx
 | 
						||
	 (long) DateTime.ToFileTime Method ()
 | 
						||
	 A Windows file time is a 64-bit value that represents the number of 100-nanosecond intervals that have elapsed since 12:00 midnight, January 1, 1601 A.D. (C.E.) Coordinated Universal Time (UTC). Windows uses a file time to record when an application creates, accesses, or writes to a file.
 | 
						||
	 </code>
 | 
						||
	 */
 | 
						||
	VS_file_time.epoch = Date.parse('1601-01-01T00:00:00Z');
 | 
						||
 | 
						||
	// https://msdn.microsoft.com/en-us/library/system.datetime.fromfiletime.aspx
 | 
						||
	// DateTime.FromFileTime Method (Int64)
 | 
						||
	// Converts the specified Windows file time to an equivalent local time.
 | 
						||
	function VS_file_time(file_time, return_Date) {
 | 
						||
		var date_value = VS_file_time.epoch + file_time / (1e-3 / (1e-9 * 100));
 | 
						||
		return return_Date ? new Date(date_value) : date_value;
 | 
						||
	}
 | 
						||
 | 
						||
	_.VS_file_time = VS_file_time;
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
 | 
						||
	// ISO 8601
 | 
						||
	var PATTERN_ISO_DATE = /^-?\d{4,8}-[01]\d-[0-3]\d(T[012]\d:[0-6]\d:[0-6]\d(\.\d{3})?(Z|[+\-][012]\d:\d{2}))?$/;
 | 
						||
 | 
						||
	/**
 | 
						||
	 * convert the string to Date object.
 | 
						||
	 * 
 | 
						||
	 * TODO: parse /([今昨明]|大?[前後])天/, '01-03' (相對於當前),
 | 
						||
	 * /\d+(分[鐘钟]?|小[時时]|毫?秒|[日天週年]|星期|[禮礼]拜|[個个]月)([前後])/; 相對於
 | 
						||
	 * options.base_date . also see indicate_date_time()
 | 
						||
	 * 
 | 
						||
	 * @example <code>
 | 
						||
	 * '2003/1-4 12:53:5.45PM'.to_Date('CST').format();
 | 
						||
	 * '12:53:5.45PM 2003/1-4'.to_Date('CST').format();
 | 
						||
	 * </code>
 | 
						||
	 * 
 | 
						||
	 * @param {String}date_string
 | 
						||
	 *            date string
 | 
						||
	 * @param {Object}options {
 | 
						||
	 *            <br />
 | 
						||
	 *            {String|RegExp}format: the format used.<br />
 | 
						||
	 *            {Function}parser: the parser used. if set to unrecognized
 | 
						||
	 *            (e.g., null) parser, it will use Date.parse() ONLY.<br />
 | 
						||
	 *            {String|Number}zone: 設定 date_string 之 time zone or country
 | 
						||
	 *            (e.g., 'CST', 'TW') || 時差 in hour (例如 TW: UTC+8 → 8, 可使用.5).<br />
 | 
						||
	 *            {Date}reform: 對某些特殊 paser,如 CE,需要特別設定改曆日期時用。<br />
 | 
						||
	 *            <br />
 | 
						||
	 *            {Boolean}period_end:<br />
 | 
						||
	 *            將指定內容視為一時段,並取得此期間之結束(終結)時間,因此 parse 後將得到第一個不屬於此範圍之時刻。<br />
 | 
						||
	 *            e.g., '2000/5' → 2000/6/1 0:0:0<br />
 | 
						||
	 *            e.g., '5/3' → 5/4 0:0:0<br />
 | 
						||
	 *            e.g., '5/3 12:' → 5/4 13:0:0<br />
 | 
						||
	 *            e.g., '5/3 12:50' → 5/4 12:51:0<br /> }
 | 
						||
	 * 
 | 
						||
	 * @returns {Date} new Date
 | 
						||
	 * @since 2012/3/22 23:58:38 重構並測試。
 | 
						||
	 * @see <a href="http://msdn.microsoft.com/zh-tw/library/t5580e8h.aspx"
 | 
						||
	 *      accessdate="2012/3/23 23:26">JScript Date 物件</a>
 | 
						||
	 * @see wikitext: {{#time:Y年n月j日|+1 day}}
 | 
						||
	 */
 | 
						||
	function String_to_Date(date_string, options) {
 | 
						||
		// 檢測輸入引數(arguments),將之正規化(normalization),處理、轉換為規範之標準型態。
 | 
						||
		library_namespace.debug('parse (' + typeof date_string + ') ['
 | 
						||
				+ date_string + ']', 3, 'String_to_Date');
 | 
						||
 | 
						||
		if (typeof date_string === 'date') {
 | 
						||
			// 應對在 Excel 等外部程式會出現的東西。
 | 
						||
			return new Date(date_string);
 | 
						||
		}
 | 
						||
		if (is_Date(date_string)) {
 | 
						||
			return date_string;
 | 
						||
		}
 | 
						||
 | 
						||
		date_string = date_string.trim();
 | 
						||
		if (!date_string) {
 | 
						||
			// this.toString();
 | 
						||
			// date_string = this.valueOf();
 | 
						||
			return;
 | 
						||
		}
 | 
						||
 | 
						||
		if (PATTERN_ISO_DATE.test(date_string)) {
 | 
						||
			// 對於有明確指定之 UTC date 如 .toISOString() 之產出或 ISO 8601,
 | 
						||
			// 應當不管 time zone 如何設定,直接回傳。
 | 
						||
			return new Date(date_string);
 | 
						||
		}
 | 
						||
 | 
						||
		var tmp, matched, minute_offset;
 | 
						||
 | 
						||
		if (library_namespace.is_RegExp(options)) {
 | 
						||
			// 將 options 當作 pattern。
 | 
						||
			options = {
 | 
						||
				pattern : options
 | 
						||
			};
 | 
						||
		} else if (!library_namespace.is_Object(options)) {
 | 
						||
			// 前置處理。
 | 
						||
			tmp = options;
 | 
						||
			options = Object.create(null);
 | 
						||
			if (tmp) {
 | 
						||
				if (tmp in String_to_Date.parser) {
 | 
						||
					options.parser = String_to_Date.parser[tmp];
 | 
						||
				} else if ((tmp in String_to_Date.zone) || !isNaN(tmp)) {
 | 
						||
					options.zone = tmp;
 | 
						||
				} else {
 | 
						||
					// 判斷是否為正規 format。
 | 
						||
					options.format = tmp;
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		// console.trace(date_string);
 | 
						||
		if (library_namespace.is_RegExp(options.pattern)
 | 
						||
		//
 | 
						||
		&& (matched = date_string.match(options.pattern))) {
 | 
						||
			// 依照 matched 匹配的來正規化/設定年月日。
 | 
						||
			// e.g., new Date('1234/5/6')
 | 
						||
			// === '1234年5月6日'.to_Date(/(\d+)年(\d+)月(\d+)日/)
 | 
						||
			// ===
 | 
						||
			// '5/6/1234'.to_Date({pattern:/(\d+)\/(\d+)\/(\d+)/,pattern_matched:[3,1,2]})
 | 
						||
			tmp = Array.isArray(options.pattern_matched) ? options.pattern_matched
 | 
						||
					: [ 1, 2, 3 ];
 | 
						||
			date_string = tmp.map(function(processor) {
 | 
						||
				return typeof processor === 'function'
 | 
						||
				//
 | 
						||
				? processor(matched) : matched[processor];
 | 
						||
			}).join(
 | 
						||
			// 長度3時當作年月日,否則當作自訂處理。
 | 
						||
			tmp.length === 3 ? '/' : '');
 | 
						||
		}
 | 
						||
		// console.trace(date_string);
 | 
						||
 | 
						||
		// 設定指定 time zone 之 offset in minutes.
 | 
						||
		tmp = options.zone;
 | 
						||
		library_namespace.debug('設定 time zone / offset hours: ' + tmp, 2);
 | 
						||
		// TODO: for Daylight Saving Time (DST) time zones, etc.
 | 
						||
		if (tmp in String_to_Date.zone) {
 | 
						||
			tmp = String_to_Date.zone[tmp];
 | 
						||
		}
 | 
						||
		if (library_namespace.is_Function(tmp)) {
 | 
						||
			tmp = tmp();
 | 
						||
		}
 | 
						||
		if (typeof tmp !== 'string'
 | 
						||
				|| isNaN(minute_offset = get_minute_offset(tmp))) {
 | 
						||
			minute_offset =
 | 
						||
			// 測試純數字小時。
 | 
						||
			-12 <= tmp && tmp <= 14 ? 60 * tmp
 | 
						||
			// 再測試純數字分鐘。
 | 
						||
			: isNaN(tmp)
 | 
						||
			//
 | 
						||
			? DEFAULT_TIME_ZONE : +tmp;
 | 
						||
		}
 | 
						||
		library_namespace.debug('最終設定 offset '
 | 
						||
				+ (minute_offset === DEFAULT_TIME_ZONE ? '(default = '
 | 
						||
						+ String_to_Date.default_offset + ')' : minute_offset)
 | 
						||
				+ ' minutes.', 2);
 | 
						||
 | 
						||
		// 判別 parser。
 | 
						||
		tmp = library_namespace.is_Function(tmp = options.parser) ? tmp
 | 
						||
				: String_to_Date.parser[tmp] || String_to_Date.default_parser;
 | 
						||
 | 
						||
		if (library_namespace.is_Function(tmp)) {
 | 
						||
			library_namespace.debug('use customize parser to parse ('
 | 
						||
					+ typeof date_string + ') [' + date_string + '].', 2);
 | 
						||
			// console.trace(date_string);
 | 
						||
			if (tmp = tmp(date_string,
 | 
						||
			// assert: parser 亦負責 parse time zone offset.
 | 
						||
			minute_offset, options)) {
 | 
						||
				return tmp;
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		library_namespace.debug('無法以 parser 判別。use Date.parse() to parse.', 2);
 | 
						||
		if (tmp = Date.parse(date_string)) {
 | 
						||
			// TODO: period_end 無效。
 | 
						||
			// native parser 會處理 time zone offset.
 | 
						||
			tmp = new Date(tmp);
 | 
						||
			if (!isNaN(minute_offset) && minute_offset !== DEFAULT_TIME_ZONE) {
 | 
						||
				tmp.setMinutes(tmp.getMinutes() - tmp.getTimezoneOffset()
 | 
						||
						- minute_offset);
 | 
						||
			}
 | 
						||
			return tmp;
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	// 本地之 time zone / time offset (UTC offset by minutes)。
 | 
						||
	// e.g., UTC+8: 8 * 60 = +480
 | 
						||
	// e.g., UTC-5: -5 * 60
 | 
						||
	// 亦為 Date.parse(date_string) 與 new Date() 會自動附上的當地時間差距。
 | 
						||
	// assert: String_to_Date.default_offset 為整數。
 | 
						||
	String_to_Date.default_offset = -present_local_minute_offset;
 | 
						||
 | 
						||
	/**
 | 
						||
	 * <code>
 | 
						||
	主要指是否計算 0 year。
 | 
						||
	.no_year_0 = true: 將 astronomical year numbering 轉成一般紀年法(1 BCE→1 CE)。
 | 
						||
	僅用於計算 Gregorian calendar, Julian calendar。
 | 
						||
	normal	astronomical
 | 
						||
	2	2
 | 
						||
	1	1
 | 
						||
	-1	0
 | 
						||
	-2	-1
 | 
						||
	-3	-2
 | 
						||
	</code>
 | 
						||
	 */
 | 
						||
	String_to_Date.no_year_0 = Date_to_String.no_year_0 = true;
 | 
						||
 | 
						||
	var stem_branch_date_pattern,
 | 
						||
	// 精密度: 千紀,世紀,年代,年,月,日,時,分,秒,毫秒
 | 
						||
	index_precision = 'millennium,century,decade,year,month,day,hour,minute,second,microsecond'
 | 
						||
			.split(',');
 | 
						||
	(function() {
 | 
						||
		// e.g., for '公元前720年2月22日'
 | 
						||
		var start_pattern = '^[^\\d:\\-−‐前.]*',
 | 
						||
		// with weekday 星期
 | 
						||
		mid_pattern = '(?:\\s*\\(?[日月火水木金土一二三四五六]\\)?)?(?:\\s+',
 | 
						||
		// e.g., for '1616年2月壬午', '7時'
 | 
						||
		end_pattern = ')?[^\\d日時]*$',
 | 
						||
 | 
						||
		// pattern of date. 當今會準確使用的時間,
 | 
						||
		// 為 -47xx BCE (Julian day 0) 至 2xxx CE。
 | 
						||
		date_pattern = /(?:([\-−‐前]?(?:[0-4]?\d{3}|\d{1,3}))[\/.\-年 ])?\s*([01]?\d)(?:[\/.\-月 ]\s*([0-3]?\d)日?)?/.source,
 | 
						||
		// pattern of time. [0-6]: 支持閏秒
 | 
						||
		time_pattern = /([0-2]?\d)[:時时]\s*(?:([0-6]?\d)[:分]?\s*(?:([0-6]?\d)(?:\.(\d+))?)?)?秒?\s*(?:([PA])M)?/i.source;
 | 
						||
 | 
						||
		// 日期先: date [time]
 | 
						||
		String_to_Date_default_parser.date_first = new RegExp(start_pattern
 | 
						||
				+ date_pattern + mid_pattern + time_pattern + end_pattern, 'i');
 | 
						||
		// 時間先: time [date]
 | 
						||
		String_to_Date_default_parser.time_first = new RegExp(start_pattern
 | 
						||
				+ time_pattern + mid_pattern + date_pattern + end_pattern, 'i');
 | 
						||
 | 
						||
		// 將於下方作初始化。
 | 
						||
		stem_branch_date_pattern = date_pattern;
 | 
						||
	})();
 | 
						||
 | 
						||
	// [ all, start month, end month, year, misc ]
 | 
						||
	var PATTERN_EN_MONTH_YEAR = /^(?:([a-z]{3,9})\s*[.\/\-–-—─~~〜﹣])?\s*([a-z]{3,9}),?\s+(\d{1,4})( +\D.*)?$/i,
 | 
						||
	// [ all, year, start month, end month, misc ]
 | 
						||
	PATTERN_EN_YEAR_MONTH = /^(\d{1,4})\s+(?:([a-z]{3,9})\s*[.\/\-–-—─~~〜﹣])?\s*([a-z]{3,9})( +\D.*)?$/i,
 | 
						||
	// U+2212 '−': minus sign
 | 
						||
	// 為了 calendar 測試,年分需要能 parse 0–9999。
 | 
						||
	// [ all, .*年, \d+, [百千] ]
 | 
						||
	PATTERN_YEAR_ONLY = /^[^\d\/:\-−‐前日月年]*(\d{3,4}|([\-−‐前]?\d{1,4})([百千]?)年|[\-−‐前]\d{1,4})[^\d\/:\-−‐前日月年]*$/,
 | 
						||
	//
 | 
						||
	PATTERN_BCE = /(?:^|[^a-z.])B\.?C\.?E?(?:[^a-z.]|$)/i, time_boundary = new Date(
 | 
						||
			0, 0, 1);
 | 
						||
	time_boundary.setFullYear(0);
 | 
						||
	time_boundary = time_boundary.getTime();
 | 
						||
 | 
						||
	/**
 | 
						||
	 * parse date_string and return the new Date.
 | 
						||
	 * 
 | 
						||
	 * @param {String}date_string
 | 
						||
	 *            date string.
 | 
						||
	 * @param {Integer}minute_offset
 | 
						||
	 *            (指定 time zone 之) offset in minutes.
 | 
						||
	 * @param {Object}options {
 | 
						||
	 *            {Boolean}period_end:<br />
 | 
						||
	 *            將指定內容視為一時段,並取得此期間之結束(終結)時間,因此 parse 後將得到第一個不屬於此範圍之時刻。<br />
 | 
						||
	 *            e.g., '2000/5' → 2000/6/1 0:0:0<br />
 | 
						||
	 *            e.g., '5/3' → 5/4 0:0:0<br />
 | 
						||
	 *            e.g., '5/3 12:' → 5/4 13:0:0<br />
 | 
						||
	 *            e.g., '5/3 12:50' → 5/4 12:51:0<br /> }
 | 
						||
	 * 
 | 
						||
	 * @returns {Date} new Date
 | 
						||
	 * @see <a href="http://php.net/manual/en/function.date.php"
 | 
						||
	 *      accessdate="2012/3/23 20:51">PHP: date - Manual</a>
 | 
						||
	 */
 | 
						||
	function String_to_Date_default_parser(date_string, minute_offset, options) {
 | 
						||
		// console.trace(date_string);
 | 
						||
		if (is_Date(date_string)) {
 | 
						||
			return date_string;
 | 
						||
		}
 | 
						||
 | 
						||
		// 前置處理。
 | 
						||
		if (!library_namespace.is_Object(options)) {
 | 
						||
			options = Object.create(null);
 | 
						||
		}
 | 
						||
 | 
						||
		var date_data,
 | 
						||
		// 精密度
 | 
						||
		precision, period_end = options.period_end,
 | 
						||
		// matched string
 | 
						||
		matched, tmp,
 | 
						||
		//
 | 
						||
		no_year_0 = 'no_year_0' in options ? options.no_year_0
 | 
						||
				: String_to_Date.no_year_0;
 | 
						||
 | 
						||
		date_string = date_string.trim()
 | 
						||
		// 注意:"紀"會轉換成結束時間。
 | 
						||
		.replace(/世[紀纪]/g, '百年').replace(/千[紀纪]/g, '千年');
 | 
						||
 | 
						||
		// ------------------------------------------------
 | 
						||
 | 
						||
		// [ all, start month, end month, year, misc ]
 | 
						||
		matched = date_string.match(PATTERN_EN_MONTH_YEAR);
 | 
						||
		if (!matched && (matched = date_string.match(PATTERN_EN_YEAR_MONTH))) {
 | 
						||
			matched.splice(4, 0, matched[1]);
 | 
						||
			matched.splice(1, 1);
 | 
						||
		}
 | 
						||
		if (matched) {
 | 
						||
			// e.g., 'May–June 1998', 'June 1998 UTC+6'
 | 
						||
			// console.trace(period_end, matched);
 | 
						||
			var date_value = Date.parse(
 | 
						||
			//
 | 
						||
			(!period_end && matched[1] || matched[2]) + ' ' + matched[3]
 | 
						||
			// matched[4]: e.g., 'UTC+8'
 | 
						||
			+ (matched[4] || ''));
 | 
						||
			if (isNaN(date_value)) {
 | 
						||
				// Cannot parse "month year"
 | 
						||
				library_namespace.debug('無法 parse: [' + date_string + ']', 2,
 | 
						||
						'String_to_Date_default_parser');
 | 
						||
				return;
 | 
						||
			}
 | 
						||
 | 
						||
			if (!/UTC(?:\W|$)/.test(matched[4])
 | 
						||
			//
 | 
						||
			&& !isNaN(minute_offset) && minute_offset !== DEFAULT_TIME_ZONE) {
 | 
						||
				date_value -= (present_local_minute_offset + minute_offset)
 | 
						||
						* ONE_MINUTE_LENGTH_VALUE;
 | 
						||
			}
 | 
						||
			date_value = new Date(date_value);
 | 
						||
			if (period_end) {
 | 
						||
				date_value.setMonth(date_value.getMonth() + 1);
 | 
						||
			} else if (false && matched[1]) {
 | 
						||
				library_namespace.warn('Cannot handle date range: '
 | 
						||
						+ date_string);
 | 
						||
			}
 | 
						||
 | 
						||
			// .precision 將會影響 function wikidata_datavalue() @
 | 
						||
			// CeL.application.net.wiki.data
 | 
						||
			date_value.precision = 'month';
 | 
						||
			return date_value;
 | 
						||
		}
 | 
						||
 | 
						||
		// ------------------------------------------------
 | 
						||
 | 
						||
		if (isNaN(minute_offset)
 | 
						||
				&& !isNaN(tmp = get_minute_offset(date_string))) {
 | 
						||
			minute_offset = tmp;
 | 
						||
			// 留下此 pattern 在 match 時會出錯。
 | 
						||
			date_string = date_string.replace(UTC_PATTERN, '').trim();
 | 
						||
		}
 | 
						||
 | 
						||
		// TODO:
 | 
						||
		// e.g., '10.12', '10/12'
 | 
						||
		// e.g., '10/12, 2001'
 | 
						||
		// e.g., '10 12, 2001'
 | 
						||
		// e.g., '2001 10 12'
 | 
						||
 | 
						||
		if (matched = date_string.match(PATTERN_YEAR_ONLY)) {
 | 
						||
			// 僅有 xxx/1xxx/2xxx 年(year) 時。
 | 
						||
			precision = matched[3] === '百' ? 'century'
 | 
						||
					: matched[3] === '千' ? 'millennium'
 | 
						||
					// 注意:這邊不會檢查如"2016年代"之合理性(應當為"2010年代")
 | 
						||
					: date_string.includes('年代') ? 'decade' : 'year';
 | 
						||
			date_string = (matched[2] || matched[1]).replace(/^[−‐前]/, '-000');
 | 
						||
			if (period_end) {
 | 
						||
				if (matched[3]) {
 | 
						||
					// 將於後面才作位數處理。
 | 
						||
					++date_string;
 | 
						||
				} else {
 | 
						||
					// 作位數處理。
 | 
						||
					matched = date_string.includes('00');
 | 
						||
					if (!++date_string) {
 | 
						||
						// 預防 前1年 → 0年。
 | 
						||
						date_string = no_year_0 ? '0001' : '0000';
 | 
						||
					} else if (matched
 | 
						||
							&& (date_string = '00' + date_string).length < 4) {
 | 
						||
						date_string = '0' + date_string;
 | 
						||
					}
 | 
						||
				}
 | 
						||
				// 已處理過 period_end,因此除去此 flag。
 | 
						||
				period_end = false;
 | 
						||
			}
 | 
						||
			if (matched[3]) {
 | 
						||
				date_string = date_string
 | 
						||
				// 轉換到正確的年份。
 | 
						||
				* (precision === 'century' ? 100 : 1000);
 | 
						||
				// 作位數處理。
 | 
						||
				if (0 < date_string && date_string < 1000) {
 | 
						||
					date_string = '0' + date_string;
 | 
						||
				} else if (date_string === 0) {
 | 
						||
					date_string = '000';
 | 
						||
				}
 | 
						||
			}
 | 
						||
			// 添加月份以利parse。
 | 
						||
			date_string += '/1';
 | 
						||
		} else {
 | 
						||
			// 依照中文之習慣,日期 + 時間中間可不加空格。
 | 
						||
			date_string = date_string.replace(/日(\d)/, '日 $1');
 | 
						||
		}
 | 
						||
 | 
						||
		if (false &&
 | 
						||
		// 速度似乎差不多。
 | 
						||
		(date_data = date_string.match(/^(-?\d{1,4})\/(\d{1,2})\/(\d{1,2})$/))) {
 | 
						||
			// library_namespace.debug('輸入格式: 日期', 2);
 | 
						||
			date_data.shift();
 | 
						||
 | 
						||
		} else if ((date_data = date_string
 | 
						||
				.match(String_to_Date_default_parser.date_first))
 | 
						||
				&& isNaN(date_data[0])) {
 | 
						||
			// library_namespace.debug('輸入格式: 日期 (+ 時間)', 2);
 | 
						||
			date_data.shift();
 | 
						||
 | 
						||
		} else if (date_data = date_string
 | 
						||
				.match(String_to_Date_default_parser.time_first)) {
 | 
						||
			// library_namespace.debug('輸入格式: 時間 (+ 日期): 未匹配或僅有一數字', 2);
 | 
						||
			// [ 1, 2, 3, 4, 5, 6, 7, 8 ]
 | 
						||
			// → [ 6, 7, 8, 1, 2, 3, 4, 5 ]
 | 
						||
			date_data.shift();
 | 
						||
			date_data.unshift(date_data[5], date_data[6], date_data[7]);
 | 
						||
			date_data.length = 8;
 | 
						||
 | 
						||
		} else {
 | 
						||
			library_namespace.debug('無法 parse: [' + date_string + ']', 2,
 | 
						||
					'String_to_Date_default_parser');
 | 
						||
			return;
 | 
						||
		}
 | 
						||
 | 
						||
		if (!precision) {
 | 
						||
			// 這邊僅處理年以下的單位。
 | 
						||
			date_data.some(function(value, index) {
 | 
						||
				if (!value) {
 | 
						||
					// value should be undefined.
 | 
						||
					if (index > 0) {
 | 
						||
						precision = index_precision[index + 2];
 | 
						||
					}
 | 
						||
					return true;
 | 
						||
				}
 | 
						||
			});
 | 
						||
		}
 | 
						||
 | 
						||
		// ----------------------------------------------------
 | 
						||
 | 
						||
		// date_data: index: [ year, month, month_day (Day of
 | 
						||
		// the month), hour, minute, second, milliseconds, Ante
 | 
						||
		// meridiem or Post meridiem ]
 | 
						||
 | 
						||
		library_namespace.debug(date_data.join('<br />'), 2,
 | 
						||
				'String_to_Date_default_parser');
 | 
						||
 | 
						||
		tmp = date_data.length === 8 && date_data.pop();
 | 
						||
		if (tmp === 'P' || tmp === 'p') {
 | 
						||
			// is PM (else: AM or 24-hour format)
 | 
						||
			date_data[3] = 12 + (date_data[3] | 0);
 | 
						||
		}
 | 
						||
 | 
						||
		var year = +date_data[0];
 | 
						||
		if (isNaN(year) && /^前/.test(date_data[0])) {
 | 
						||
			year = -date_data[0].slice(1);
 | 
						||
		}
 | 
						||
		// fix browser Date.parse() bug for BCE date.
 | 
						||
		else if (year > 0 && PATTERN_BCE.test(date_string)) {
 | 
						||
			year = -year;
 | 
						||
			if (!('no_year_0' in options)) {
 | 
						||
				// default: no year 0
 | 
						||
				no_year_0 = true;
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		// 確定正確年份: 若無 year 0 則所有負的年份皆 +1,
 | 
						||
		// 轉成<a
 | 
						||
		// href="http://en.wikipedia.org/wiki/Astronomical_year_numbering"
 | 
						||
		// accessdate="2013/2/11 15:40" title="Astronomical year
 | 
						||
		// numbering">天文年號</a>。
 | 
						||
		// (BCE) -1 → 0, -2 → -1, -3 → -2, ...
 | 
						||
		if (year < 0) {
 | 
						||
			if (no_year_0) {
 | 
						||
				year++;
 | 
						||
			}
 | 
						||
 | 
						||
		} else if (year < 100 && date_data[0].length < 3
 | 
						||
		// year padding: 0–99 的年份會加上此年份。
 | 
						||
		&& (tmp = isNaN(options.year_padding)
 | 
						||
		//
 | 
						||
		? String_to_Date_default_parser.year_padding : options.year_padding)) {
 | 
						||
			year += tmp;
 | 
						||
		}
 | 
						||
 | 
						||
		date_data[0] = year;
 | 
						||
		if (period_end) {
 | 
						||
			tmp = date_data.length;
 | 
						||
			// 由小至大,將第一個有指定的加一即可。
 | 
						||
			while (tmp-- > 0) {
 | 
						||
				// IE 中,String.prototype.match() 未成功時會回傳 '',
 | 
						||
				// 而 !isNaN('')===true,因此無法正確判別。
 | 
						||
				if (!isNaN(date_data[tmp]) && date_data[tmp] !== '') {
 | 
						||
					date_data[tmp]++;
 | 
						||
					break;
 | 
						||
				}
 | 
						||
			}
 | 
						||
			year = date_data[0];
 | 
						||
		}
 | 
						||
 | 
						||
		if (!(0 < (date_data[2] = +date_data[2]))) {
 | 
						||
			date_data[2] = 1;
 | 
						||
		}
 | 
						||
		if (typeof options.post_process === 'function') {
 | 
						||
			options.post_process(date_data);
 | 
						||
		}
 | 
						||
 | 
						||
		year = +year || 0;
 | 
						||
		// time zone.
 | 
						||
		tmp = +date_data[4] || 0;
 | 
						||
		var base_on_UTC = !isNaN(minute_offset)
 | 
						||
				&& minute_offset !== DEFAULT_TIME_ZONE;
 | 
						||
		// 若是未設定,則當作 local time zone。
 | 
						||
		if (base_on_UTC) {
 | 
						||
			// 否則基於本機當前的時區來調整成基於 UTC 之 `minute_offset`
 | 
						||
			// local time + .getTimezoneOffset() = UTC
 | 
						||
			tmp -= present_local_minute_offset + minute_offset;
 | 
						||
		}
 | 
						||
 | 
						||
		if (year < 100 && year >= 0) {
 | 
						||
			// 僅使用 new Date(0) 的話,會含入 timezone offset (.getTimezoneOffset)。
 | 
						||
			// 因此得使用 new Date(0, 0)。
 | 
						||
			date_value = new Date(0, 0);
 | 
						||
			// 先設定小單位,再設定大單位:設定小單位時會影響到大單位。反之不然。
 | 
						||
			// 下兩者得到的值不同。
 | 
						||
			// (d=new Date(0, 0)).setFullYear(0, 0, -1, 0, 480, 0, 0);
 | 
						||
			// d.toISOString()
 | 
						||
			//
 | 
						||
			// (d=new Date(0, 0)).setHours(0, 480, 0, 0);
 | 
						||
			// d.setFullYear(0, 0, -1);d.toISOString()
 | 
						||
			date_value.setHours(+date_data[3] || 0, tmp, +date_data[5] || 0,
 | 
						||
					+date_data[6] || 0);
 | 
						||
			date_value.setFullYear(
 | 
						||
			// new Date(10, ..) === new Date(1910, ..)
 | 
						||
			year, date_data[1] ? date_data[1] - 1 : 0, date_data[2]);
 | 
						||
		} else {
 | 
						||
			date_value = new Date(year, date_data[1] ? date_data[1] - 1 : 0,
 | 
						||
					date_data[2], +date_data[3] || 0, tmp, +date_data[5] || 0,
 | 
						||
					+date_data[6] || 0);
 | 
						||
		}
 | 
						||
		if (base_on_UTC
 | 
						||
				&& date_value.getTimezoneOffset() !== present_local_minute_offset) {
 | 
						||
			/**
 | 
						||
			 * 當基於本機當前的時區來調整成UTC時間時,若是 time zone 和預設的
 | 
						||
			 * `present_local_minute_offset` 不同,就必須在以 new Date() 設定時間後,才調整 time
 | 
						||
			 * zone。
 | 
						||
			 */
 | 
						||
			date_value.setMinutes(date_value.getMinutes()
 | 
						||
					+ present_local_minute_offset
 | 
						||
					- date_value.getTimezoneOffset());
 | 
						||
		}
 | 
						||
 | 
						||
		// 測試僅輸入時刻的情況。e.g., '7時'
 | 
						||
		if (options.near && date_value.getFullYear() === 0
 | 
						||
				&& date_value - time_boundary > 0) {
 | 
						||
			// 判別未輸入時預設年份設對了沒:以最接近 options.near 的為基準。
 | 
						||
			tmp = is_Date(options.near) ? options.near : new Date;
 | 
						||
			date_string = tmp.getFullYear();
 | 
						||
			matched = new Date(date_value.getTime());
 | 
						||
			date_value.setFullYear(date_string);
 | 
						||
			matched.setFullYear(date_value - tmp > 0 ? date_string - 1
 | 
						||
					: date_string + 1);
 | 
						||
			if (date_value - tmp > 0 && date_value - tmp > tmp - matched
 | 
						||
					|| date_value - tmp < 0 && date_value - tmp < tmp - matched) {
 | 
						||
				date_value = matched;
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		if (precision) {
 | 
						||
			date_value.precision = precision;
 | 
						||
		}
 | 
						||
		return date_value;
 | 
						||
	}
 | 
						||
 | 
						||
	// 0–99 的年份會加上此年份 (1900)。
 | 
						||
	String_to_Date_default_parser.year_padding = (new Date(0, 0, 1))
 | 
						||
			.getFullYear();
 | 
						||
	String_to_Date.default_parser = String_to_Date_default_parser;
 | 
						||
 | 
						||
	// date_string.match(String_to_Date.parser_PATTERN)
 | 
						||
	// === [, parser name, date string ]
 | 
						||
	// e.g., "Âm lịch"
 | 
						||
	String_to_Date.parser_PATTERN = /^\s*(?:([^:]+):)?\s*(.+)/i;
 | 
						||
 | 
						||
	String_to_Date.parser = {
 | 
						||
 | 
						||
		Julian : Julian_String_to_Date,
 | 
						||
		// Common Era / Before the Common Era, CE / BCE. 公元/西元.
 | 
						||
		CE : function(date_string, minute_offset, options) {
 | 
						||
			if (!library_namespace.is_Object(options)) {
 | 
						||
				options = Object.create(null);
 | 
						||
			}
 | 
						||
			if (!('no_year_0' in options)) {
 | 
						||
				options.no_year_0 = true;
 | 
						||
			}
 | 
						||
			var date_value = String_to_Date_default_parser(date_string,
 | 
						||
					minute_offset, options);
 | 
						||
			return date_value - Gregorian_reform_of(options.reform) < 0
 | 
						||
			//
 | 
						||
			? Julian_String_to_Date(date_string, minute_offset, options)
 | 
						||
					: date_value;
 | 
						||
		},
 | 
						||
 | 
						||
		// <a href="http://php.net/manual/en/function.date.php"
 | 
						||
		// accessdate="2012/3/23 20:51">PHP: date - Manual</a>
 | 
						||
		PHP : function() {
 | 
						||
			// TODO
 | 
						||
			throw new Error('String_to_Date.parser.PHP: Not Yet Implemented!');
 | 
						||
		},
 | 
						||
		// <a href="http://www.freebsd.org/cgi/man.cgi?query=strftime"
 | 
						||
		// accessdate="2012/3/23 20:59">strftime</a>,
 | 
						||
		// <a href="http://hacks.bluesmoon.info/strftime/" accessdate="2012/3/23
 | 
						||
		// 21:9">strftime: strftime for Javascript</a>
 | 
						||
		strftime : function() {
 | 
						||
			// TODO
 | 
						||
			throw new Error(
 | 
						||
					'String_to_Date.parser.strftime: Not Yet Implemented!');
 | 
						||
		}
 | 
						||
	};
 | 
						||
 | 
						||
	// 時區縮寫。
 | 
						||
	// <a href="http://en.wikipedia.org/wiki/List_of_time_zone_abbreviations"
 | 
						||
	// accessdate="2012/12/2 13:0" title="List of time zone abbreviations">time
 | 
						||
	// zone abbreviations</a> and offset in hour.
 | 
						||
	// TODO: Daylight Saving Time (DST).
 | 
						||
	// @see CeL.application.locale.time_zone_of_language()
 | 
						||
	String_to_Date.zone = {
 | 
						||
		// UTC+08:00
 | 
						||
		// China Standard Time
 | 
						||
		CST : 8,
 | 
						||
		Z中國 : 8,
 | 
						||
 | 
						||
		JST : 9,
 | 
						||
		Z日本 : 9,
 | 
						||
 | 
						||
		EST : -5,
 | 
						||
		PST : -8,
 | 
						||
 | 
						||
		// Greenwich Mean Time
 | 
						||
		GMT : 0,
 | 
						||
		// Coordinated Universal Time
 | 
						||
		UTC : 0
 | 
						||
	};
 | 
						||
 | 
						||
	_// JSDT:_module_
 | 
						||
	.String_to_Date = String_to_Date;
 | 
						||
 | 
						||
	// ---------------------------------------------------------
 | 
						||
 | 
						||
	/**
 | 
						||
	 * test if the year is leap year. has year 0!<br />
 | 
						||
	 * 
 | 
						||
	 * @param {Integer}year
 | 
						||
	 * @param type
 | 
						||
	 *            calendar type: true: use Julian calendar, false: use Gregorian
 | 
						||
	 *            calendar, 'CE': use CE
 | 
						||
	 * 
 | 
						||
	 * @returns {Boolean}
 | 
						||
	 */
 | 
						||
	function is_leap_year(year, type) {
 | 
						||
		if (type === 'CE') {
 | 
						||
			if (reform_year < year) {
 | 
						||
				type = false;
 | 
						||
			} else if (year < 0) {
 | 
						||
				year++;
 | 
						||
			}
 | 
						||
		}
 | 
						||
		// Julian calendar
 | 
						||
		return type ? year % 4 === 0
 | 
						||
		// Gregorian calendar
 | 
						||
		: year % 400 === 0 || year % 100 !== 0 && year % 4 === 0;
 | 
						||
	}
 | 
						||
	_.is_leap_year = is_leap_year;
 | 
						||
 | 
						||
	/**
 | 
						||
	 * test if in the year, Gregorian calendar and Julian calendar have
 | 
						||
	 * different intercalation.
 | 
						||
	 * 
 | 
						||
	 * @param {Integer}year
 | 
						||
	 * @returns {Boolean} 當年 Julian 與 UTC 為不同閏年規定: Gregorian 當年沒有閏日,但 Julian 有。
 | 
						||
	 */
 | 
						||
	function is_different_leap_year(year) {
 | 
						||
		return year % 100 === 0 && year % 400 !== 0;
 | 
						||
	}
 | 
						||
	_.is_different_leap_year = is_different_leap_year;
 | 
						||
 | 
						||
	/**
 | 
						||
	 * 計算 Gregorian 與 Julian 的日數差距。 the secular difference between the two
 | 
						||
	 * calendars.<br />
 | 
						||
	 * 會將 date_data: Julian → Gregorian.
 | 
						||
	 * 
 | 
						||
	 * @param {Array}date_data
 | 
						||
	 *            Julian date [year, month, date]
 | 
						||
	 * 
 | 
						||
	 * @returns {Number} Julian → Gregorian 時,需要減去的日數。(除少數特例外,即 Gregorian →
 | 
						||
	 *          Julian 時,需要加上的日數。)
 | 
						||
	 * 
 | 
						||
	 * @see https://en.wikipedia.org/wiki/Gregorian_calendar#Difference_between_Gregorian_and_Julian_calendar_dates
 | 
						||
	 */
 | 
						||
	function Julian_shift_days(date_data) {
 | 
						||
		var year = +date_data[0];
 | 
						||
		// 測試是否為有差異的當年
 | 
						||
		if (is_different_leap_year(year)
 | 
						||
		// 測試是否為閏日。
 | 
						||
		// 閏日前(before Julian calendar leap day)還要算是上一階段。
 | 
						||
		&& date_data[1] < 3) {
 | 
						||
			year--;
 | 
						||
		}
 | 
						||
		// 計算 Gregorian 與 Julian 的 different days。
 | 
						||
		// 2: 0年時,差了2日。
 | 
						||
		// -701: 8, -700: 7; -601: 7, -600: 6; 99: 2, 100: 1;
 | 
						||
		year = 2 + Math.floor(year / 400) - Math.floor(year / 100);
 | 
						||
		// 這演算法對差異大至 31+28 日的時段不適用。
 | 
						||
		date_data[2] -= year;
 | 
						||
		return year;
 | 
						||
	}
 | 
						||
	_.Julian_shift_days = Julian_shift_days;
 | 
						||
 | 
						||
	/**
 | 
						||
	 * parse proleptic Julian calendar date_string and return the new Date.<br />
 | 
						||
	 * 
 | 
						||
	 * 借用系統內建的計時機制。其他 arguments 見 String_to_Date_default_parser()。
 | 
						||
	 * 
 | 
						||
	 * @param {String}date_string
 | 
						||
	 *            Julian calendar date string.
 | 
						||
	 * 
 | 
						||
	 * @returns {Date} new Date
 | 
						||
	 * 
 | 
						||
	 * @see http://en.wikipedia.org/wiki/Old_Style_and_New_Style_dates
 | 
						||
	 * @see http://eclipse.gsfc.nasa.gov/SEhelp/julian.html
 | 
						||
	 */
 | 
						||
	function Julian_String_to_Date(date_string, minute_offset, options) {
 | 
						||
		if (!library_namespace.is_Object(options)) {
 | 
						||
			options = Object.create(null);
 | 
						||
		}
 | 
						||
 | 
						||
		options.post_process = Julian_shift_days;
 | 
						||
 | 
						||
		return String_to_Date_default_parser(date_string, minute_offset,
 | 
						||
				options);
 | 
						||
	}
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
 | 
						||
	function parse_English_date(date) {
 | 
						||
		date = date.trim().replace(/.+\[(?:\d{1,2}|note \d+)\]$/i, '');
 | 
						||
		var accuracy = date.match(/^(?:before|after)[\s ](.+)$/i), matched;
 | 
						||
		if (accuracy) {
 | 
						||
			date = accuracy[1].trim();
 | 
						||
			accuracy = accuracy[0];
 | 
						||
		}
 | 
						||
		if (accuracy = date.match(/^ca?.(.+)$/)) {
 | 
						||
			date = accuracy[1].trim();
 | 
						||
			accuracy = accuracy[0];
 | 
						||
		}
 | 
						||
		if (/^[a-z]{3,9}\s+-?\d+$/i.test(date)) {
 | 
						||
			date = '1 ' + date;
 | 
						||
			accuracy = date;
 | 
						||
		}
 | 
						||
 | 
						||
		if (date.includes('?')) {
 | 
						||
			accuracy = date;
 | 
						||
			date = date.replace(/\?/g, '');
 | 
						||
		}
 | 
						||
 | 
						||
		if (!isNaN(date) || /^\d+\/\d+$/.test(date)) {
 | 
						||
			accuracy = date;
 | 
						||
		} else if (!isNaN(matched = Date.parse(date))) {
 | 
						||
			date = new Date(matched + String_to_Date.default_offset
 | 
						||
					* ONE_MINUTE_LENGTH_VALUE).toISOString()
 | 
						||
			//
 | 
						||
			.match(/^\d+-\d+-\d+/)[0].replace(/^0+/, '').replace(/(\d)-0*/g,
 | 
						||
					'$1\/');
 | 
						||
		} else {
 | 
						||
			library_namespace.warn(date);
 | 
						||
			return;
 | 
						||
		}
 | 
						||
		return [ date, accuracy ];
 | 
						||
	}
 | 
						||
 | 
						||
	_.parse_English_date = parse_English_date;
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
 | 
						||
	/**
 | 
						||
	 * 顯示格式化日期時間 string:依照指定格式輸出日期與時間。<br />
 | 
						||
	 * TODO:<br />
 | 
						||
	 * 各 locale 有不同 format 與 time zone offset.
 | 
						||
	 * 
 | 
						||
	 * @param {Date}date_value
 | 
						||
	 *            要轉換的 date, TODO? 值過小時當作時間, <0 轉成當下時間.
 | 
						||
	 * @param {Object|String|Function}options
 | 
						||
	 *            選擇性功能: {<br />
 | 
						||
	 *            {String|Function}parser: 格式字串分析器 'strftime',<br />
 | 
						||
	 *            {String}format: 格式字串 '%Y/%m/%d %H:%M:%S.%f',<br />
 | 
						||
	 *            {String}locale: 地區語系設定<br /> }
 | 
						||
	 * 
 | 
						||
	 * @returns {String} 依照指定格式格式化後輸出的日期與時間.
 | 
						||
	 * 
 | 
						||
	 * @see<br />
 | 
						||
	 *      <a href="http://blog.csdn.net/xzknet/article/details/2278101"
 | 
						||
	 *      accessdate="2012/3/24 15:11" title="如何使用Javascript格式化日期显示 -
 | 
						||
	 *      虫二的专栏~~在路上~~~ - 博客频道 - CSDN.NET">JsJava中提供了專用的類,專門對日期進行指定格式的字符串輸出</a>,<br />
 | 
						||
	 *      <a href="http://www.merlyn.demon.co.uk/js-date8.htm"
 | 
						||
	 *      accessdate="2012/3/25 1:42">Merlyn - JSDT 8 : Enhancing the Object -
 | 
						||
	 *      J R Stockton</a>,<br />
 | 
						||
	 *      U.S. Naval Observatory <a
 | 
						||
	 *      href="http://aa.usno.navy.mil/data/docs/JulianDate.php"
 | 
						||
	 *      accessdate="2012/3/25 1:42">Julian Date Converter</a><br />
 | 
						||
	 *      ISO 8601:2004(E)
 | 
						||
	 * 
 | 
						||
	 * @_memberOf _module_
 | 
						||
	 */
 | 
						||
	function Date_to_String(date_value, options) {
 | 
						||
		// 前置處理。
 | 
						||
		if (typeof options === 'string') {
 | 
						||
			options = options in Date_to_String.parser ? {
 | 
						||
				parser : Date_to_String.parser[options]
 | 
						||
			} : {
 | 
						||
				format : options
 | 
						||
			};
 | 
						||
		} else if (typeof options === 'function') {
 | 
						||
			options = {
 | 
						||
				parser : options
 | 
						||
			};
 | 
						||
		} else if (!library_namespace.is_Object(options)) {
 | 
						||
			options = Object.create(null);
 | 
						||
		}
 | 
						||
 | 
						||
		if (false) {
 | 
						||
			if (options.parser
 | 
						||
					&& !library_namespace.is_Function(options.parser)
 | 
						||
					&& !library_namespace
 | 
						||
							.is_Function(String_to_Date.parser[options.parser])) {
 | 
						||
				library_namespace.warn('Date_to_String: 無法判斷 parser ['
 | 
						||
						+ options.parser + ']!');
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		// if (!date_value) date_value = new Date;
 | 
						||
 | 
						||
		if (date_value && !is_Date(date_value)
 | 
						||
		// String_to_Date() 會幫忙做正規化。
 | 
						||
		? String_to_Date(date_value) : date_value) {
 | 
						||
			return (library_namespace.is_Function(options.parser) ? options.parser
 | 
						||
					: Date_to_String.parser[options.parser]
 | 
						||
							|| Date_to_String.default_parser)(date_value,
 | 
						||
					options.format, library_namespace.gettext.to_standard
 | 
						||
					//
 | 
						||
					? library_namespace.gettext.to_standard(options.locale)
 | 
						||
							: options.locale, options);
 | 
						||
		}
 | 
						||
 | 
						||
		library_namespace.warn('Date_to_String: 無法判斷 date value [' + date_value
 | 
						||
				+ ']!');
 | 
						||
	}
 | 
						||
 | 
						||
	// default parser.
 | 
						||
	Date_to_String.default_parser = strftime;
 | 
						||
 | 
						||
	Date_to_String.parser = {
 | 
						||
 | 
						||
		// <a href="http://php.net/manual/en/function.date.php"
 | 
						||
		// accessdate="2012/3/23 20:51">PHP: date - Manual</a>
 | 
						||
		PHP : function(date_value, format, locale) {
 | 
						||
			// TODO
 | 
						||
			throw new Error('Date_to_String.parser.PHP: Not Yet Implemented!');
 | 
						||
		},
 | 
						||
		// ISO 8601:2004(E)
 | 
						||
		ISO8601 : function(date_value, format, locale) {
 | 
						||
			// TODO
 | 
						||
			throw new Error(
 | 
						||
					'Date_to_String.parser.ISO8601: Not Yet Implemented!');
 | 
						||
		},
 | 
						||
		// .NET standard format string (standard date and time format string) <a
 | 
						||
		// href="http://msdn.microsoft.com/zh-tw/library/az4se3k1.aspx"
 | 
						||
		// accessdate="2012/3/24 17:43">標準日期和時間格式字串</a>
 | 
						||
		SFS : function(date_value, format, locale) {
 | 
						||
			// TODO
 | 
						||
			throw new Error('Date_to_String.parser.SFS: Not Yet Implemented!');
 | 
						||
		},
 | 
						||
		// <a href="http://www.freebsd.org/cgi/man.cgi?query=strftime"
 | 
						||
		// accessdate="2012/3/23 20:59">strftime</a>,
 | 
						||
		// <a href="http://hacks.bluesmoon.info/strftime/" accessdate="2012/3/23
 | 
						||
		// 21:9">strftime: strftime for Javascript</a>
 | 
						||
		strftime : strftime,
 | 
						||
 | 
						||
		Gregorian : Date_to_Gregorian,
 | 
						||
		Julian : Date_to_Julian,
 | 
						||
		// Common Era / Before the Common Era, CE / BCE.
 | 
						||
		CE : function(date_value, format, locale, options) {
 | 
						||
			// 前置處理。
 | 
						||
			if (!library_namespace.is_Object(options)) {
 | 
						||
				options = Object.create(null);
 | 
						||
			}
 | 
						||
			if (!('no_year_0' in options)) {
 | 
						||
				options.no_year_0 = true;
 | 
						||
			}
 | 
						||
 | 
						||
			return (date_value - Gregorian_reform_of(options.reform) < 0
 | 
						||
			//
 | 
						||
			? Date_to_Julian : Date_to_Gregorian)(date_value, format, locale,
 | 
						||
					options);
 | 
						||
		},
 | 
						||
 | 
						||
		// Turn to RFC 822 date-time
 | 
						||
		// <a
 | 
						||
		// href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/toUTCString"
 | 
						||
		// accessdate="2012/3/24 8:5" title="toUTCString - MDN">The most common
 | 
						||
		// return value is a RFC-1123 formatted date stamp, which is a slightly
 | 
						||
		// updated version of RFC-822 date stamps.</a>
 | 
						||
		// Date_to_RFC822[generateCode.dLK]='String_to_Date';
 | 
						||
		RFC822 : function(date_value) {
 | 
						||
			// e.g., "Wed, 14 Jun 2017 07:00:00 GMT"
 | 
						||
			return date_value.toUTCString().replace(/UTC/gi, 'GMT');
 | 
						||
		}
 | 
						||
	};
 | 
						||
 | 
						||
	_// JSDT:_module_
 | 
						||
	.Date_to_String = Date_to_String;
 | 
						||
 | 
						||
	// ---------------------------------------------------------
 | 
						||
 | 
						||
	/**
 | 
						||
	 * 依照指定 strftime 格式輸出日期與時間。
 | 
						||
	 * 
 | 
						||
	 * @param {Date}date_value
 | 
						||
	 *            要格式化的日期。
 | 
						||
	 * @param {String}format
 | 
						||
	 *            輸出的格式字串。
 | 
						||
	 * @param {String}locale
 | 
						||
	 *            輸出的地區語系設定。
 | 
						||
	 * @param {Object}options
 | 
						||
	 *            選擇性功能。
 | 
						||
	 * 
 | 
						||
	 * @returns {String} 依照指定格式輸出的日期與時間。
 | 
						||
	 * 
 | 
						||
	 * @see<br />
 | 
						||
	 *      <a href="http://www.freebsd.org/cgi/man.cgi?query=strftime"
 | 
						||
	 *      accessdate="2012/3/23 20:59">strftime</a>,<br />
 | 
						||
	 *      <a href="http://hacks.bluesmoon.info/strftime/"
 | 
						||
	 *      accessdate="2012/3/23 21:9">strftime: strftime for Javascript</a>,
 | 
						||
	 */
 | 
						||
	function strftime(date_value, format, locale, options) {
 | 
						||
		// 前置處理。
 | 
						||
		if (!library_namespace.is_Object(options)) {
 | 
						||
			options = Object.create(null);
 | 
						||
		}
 | 
						||
 | 
						||
		var original_Date = options.original_Date || date_value,
 | 
						||
		/**
 | 
						||
		 * 支援的 conversion specifications (轉換規格).
 | 
						||
		 */
 | 
						||
		conversion = strftime.conversion[locale]
 | 
						||
				|| strftime.conversion[strftime.null_domain],
 | 
						||
		/**
 | 
						||
		 * 所須搜尋的 conversion specifications (轉換規格) pattern.
 | 
						||
		 */
 | 
						||
		search = strftime.search[locale]
 | 
						||
				|| strftime.search[strftime.null_domain];
 | 
						||
 | 
						||
		// 也可以使用 options.zone 設定要轉換成的時區(timezone)。
 | 
						||
		if (isNaN(options.offset) && !isNaN(options.zone)) {
 | 
						||
			options.offset = options.zone * 60;
 | 
						||
		}
 | 
						||
		// console.log(options);
 | 
						||
		// library_namespace.debug('options.offset = ' + options.offset, 0);
 | 
						||
 | 
						||
		// to this minute offset. UTC+8: 8 * 60 = +480
 | 
						||
		// or using options.minute_offset?
 | 
						||
		if (!isNaN(options.offset)) {
 | 
						||
			date_value = new Date(date_value.getTime()
 | 
						||
					+ ONE_MINUTE_LENGTH_VALUE
 | 
						||
					* (options.offset - String_to_Date.default_offset));
 | 
						||
		}
 | 
						||
 | 
						||
		function convertor(s) {
 | 
						||
			return s.replace(search, function($0, $1, $2) {
 | 
						||
				// 可以在 conversion 中,用
 | 
						||
				// this[conversion name].apply(this, arguments)
 | 
						||
				// 來取得另一 conversion 之結果。
 | 
						||
				var v = $2 in original_Date ? original_Date[$2]
 | 
						||
				// original_Date[$2] 為物件本身之特殊屬性,應當排在泛用函數 conversion[$2] 之前。
 | 
						||
				: typeof (v = conversion[$2]) === 'function' ? conversion[$2](
 | 
						||
						date_value, options)
 | 
						||
				//
 | 
						||
				: v in original_Date ? original_Date[v]
 | 
						||
				// 將之當作 format。
 | 
						||
				: /%/.test(v) ? parse_escape(v, convertor)
 | 
						||
				//
 | 
						||
				: v;
 | 
						||
				// library_namespace.debug([v, $1, $2]);
 | 
						||
				return $1 ? pad(v, $1) : v;
 | 
						||
			});
 | 
						||
		}
 | 
						||
 | 
						||
		return parse_escape(format || strftime.default_format, convertor);
 | 
						||
	}
 | 
						||
 | 
						||
	// .toISOString(): '%4Y-%2m-%2dT%2H:%2M:%2S.%fZ'
 | 
						||
	strftime.default_format = '%Y/%m/%d %H:%M:%S.%f';
 | 
						||
 | 
						||
	/**
 | 
						||
	 * 設定支援的 conversion specifications (轉換規格).<br />
 | 
						||
	 * 將直接使用輸入,因此呼叫後若改變 conversion specifications object 將出現問題!<br />
 | 
						||
	 * 
 | 
						||
	 * @example <code>
 | 
						||
	 * library_namespace.Date_to_String.parser.strftime.set_conversion({
 | 
						||
	 date : function() {
 | 
						||
	 return this.getDate();
 | 
						||
	 }
 | 
						||
	 }, 'cmn-Hant-TW');
 | 
						||
	 * </code>
 | 
						||
	 * 
 | 
						||
	 * @param {Object}conversion
 | 
						||
	 *            conversion specifications (轉換規格)。
 | 
						||
	 * @param {String}locale
 | 
						||
	 *            輸出的地區語系設定。
 | 
						||
	 * 
 | 
						||
	 * @returns {RegExp} 所須搜尋的 conversion specifications (轉換規格) pattern。
 | 
						||
	 */
 | 
						||
	strftime.set_conversion = function(conversion, locale, options) {
 | 
						||
		var i, v, locale_conversion, gettext = library_namespace.gettext,
 | 
						||
		// escape special char.
 | 
						||
		escape_string = function(s) {
 | 
						||
			return s.replace(/[*?!+.()\[\]\|\-^$\\\/]/g, '\\$0');
 | 
						||
		},
 | 
						||
		/**
 | 
						||
		 * 所須搜尋的 conversion specifications (轉換規格) pattern.
 | 
						||
		 */
 | 
						||
		search;
 | 
						||
 | 
						||
		if (!strftime.search) {
 | 
						||
			library_namespace.debug('初始化 strftime', 2,
 | 
						||
					'strftime.set_conversion');
 | 
						||
			strftime.search = Object.create(null);
 | 
						||
			strftime.conversion = Object.create(null);
 | 
						||
		}
 | 
						||
 | 
						||
		// 前置處理。
 | 
						||
		if (!library_namespace.is_Object(options)) {
 | 
						||
			options = Object.create(null);
 | 
						||
		}
 | 
						||
 | 
						||
		if (locale && gettext.to_standard && !options.no_gettext) {
 | 
						||
			locale = gettext.to_standard(locale);
 | 
						||
		}
 | 
						||
		if (!locale) {
 | 
						||
			locale = strftime.null_domain;
 | 
						||
		}
 | 
						||
 | 
						||
		if (!(locale in (locale_conversion = strftime.conversion))) {
 | 
						||
			locale_conversion[locale] = Object.create(null);
 | 
						||
		}
 | 
						||
		locale_conversion = locale_conversion[locale];
 | 
						||
		for (i in conversion) {
 | 
						||
			// 不變更引數 conversion,且允許重複添增。
 | 
						||
			locale_conversion[i] = v = conversion[i];
 | 
						||
			if (v in locale_conversion) {
 | 
						||
				search = [ v ];
 | 
						||
				while (v in locale_conversion) {
 | 
						||
					// 處理 alias。
 | 
						||
					locale_conversion[i] = v = locale_conversion[v];
 | 
						||
					if (search.includes(v)) {
 | 
						||
						// 預防迴圈。
 | 
						||
						break;
 | 
						||
					}
 | 
						||
					search.push(v);
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		v = '';
 | 
						||
		search = [];
 | 
						||
		// 造出 search pattern。
 | 
						||
		for (i in locale_conversion) {
 | 
						||
			if ((i = escape_string(i)).length > 1) {
 | 
						||
				search.push(i);
 | 
						||
			} else {
 | 
						||
				v += i;
 | 
						||
			}
 | 
						||
		}
 | 
						||
		// 從長的排到短的。
 | 
						||
		search.sort(function(a, b) {
 | 
						||
			// 長→短
 | 
						||
			return b.length - a.length;
 | 
						||
		});
 | 
						||
		if (v) {
 | 
						||
			search.push('[' + v + ']');
 | 
						||
		}
 | 
						||
		strftime.search[locale] = search = new RegExp('%(\\d?)('
 | 
						||
				+ search.join('|') + ')', 'g');
 | 
						||
		library_namespace.debug('use conversion specifications ' + locale
 | 
						||
				+ ': ' + search, 2, 'strftime.set_conversion');
 | 
						||
 | 
						||
		return search;
 | 
						||
	};
 | 
						||
 | 
						||
	strftime.null_domain = '';
 | 
						||
 | 
						||
	var gettext_date = library_namespace.gettext.date;
 | 
						||
	if (!gettext_date) {
 | 
						||
		gettext_date = function(v) {
 | 
						||
			if (library_namespace.locale.gettext) {
 | 
						||
				gettext_date = library_namespace.locale.gettext.date;
 | 
						||
				return gettext_date[this].apply(gettext_date, arguments);
 | 
						||
			}
 | 
						||
			return v;
 | 
						||
		};
 | 
						||
		'year,month,date,week,full_week'.split(',').forEach(function(type) {
 | 
						||
			gettext_date[type] = gettext_date.bind(type);
 | 
						||
		});
 | 
						||
	}
 | 
						||
 | 
						||
	// common year 月初累積日。查表法用。
 | 
						||
	// accumulated_days[0] = 到一月月初時累積的天數。
 | 
						||
	var accumulated_days = [ 0 ],
 | 
						||
	// common year 每月日數。
 | 
						||
	// [ 31, 28, 31, .. ]
 | 
						||
	month_days = [];
 | 
						||
	(function() {
 | 
						||
		// select a common year.
 | 
						||
		var date = new Date(1902, 0, 1), month = 0, days, all_days = 0;
 | 
						||
		while (month++ < 12) {
 | 
						||
			// 設在月底,以取得當月日數。
 | 
						||
			date.setMonth(month, 0);
 | 
						||
			month_days.push(days = date.getDate());
 | 
						||
			accumulated_days.push(all_days += days);
 | 
						||
		}
 | 
						||
	})();
 | 
						||
	Object.seal(month_days);
 | 
						||
	Object.seal(accumulated_days);
 | 
						||
 | 
						||
	/**
 | 
						||
	 * 自 year/month/date 開始之本年曆數(每月日數)。
 | 
						||
	 * 
 | 
						||
	 * @param {Integer|Boolean}[is_leap]
 | 
						||
	 *            is leap year
 | 
						||
	 * @param {Integer}[month]
 | 
						||
	 *            月
 | 
						||
	 * @param {Integer}[date]
 | 
						||
	 *            月日期
 | 
						||
	 * @returns {Array} 每月日數。
 | 
						||
	 */
 | 
						||
	function get_month_days(is_leap, month, date) {
 | 
						||
		// month: serial → index
 | 
						||
		if (month |= 0)
 | 
						||
			--month;
 | 
						||
		// assert: month: 0–11, date: 1–28/29/30/31
 | 
						||
 | 
						||
		var year_data = month_days.slice(month);
 | 
						||
 | 
						||
		if (month < 2
 | 
						||
		// 處理閏年。
 | 
						||
		&& (typeof is_leap === 'number' ? is_leap_year(is_leap) : is_leap)) {
 | 
						||
			year_data[1 - month]++;
 | 
						||
		}
 | 
						||
 | 
						||
		// 處理首月的曆數。
 | 
						||
		if (date > 1) {
 | 
						||
			year_data[0] -= date - 1;
 | 
						||
		}
 | 
						||
 | 
						||
		return year_data;
 | 
						||
	}
 | 
						||
	_.get_month_days = get_month_days;
 | 
						||
 | 
						||
	/**
 | 
						||
	 * ordinal date, 年日期.<br />
 | 
						||
	 * 本年開始至 year/month/date 之本年年內的天數。
 | 
						||
	 * 
 | 
						||
	 * @param {Integer}month
 | 
						||
	 *            月
 | 
						||
	 * @param {Integer}date
 | 
						||
	 *            月日期
 | 
						||
	 * @param {Integer|Boolean}[is_leap]
 | 
						||
	 *            is leap year
 | 
						||
	 * @returns {Integer} 年內的日數。
 | 
						||
	 * @see https://en.wikipedia.org/wiki/Ordinal_date
 | 
						||
	 */
 | 
						||
	function ordinal_date(month, date, is_leap) {
 | 
						||
		if (is_Date(month)) {
 | 
						||
			date = month.getDate() | 0, is_leap = is_leap_year(month
 | 
						||
					.getFullYear()), month = month.getMonth() | 0;
 | 
						||
		} else {
 | 
						||
			--month;
 | 
						||
			if (typeof is_leap === 'number') {
 | 
						||
				// treat as year.
 | 
						||
				is_leap = is_leap_year(is_leap);
 | 
						||
			}
 | 
						||
		}
 | 
						||
		if (is_leap && month > 1) {
 | 
						||
			date++;
 | 
						||
		}
 | 
						||
		return accumulated_days[month] + date | 0;
 | 
						||
	}
 | 
						||
	_.ordinal_date = ordinal_date;
 | 
						||
 | 
						||
	// week date, 週日期, 表示年內的星期數天數,再加上星期內第幾天。
 | 
						||
	// https://en.wikipedia.org/wiki/ISO_week_date
 | 
						||
	// return [ year, week (1-52 or 53), weekday (1-7) ]
 | 
						||
	// var w = week_date(new Date);
 | 
						||
	// w[0].pad(4) + '-W' + w[1].pad(2) + '-' + w[2]
 | 
						||
	function week_date(date, to_ISO) {
 | 
						||
		var year = date.getFullYear() | 0, weekday = date.getDay() | 0, days = ordinal_date(date) | 0, week;
 | 
						||
		if (weekday === 0) {
 | 
						||
			weekday = 7;
 | 
						||
		}
 | 
						||
		week = (10 + days - weekday) / 7 | 0;
 | 
						||
		if (week === 0) {
 | 
						||
			week = 53;
 | 
						||
			year--;
 | 
						||
		}
 | 
						||
		// 計算首日是否為 星期四 或 (星期三 + leap);
 | 
						||
		// 此為有 W53 之條件。
 | 
						||
		else if (week > 52 && (days = (weekday + 1 - days).mod(7)) !== 4
 | 
						||
				&& (days !== 3 || !is_leap_year(year))) {
 | 
						||
			week = 1;
 | 
						||
			year++;
 | 
						||
		}
 | 
						||
 | 
						||
		if (to_ISO) {
 | 
						||
			// TODO: 在IS0 8601中星期以星期一開始
 | 
						||
			// 一年的首星期必須包含1月4日, 包含一年的首個星期四
 | 
						||
			year = year.pad(4);
 | 
						||
			week = 'W' + week.pad(2);
 | 
						||
		}
 | 
						||
		days = [ year, week, weekday ];
 | 
						||
		if (to_ISO === 1) {
 | 
						||
			days = days.join('');
 | 
						||
		} else if (to_ISO !== 2) {
 | 
						||
			days = days.join('-');
 | 
						||
		}
 | 
						||
 | 
						||
		return days;
 | 
						||
	}
 | 
						||
	_.week_date = week_date;
 | 
						||
 | 
						||
	// <a href="http://www.cppreference.com/wiki/cn/chrono/c/strftime"
 | 
						||
	// accessdate="2012/3/24 15:23">strftime [C++ Reference]</a>
 | 
						||
	// <a
 | 
						||
	// href="http://help.adobe.com/zh_TW/as2/reference/flashlite/WS5b3ccc516d4fbf351e63e3d118cd9b5f6e-7923.html"
 | 
						||
	// accessdate="2012/3/24 15:29">Adobe Flash Platform * Date</a>
 | 
						||
	// <a href="http://msdn.microsoft.com/zh-tw/library/dca21baa.aspx"
 | 
						||
	// accessdate="2012/3/24 15:30">Date 物件</a>
 | 
						||
	// 除非必要,這邊不應用上 options.original_Date。
 | 
						||
	strftime.default_conversion = {
 | 
						||
		// ----------------------------
 | 
						||
		// date
 | 
						||
 | 
						||
		// 完整年份(非兩位數的數字,近十年應為四位數的數字,如2013) 以4位十進制數寫年份。
 | 
						||
		Y : function(date_value, options) {
 | 
						||
			return date_value.getFullYear();
 | 
						||
		},
 | 
						||
		// 以替用方式寫年。例如在 ja_JP 本地環境中,以「平成23年」取代「2011年」。
 | 
						||
		EY : function(date_value, options) {
 | 
						||
			return gettext_date.year(
 | 
						||
			// (options && options.original_Date || date_value)
 | 
						||
			date_value.getFullYear(), options.numeral || options.locale);
 | 
						||
		},
 | 
						||
 | 
						||
		// 月分 (1-12)。 將月份寫作十進制數(範圍[01,12])。
 | 
						||
		// also: %B, %b
 | 
						||
		m : function(date_value, options) {
 | 
						||
			return 1 + date_value.getMonth();
 | 
						||
		},
 | 
						||
		// 寫完整月名,例如 October。
 | 
						||
		B : function full_month_name(date_value, options) {
 | 
						||
			// console.trace(options);
 | 
						||
			return gettext_date.month(1 +
 | 
						||
			// (options && options.original_Date || date_value)
 | 
						||
			date_value.getMonth(), options.locale);
 | 
						||
		},
 | 
						||
		// 寫縮略月名,例如 Oct (本地環境依賴)。
 | 
						||
		b : function(date_value, options) {
 | 
						||
			var month_name = strftime.default_conversion.B.apply(this,
 | 
						||
					arguments);
 | 
						||
			if (/^en/.test(options.locale))
 | 
						||
				month_name = month_name.slice(0, 3);
 | 
						||
			return month_name;
 | 
						||
		},
 | 
						||
 | 
						||
		// 月中的第幾天 (1-31) 以十進制數寫月的第幾日(範圍[01,31])。
 | 
						||
		d : function(date_value, options) {
 | 
						||
			return date_value.getDate();
 | 
						||
		},
 | 
						||
		// 以替用數字系統寫零基的月的第幾日。例如 ja_JP 本地環境中「二十七」取代「 27 」。
 | 
						||
		Od : function(date_value, options) {
 | 
						||
			return gettext_date.date(
 | 
						||
			// (options && options.original_Date || date_value)
 | 
						||
			date_value.getDate(), options.locale);
 | 
						||
		},
 | 
						||
 | 
						||
		// ----------------------------
 | 
						||
		// week
 | 
						||
 | 
						||
		// 2017/8/16 增加在地化的星期名稱表示法。之前曾經用過"%w"這個方式的,都需要改成"%A"。
 | 
						||
		// 寫縮略的星期日期名,例如Fri(本地環境依賴)。
 | 
						||
		a : function(date_value, options) {
 | 
						||
			return gettext_date.week(
 | 
						||
			// (options && options.original_Date || date_value)
 | 
						||
			date_value.getDay(), options.locale);
 | 
						||
		},
 | 
						||
		// 寫完整的星期日期名,例如Friday(本地環境依賴)。
 | 
						||
		A : function(date_value, options) {
 | 
						||
			return gettext_date.full_week(
 | 
						||
			// (options && options.original_Date || date_value)
 | 
						||
			date_value.getDay(), options.locale);
 | 
						||
		},
 | 
						||
		// 星期 (0-6) 以十進制數寫星期日期,其中星期日是0(範圍[0-6])。
 | 
						||
		w : function(date_value, options) {
 | 
						||
			return date_value.getDay();
 | 
						||
		},
 | 
						||
 | 
						||
		// ----------------------------
 | 
						||
		// time
 | 
						||
 | 
						||
		// 小時數 (0-23) 以十進制數寫時,24小時制(範圍[00-23])。
 | 
						||
		H : function(date_value, options) {
 | 
						||
			return date_value.getHours();
 | 
						||
		},
 | 
						||
		// 分鐘數 (0-59) 以十進制數寫分(範圍[00,59])。
 | 
						||
		M : function(date_value, options) {
 | 
						||
			return date_value.getMinutes();
 | 
						||
		},
 | 
						||
		// 秒數 (0-59) 以十進制數寫秒(範圍[00,59])。
 | 
						||
		S : function(date_value, options) {
 | 
						||
			return date_value.getSeconds();
 | 
						||
		},
 | 
						||
		/**
 | 
						||
		 * 毫秒(milliseconds) (000-999)
 | 
						||
		 * 
 | 
						||
		 * @see %f: zero-padded millisecond / microsecond: <a
 | 
						||
		 *      href="http://bugs.python.org/issue1982" accessdate="2012/3/24
 | 
						||
		 *      12:44">Issue 1982: Feature: extend strftime to accept
 | 
						||
		 *      milliseconds - Python tracker</a>
 | 
						||
		 */
 | 
						||
		f : function(date_value, options) {
 | 
						||
			var ms = date_value.getMilliseconds();
 | 
						||
			return ms > 99 ? ms : ms > 9 ? '0' + ms : ms >= 0 ? '00' + ms : ms;
 | 
						||
		},
 | 
						||
 | 
						||
		// 以 ISO 8601 格式(例如 -0430 )寫距 UTC 的偏移
 | 
						||
		z : function(date_value, options) {
 | 
						||
			var offset = '+', minutes = -date_value.getTimezoneOffset();
 | 
						||
			if (minutes < 0) {
 | 
						||
				offset = '-';
 | 
						||
				minutes = -minutes;
 | 
						||
			}
 | 
						||
			var hours = minutes / 60 | 0;
 | 
						||
			offset += hours.pad(2) + (minutes % 60).pad(2);
 | 
						||
			return offset;
 | 
						||
		},
 | 
						||
 | 
						||
		// ----------------------------
 | 
						||
		// misc
 | 
						||
 | 
						||
		// 年日數 以十進制數寫年的第幾日(範圍[001,366])。
 | 
						||
		// (new Date).format('%4Y-%3o')
 | 
						||
		j : function(date_value, options) {
 | 
						||
			return ordinal_date(date_value);
 | 
						||
		},
 | 
						||
		// 週數 以十進制數寫年的第幾個星期(星期一是星期的首日)(範圍[00,53])。
 | 
						||
		W : function(date_value, options) {
 | 
						||
			return week_date(date_value)[1];
 | 
						||
		},
 | 
						||
 | 
						||
		// 有相同開頭的時候,長的要放前面!
 | 
						||
		// (new Date).format('%JDN')
 | 
						||
		JDN : Date_to_JDN,
 | 
						||
		// (new Date).format('%JD')
 | 
						||
		JD : Date_to_JD,
 | 
						||
 | 
						||
		// gettext_config:{"id":"year-of-the-sexagenary-cycle"}
 | 
						||
		"歲次" : guess_year_stem_branch,
 | 
						||
		// alias
 | 
						||
		// gettext_config:{"id":"year-of-the-sexagenary-cycle"}
 | 
						||
		年干支 : '歲次',
 | 
						||
		// gettext_config:{"id":"year-of-the-sexagenary-cycle"}
 | 
						||
		年柱 : '歲次',
 | 
						||
 | 
						||
		日干支序 : date_stem_branch_index,
 | 
						||
		// 計算距離甲子共有幾日,再於 index_to_stem_branch() 取模數。
 | 
						||
		// 假定為不間斷之循環紀日。
 | 
						||
		// gettext_config:{"id":"day-of-the-sexagenary-cycle"}
 | 
						||
		"日干支" : function(date_value, options) {
 | 
						||
			return index_to_stem_branch(date_stem_branch_index(date_value,
 | 
						||
					options));
 | 
						||
		},
 | 
						||
		// gettext_config:{"id":"day-of-the-sexagenary-cycle"}
 | 
						||
		日柱 : '日干支',
 | 
						||
 | 
						||
		// 時辰干支: 計算距離甲子共有幾個時辰,再於 index_to_stem_branch() 取模數。
 | 
						||
		// 時干支不受子初分日(子初換日/子正換日)影響。
 | 
						||
		時干支序 : hour_stem_branch_index,
 | 
						||
		時干支 : function(date_value, options) {
 | 
						||
			return index_to_stem_branch(hour_stem_branch_index(date_value,
 | 
						||
					options));
 | 
						||
		},
 | 
						||
		時柱 : '時干支',
 | 
						||
		// 每刻15分。
 | 
						||
		// e.g., 子正初刻
 | 
						||
		// 隋後普遍行百刻制,每天100刻。至順治二年(公元1645年)頒行時憲曆後,改為日96刻,每時辰八刻(初初刻、初一刻、初二刻、初三刻、正初刻、正一刻、正二刻、正三刻)[7,13,14]。自此每刻15分,無「四刻」之名。
 | 
						||
		時刻 : function(date_value, options) {
 | 
						||
			var diff = Math.floor(hour_stem_branch_index(date_value, options))
 | 
						||
			// 12: BRANCH_LIST.length
 | 
						||
			.mod(12);
 | 
						||
			return BRANCH_LIST.charAt(diff) + (diff % 2 ? '正' : '初')
 | 
						||
					+ '初一二三'.charAt(date_value.getMinutes() / 4 | 0) + '刻';
 | 
						||
		}
 | 
						||
	};
 | 
						||
 | 
						||
	strftime.set_conversion(strftime.default_conversion, strftime.null_domain);
 | 
						||
 | 
						||
	function Date_to_Gregorian(date_value, format, locale, options) {
 | 
						||
 | 
						||
		if ('no_year_0' in options ? options.no_year_0
 | 
						||
				: Date_to_String.no_year_0) {
 | 
						||
			var Year = date_value.getYear(), FullYear = date_value
 | 
						||
					.getFullYear(), UTCFullYear = date_value.getUTCFullYear();
 | 
						||
 | 
						||
			if (FullYear < 1 || UTCFullYear < 1) {
 | 
						||
				// 處理 0 year: Gregorian 與 Julian 沒有 0 year。
 | 
						||
				// 因為公元前之公曆閏年出現在 1, 5, 9, 13, .. BC,因此無法以「除以4」計算。
 | 
						||
				// 應該直接改年分,而不是將時間直接將其值減掉 (0/1/1 0:-1/1/1 0:),
 | 
						||
				// 而當作彷彿沒有 0年這段期間的存在,直接從-1年開始。
 | 
						||
				//
 | 
						||
				// <a
 | 
						||
				// href="http://en.wikipedia.org/wiki/Astronomical_year_numbering"
 | 
						||
				// accessdate="2013/2/11 15:40" title="Astronomical year
 | 
						||
				// numbering">天文年號</a>
 | 
						||
				// <a
 | 
						||
				// href="http://en.wikipedia.org/wiki/Gregorian_calendar#Proleptic_Gregorian_calendar"
 | 
						||
				// accessdate="2013/2/11 9:7">Gregorian calendar</a>
 | 
						||
				//
 | 
						||
				// For dates before the year 1, unlike the proleptic Gregorian
 | 
						||
				// calendar used in the international standard ISO 8601, the
 | 
						||
				// traditional
 | 
						||
				// proleptic Gregorian calendar (like the Julian calendar) does
 | 
						||
				// not
 | 
						||
				// have a year 0 and instead uses the ordinal numbers 1, 2, …
 | 
						||
				// both for
 | 
						||
				// years AD and BC.
 | 
						||
 | 
						||
				// 前置處理。
 | 
						||
				if (!library_namespace.is_Object(options)) {
 | 
						||
					options = Object.create(null);
 | 
						||
				}
 | 
						||
 | 
						||
				if (!options.no_new_Date) {
 | 
						||
					// IE 需要 .getTime():IE8 以 new Date(Date
 | 
						||
					// object) 會得到 NaN!
 | 
						||
					date_value = new Date(date_value.getTime());
 | 
						||
				}
 | 
						||
 | 
						||
				if (FullYear < 1) {
 | 
						||
					date_value.getYear = new Function('return ' + (Year - 1));
 | 
						||
					date_value.getFullYear = new Function('return '
 | 
						||
							+ (FullYear - 1));
 | 
						||
				}
 | 
						||
				if (UTCFullYear < 1) {
 | 
						||
					date_value.getUTCFullYear = FullYear === UTCFullYear ?
 | 
						||
					// 一般情況 (FullYear === UTCFullYear)。
 | 
						||
					// else: 預防 1/1, 12/31 時,getYear()
 | 
						||
					// 與 getUTCFullYear() 可能會有不同值。
 | 
						||
					// 盡量少帶入變數。
 | 
						||
					date_value.getFullYear : new Function('return '
 | 
						||
							+ (UTCFullYear - 1));
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		return strftime(date_value, format, locale, options);
 | 
						||
	}
 | 
						||
 | 
						||
	// 代替 getDate() 用。
 | 
						||
	var leap_date = new Function('return 29');
 | 
						||
 | 
						||
	/**
 | 
						||
	 * proleptic Julian calendar.<br />
 | 
						||
	 * 
 | 
						||
	 * TODO: 太沒效率。
 | 
						||
	 * 
 | 
						||
	 * 以系統內建的計時機制,<br />
 | 
						||
	 * 將擴展/外推/延伸的格里曆, proleptic Gregorian calendar<br /> → proleptic Julian
 | 
						||
	 * calendar for 4713 BCE-2200 CE.
 | 
						||
	 * 
 | 
						||
	 * @param {Date}date_value
 | 
						||
	 *            要格式化的日期。
 | 
						||
	 * @param {String}format
 | 
						||
	 *            輸出的格式字串。
 | 
						||
	 * @param {String}locale
 | 
						||
	 *            輸出的地區語系設定。
 | 
						||
	 * @param {Object}options
 | 
						||
	 *            選擇性功能。
 | 
						||
	 * 
 | 
						||
	 * @returns {String} 依照指定格式輸出的日期與時間。
 | 
						||
	 * 
 | 
						||
	 * @see<br />
 | 
						||
	 *      <a
 | 
						||
	 *      href="http://en.wikipedia.org/wiki/Conversion_between_Julian_and_Gregorian_calendars"
 | 
						||
	 *      accessdate="2013/2/11 9:8">Conversion between Julian and Gregorian
 | 
						||
	 *      calendars</a>
 | 
						||
	 */
 | 
						||
	function Date_to_Julian(date_value, format, locale, options) {
 | 
						||
		// 計算與 UTC 的差距。
 | 
						||
		var year = date_value.getFullYear(),
 | 
						||
		// 計算 Gregorian 與 Julian 的 different days。
 | 
						||
		// 0年時,差了 2日。這演算法對差異大至 31+28 日的時段不適用。
 | 
						||
		shift_days = 2 + Math.floor(year / 400) - Math.floor(year / 100),
 | 
						||
		// 當年 Julian 與 UTC 為不同閏年規定: Gregorian 當年沒有閏日,但 Julian 有。
 | 
						||
		is_leap_year = is_different_leap_year(year);
 | 
						||
 | 
						||
		if (shift_days || is_leap_year) {
 | 
						||
			var week_day = date_value.getDay();
 | 
						||
 | 
						||
			// 前置處理。
 | 
						||
			if (!library_namespace.is_Object(options)) {
 | 
						||
				options = Object.create(null);
 | 
						||
			}
 | 
						||
			// 因為可能會更改 date_value,因此把本來的 date_value 放在 options
 | 
						||
			// 中供有需要的取用。
 | 
						||
			if (!options.original_Date) {
 | 
						||
				options.original_Date = date_value;
 | 
						||
			}
 | 
						||
			// 不改變原先的 date_value。
 | 
						||
			options.no_new_Date = true;
 | 
						||
			// 以新的 UTC Date instance 模擬 Julian calendar。
 | 
						||
			// IE 需要 .getTime():IE8 以 new Date(Date object) 會得到 NaN!
 | 
						||
			date_value = new Date(date_value.getTime());
 | 
						||
			date_value.setDate(date_value.getDate() + shift_days);
 | 
						||
 | 
						||
			if (is_leap_year && date_value.getFullYear() <= year) {
 | 
						||
				// 原 → 現 → 應
 | 
						||
				// ... → 2/27 → 2/28
 | 
						||
				// ... → 2/28 → 2/29
 | 
						||
				// ... → 3/1 → 3/1
 | 
						||
				var month = date_value.getMonth();
 | 
						||
				// 分水嶺: 以 Julian date 3/1 0:0 為分界。
 | 
						||
				// 在不同閏年當年,Julian date 3/1 前需要特別處理。
 | 
						||
				if (month < 2 || date_value.getFullYear() < year) {
 | 
						||
					// 因為加至當年沒有閏日的 Gregorian,2/29 會變成 3/1。
 | 
						||
					if (month === 1 && date_value.getDate() === 28) {
 | 
						||
						// is leap day
 | 
						||
						// 便宜行事: 不設 delta,直接把 3/1 → 2/28,再強制使 .getDate() = 29。
 | 
						||
						// TODO: .getDay() 恐有誤。
 | 
						||
						// 當 Julian date 2/29 閏日當日,UTC 非閏日的時候,需要特別處理。
 | 
						||
						// TODO: 處理其他。
 | 
						||
						date_value.getDate = leap_date;
 | 
						||
					} else {
 | 
						||
						// Julian date 2/29 閏日前。少算的,須更正。
 | 
						||
						// 閏日前(before Julian calendar leap day)還要算是上一階段。
 | 
						||
						date_value.setDate(date_value.getDate() + 1);
 | 
						||
					}
 | 
						||
				}
 | 
						||
			}
 | 
						||
 | 
						||
			// 處理 day of the week: 就算以另一個日期模擬 UTC,原先的星期不會改變。
 | 
						||
			if (date_value.getDay() !== week_day) {
 | 
						||
				date_value.getDay = new Function('return ' + week_day);
 | 
						||
			}
 | 
						||
			// TODO: .getUTCDay()
 | 
						||
		}
 | 
						||
 | 
						||
		date_value = Date_to_Gregorian(date_value, format, locale, options);
 | 
						||
		if (library_namespace.is_Object(options)) {
 | 
						||
			// 預防此 options 為重複使用。
 | 
						||
			delete options.original_Date;
 | 
						||
		}
 | 
						||
		return date_value;
 | 
						||
	}
 | 
						||
 | 
						||
	/**
 | 
						||
	 * <code>
 | 
						||
 | 
						||
 | 
						||
	There is no year 0 in the Julian system!
 | 
						||
 | 
						||
 | 
						||
	The Julian date for CE  1582 October  4 00:00:00.0 UT is
 | 
						||
	JD 2299159.500000
 | 
						||
 | 
						||
	The dates 5 through 14 October, 1582, do not exist in the Gregorian Calendar!
 | 
						||
 | 
						||
	The Julian date for CE  1582 October 15 00:00:00.0 UT is
 | 
						||
	JD 2299160.500000
 | 
						||
 | 
						||
	JD 2299159.000000 is
 | 
						||
	CE 1582 October 03 12:00:00.0 UT  Wednesday
 | 
						||
 | 
						||
	JD 2299160.000000 is
 | 
						||
	CE 1582 October 04 12:00:00.0 UT  Thursday
 | 
						||
 | 
						||
	JD 2299161.000000 is
 | 
						||
	CE 1582 October 15 12:00:00.0 UT  Friday
 | 
						||
 | 
						||
 | 
						||
 | 
						||
	JD      0.000000 is
 | 
						||
	BCE 4713 January 01 12:00:00.0 UT  Monday
 | 
						||
 | 
						||
	JD      1.000000 is
 | 
						||
	BCE 4713 January 02 12:00:00.0 UT  Tuesday
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
	The Julian date for CE     1 January  1 00:00:00.0 UT is
 | 
						||
	JD 1721423.500000
 | 
						||
 | 
						||
	JD 1721423.000000 is
 | 
						||
	BCE    1 December 31 12:00:00.0 UT  Friday
 | 
						||
 | 
						||
	JD 1721424.000000 is
 | 
						||
	CE    1 January 01 12:00:00.0 UT  Saturday
 | 
						||
 | 
						||
 | 
						||
	The Julian date for CE     1 March  1 00:00:00.0 UT is
 | 
						||
	JD 1721482.500000
 | 
						||
 | 
						||
	JD 1721482.000000 is
 | 
						||
	CE    1 February 28 12:00:00.0 UT  Monday
 | 
						||
 | 
						||
 | 
						||
 | 
						||
	The Julian date for CE  1000 February  1 00:00:00.0 UT is
 | 
						||
	JD 2086338.500000
 | 
						||
 | 
						||
	The Julian date for CE  1000 February 29 00:00:00.0 UT is
 | 
						||
	JD 2086366.500000
 | 
						||
 | 
						||
	JD 2086367.000000 is
 | 
						||
	CE 1000 February 29 12:00:00.0 UT  Thursday
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
	The Julian date for CE   500 February 29 00:00:00.0 UT is
 | 
						||
	JD 1903741.500000
 | 
						||
 | 
						||
	JD 1903742.000000 is
 | 
						||
	CE  500 February 29 12:00:00.0 UT  Tuesday
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
	The Julian date for CE   100 March  1 00:00:00.0 UT is
 | 
						||
	JD 1757642.500000
 | 
						||
 | 
						||
	JD 1757642.000000 is
 | 
						||
	CE  100 February 29 12:00:00.0 UT  Saturday
 | 
						||
 | 
						||
	The Julian date for CE     4 March  1 00:00:00.0 UT is
 | 
						||
	JD 1722578.500000
 | 
						||
 | 
						||
	JD 1722578.000000 is
 | 
						||
	CE    4 February 29 12:00:00.0 UT  Friday
 | 
						||
 | 
						||
	The Julian date for CE  2000 January  1 12:00:00.0 UT is
 | 
						||
	JD 2451545.000000
 | 
						||
 | 
						||
 | 
						||
 | 
						||
 | 
						||
	The Julian date for BCE  1000 January  1 12:00:00.0 UT is
 | 
						||
	JD 1356174.000000
 | 
						||
 | 
						||
 | 
						||
	The Julian date for BCE  4000 January  1 12:00:00.0 UT is
 | 
						||
	JD 260424.000000
 | 
						||
 | 
						||
 | 
						||
	The Julian date for BCE  4710 January  1 12:00:00.0 UT is
 | 
						||
	JD   1096.000000
 | 
						||
 | 
						||
 | 
						||
	The Julian date for BCE  4701 January  1 12:00:00.0 UT is
 | 
						||
	JD   4383.000000
 | 
						||
 | 
						||
	The Julian date for BCE  4700 January  1 12:00:00.0 UT is
 | 
						||
	JD   4749.000000
 | 
						||
 | 
						||
	The Julian date for BCE  4700 February 28 12:00:00.0 UT is
 | 
						||
	JD   4807.000000
 | 
						||
 | 
						||
	The Julian date for BCE   101 February 29 12:00:00.0 UT is
 | 
						||
	JD 1684592.000000
 | 
						||
 | 
						||
 | 
						||
 | 
						||
	date=new Date(1582,10,15);
 | 
						||
	CeL.Date_to_JDN(new Date(1582,10,15));
 | 
						||
 | 
						||
	</code>
 | 
						||
	 */
 | 
						||
 | 
						||
	// 設定 .as_UTC_time 當作 UTC 時,將加上為本地時間調整所需之 offset。
 | 
						||
	var Julian_Date_local_offset = String_to_Date.default_offset / 60 / 24;
 | 
						||
 | 
						||
	// Julian Date (JD)
 | 
						||
	// <a href="http://aa.usno.navy.mil/data/docs/JulianDate.php"
 | 
						||
	// accessdate="2013/2/11 9:10">Julian Date Converter</a>
 | 
						||
	function Date_to_JD(date_value, options) {
 | 
						||
		date_value = ((options && options.original_Date || date_value) - Julian_Date_epoch)
 | 
						||
				/ ONE_DAY_LENGTH_VALUE;
 | 
						||
		if (options && options.as_UTC_time) {
 | 
						||
			date_value += Julian_Date_local_offset;
 | 
						||
		}
 | 
						||
		return date_value;
 | 
						||
	}
 | 
						||
	// Julian Day Number (JDN)
 | 
						||
	function Date_to_JDN(date_value, options) {
 | 
						||
		// JDN0: JD = -.5 – .5⁻
 | 
						||
		// JDN0: JD + .5 = 0 – 1⁻
 | 
						||
		// 精神:以 UTC 計算時,當天從頭至尾都是相同的 JDN。
 | 
						||
		// 基本上世界每個地方在當地當天 12:0 都有相同的 JDN,但不保證世界每個地方在當地當天 0:0 都有相同的 JDN。
 | 
						||
		return Math.floor(Date_to_JD(date_value, options) + .5);
 | 
						||
	}
 | 
						||
	// Time Conversion Tool
 | 
						||
	// http://ssd.jpl.nasa.gov/tc.cgi
 | 
						||
	function JD_to_Date(JD) {
 | 
						||
		// 注意:此輸出常顯示為系統之 proleptic Gregorian calendar,
 | 
						||
		// 而一般天文計算使用 proleptic Julian calendar!
 | 
						||
		return new Date(Julian_Date_epoch + ONE_DAY_LENGTH_VALUE * JD);
 | 
						||
	}
 | 
						||
 | 
						||
	_.Date_to_JD = Date_to_JD;
 | 
						||
	_.Date_to_JDN = Date_to_JDN;
 | 
						||
	_.JD_to_Date = JD_to_Date;
 | 
						||
 | 
						||
	// ---------------------------------------------------------------------------//
 | 
						||
	// basic constants. 定義基本常數。
 | 
						||
 | 
						||
	// for Julian Date (JD), Julian Day Number (JDN).
 | 
						||
	// Julian Date: 由公元前4713年1月1日,協調世界時中午12時開始所經過的天數。
 | 
						||
	// 原點實際設在 -004713-11-24T12:00:00.000Z。
 | 
						||
	// http://www.tondering.dk/claus/cal/julperiod.php
 | 
						||
	// http://aa.usno.navy.mil/data/docs/JulianDate.php
 | 
						||
	var Julian_Date_epoch = Date.parse('-004713-11-24T12:00:00.000Z');
 | 
						||
	if (!Julian_Date_epoch) {
 | 
						||
		// 替代方法. 慢.
 | 
						||
		Julian_Date_epoch = String_to_Date('-4713/1/1 12:0', {
 | 
						||
			parser : 'Julian',
 | 
						||
			zone : 0
 | 
						||
		});
 | 
						||
		Julian_Date_epoch = Julian_Date_epoch.getTime()
 | 
						||
				+ (present_local_minute_offset - (Julian_Date_epoch
 | 
						||
						.getTimezoneOffset() || 0)) * ONE_MINUTE_LENGTH_VALUE;
 | 
						||
	}
 | 
						||
 | 
						||
	// 預設的 Gregorian calendar 改曆日期:
 | 
						||
	// Julian calendar → Gregorian calendar.
 | 
						||
	//
 | 
						||
	// 這天開始使用 Gregorian calendar。之前使用 Julian calendar。
 | 
						||
	// e.g., UTC/Gregorian 1582/10/14 ⇔ Julian 1582/10/4.
 | 
						||
	//
 | 
						||
	// 西曆改曆分界點。這天之後採用 Gregorian calendar 表示。
 | 
						||
	// 西曆以1582年10月15日為改曆分界點,Julian calendar(儒略曆)1582年10月4日的下一日為 Gregorian
 | 
						||
	// calendar(格里高利曆)1582年10月15日。
 | 
						||
	var reform_year = 1582;
 | 
						||
	_.Gregorian_reform_date = new Date(reform_year, 10 - 1, 15);
 | 
						||
 | 
						||
	// gcal-3.6/doc/GREG-REFORM
 | 
						||
	// http://www.tondering.dk/claus/cal/gregorian.php
 | 
						||
	// http://www.webexhibits.org/calendars/year-countries.html
 | 
						||
	// http://sizes.com/time/cal_Gregadoption.htm
 | 
						||
	/*
 | 
						||
	 * https://en.wikipedia.org/wiki/List_of_adoption_dates_of_the_Gregorian_calendar_per_country
 | 
						||
	 * 
 | 
						||
	 * 使用公共轉換組「外國地名翻譯」
 | 
						||
	 * https://zh.wikipedia.org/wiki/%E6%A8%A1%E5%9D%97:CGroup/%E5%9C%B0%E5%90%8D
 | 
						||
	 */
 | 
						||
	var reform_by_region = {
 | 
						||
		// gettext_config:{"id":"italy"}
 | 
						||
		'Italy' : '1582/10/15',
 | 
						||
		// gettext_config:{"id":"poland"}
 | 
						||
		'Poland' : '1582/10/15',
 | 
						||
		// gettext_config:{"id":"portugal"}
 | 
						||
		'Portugal' : '1582/10/15',
 | 
						||
		// gettext_config:{"id":"spain"}
 | 
						||
		'Spain' : '1582/10/15',
 | 
						||
		// gettext_config:{"id":"france"}
 | 
						||
		'France' : '1582/12/20',
 | 
						||
		// 盧森堡 Source?
 | 
						||
		// gettext_config:{"id":"luxembourg"}
 | 
						||
		'Luxembourg' : '1583/1/1',
 | 
						||
		// Holland: 1583/1/12
 | 
						||
		// gettext_config:{"id":"netherlands"}
 | 
						||
		'Netherlands' : '1583/1/12',
 | 
						||
		// Source?
 | 
						||
		// gettext_config:{"id":"bavaria"}
 | 
						||
		'Bavaria' : '1583/10/16',
 | 
						||
		// gettext_config:{"id":"austria"}
 | 
						||
		'Austria' : '1584/1/17',
 | 
						||
		// gettext_config:{"id":"switzerland"}
 | 
						||
		'Switzerland' : '1584/1/22',
 | 
						||
		// gettext_config:{"id":"hungary"}
 | 
						||
		'Hungary' : '1587/11/1',
 | 
						||
		// gettext_config:{"id":"germany"}
 | 
						||
		'Germany' : '1700/3/1',
 | 
						||
		// gettext_config:{"id":"norway"}
 | 
						||
		'Norway' : '1700/3/1',
 | 
						||
		// gettext_config:{"id":"denmark"}
 | 
						||
		'Denmark' : '1700/3/1',
 | 
						||
		// Kingdom of Great Britain, 大不列顛王國, グレートブリテン王国, 英國
 | 
						||
		// https://en.wikipedia.org/wiki/Calendar_%28New_Style%29_Act_1750
 | 
						||
		// gettext_config:{"id":"great-britain"}
 | 
						||
		'Great Britain' : '1752/9/14',
 | 
						||
		// gettext_config:{"id":"sweden"}
 | 
						||
		'Sweden' : '1753/3/1',
 | 
						||
		// gettext_config:{"id":"finland"}
 | 
						||
		'Finland' : '1753/3/1',
 | 
						||
		// 日本
 | 
						||
		// 'Japan' : '1873/1/1',
 | 
						||
		// 中國
 | 
						||
		// 'China' : '1911/11/20',
 | 
						||
		// gettext_config:{"id":"bulgaria"}
 | 
						||
		'Bulgaria' : '1916/4/14',
 | 
						||
		// USSR, U.S.S.R., 蘇聯
 | 
						||
		// gettext_config:{"id":"soviet-union"}
 | 
						||
		'Soviet Union' : '1918/2/14',
 | 
						||
		// gettext_config:{"id":"serbia"}
 | 
						||
		'Serbia' : '1919/2/1',
 | 
						||
		// gettext_config:{"id":"romania"}
 | 
						||
		'Romania' : '1919/2/1',
 | 
						||
		// gettext_config:{"id":"greece"}
 | 
						||
		'Greece' : '1924/3/23',
 | 
						||
		// gettext_config:{"id":"turkey"}
 | 
						||
		'Türkiye' : '1926/1/1',
 | 
						||
		// gettext_config:{"id":"egypt"}
 | 
						||
		'Egypt' : '1928/10/1'
 | 
						||
	// 之前使用伊斯蘭曆法。
 | 
						||
	// 'Saudi Arabia':'2016/10/1'
 | 
						||
	};
 | 
						||
 | 
						||
	(function() {
 | 
						||
		for ( var region in reform_by_region)
 | 
						||
			reform_by_region[region] = Date.parse(reform_by_region[region]);
 | 
						||
	})();
 | 
						||
 | 
						||
	/**
 | 
						||
	 * 取得特定區域之 Gregorian calendar 改曆日期。
 | 
						||
	 * 
 | 
						||
	 * @param {String}region
 | 
						||
	 *            {String}region, {Date}reform date, {Number}Date value
 | 
						||
	 * @returns {Number}reform Date value
 | 
						||
	 */
 | 
						||
	function Gregorian_reform_of(region) {
 | 
						||
		if (is_Date(region)) {
 | 
						||
			region = region.getTime();
 | 
						||
		} else if (typeof region === 'string' && (region in reform_by_region)) {
 | 
						||
			region = reform_by_region[region];
 | 
						||
		}
 | 
						||
		return Number.isFinite(region) ? region : _.Gregorian_reform_date;
 | 
						||
	}
 | 
						||
	Gregorian_reform_of.regions = reform_by_region;
 | 
						||
	_.Gregorian_reform_of = Gregorian_reform_of;
 | 
						||
 | 
						||
	// ---------------------------------------------------------------------------//
 | 
						||
	// 文化/區域功能。
 | 
						||
 | 
						||
	var
 | 
						||
	/** {Number}未發現之index。 const: 基本上與程式碼設計合一,僅表示名義,不可更改。(=== -1) */
 | 
						||
	NOT_FOUND = ''.indexOf('_'),
 | 
						||
	// 10 天干. celestial stem, Heavenly Stem.
 | 
						||
	STEM_LIST = '甲乙丙丁戊己庚辛壬癸',
 | 
						||
	// 12 地支. Earthly Branches. TODO: 異體字如"夘"
 | 
						||
	BRANCH_LIST = '子丑寅卯辰巳午未申酉戌亥',
 | 
						||
	// 60: lcm(STEM_LIST.length, BRANCH_LIST.length);
 | 
						||
	SEXAGENARY_CYCLE_LENGTH = 60,
 | 
						||
 | 
						||
	// 當考慮以 CST,或以當地時間為準。
 | 
						||
	// 因為八字與經度,以及該經度與太陽的日照相對夾角有關,因此採當地時間為準。
 | 
						||
 | 
						||
	// CE YEAR_STEM_BRANCH_EPOCH 3-12月為甲子年。
 | 
						||
	// 以此計算 new Date(0) 之 offset。
 | 
						||
	// 黃帝紀元元年 (year -2696 in proleptic Gregorian calendar) 為甲子年。
 | 
						||
	YEAR_STEM_BRANCH_EPOCH = -2696,
 | 
						||
	// 1984/3/31 0:0 (甲子年丁卯月)甲子日始: 以此基準點計算 new Date(0) 之 offset。
 | 
						||
	DATE_STEM_BRANCH_EPOCH = new Date(1984, 3 - 1, 31),
 | 
						||
	//
 | 
						||
	DATE_STEM_BRANCH_minute_offset = DATE_STEM_BRANCH_EPOCH.getTimezoneOffset() || 0,
 | 
						||
	// 1984/3/31 23:0 (甲子年丁卯月)甲子日甲子時始: 以此基準點計算 new Date(0) 之 offset。
 | 
						||
	HOUR_STEM_BRANCH_epoch = new Date(1984, 3 - 1, 30, 23),
 | 
						||
	//
 | 
						||
	HOUR_STEM_BRANCH_minute_offset = HOUR_STEM_BRANCH_epoch.getTimezoneOffset() || 0;
 | 
						||
	DATE_STEM_BRANCH_EPOCH = DATE_STEM_BRANCH_EPOCH.getTime();
 | 
						||
	HOUR_STEM_BRANCH_epoch = HOUR_STEM_BRANCH_epoch.getTime();
 | 
						||
 | 
						||
	_.STEM_LIST = STEM_LIST;
 | 
						||
	_.BRANCH_LIST = BRANCH_LIST;
 | 
						||
	_.SEXAGENARY_CYCLE_LENGTH = SEXAGENARY_CYCLE_LENGTH;
 | 
						||
 | 
						||
	_.YEAR_STEM_BRANCH_EPOCH = YEAR_STEM_BRANCH_EPOCH;
 | 
						||
 | 
						||
	// 日干支序。
 | 
						||
	// 注意:此處"序"指的是 Array index,從 0 開始。
 | 
						||
	function date_stem_branch_index(date_value, options) {
 | 
						||
		if (options && options.original_Date) {
 | 
						||
			date_value = options.original_Date;
 | 
						||
		}
 | 
						||
		var index = Math.floor((date_value - DATE_STEM_BRANCH_EPOCH
 | 
						||
		// 修正不同年代時刻間的時區差。
 | 
						||
		- (date_value.getTimezoneOffset() - DATE_STEM_BRANCH_minute_offset)
 | 
						||
				* ONE_MINUTE_LENGTH_VALUE)
 | 
						||
				/ ONE_DAY_LENGTH_VALUE);
 | 
						||
		// 針對需要子初分日者特別處理:直接算入下一天。
 | 
						||
		if (Date_to_String['子初分日'] && date_value.getHours() === 23) {
 | 
						||
			index++;
 | 
						||
		}
 | 
						||
		if ((index %= SEXAGENARY_CYCLE_LENGTH) < 0) {
 | 
						||
			index += SEXAGENARY_CYCLE_LENGTH;
 | 
						||
		}
 | 
						||
		return index;
 | 
						||
	}
 | 
						||
 | 
						||
	// 時干支序。
 | 
						||
	// 注意:此處"序"指的是 Array index,從 0 開始。
 | 
						||
	function hour_stem_branch_index(date_value, options) {
 | 
						||
		date_value = options && options.original_Date || date_value;
 | 
						||
		date_value = date_value - HOUR_STEM_BRANCH_epoch
 | 
						||
		// 修正不同年代時刻間的時區差。
 | 
						||
		- (date_value.getTimezoneOffset() - HOUR_STEM_BRANCH_minute_offset)
 | 
						||
				* ONE_MINUTE_LENGTH_VALUE;
 | 
						||
		return date_value / ONE_時辰_LENGTH_VALUE;
 | 
						||
	}
 | 
						||
 | 
						||
	// 極大程度依賴 date_pattern 的寫法!
 | 
						||
	stem_branch_date_pattern = new RegExp(stem_branch_date_pattern.replace(
 | 
						||
			/\([^()]+\)日/, '([' + STEM_LIST + '][' + BRANCH_LIST + '])日'));
 | 
						||
 | 
						||
	/**
 | 
						||
	 * parse 日干支:取得最接近 date_value 之指定日干支。
 | 
						||
	 * 
 | 
						||
	 * @param {String|Integer}stem_branch
 | 
						||
	 *            指定日干支。
 | 
						||
	 * @param {Date}date_value
 | 
						||
	 *            基準日。
 | 
						||
	 * @param {Object}[options]
 | 
						||
	 *            附加參數/設定選擇性/特殊功能與選項
 | 
						||
	 * @returns
 | 
						||
	 */
 | 
						||
	function convert_stem_branch_date(stem_branch, date_value, end_date_diff) {
 | 
						||
 | 
						||
		if (!isNaN(stem_branch)
 | 
						||
				|| !isNaN(stem_branch = stem_branch_to_index(stem_branch))) {
 | 
						||
 | 
						||
			if (!date_value) {
 | 
						||
				date_value = new Date;
 | 
						||
			}
 | 
						||
 | 
						||
			// assert: 加入干支之 date 可被正常 parse,但日干支本身會被忽略。
 | 
						||
			// stem_branch_diff. [3]: see date_pattern.
 | 
						||
 | 
						||
			// 取得日干支差距。
 | 
						||
			stem_branch -= date_stem_branch_index(date_value);
 | 
						||
 | 
						||
			if (!end_date_diff) {
 | 
						||
				// 設在月底。
 | 
						||
				end_date_diff = new Date(date_value).setMonth(date_value
 | 
						||
						.getMonth() + 1, 0)
 | 
						||
						- date_value;
 | 
						||
			} else if (end_date_diff < 0) {
 | 
						||
				// assert: !!period_end === true
 | 
						||
				stem_branch += end_date_diff;
 | 
						||
			}
 | 
						||
 | 
						||
			if (stem_branch < 0) {
 | 
						||
				// 轉成最接近 0 之正 index。
 | 
						||
				stem_branch = SEXAGENARY_CYCLE_LENGTH
 | 
						||
						+ (stem_branch % SEXAGENARY_CYCLE_LENGTH);
 | 
						||
			}
 | 
						||
			if (Math.abs(end_date_diff) < stem_branch) {
 | 
						||
				// 找出距離範圍最近的日干支。
 | 
						||
				if (library_namespace.is_debug()) {
 | 
						||
					library_namespace
 | 
						||
							.warn('convert_stem_branch_date: 所欲求之日干支 ['
 | 
						||
									+ stem_branch + '] 並不在範圍內!');
 | 
						||
				}
 | 
						||
				if (stem_branch - Math.abs(end_date_diff) > SEXAGENARY_CYCLE_LENGTH
 | 
						||
						- stem_branch) {
 | 
						||
					stem_branch -= SEXAGENARY_CYCLE_LENGTH;
 | 
						||
				}
 | 
						||
			}
 | 
						||
			date_value.setDate(date_value.getDate() + stem_branch
 | 
						||
					+ (end_date_diff < 0 ? end_date_diff : 0));
 | 
						||
		}
 | 
						||
 | 
						||
		return date_value;
 | 
						||
	}
 | 
						||
 | 
						||
	_.convert_stem_branch_date = convert_stem_branch_date;
 | 
						||
 | 
						||
	// 會變更 options!
 | 
						||
	String_to_Date.parser.Chinese = function(date_string, minute_offset,
 | 
						||
			options) {
 | 
						||
		// 前置處理。
 | 
						||
		if (!library_namespace.is_Object(options)) {
 | 
						||
			options = Object.create(null);
 | 
						||
		}
 | 
						||
 | 
						||
		if (isNaN(options.year_padding)) {
 | 
						||
			options.year_padding = 0;
 | 
						||
		}
 | 
						||
 | 
						||
		var date_value = String_to_Date.parser.CE(date_string, minute_offset,
 | 
						||
				options),
 | 
						||
		//
 | 
						||
		stem_branch = date_value && date_string.match(stem_branch_date_pattern),
 | 
						||
		//
 | 
						||
		end_date_diff, period_end;
 | 
						||
 | 
						||
		// for 中國年號/中華民國紀年。
 | 
						||
		// date_value.setFullYear(1911 + date_value.getFullYear());
 | 
						||
 | 
						||
		if (stem_branch
 | 
						||
		// 確定真有 match 到干支。
 | 
						||
		// 若沒 match 到,IE 中為 "",其他為 undefined。
 | 
						||
		&& (stem_branch = stem_branch[3])) {
 | 
						||
			// 取得評估日期範圍。
 | 
						||
			options.period_end = !(period_end = options.period_end);
 | 
						||
			end_date_diff = (String_to_Date.parser.CE(date_string,
 | 
						||
					minute_offset, options) - date_value)
 | 
						||
					/ ONE_DAY_LENGTH_VALUE;
 | 
						||
			// assert: if (end_date_diff < 0) than !!period_end === true
 | 
						||
 | 
						||
			// 復原 options。
 | 
						||
			options.period_end = period_end;
 | 
						||
 | 
						||
			date_value = convert_stem_branch_date(stem_branch, date_value,
 | 
						||
					end_date_diff);
 | 
						||
		}
 | 
						||
 | 
						||
		return date_value;
 | 
						||
	};
 | 
						||
 | 
						||
	/**
 | 
						||
	 * 計算干支/天干與地支<br />
 | 
						||
	 * Chinese sexagenary cycle.
 | 
						||
	 * 
 | 
						||
	 * @param {Integer}index
 | 
						||
	 *            序數 (0-59)。
 | 
						||
	 * @returns
 | 
						||
	 */
 | 
						||
	function index_to_stem_branch(index) {
 | 
						||
		// NaN | 0 → 0, 過大的數 | 0 可能成負數。
 | 
						||
		//
 | 
						||
		// 當 < 0: 需要修正對負小數之模數計算。
 | 
						||
		// 原因出自上面計算負小數時,其實應採用 Math.floor()。
 | 
						||
		// Math.floor(-1.1) → -2。
 | 
						||
		// 但以 "-1.1 | 0", "-1.1 >> 0", "≈-1.1" 會 → -1。
 | 
						||
		// 效能:
 | 
						||
		// http://jsperf.com/math-floor-vs-math-round-vs-parseint
 | 
						||
		if (!isNaN(index = +index % SEXAGENARY_CYCLE_LENGTH)) {
 | 
						||
			if ((index = Math.floor(index)) < 0) {
 | 
						||
				index += SEXAGENARY_CYCLE_LENGTH;
 | 
						||
			}
 | 
						||
 | 
						||
			// 10: STEM_LIST.length
 | 
						||
			return STEM_LIST.charAt(index % 10)
 | 
						||
			// 12: BRANCH_LIST.length
 | 
						||
			+ BRANCH_LIST.charAt(index % 12);
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	// 取得指定日干支之序數 (0-59)。
 | 
						||
	function stem_branch_to_index(stem_branch) {
 | 
						||
		if (!stem_branch || !(stem_branch = String(stem_branch))) {
 | 
						||
			return;
 | 
						||
		}
 | 
						||
 | 
						||
		var _0 = stem_branch.charAt(0), index = STEM_LIST.indexOf(_0);
 | 
						||
		if (stem_branch.length > 1) {
 | 
						||
			// '甲子'
 | 
						||
			if (index !== NOT_FOUND
 | 
						||
					&& (_0 = BRANCH_LIST.indexOf(stem_branch.charAt(1))) !== NOT_FOUND) {
 | 
						||
				// 解一次同餘式組
 | 
						||
				// index = (SEXAGENARY_CYCLE_LENGTH + 6 * index - 5 * _0) %
 | 
						||
				// SEXAGENARY_CYCLE_LENGTH;
 | 
						||
				index = (6 * index + 55 * _0) % SEXAGENARY_CYCLE_LENGTH;
 | 
						||
			}
 | 
						||
		} else if (index === NOT_FOUND) {
 | 
						||
			// '甲' / '子'
 | 
						||
			index = BRANCH_LIST.indexOf(_0);
 | 
						||
		}
 | 
						||
 | 
						||
		if (index >= 0) {
 | 
						||
			return index;
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	_.to_stem_branch = index_to_stem_branch;
 | 
						||
	// 可能回傳 0。若無法轉換,會回傳 undefined。
 | 
						||
	_.stem_branch_index = function(value, options) {
 | 
						||
		return is_Date(value) ? options && options.hour ? hour_stem_branch_index(value)
 | 
						||
				: date_stem_branch_index(value)
 | 
						||
				: stem_branch_to_index(value);
 | 
						||
	};
 | 
						||
 | 
						||
	/**
 | 
						||
	 * guess the year-stem-branch (歲次/年干支) of the date.
 | 
						||
	 * 
 | 
						||
	 * @param {Date|Integer}date_value
 | 
						||
	 *            date specified.
 | 
						||
	 * @param {Object|Boolean}options
 | 
						||
	 *            true or {get_index:true} : get {Integer}index (年干支序) instead
 | 
						||
	 *            of year-stem-branch.
 | 
						||
	 * 
 | 
						||
	 * @returns {String} year-stem-branch (歲次/年干支)
 | 
						||
	 */
 | 
						||
	function guess_year_stem_branch(date_value, options) {
 | 
						||
		if (options && options.original_Date) {
 | 
						||
			date_value = options.original_Date;
 | 
						||
		}
 | 
						||
 | 
						||
		var year, month, date;
 | 
						||
		if (is_Date(date_value)) {
 | 
						||
			year = date_value.getFullYear();
 | 
						||
			month = date_value.getMonth();
 | 
						||
		} else if (isNaN(date_value)) {
 | 
						||
			// TypeError
 | 
						||
			throw new Error('guess_year_stem_branch: Not a Date');
 | 
						||
		} else {
 | 
						||
			year = date_value | 0;
 | 
						||
			month = 7;
 | 
						||
		}
 | 
						||
 | 
						||
		year -= YEAR_STEM_BRANCH_EPOCH;
 | 
						||
 | 
						||
		if (month < 2)
 | 
						||
			// 正月初一的日期落在大寒至雨水
 | 
						||
			// (一般在公曆1月19日至2月20日)之間。
 | 
						||
			// 立春則一般在2月4日或2月5日。
 | 
						||
			if ((date = date_value.getDate()) < 19 && month === 0) {
 | 
						||
				// 上一年。
 | 
						||
				year--;
 | 
						||
			} else if (date < 20 || month < 1) {
 | 
						||
				// 無法判別歲次:需要 include 'data.date.era'!
 | 
						||
				return '(' + index_to_stem_branch(year - 1) + '或'
 | 
						||
						+ index_to_stem_branch(year) + ')';
 | 
						||
			}
 | 
						||
 | 
						||
		if (!options || options !== true && !options.get_index) {
 | 
						||
			year = index_to_stem_branch(year);
 | 
						||
		} else if ((year %= SEXAGENARY_CYCLE_LENGTH) < 0) {
 | 
						||
			// see index_to_stem_branch()
 | 
						||
			year += SEXAGENARY_CYCLE_LENGTH;
 | 
						||
		}
 | 
						||
 | 
						||
		return year;
 | 
						||
	}
 | 
						||
 | 
						||
	_.guess_year_stem_branch = guess_year_stem_branch;
 | 
						||
 | 
						||
	// Date_to_String['子初分日'] = false;
 | 
						||
	// Date_to_String.子初分日 = false;
 | 
						||
 | 
						||
	strftime.set_conversion(Object.assign({
 | 
						||
		// 完整民國紀年年份(2 or 3位數的數字,如 98, 102)
 | 
						||
		R : function(date_value) {
 | 
						||
			if ((date_value = date_value.getFullYear()) > 900) {
 | 
						||
				// 民前0年是1911年。
 | 
						||
				date_value -= 1911;
 | 
						||
			}
 | 
						||
			return date_value;
 | 
						||
		}
 | 
						||
	}, strftime.default_conversion), 'cmn-Hant-TW');
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
	// timevalue → {String}日期及時間表達式
 | 
						||
 | 
						||
	/**
 | 
						||
	 * 計算大略的時間間隔,以適當的時間單位縮寫簡略顯示。 count roughly duration, count date.<br />
 | 
						||
	 * CeL.age_of(new Date(0, 0, 0), new Date(0, 0, 0, 0, 0, 8)) === '8s'
 | 
						||
	 * 
 | 
						||
	 * TODO: locale
 | 
						||
	 * 
 | 
						||
	 * @param {Date|Number}start
 | 
						||
	 * @param {Date|Number}[end]
 | 
						||
	 * @param {Object}[options]
 | 
						||
	 *            附加參數/設定選擇性/特殊功能與選項
 | 
						||
	 */
 | 
						||
	function age_of(start, end, options) {
 | 
						||
		options = library_namespace.setup_options(options);
 | 
						||
		if (!end) {
 | 
						||
			// count till now.
 | 
						||
			end = end === 0 ? new Date(0) : new Date;
 | 
						||
		}
 | 
						||
		var difference = end - start, diff, diff2,
 | 
						||
		//
 | 
						||
		gettext = library_namespace.gettext;
 | 
						||
		if (!(difference >= 0) || !isFinite(difference)) {
 | 
						||
			return;
 | 
						||
		}
 | 
						||
 | 
						||
		if (!is_Date(start)) {
 | 
						||
			start = new Date(start);
 | 
						||
		}
 | 
						||
		if (!is_Date(end)) {
 | 
						||
			end = new Date(end);
 | 
						||
		}
 | 
						||
		// assert: new Date(0) 得到1月1日
 | 
						||
		// assert: (new Date(0)).getMonth()===0&&(new Date(0)).getDate()===1
 | 
						||
		diff2 = new Date(end - start);
 | 
						||
		// ↑ 如此可處理年尾到年首的差異。
 | 
						||
		// 計算兩者相差年分計數。
 | 
						||
		diff = diff2.getFullYear() - /* 1970 */new Date(0).getFullYear();
 | 
						||
		// 計算兩者相差大概月分。
 | 
						||
		diff2 = diff2.getMonth() + (diff2.getDate() - 1) / 30;
 | 
						||
 | 
						||
		// 將數字四捨五入到指定的小數位數。 TODO: 把時間表示方式改為60進位。
 | 
						||
		var to_fixed_digits = (options.digits | 0) >= 0
 | 
						||
		// default: 0. e.g., {digits:0}
 | 
						||
		? options.digits | 0 : 0;
 | 
						||
		var long_format = options.long_format;
 | 
						||
 | 
						||
		if (diff) {
 | 
						||
			// assert: {Integer}diff 年 {Float}diff2 月, diff > 0.
 | 
						||
			// → difference = {Float} 年(至小數)
 | 
						||
			difference = diff + diff2 / 12;
 | 
						||
			// diff = {String} format to show
 | 
						||
			if (options.月) {
 | 
						||
				diff = gettext(long_format ?
 | 
						||
				// gettext_config:{"id":"$1-years-and-$2-months"}
 | 
						||
				'%1 {{PLURAL:%1|year|years}} and %2 {{PLURAL:%2|month|months}}'
 | 
						||
				// gettext_config:{"id":"$1-y-$2-m"}
 | 
						||
				: '%1 Y %2 M', diff, Math.round(diff2));
 | 
						||
			} else {
 | 
						||
				// years 近一年, 一年多
 | 
						||
				// SI symbol: a (for Latin annus)
 | 
						||
				diff = gettext(long_format ?
 | 
						||
				// gettext_config:{"id":"$1-years"}
 | 
						||
				'%1 {{PLURAL:%1|year|years}}'
 | 
						||
				// gettext_config:{"id":"$1-y"}
 | 
						||
				: '%1 Y', difference.to_fixed(to_fixed_digits));
 | 
						||
			}
 | 
						||
			if (options.歲) {
 | 
						||
				// 計算年齡(虛歲)幾歲。
 | 
						||
				difference = end.getFullYear() - start.getFullYear()
 | 
						||
				// + 1: 一出生即虛歲一歲(YO, years old, "Y/O.")。之後過年長一歲。
 | 
						||
				// else: 計算周歲(又稱實歲、足歲)幾歲。
 | 
						||
				+ (options.歲 === '虛' ? 1 : 0);
 | 
						||
				if (start - age_of.get_new_year(start.getFullYear()) < 0) {
 | 
						||
					difference++;
 | 
						||
				}
 | 
						||
				if (end - age_of.get_new_year(end.getFullYear()) < 0) {
 | 
						||
					difference--;
 | 
						||
				}
 | 
						||
				// 時年實歲。
 | 
						||
				diff = difference + '歲, ' + diff;
 | 
						||
			}
 | 
						||
 | 
						||
			return diff;
 | 
						||
		}
 | 
						||
 | 
						||
		if (diff2 >= 1 && options.max_unit !== 'day') {
 | 
						||
			return gettext(long_format ?
 | 
						||
			// gettext_config:{"id":"$1-months"}
 | 
						||
			'%1 {{PLURAL:%1|month|months}}'
 | 
						||
			// gettext_config:{"id":"$1-m"}
 | 
						||
			: '%1 M', diff2.to_fixed(to_fixed_digits));
 | 
						||
		}
 | 
						||
 | 
						||
		if (difference < 1000) {
 | 
						||
			return gettext(long_format ?
 | 
						||
			// gettext_config:{"id":"$1-milliseconds"}
 | 
						||
			'%1 {{PLURAL:%1|millisecond|milliseconds}}'
 | 
						||
			// gettext_config:{"id":"$1-ms"}
 | 
						||
			: '%1 ms', difference | 0);
 | 
						||
		}
 | 
						||
 | 
						||
		if ((difference /= 1000) < 60) {
 | 
						||
			return gettext(long_format ?
 | 
						||
			// gettext_config:{"id":"$1-seconds"}
 | 
						||
			'%1 {{PLURAL:%1|second|seconds}}'
 | 
						||
			// gettext_config:{"id":"$1-s"}
 | 
						||
			: '%1 s', difference.to_fixed(to_fixed_digits));
 | 
						||
		}
 | 
						||
 | 
						||
		if ((difference /= 60) < 60) {
 | 
						||
			return gettext(long_format ?
 | 
						||
			// gettext_config:{"id":"$1-minutes"}
 | 
						||
			'%1 {{PLURAL:%1|minute|minutes}}'
 | 
						||
			// gettext_config:{"id":"$1-min"}
 | 
						||
			: '%1 min', difference.to_fixed(to_fixed_digits));
 | 
						||
		}
 | 
						||
 | 
						||
		if ((difference /= 60) < 24) {
 | 
						||
			return gettext(long_format ?
 | 
						||
			// gettext_config:{"id":"$1-hours"}
 | 
						||
			'%1 {{PLURAL:%1|hour|hours}}'
 | 
						||
			// gettext_config:{"id":"$1-hr"}
 | 
						||
			: '%1 hr', difference.to_fixed(to_fixed_digits));
 | 
						||
		}
 | 
						||
 | 
						||
		if ((difference /= 24) < 7 || !options.weeks) {
 | 
						||
			return gettext(long_format ?
 | 
						||
			// gettext_config:{"id":"$1-days"}
 | 
						||
			'%1 {{PLURAL:%1|day|days}}'
 | 
						||
			// gettext_config:{"id":"$1-d"}
 | 
						||
			: '%1 d', difference.to_fixed(to_fixed_digits));
 | 
						||
		}
 | 
						||
 | 
						||
		return gettext(long_format ?
 | 
						||
		// gettext_config:{"id":"$1-weeks"}
 | 
						||
		'%1 {{PLURAL:%1|week|weeks}}'
 | 
						||
		// gettext_config:{"id":"$1-w"}
 | 
						||
		: '%1 W', (difference / 7).to_fixed(to_fixed_digits));
 | 
						||
	}
 | 
						||
 | 
						||
	// 將在 data.date.era 更正。
 | 
						||
	age_of.get_new_year = function(year, 月, 日, era_key) {
 | 
						||
		// 取平均值。因無法準確判別春節(農曆正月初一)日期,此方法尚有誤差!
 | 
						||
		return new Date((year < 0 ? year : '000' + year) + '/2/1');
 | 
						||
	};
 | 
						||
 | 
						||
	_.age_of = age_of;
 | 
						||
 | 
						||
	// ------------------------------------------
 | 
						||
 | 
						||
	var recent_date_name = [
 | 
						||
	// gettext_config:{"id":"2-days-before-yesterday-$h-$m"}
 | 
						||
	'2 days before yesterday, %H:%M',
 | 
						||
	// gettext_config:{"id":"the-day-before-yesterday-$h-$m"}
 | 
						||
	'the day before yesterday, %H:%M',
 | 
						||
	// gettext_config:{"id":"yesterday-$h-$m"}
 | 
						||
	'yesterday, %H:%M',
 | 
						||
	// gettext_config:{"id":"today-$h-$m"}
 | 
						||
	'today, %H:%M',
 | 
						||
	// gettext_config:{"id":"tomorrow-$h-$m"}
 | 
						||
	'tomorrow, %H:%M',
 | 
						||
	// gettext_config:{"id":"the-day-after-tomorrow-$h-$m"}
 | 
						||
	'the day after tomorrow, %H:%M',
 | 
						||
	// gettext_config:{"id":"2-days-after-tomorrow-$h-$m"}
 | 
						||
	'2 days after tomorrow, %H:%M',
 | 
						||
	// gettext_config:{"id":"3-days-after-tomorrow-$h-$m"}
 | 
						||
	'3 days after tomorrow, %H:%M' ];
 | 
						||
	var recent_date_name_today_index
 | 
						||
	// gettext_config:{"id":"today-$h-$m"}
 | 
						||
	= recent_date_name.indexOf('today, %H:%M');
 | 
						||
 | 
						||
	// @see https://en.wikipedia.org/wiki/Wikipedia:Comments_in_Local_Time
 | 
						||
	// https://en.wikipedia.org/wiki/User:Gary/comments_in_local_time.js
 | 
						||
	// https://zh.wikipedia.org/wiki/MediaWiki:Gadget-CommentsinLocalTime.js
 | 
						||
	function indicate_date_time(date, options) {
 | 
						||
		options = library_namespace.setup_options(options);
 | 
						||
		if (!is_Date(date)) {
 | 
						||
			// e.g., time value, ISO string
 | 
						||
			// console.trace(date);
 | 
						||
			date = new Date(date);
 | 
						||
		}
 | 
						||
 | 
						||
		var base_date = options.base_date;
 | 
						||
		if (isNaN(base_date) && !is_Date(base_date)) {
 | 
						||
			base_date = typeof options.base_date === 'string' ? Date
 | 
						||
					.parse(options.base_date) : Date.now();
 | 
						||
		}
 | 
						||
 | 
						||
		var date_value_diff = date - base_date;
 | 
						||
		if (isNaN(date_value_diff)) {
 | 
						||
			// something wrong
 | 
						||
			return;
 | 
						||
		}
 | 
						||
 | 
						||
		if (Math.abs(date_value_diff) > (options.max_interval || 35 * ONE_DAY_LENGTH_VALUE)) {
 | 
						||
			return date.format(options.general_format
 | 
						||
					|| indicate_date_time.general_format);
 | 
						||
		}
 | 
						||
 | 
						||
		var passed = date_value_diff <= 0,
 | 
						||
		//
 | 
						||
		gettext = library_namespace.gettext;
 | 
						||
 | 
						||
		if (passed) {
 | 
						||
			date_value_diff = -date_value_diff;
 | 
						||
		} else {
 | 
						||
			// is future
 | 
						||
		}
 | 
						||
 | 
						||
		// → seconds
 | 
						||
		date_value_diff /= 1000;
 | 
						||
 | 
						||
		if (date_value_diff < 1) {
 | 
						||
			// gettext_config:{"id":"now"}
 | 
						||
			return gettext('now');
 | 
						||
		}
 | 
						||
 | 
						||
		if (date_value_diff < 10) {
 | 
						||
			// gettext_config:{"id":"several-seconds-ago"}
 | 
						||
			return gettext(passed ? 'several seconds ago'
 | 
						||
			// gettext_config:{"id":"soon"}
 | 
						||
			: 'soon');
 | 
						||
		}
 | 
						||
		if (date_value_diff < 60) {
 | 
						||
			// gettext_config:{"id":"$1-seconds-ago"}
 | 
						||
			return gettext(passed ? '%1 {{PLURAL:%1|second|seconds}} ago'
 | 
						||
			// gettext_config:{"id":"$1-seconds-later"}
 | 
						||
			: '%1 {{PLURAL:%1|second|seconds}} later', Math
 | 
						||
					.round(date_value_diff));
 | 
						||
		}
 | 
						||
 | 
						||
		// → minutes
 | 
						||
		date_value_diff /= 60;
 | 
						||
		if (date_value_diff < 60) {
 | 
						||
			// gettext_config:{"id":"$1-minutes-ago"}
 | 
						||
			return gettext(passed ? '%1 {{PLURAL:%1|minute|minutes}} ago'
 | 
						||
			// gettext_config:{"id":"$1-minutes-later"}
 | 
						||
			: '%1 {{PLURAL:%1|minute|minutes}} later', Math
 | 
						||
					.round(date_value_diff));
 | 
						||
		}
 | 
						||
 | 
						||
		// → hours
 | 
						||
		date_value_diff /= 60;
 | 
						||
		if (date_value_diff < 3) {
 | 
						||
			// gettext_config:{"id":"$1-hours-ago"}
 | 
						||
			return gettext(passed ? '%1 {{PLURAL:%1|hour|hours}} ago'
 | 
						||
			// gettext_config:{"id":"$1-hours-later"}
 | 
						||
			: '%1 {{PLURAL:%1|hour|hours}} later', Math.round(date_value_diff));
 | 
						||
		}
 | 
						||
 | 
						||
		// ----------------------------
 | 
						||
 | 
						||
		// → days
 | 
						||
		date_value_diff /= 24;
 | 
						||
 | 
						||
		if (date_value_diff <= 3) {
 | 
						||
			// the day of the month
 | 
						||
			var message = /* date_of_month */date.getDate()
 | 
						||
					- (is_Date(base_date) ? base_date : new Date(base_date))
 | 
						||
							.getDate() + recent_date_name_today_index;
 | 
						||
			// console.log([date_value_diff, message, (new Date).getDate(),
 | 
						||
			// date.getDate()]);
 | 
						||
			if (message > recent_date_name.length) {
 | 
						||
				// assert: (new Date) 於本月初,date 於上月底。
 | 
						||
				date_value_diff = new Date;
 | 
						||
				date_value_diff.setDate(0);
 | 
						||
				message -= date_value_diff.getDate();
 | 
						||
			}
 | 
						||
			message = recent_date_name[message];
 | 
						||
			return date.format(gettext(message/* + ', %H:%M' */));
 | 
						||
		}
 | 
						||
 | 
						||
		// TODO: week, 周六
 | 
						||
 | 
						||
		if (date_value_diff <= 35) {
 | 
						||
			// gettext_config:{"id":"$1-days-ago"}
 | 
						||
			return gettext(passed ? '%1 {{PLURAL:%1|day|days}} ago'
 | 
						||
			// gettext_config:{"id":"$1-days-later"}
 | 
						||
			: '%1 {{PLURAL:%1|day|days}} later', Math.round(date_value_diff));
 | 
						||
		}
 | 
						||
 | 
						||
		// ----------------------------
 | 
						||
 | 
						||
		if (false && date_value_diff < 30) {
 | 
						||
			// gettext_config:{"id":"$1-weeks-ago"}
 | 
						||
			return gettext(passed ? '%1 {{PLURAL:%1|week|weeks}} ago'
 | 
						||
			// gettext_config:{"id":"$1-weeks-later"}
 | 
						||
			: '%1 {PLURAL:%1|week|weeks}} later', Math
 | 
						||
					.round(date_value_diff / 7));
 | 
						||
		}
 | 
						||
 | 
						||
		// ----------------------------
 | 
						||
 | 
						||
		// for dates in this year, *月*日
 | 
						||
 | 
						||
		// ----------------------------
 | 
						||
 | 
						||
		// *年*月*日
 | 
						||
 | 
						||
		return date.format(options.general_format
 | 
						||
				|| indicate_date_time.general_format);
 | 
						||
	}
 | 
						||
 | 
						||
	indicate_date_time.general_format = '%Y-%2m-%2d %2H:%2M';
 | 
						||
 | 
						||
	_.indicate_date_time = indicate_date_time;
 | 
						||
 | 
						||
	// ------------------------------------------
 | 
						||
	// {String}日期及時間表達式 → {Natural}timevalue in milliseconds
 | 
						||
 | 
						||
	// https://en.wikipedia.org/wiki/ISO_8601#Durations
 | 
						||
	var PATTERN_ISO_8601_durations = /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/,
 | 
						||
	// 日期及時間 由小到大排列。
 | 
						||
	PATTERN_time_units = [ [ '(?:ms|milliseconds?|毫秒|ミリ秒)', 1 ],
 | 
						||
			[ '(?:s(?:ec)?|秒[鐘钟]?)', 1000 ], [ '(?:m(?:in)?|分[鐘钟]?)', 60 ],
 | 
						||
			[ '(?:h(?:r|ours?)?|[個个]?小?[時时]|[個个]?時間)', 60 ],
 | 
						||
			// T: for ISO 8601 Durations. e.g., 'P21DT3H'
 | 
						||
			[ '(?:d(?:ays?|T)?|[日天])', 24 ],
 | 
						||
			[ '(?:w(?:eeks?)?|週|[個个]?星期|個?禮拜|个?礼拜)', 7, true ],
 | 
						||
			// 以下僅僅給出約略大小。 1 month: 30 days
 | 
						||
			[ '(?:mon(?:ths?)?|[個个ヶ]?月)', 30 ],
 | 
						||
			// https://en.wikipedia.org/wiki/Year#SI_prefix_multipliers
 | 
						||
			// NIST SP811 和ISO 80000-3:2006 支持將符號a作為一年的時間單位。在英語中,還使用縮寫y和yr。
 | 
						||
			// IUPAC: 1 a = 31556925.445 seconds
 | 
						||
			[ '(?:y(?:r|ears?)?|年)', 12 ] ];
 | 
						||
 | 
						||
	(function() {
 | 
						||
		// [ all, amount ]
 | 
						||
		var ms = 1, template = /(\d+(?:\.\d+)?|\.\d+) ?UNIT(?:[^a-z]|$)/.source;
 | 
						||
		PATTERN_time_units = PATTERN_time_units.map(function(pair) {
 | 
						||
			return [ new RegExp(template.replace('UNIT', pair[0]), 'i'),
 | 
						||
					pair[2] ? ms * pair[1] : ms *= pair[1] ];
 | 
						||
		});
 | 
						||
	})();
 | 
						||
 | 
						||
	// parser time interval to timevalue (get how many millisecond)
 | 
						||
	// TODO: 半小時
 | 
						||
	function time_interval_to_millisecond(interval) {
 | 
						||
		if (typeof interval !== 'string') {
 | 
						||
			// 允許函數之類其他種類的設定,需要呼叫端進一步處理。
 | 
						||
			return interval > 0 ? +interval : interval || 0;
 | 
						||
		}
 | 
						||
 | 
						||
		var timevalue = 0, has_matched, matched = interval
 | 
						||
				.match(PATTERN_ISO_8601_durations);
 | 
						||
		if (matched) {
 | 
						||
			if (false && (+matched[1] || +matched[2])) {
 | 
						||
				throw 'We cannot handle year / month: ' + interval;
 | 
						||
			}
 | 
						||
 | 
						||
			// 這可以順便用來 parse:
 | 
						||
			// https://en.wikipedia.org/wiki/ISO_8601#Durations
 | 
						||
			// e.g., "P23DT23H"
 | 
						||
			interval = matched[0];
 | 
						||
 | 
						||
		} else if (matched = interval
 | 
						||
		// e.g., "12:34", "12:34:56"
 | 
						||
		.match(/(?:^|\s)(\d{1,3}):(\d{1,2})(?::(\d{1,2}))?(?:\s|$)/)) {
 | 
						||
			interval = matched[1] + 'H' + matched[2] + 'M'
 | 
						||
					+ (matched[3] ? matched[3] + 'S' : '');
 | 
						||
		}
 | 
						||
 | 
						||
		PATTERN_time_units.forEach(function(pair) {
 | 
						||
			matched = interval.match(pair[0]);
 | 
						||
			if (matched) {
 | 
						||
				has_matched = true;
 | 
						||
				timevalue += matched[1] * pair[1];
 | 
						||
			}
 | 
						||
		});
 | 
						||
 | 
						||
		if (has_matched)
 | 
						||
			return timevalue;
 | 
						||
 | 
						||
		matched = interval.split('/');
 | 
						||
		if (matched.length === 2) {
 | 
						||
			// https://en.wikipedia.org/wiki/ISO_8601#Time_intervals
 | 
						||
			matched = matched.map(function(time) {
 | 
						||
				return Date.parse(time);
 | 
						||
			});
 | 
						||
			if (!isNaN(matched[0]) && !isNaN(matched[1])) {
 | 
						||
				return matched[1] - matched[0];
 | 
						||
			}
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	_.to_millisecond = time_interval_to_millisecond;
 | 
						||
 | 
						||
	// ---------------------------------------------------------------------//
 | 
						||
 | 
						||
	// parse durations. period 是比較久的時間
 | 
						||
	function parse_period(period) {
 | 
						||
		var matched = period.match(parse_period.PATTERN);
 | 
						||
		if (matched && (!/[日時]/.test(matched[2])
 | 
						||
		// 預防 "10月22日夜7-8時"
 | 
						||
		|| !/[時分秒\/]/.test(matched[2].match(/^(?:.*?)([年月日時分秒\/])/)[1]))) {
 | 
						||
			(period = matched).shift();
 | 
						||
			if (!period[1].includes('月')
 | 
						||
					&& (matched = period[0].match(/[^年月]+月/))) {
 | 
						||
				period[1] = matched[0] + period[1];
 | 
						||
			}
 | 
						||
			if (!period[1].includes('年')
 | 
						||
					&& (matched = period[0].match(/[^年]+年/))) {
 | 
						||
				period[1] = matched[0] + period[1];
 | 
						||
			}
 | 
						||
		} else if (library_namespace.is_debug(2)) {
 | 
						||
			library_namespace.warn('parse_period: Cannot parse period ['
 | 
						||
					+ period + ']');
 | 
						||
		}
 | 
						||
		return period;
 | 
						||
	}
 | 
						||
 | 
						||
	// '1/1/1-2/1/1' → [, '1/1/1', '2/1/1' ]
 | 
						||
	// 'Neo-Babylonian/Nabopolassar' → null
 | 
						||
	// "[^a-z]": 避免類似 "Neo-Babylonian" 被當作 period。
 | 
						||
	//
 | 
						||
	// "Maya:9.2.15.3.8 12 Lamat 6 Wo-Maya:9.6.11.0.16 7 Kib' 4 K'ayab'"
 | 
						||
	// →
 | 
						||
	// [, "Maya:9.2.15.3.8 12 Lamat 6 Wo", "Maya:9.6.11.0.16 7 Kib' 4 K'ayab'" ]
 | 
						||
	parse_period.PATTERN = /^(.+[^a-z]|[^\d]*\d.+)[\-–~-—─~〜﹣至]\s*([^\-].+)$/i;
 | 
						||
	// @see String_to_Date.parser_PATTERN
 | 
						||
 | 
						||
	_.parse_period = parse_period;
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
 | 
						||
	// count=1: 1–31
 | 
						||
	// count=2: 1–15, 16–31
 | 
						||
	// count=3: 1–10, 11–20, 21–31
 | 
						||
	// count=4: 1–7, 8–14, 15–21, 22–31
 | 
						||
	// count=5: 1–6, ...
 | 
						||
	/**
 | 
						||
	 * 將`date`所屬的月份以日子為單位平均切割成`count`個slice,回傳`date`所在slice的首尾日期。
 | 
						||
	 * 
 | 
						||
	 * @param {Array}date
 | 
						||
	 *            基準日期: [year, month, date]。assert: 必須為合理日期,不可輸入[2001,1,32]之類。
 | 
						||
	 * @param {Natural}count
 | 
						||
	 *            將`date`所屬的月份以日子為單位平均切割成`count`個slice。 assert: count<=31
 | 
						||
	 * @param {Object}[options]
 | 
						||
	 *            附加參數/設定選擇性/特殊功能與選項
 | 
						||
	 * 
 | 
						||
	 * @returns `date`所在slice的首尾日期: {Array}[起始日期, 結束日期]
 | 
						||
	 */
 | 
						||
	function get_date_range_via_cutting_month(date, count, options) {
 | 
						||
		if (!options && !(count >= 1)) {
 | 
						||
			options = library_namespace.setup_options(count);
 | 
						||
			count = options.count;
 | 
						||
		} else {
 | 
						||
			options = library_namespace.setup_options(options);
 | 
						||
		}
 | 
						||
		if (!Array.isArray(date)) {
 | 
						||
			if (!is_Date(date)) {
 | 
						||
				date = new Date(date);
 | 
						||
			}
 | 
						||
			date = Julian_day(date);
 | 
						||
			date = Julian_day.to_YMD(date, 'CE');
 | 
						||
		}
 | 
						||
		// assert: Array.isArray(date)
 | 
						||
		var year = date[0], month = date[1];
 | 
						||
		date = date[2];
 | 
						||
 | 
						||
		var days_in_this_month = Julian_day.from_YMD(year, month + 1, 1, 'CE')
 | 
						||
				- Julian_day.from_YMD(year, month, 1, 'CE');
 | 
						||
 | 
						||
		// const
 | 
						||
		var slice_days;
 | 
						||
		if (options.slice_days >= 1 && !count) {
 | 
						||
			slice_days = options.slice_days;
 | 
						||
			count = days_in_this_month / slice_days | 0;
 | 
						||
		} else {
 | 
						||
			slice_days = days_in_this_month / count | 0;
 | 
						||
			if (days_in_this_month - count * slice_days > 2 * slice_days) {
 | 
						||
				// 預防最後一區塊太大。
 | 
						||
				// e.g., ([2000,2,28], 10)
 | 
						||
				slice_days++;
 | 
						||
			}
 | 
						||
		}
 | 
						||
		var index = (date - 1) / slice_days | 0;
 | 
						||
		if (index >= count)
 | 
						||
			index = count - 1;
 | 
						||
		var slice_start_date = index * slice_days + 1;
 | 
						||
		var slice_end_date = slice_start_date + slice_days
 | 
						||
		// 從下一個片段第一天移到本片段最後一天。
 | 
						||
		- 1;
 | 
						||
		if (slice_days === 0) {
 | 
						||
			slice_start_date = slice_end_date = date;
 | 
						||
		} else if (index === count - 1) {
 | 
						||
			// assert: date 處在最後一個片段。
 | 
						||
			// 最後一個片段必須包含到最後一天。
 | 
						||
			slice_end_date = days_in_this_month;
 | 
						||
		}
 | 
						||
 | 
						||
		if (options.get_Date) {
 | 
						||
			if (false) {
 | 
						||
				console.trace([ [ year, month, slice_start_date ],
 | 
						||
						[ year, month, slice_end_date ] ]);
 | 
						||
			}
 | 
						||
			slice_start_date = Julian_day.YMD_to_Date(year, month,
 | 
						||
					slice_start_date, 'CE');
 | 
						||
			slice_end_date = Julian_day.YMD_to_Date(year, month,
 | 
						||
					slice_end_date, 'CE');
 | 
						||
		} else if (options.get_full_date) {
 | 
						||
			slice_start_date = [ year, month, slice_start_date ];
 | 
						||
			slice_end_date = [ year, month, slice_end_date ];
 | 
						||
		}
 | 
						||
		return [ slice_start_date, slice_end_date ];
 | 
						||
	}
 | 
						||
 | 
						||
	_.get_date_range_via_cutting_month = get_date_range_via_cutting_month;
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
 | 
						||
	/**
 | 
						||
	 * <code>
 | 
						||
	mode:
 | 
						||
		+4:不顯示時間,
 | 
						||
		+3:顯示時間至時,
 | 
						||
		+2:顯示時間至分,
 | 
						||
		+1:顯示時間至秒,
 | 
						||
		+0:顯示時間至毫秒(milliseconds)
 | 
						||
 | 
						||
		+32(4<<3):不顯示日期,
 | 
						||
		+24(3<<3):顯示日期mm/dd,
 | 
						||
		+16(2<<3):顯示日期yyyy/mm,
 | 
						||
		+8(1<<3):顯示日期yyyy/mm/dd(星期),
 | 
						||
		+0:顯示日期yyyy/mm/dd
 | 
						||
 | 
						||
		+64(1<<6):input UTC
 | 
						||
		+128(2<<6):output UTC
 | 
						||
 | 
						||
	NOTE:
 | 
						||
	在現有時制下要轉換其他時區之時間成正確time:
 | 
						||
	d=_其他時區之時間_;
 | 
						||
	diff=其他時區之時差(例如 TW: UTC+8)
 | 
						||
	d.setTime(d.getTime()-60000*((new Date).getTimezoneOffset()+diff*60))
 | 
						||
 | 
						||
	</code>
 | 
						||
	 */
 | 
						||
 | 
						||
	/**
 | 
						||
	 * 顯示格式化日期 string
 | 
						||
	 * 
 | 
						||
	 * @deprecated use Date_to_String.
 | 
						||
	 * 
 | 
						||
	 * @param date_value
 | 
						||
	 *            要轉換的 date, 值過小時當作時間, <0 轉成當下時間
 | 
						||
	 * @param {Number}
 | 
						||
	 *            mode 要轉換的 mode
 | 
						||
	 * @param {Boolean}
 | 
						||
	 *            zero_fill 對 0-9 是否補零
 | 
						||
	 * @param {String}
 | 
						||
	 *            date_separator date separator
 | 
						||
	 * @param {String}
 | 
						||
	 *            time_separator time separator
 | 
						||
	 * @return {String} 格式化後的日期
 | 
						||
	 * 
 | 
						||
	 * @example alert(format_date());
 | 
						||
	 * 
 | 
						||
	 * @since 2003/10/18 1:04 修正
 | 
						||
	 * @since 2010/4/16 10:37:30 重構(refactoring)
 | 
						||
	 * 
 | 
						||
	 * @requires to_fixed
 | 
						||
	 * 
 | 
						||
	 * @see http://www.merlyn.demon.co.uk/js-dates.htm,
 | 
						||
	 *      http://aa.usno.navy.mil/data/docs/JulianDate.html
 | 
						||
	 * 
 | 
						||
	 * @_memberOf _module_
 | 
						||
	 */
 | 
						||
	function format_date(date_value, mode, zero_fill, date_separator,
 | 
						||
			time_separator) {
 | 
						||
		library_namespace.debug('[' + (typeof date_value) + '] ' + date_value
 | 
						||
				+ ', mode: ' + mode, 3);
 | 
						||
 | 
						||
		// initialization
 | 
						||
		if (!mode) {
 | 
						||
			mode = 0;
 | 
						||
		}
 | 
						||
 | 
						||
		var output_UTC, a, b = mode, time_mode, return_string = '', show_number = zero_fill ? function(
 | 
						||
				n) {
 | 
						||
			return n > 9 ? n : '0' + n;
 | 
						||
		}
 | 
						||
				: function(n) {
 | 
						||
					return n;
 | 
						||
				};
 | 
						||
 | 
						||
		// date & time mode
 | 
						||
		mode %= 64;
 | 
						||
		// UTC mode
 | 
						||
		b = (b - mode) / 64;
 | 
						||
		// input UTC
 | 
						||
		a = b % 2 == 1 ? 1 : 0;
 | 
						||
		output_UTC = b - a === 1;
 | 
						||
 | 
						||
		time_mode = mode % 8;
 | 
						||
		// date mode
 | 
						||
		mode = (mode - time_mode) / 8;
 | 
						||
		// time_mode > 4 && mode > 3: error mode: 沒啥好顯示的了
 | 
						||
 | 
						||
		// 處理各種不同的 date
 | 
						||
		b = typeof date_value;
 | 
						||
		if (b === 'number' && date_value >= 0) {
 | 
						||
			// 全球標準時間(UCT)與本地時間之差距
 | 
						||
			// UTC time = local time + format_date.UTC_offset(milliseconds)
 | 
						||
			if (!a && isNaN(a = format_date.UTC_offset)) {
 | 
						||
				// input UTC 時之差距(milliseconds)
 | 
						||
				// .getTimezoneOffset() is in minute.
 | 
						||
				a = format_date.UTC_offset = ONE_MINUTE_LENGTH_VALUE
 | 
						||
						* present_local_minute_offset;
 | 
						||
			}
 | 
						||
 | 
						||
			// 值過小時當作時間: d < 90000000 ≈ 24*60*60*1000,判別為當天,只顯示時間。不允許 d < 0!
 | 
						||
			date_value = new Date(Math.abs(a += date_value) < 9e7 ? a
 | 
						||
					: date_value);
 | 
						||
			mode = 32;
 | 
						||
		} else if (b === 'string' && (a = date_value.toDate())) {
 | 
						||
			date_value = a;
 | 
						||
		} else if (b === 'date') {
 | 
						||
			// 應對在 Excel 等外部程式會出現的東西。
 | 
						||
			date_value = new Date(date_value);
 | 
						||
		} else if (
 | 
						||
		// http://www.interq.or.jp/student/exeal/dss/ejs/1/1.html
 | 
						||
		// 引数がオブジェクトを要求してくる場合は instanceof 演算子を使用します
 | 
						||
		// typeof date_value!=='object'||date_value.constructor!=Date
 | 
						||
		!(date_value instanceof Date)) {
 | 
						||
			// new Date === new Date()
 | 
						||
			date_value = new Date;
 | 
						||
		}
 | 
						||
 | 
						||
		// 處理 date
 | 
						||
		if (mode < 4) {
 | 
						||
			return_string = show_number((output_UTC ? date_value.getUTCMonth()
 | 
						||
					: date_value.getMonth()) + 1);
 | 
						||
			if (!date_separator) {
 | 
						||
				date_separator = '/';
 | 
						||
			}
 | 
						||
			if (mode < 3) {
 | 
						||
				return_string = (output_UTC ? date_value.getUTCFullYear()
 | 
						||
						: date_value.getFullYear())
 | 
						||
						+ date_separator + return_string;
 | 
						||
			}
 | 
						||
			if (mode !== 2) {
 | 
						||
				return_string += date_separator
 | 
						||
						+ show_number(output_UTC ? date_value.getUTCDate()
 | 
						||
								: date_value.getDate());
 | 
						||
				if (mode === 1) {
 | 
						||
					return_string += '('
 | 
						||
							+ (output_UTC ? date_value.getUTCDay() : date_value
 | 
						||
									.getDay()) + ')';
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		// 處理 time
 | 
						||
		if (time_mode < 4) {
 | 
						||
			if (mode < 4) {
 | 
						||
				// 日期 & 時間中間分隔
 | 
						||
				return_string += ' ';
 | 
						||
			}
 | 
						||
			if (!time_separator) {
 | 
						||
				time_separator = ':';
 | 
						||
			}
 | 
						||
			return_string += show_number(output_UTC ? date_value.getUTCHours()
 | 
						||
					: date_value.getHours());
 | 
						||
			if (time_mode < 3) {
 | 
						||
				return_string += time_separator
 | 
						||
						+ show_number(output_UTC ? date_value.getUTCMinutes()
 | 
						||
								: date_value.getMinutes());
 | 
						||
				if (time_mode < 2) {
 | 
						||
					return_string += time_separator
 | 
						||
							+ (time_mode
 | 
						||
							//
 | 
						||
							? show_number(output_UTC ? date_value
 | 
						||
									.getUTCSeconds() : date_value.getSeconds())
 | 
						||
							//
 | 
						||
							: (output_UTC ? date_value.getUTCSeconds()
 | 
						||
									+ date_value.getUTCMilliseconds() / 1e3
 | 
						||
							//
 | 
						||
							: date_value.getSeconds()
 | 
						||
									+ date_value.getMilliseconds() / 1e3)
 | 
						||
									.to_fixed(3));
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		return return_string;
 | 
						||
	}
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
 | 
						||
	// @inner
 | 
						||
	function serial_token_list_toString() {
 | 
						||
		return this.join('');
 | 
						||
	}
 | 
						||
 | 
						||
	// @inner
 | 
						||
	function to_serial_token_list(item, options) {
 | 
						||
		var serial_token_list = [];
 | 
						||
		if (!item && item !== 0)
 | 
						||
			return serial_token_list;
 | 
						||
 | 
						||
		serial_token_list.toString = serial_token_list_toString;
 | 
						||
		var PATTERN_serial_token = /([^\d]+)(0*)([1-9]\d*)?/g;
 | 
						||
		var max_serial_length = options.max_serial_length;
 | 
						||
 | 
						||
		item = String(item);
 | 
						||
		var matched = item.match(/^\d+/);
 | 
						||
		if (matched) {
 | 
						||
			matched = matched[0];
 | 
						||
			if (matched.length > max_serial_length) {
 | 
						||
				serial_token_list.push(matched.slice(0, -max_serial_length),
 | 
						||
						matched.slice(-max_serial_length));
 | 
						||
			} else {
 | 
						||
				serial_token_list.push('', matched);
 | 
						||
			}
 | 
						||
			PATTERN_serial_token.lastIndex = matched.length;
 | 
						||
		}
 | 
						||
 | 
						||
		while (matched = PATTERN_serial_token.exec(item)) {
 | 
						||
			if (!matched[3])
 | 
						||
				matched[3] = '';
 | 
						||
			var diff = max_serial_length - matched[3].length;
 | 
						||
			if (matched[2].length <= diff) {
 | 
						||
				serial_token_list.push(matched[1], matched[2] + matched[3]);
 | 
						||
			} else if (0 <= diff) {
 | 
						||
				serial_token_list.push(matched[1] + matched[2].slice(0, -diff),
 | 
						||
						'0'.repeat(diff) + matched[3]);
 | 
						||
			} else {
 | 
						||
				serial_token_list.push(matched[0], '');
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		// assert: item === serial_token_list.join('')
 | 
						||
 | 
						||
		// return pairs: [
 | 
						||
		// non-serial, digits (maybe serial),
 | 
						||
		// non-serial, digits (maybe serial),
 | 
						||
		// ... ]
 | 
						||
		return serial_token_list;
 | 
						||
	}
 | 
						||
 | 
						||
	// @inner
 | 
						||
	function generator_toString(serial, date) {
 | 
						||
		var list = this.clone();
 | 
						||
		if (typeof date === 'string') {
 | 
						||
			date = date.to_Date() || date;
 | 
						||
		} else if (typeof date === 'number') {
 | 
						||
			date = new Date(date);
 | 
						||
		}
 | 
						||
		if (!is_Date(date) || !date.getTime()) {
 | 
						||
			if (date) {
 | 
						||
				library_namespace.error([ 'generator_toString: ', {
 | 
						||
					// gettext_config:{"id":"invalid-date-$1"}
 | 
						||
					T : [ 'Invalid date: %1', date ]
 | 
						||
				} ]);
 | 
						||
			}
 | 
						||
			date = new Date;
 | 
						||
		}
 | 
						||
 | 
						||
		for (var arg_index = 1, index = 1; index < list.length; index++) {
 | 
						||
			var operator = list[index];
 | 
						||
			if (Number.isSafeInteger(serial)) {
 | 
						||
				if (typeof operator === 'number') {
 | 
						||
					list[index] = operator ? serial.pad(operator) : serial;
 | 
						||
				} else {
 | 
						||
					list[index] = operator ? date.format(operator) :
 | 
						||
					// assert: operator === ''
 | 
						||
					'';
 | 
						||
				}
 | 
						||
			} else {
 | 
						||
				list[index] = typeof operator === 'number' ? '%' + arg_index++
 | 
						||
						: operator;
 | 
						||
			}
 | 
						||
		}
 | 
						||
		return list.join('');
 | 
						||
	}
 | 
						||
 | 
						||
	// @inner
 | 
						||
	function get_pattern_and_generator(serial_token_list, options) {
 | 
						||
		if (!serial_token_list)
 | 
						||
			return;
 | 
						||
 | 
						||
		var pattern = serial_token_list.clone();
 | 
						||
		var generator = serial_token_list.clone();
 | 
						||
 | 
						||
		for (var index = 1; index < pattern.length; index++) {
 | 
						||
			var token = pattern[index];
 | 
						||
			if (index % 2 === 0) {
 | 
						||
				// assert: generator[index] === token;
 | 
						||
				pattern[index]
 | 
						||
				//
 | 
						||
				= library_namespace.to_RegExp_pattern(token);
 | 
						||
				continue;
 | 
						||
			}
 | 
						||
 | 
						||
			if (!token)
 | 
						||
				continue;
 | 
						||
 | 
						||
			var fixed_length = options.fixed_length || token.startsWith(0);
 | 
						||
			generator[index] = fixed_length ? token.length : 0;
 | 
						||
			pattern[index] = fixed_length
 | 
						||
			//
 | 
						||
			? '(\\d{' + token.length + '})'
 | 
						||
			//
 | 
						||
			: '(\\d{1,' + options.max_serial_length + '})';
 | 
						||
		}
 | 
						||
 | 
						||
		// assert: new RegExp('^'+pattern+'$').test(serial_token_list.join(''))
 | 
						||
		// === true
 | 
						||
 | 
						||
		// assert: CeL.gettext(generator, index, index.pad()) may generate
 | 
						||
		// serial_token_list.join('')
 | 
						||
 | 
						||
		return [ pattern, generator ];
 | 
						||
	}
 | 
						||
 | 
						||
	// @inner
 | 
						||
	function new_generator(generator_array) {
 | 
						||
		return generator_toString.bind(generator_array);
 | 
						||
	}
 | 
						||
 | 
						||
	// generator string (e.g., including "%1", "%Y") to generator
 | 
						||
	function parse_generator(generator_string) {
 | 
						||
		// matched: [ operator, pad, date, argument_NO ]
 | 
						||
		var PATTERN_operator = new RegExp(
 | 
						||
				/%(?:(\d?)(conversion)|(1)|(\d?)№)/.source.replace(
 | 
						||
						'conversion', Object.keys(strftime.default_conversion)
 | 
						||
								.join('|')), 'g');
 | 
						||
		var matched, generator = [], lastIndex = 0;
 | 
						||
		while (matched = PATTERN_operator.exec(generator_string)) {
 | 
						||
			generator.push(generator_string.slice(lastIndex, matched.index),
 | 
						||
					matched[3] ? 0 : matched[4] ? +matched[4] : matched[0]);
 | 
						||
			lastIndex = PATTERN_operator.lastIndex;
 | 
						||
		}
 | 
						||
		if (lastIndex < generator_string.length)
 | 
						||
			generator.push(generator_string.slice(lastIndex));
 | 
						||
 | 
						||
		// assert: generator.toString() === generator_string
 | 
						||
		return new_generator(generator);
 | 
						||
	}
 | 
						||
 | 
						||
	detect_serial_pattern.parse_generator = parse_generator;
 | 
						||
 | 
						||
	// digital serial instance to pattern and generator
 | 
						||
	function detect_serial_pattern(items, options) {
 | 
						||
		options = library_namespace.new_options(options);
 | 
						||
		if (!(options.max_serial_length > 0)) {
 | 
						||
			// 4: year length
 | 
						||
			options.max_serial_length = 4;
 | 
						||
		}
 | 
						||
 | 
						||
		var pattern_hash = Object.create(null);
 | 
						||
		function add_serial_token_list(serial_token_list) {
 | 
						||
			var pattern = get_pattern_and_generator(serial_token_list, options);
 | 
						||
			if (!pattern)
 | 
						||
				return;
 | 
						||
			var generator = pattern[1];
 | 
						||
			pattern = pattern[0];
 | 
						||
			var pattern_String = pattern.join('');
 | 
						||
			if (!pattern_hash[pattern_String]) {
 | 
						||
				(pattern_hash[pattern_String] = []).generator = generator;
 | 
						||
			}
 | 
						||
			pattern_hash[pattern_String].push(serial_token_list);
 | 
						||
		}
 | 
						||
 | 
						||
		// 創建 pattern_hash
 | 
						||
		items.forEach(function(item) {
 | 
						||
			var serial_token_list = to_serial_token_list(item, options);
 | 
						||
			add_serial_token_list(serial_token_list);
 | 
						||
		});
 | 
						||
 | 
						||
		// 掃描: 有相同 digits 的不應歸為 serial。
 | 
						||
		Object.values(pattern_hash).forEach(function(lists) {
 | 
						||
			var need_to_resettle = [];
 | 
						||
			for (var index = 1; index < lists.length; index += 2) {
 | 
						||
				for (var digits_hash = Object.create(null), index_of_lists = 0;
 | 
						||
				//
 | 
						||
				index_of_lists < lists.length; index_of_lists++) {
 | 
						||
					var serial_token_list = lists[index_of_lists];
 | 
						||
					var digits = serial_token_list[index];
 | 
						||
					if (!digits) {
 | 
						||
						// assert: lists[*][index] === ''
 | 
						||
						break;
 | 
						||
					}
 | 
						||
					if (!(digits in digits_hash)) {
 | 
						||
						digits_hash[digits] = index_of_lists;
 | 
						||
						continue;
 | 
						||
					}
 | 
						||
					// 有相同 digits 的不應歸為 serial。
 | 
						||
					// e.g., "A1B1", "A1B2"
 | 
						||
					// 僅有 A1B<b>1</b>, A1B<b>2</b> 可歸為 serial。
 | 
						||
					// A<b>1</b> ["A", "1", ... ] 應改為 ["A1", "", ... ]
 | 
						||
					serial_token_list[index - 1] += digits;
 | 
						||
					serial_token_list[index] = '';
 | 
						||
					if (!need_to_resettle.includes(index_of_lists))
 | 
						||
						need_to_resettle.push(index_of_lists);
 | 
						||
					if (digits_hash[digits] >= 0) {
 | 
						||
						serial_token_list = lists[digits_hash[digits]];
 | 
						||
						serial_token_list[index - 1] += digits;
 | 
						||
						serial_token_list[index] = '';
 | 
						||
						need_to_resettle.unshift(digits_hash[digits]);
 | 
						||
						// 下次不再處理。
 | 
						||
						digits_hash[digits] = -1;
 | 
						||
					}
 | 
						||
				}
 | 
						||
			}
 | 
						||
 | 
						||
			if (need_to_resettle.length > 0) {
 | 
						||
				need_to_resettle.sort(library_namespace.descending)
 | 
						||
				//
 | 
						||
				.forEach(function(index_of_lists) {
 | 
						||
					var serial_token_list = lists.splice(index_of_lists, 1)[0];
 | 
						||
					// rebulid pattern_hash
 | 
						||
					add_serial_token_list(serial_token_list);
 | 
						||
				});
 | 
						||
			}
 | 
						||
		});
 | 
						||
 | 
						||
		// 把 pattern '(\\d{1,' + max_serial_length + '})'
 | 
						||
		// 搬到 '(\\d{' + token.length + '})'
 | 
						||
		Object.keys(pattern_hash).forEach(function(pattern) {
 | 
						||
			var lists = pattern_hash[pattern];
 | 
						||
			if (lists.length === 0) {
 | 
						||
				// Nothing left after "有相同 digits 的不應歸為 serial。".
 | 
						||
				delete pattern_hash[pattern];
 | 
						||
				return;
 | 
						||
			}
 | 
						||
			if (!pattern.includes('(\\d{1,')) {
 | 
						||
				// Not target.
 | 
						||
				return;
 | 
						||
			}
 | 
						||
			var _options = Object.clone(options);
 | 
						||
			_options.fixed_length = true;
 | 
						||
			for (var index_of_lists = 0;
 | 
						||
			//
 | 
						||
			index_of_lists < lists.length; index_of_lists++) {
 | 
						||
				var serial_token_list = lists[index_of_lists];
 | 
						||
				var pattern = get_pattern_and_generator(serial_token_list,
 | 
						||
				//
 | 
						||
				_options);
 | 
						||
				// var generator = pattern[1];
 | 
						||
				pattern = pattern[0];
 | 
						||
				var pattern_String = pattern.join('');
 | 
						||
				if (pattern_hash[pattern_String]) {
 | 
						||
					lists.splice(index_of_lists--, 1);
 | 
						||
					pattern_hash[pattern_String].push(serial_token_list);
 | 
						||
				}
 | 
						||
			}
 | 
						||
		});
 | 
						||
 | 
						||
		Object.keys(pattern_hash).forEach(function(pattern) {
 | 
						||
			if (pattern_hash[pattern].length === 0)
 | 
						||
				delete pattern_hash[pattern];
 | 
						||
		});
 | 
						||
 | 
						||
		// 把符合日期的數字以日期標示。
 | 
						||
		Object.values(pattern_hash).forEach(function(lists) {
 | 
						||
			var generator = lists.generator;
 | 
						||
			for (var index = 1,
 | 
						||
			// assert: length === generator.length
 | 
						||
			length = generator.length; index < length; index += 2) {
 | 
						||
				var digits_list = [];
 | 
						||
				if (lists.some(function(serial_token_list) {
 | 
						||
					var digits = serial_token_list[index];
 | 
						||
					if (!digits)
 | 
						||
						return true;
 | 
						||
					// assert: digits_list.includes(digits) === false
 | 
						||
					digits_list.push(digits);
 | 
						||
				})) {
 | 
						||
					continue;
 | 
						||
				}
 | 
						||
 | 
						||
				// checker = [ typical generator label, checker,
 | 
						||
				// has digits without 0-prefix ]
 | 
						||
				var checker_list = [ [ '%Y', function(digits) {
 | 
						||
					return 1900 <= digits && digits <= 2200;
 | 
						||
				} ], [ '%m', function(digits) {
 | 
						||
					if (1 <= digits && digits <= 12) {
 | 
						||
						if (digits < 10 && digits.length < 2)
 | 
						||
							this[2] = true;
 | 
						||
						return true;
 | 
						||
					}
 | 
						||
				} ], [ '%d', function(digits) {
 | 
						||
					if (1 <= digits && digits <= 31) {
 | 
						||
						if (digits < 10 && digits.length < 2)
 | 
						||
							this[2] = true;
 | 
						||
						return true;
 | 
						||
					}
 | 
						||
				} ] ];
 | 
						||
 | 
						||
				checker_list = checker_list.filter(function(checker) {
 | 
						||
					return digits_list.every(function(digits) {
 | 
						||
						return checker[1](digits);
 | 
						||
					});
 | 
						||
				});
 | 
						||
 | 
						||
				if (checker_list.length === 0) {
 | 
						||
					// No matched, use generator default operator
 | 
						||
					// assert: typeof generator[index] === 'number'
 | 
						||
					continue;
 | 
						||
				}
 | 
						||
 | 
						||
				// assert: typeof generator[index] === 'number'
 | 
						||
				generator[index] = [ generator[index] ]
 | 
						||
				// 登記所有相符的。
 | 
						||
				.append(checker_list);
 | 
						||
 | 
						||
				switch (checker_list = checker_list[0][0]) {
 | 
						||
				case '%Y':
 | 
						||
				case '%m':
 | 
						||
					generator['has_' + checker_list] = true;
 | 
						||
					break;
 | 
						||
				}
 | 
						||
			}
 | 
						||
 | 
						||
			for (var index = 1; index < generator.length; index += 2) {
 | 
						||
				var operator = generator[index];
 | 
						||
				if (Array.isArray(operator)) {
 | 
						||
					var digits_length = operator[0];
 | 
						||
					// best checker
 | 
						||
					operator = operator[1];
 | 
						||
					generator[index] = operator[0] === '%Y'
 | 
						||
					// assert: generator[index] = [ number, [checker] ]
 | 
						||
					|| generator['has_%Y'] && operator[0] === '%m'
 | 
						||
					//
 | 
						||
					|| generator['has_%m'] && operator[0] === '%d'
 | 
						||
					//
 | 
						||
					? digits_length || operator[2]
 | 
						||
					// digits_length: "%m" → "%2m", operator[2]: "%m" → "%0m"
 | 
						||
					? operator[0].replace('%', '%' + digits_length)
 | 
						||
					//
 | 
						||
					: operator[0] : digits_length;
 | 
						||
				}
 | 
						||
			}
 | 
						||
		});
 | 
						||
 | 
						||
		// move items of %m to %2m, %d to %2d
 | 
						||
		var pattern_group_hash = Object.create(null);
 | 
						||
		Object.keys(pattern_hash).forEach(function(pattern) {
 | 
						||
			var generalized = pattern.replace(/\\d{\d{1,2}}/g, '\\d{1,'
 | 
						||
			//
 | 
						||
			+ options.max_serial_length + '}');
 | 
						||
			if (!pattern_group_hash[generalized]) {
 | 
						||
				pattern_group_hash[generalized] = [ pattern ];
 | 
						||
				return;
 | 
						||
			}
 | 
						||
 | 
						||
			var pattern_to = pattern_hash[pattern].generator.join('');
 | 
						||
			var index_in_group_hash;
 | 
						||
			var pattern_from = pattern_group_hash[generalized]
 | 
						||
			// 找出所有與 pattern 等價的
 | 
						||
			.filter(function(_pattern, index) {
 | 
						||
				_pattern = pattern_hash[_pattern].generator.join('');
 | 
						||
				if (/%\d([md])/.test(_pattern)
 | 
						||
				//
 | 
						||
				!== /%\d([md])/.test(pattern_to)
 | 
						||
				//
 | 
						||
				&& _pattern.replace(/%\d([md])/g, '%$1')
 | 
						||
				//
 | 
						||
				=== pattern_to.replace(/%\d([md])/g, '%$1')) {
 | 
						||
					if (!(index_in_group_hash >= 0))
 | 
						||
						index_in_group_hash = index;
 | 
						||
					return true;
 | 
						||
				}
 | 
						||
			});
 | 
						||
			if (pattern_from.length === 0) {
 | 
						||
				// Warning: 可能導致出現錯誤
 | 
						||
				pattern_group_hash[generalized].push(pattern);
 | 
						||
				return;
 | 
						||
			}
 | 
						||
 | 
						||
			pattern_from = pattern_from[0];
 | 
						||
			var pattern_to;
 | 
						||
			if (/%\d([md])/.test(pattern_to)) {
 | 
						||
				pattern_group_hash[generalized].splice(index_in_group_hash, 1);
 | 
						||
				pattern_group_hash[generalized].push(pattern_to);
 | 
						||
				pattern_to = pattern;
 | 
						||
			} else {
 | 
						||
				// switch from, to
 | 
						||
				pattern_to = pattern_from;
 | 
						||
				pattern_from = pattern;
 | 
						||
			}
 | 
						||
			// assert: !/%\d([md])/.test(pattern_from)
 | 
						||
			// && /%\d([md])/.test(pattern_to)
 | 
						||
 | 
						||
			// var lists_from = pattern_hash[pattern_from];
 | 
						||
			// var lists_to = pattern_hash[pattern_to];
 | 
						||
			pattern_hash[pattern_to].append(pattern_hash[pattern_from]);
 | 
						||
			delete pattern_hash[pattern_from];
 | 
						||
		});
 | 
						||
 | 
						||
		var pattern_list = [];
 | 
						||
		Object.keys(pattern_hash).forEach(function(pattern) {
 | 
						||
			var lists = pattern_hash[pattern];
 | 
						||
			var generator = lists.generator;
 | 
						||
			// recover superfluous "0" (by "%m" → "%0m" above)
 | 
						||
			for (var index = 1; index < generator.length; index += 2) {
 | 
						||
				if (typeof generator[index] === 'string') {
 | 
						||
					generator[index] = generator[index]
 | 
						||
					// "%0m" → "%m"
 | 
						||
					.replace(/^%0([md])$/g, '%$1');
 | 
						||
				}
 | 
						||
			}
 | 
						||
			// assert (lists.length > 0)
 | 
						||
			pattern_list.push({
 | 
						||
				pattern : new RegExp('^' + pattern + '$'),
 | 
						||
				generator : new_generator(generator),
 | 
						||
				items : lists,
 | 
						||
				count : lists.length
 | 
						||
			});
 | 
						||
		});
 | 
						||
		// count 從大到小排序
 | 
						||
		pattern_list.sort(function(_1, _2) {
 | 
						||
			return _2.count - _1.count;
 | 
						||
		});
 | 
						||
 | 
						||
		return pattern_list;
 | 
						||
	}
 | 
						||
 | 
						||
	_.detect_serial_pattern = detect_serial_pattern;
 | 
						||
 | 
						||
	// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
 | 
						||
	// 組織 period list,以做後續檢索。
 | 
						||
 | 
						||
	/**
 | 
						||
	 * 取得包含 date 之 periods<br />
 | 
						||
	 * need module CeL.data.native
 | 
						||
	 * 
 | 
						||
	 * @param {Date}date
 | 
						||
	 *            欲搜尋之 date
 | 
						||
	 * 
 | 
						||
	 * @returns {Array}period data
 | 
						||
	 */
 | 
						||
	function get_contemporary_periods(date) {
 | 
						||
		date = +date;
 | 
						||
		if (date < this.start[0]) {
 | 
						||
			return;
 | 
						||
		}
 | 
						||
 | 
						||
		var start_index = this.start.search_sorted(date, {
 | 
						||
			found : true
 | 
						||
		}),
 | 
						||
		// 邊際 period
 | 
						||
		edge_period = this[start_index], contemporary = this.contemporary[start_index];
 | 
						||
 | 
						||
		if (contemporary) {
 | 
						||
			var end_list = contemporary.end;
 | 
						||
			// 把結束時間在 date 之前的去掉。
 | 
						||
			var last_index = 0;
 | 
						||
			// 因為量應該不大,採循序搜尋。
 | 
						||
			while (end_list[last_index] <= date) {
 | 
						||
				last_index++;
 | 
						||
			}
 | 
						||
			contemporary = contemporary.slice(last_index);
 | 
						||
		} else {
 | 
						||
			contemporary = [];
 | 
						||
		}
 | 
						||
 | 
						||
		if (date < this.get_end(edge_period)) {
 | 
						||
			contemporary.push(edge_period);
 | 
						||
		} else if (contemporary.length === 0) {
 | 
						||
			return;
 | 
						||
		}
 | 
						||
 | 
						||
		return contemporary;
 | 
						||
	}
 | 
						||
 | 
						||
	// TODO
 | 
						||
	function add_period(period_data) {
 | 
						||
		var start_time = this.get_start(period_data),
 | 
						||
		// index to insert
 | 
						||
		index = this.start.search_sorted(start_time, {
 | 
						||
			found : true
 | 
						||
		});
 | 
						||
		TODO;
 | 
						||
		if (start_time < this.start[0])
 | 
						||
			;
 | 
						||
	}
 | 
						||
 | 
						||
	if (false) {
 | 
						||
		test_periods = sortest_periods([
 | 
						||
				[ new Date(2015, 3, 2), new Date(2015, 7, 3), 'bbb' ],
 | 
						||
				[ new Date(2015, 1, 2), new Date(2015, 4, 3), 'aaa' ], ], 0, 1);
 | 
						||
 | 
						||
		test_periods.get_contemporary(new Date(2015, 1, 1));
 | 
						||
		test_periods.get_contemporary(new Date(2015, 3, 2));
 | 
						||
		test_periods.get_contemporary(new Date(2015, 3, 8));
 | 
						||
		test_periods.get_contemporary(new Date(2015, 4, 3));
 | 
						||
		test_periods.get_contemporary(new Date(2015, 7, 2));
 | 
						||
		test_periods.get_contemporary(new Date(2015, 7, 3));
 | 
						||
		test_periods.get_contemporary(new Date(2015, 7, 4));
 | 
						||
	}
 | 
						||
 | 
						||
	/**
 | 
						||
	 * 組織 period_list,以做後續檢索。
 | 
						||
	 * 
 | 
						||
	 * @param {Object|Array}period_list
 | 
						||
	 *            period list
 | 
						||
	 * @param {Number|Function}start_index
 | 
						||
	 *            start index. 若為 function,回傳須轉為 number,不應回傳 boolean, false。
 | 
						||
	 * @param {Number|Function}end_index
 | 
						||
	 *            end index. 若為 function,回傳須轉為 number,不應回傳 boolean, false。
 | 
						||
	 * @param {Number}[unit_length]
 | 
						||
	 * 
 | 
						||
	 * @returns {Array}period list
 | 
						||
	 */
 | 
						||
	function sort_periods(period_list, start_index, end_index, unit_length) {
 | 
						||
		var periods = [], start_list = [],
 | 
						||
		// assert: Number.isFinite(get_start(period_data))
 | 
						||
		get_start = typeof start_index === 'function' ? start_index : function(
 | 
						||
				data) {
 | 
						||
			return +data[start_index];
 | 
						||
		},
 | 
						||
		// assert: Number.isFinite(get_end(period_data))
 | 
						||
		get_end = typeof end_index === 'function' ? end_index : function(data) {
 | 
						||
			var time = +data[end_index];
 | 
						||
			return isNaN(time) ? +get_start(data) + unit_length : time;
 | 
						||
		};
 | 
						||
		if (!(unit_length > 0)) {
 | 
						||
			unit_length = ONE_DAY_LENGTH_VALUE;
 | 
						||
		}
 | 
						||
		// periods.get_start = get_start;
 | 
						||
		periods.get_end = get_end;
 | 
						||
		// periods.unit = unit_length;
 | 
						||
 | 
						||
		function add_index(period_data, index) {
 | 
						||
			start_list.push([ get_start(period_data), period_data ]);
 | 
						||
		}
 | 
						||
		if (typeof period_list.forEach === 'function') {
 | 
						||
			period_list.forEach(add_index);
 | 
						||
		} else {
 | 
						||
			for ( var key in period_list) {
 | 
						||
				add_index(period_list[key], key);
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		// sort by start time
 | 
						||
		start_list.sort(function(period1, period2) {
 | 
						||
			return period1[0] - period2[0];
 | 
						||
		});
 | 
						||
 | 
						||
		// [ start time value, ... ]
 | 
						||
		var start_time_list = [],
 | 
						||
		// [ [ period_data, period_data, ... ], ... ]
 | 
						||
		// contemporary 若存在,應該按照 end 時間由前至後 sort。
 | 
						||
		contemporary = [],
 | 
						||
		//
 | 
						||
		last_contemporary, last_end_list, last_end,
 | 
						||
		// 前一period
 | 
						||
		last_period;
 | 
						||
		periods.start = start_time_list;
 | 
						||
		periods.contemporary = contemporary;
 | 
						||
		if (false) {
 | 
						||
			periods.add = add_period;
 | 
						||
			start_list.forEach(function(period_data) {
 | 
						||
				periods.add(period_data);
 | 
						||
			});
 | 
						||
		}
 | 
						||
 | 
						||
		start_list.forEach(function(period, index) {
 | 
						||
			var start_time = period[0];
 | 
						||
			periods.push(period[1]);
 | 
						||
			start_time_list.push(start_time);
 | 
						||
			if (last_contemporary) {
 | 
						||
				last_contemporary = last_contemporary.slice();
 | 
						||
				last_end_list = last_end_list.slice();
 | 
						||
			} else {
 | 
						||
				last_contemporary = [];
 | 
						||
				last_end_list = [];
 | 
						||
			}
 | 
						||
 | 
						||
			// 除去結束時間於本 period 之前的 period。
 | 
						||
			var past_index = 0;
 | 
						||
			// 因為量應該不大,採循序搜尋。
 | 
						||
			while (last_end_list[past_index]
 | 
						||
					&& start_time < last_end_list[past_index]) {
 | 
						||
				past_index++;
 | 
						||
			}
 | 
						||
			if (past_index > 0) {
 | 
						||
				last_end_list.slice(past_index);
 | 
						||
				last_contemporary.slice(past_index);
 | 
						||
			}
 | 
						||
 | 
						||
			if (start_time < last_end) {
 | 
						||
				// last_period 與本 period 重疊。將之添入。
 | 
						||
				past_index = 0;
 | 
						||
				// 因為量應該不大,採循序搜尋。
 | 
						||
				while (last_end_list[past_index] <= last_end) {
 | 
						||
					past_index++;
 | 
						||
				}
 | 
						||
				last_end_list.splice(past_index, 0, last_end);
 | 
						||
				last_contemporary.splice(past_index, 0, last_period);
 | 
						||
			}
 | 
						||
 | 
						||
			// last_contemporary: 不包含本 period 之本 period 共存 periods。
 | 
						||
 | 
						||
			if (last_contemporary.length > 0) {
 | 
						||
				(contemporary[index] = last_contemporary).end = last_end_list;
 | 
						||
			} else {
 | 
						||
				last_contemporary = null;
 | 
						||
			}
 | 
						||
			last_end = get_end(last_period = period[1]);
 | 
						||
		});
 | 
						||
 | 
						||
		periods.get_contemporary = get_contemporary_periods;
 | 
						||
		return periods;
 | 
						||
	}
 | 
						||
 | 
						||
	_.sort_periods = sort_periods;
 | 
						||
 | 
						||
	// ---------------------------------------------------------------------//
 | 
						||
	// export 導出.
 | 
						||
 | 
						||
	library_namespace.set_method(String.prototype, {
 | 
						||
		to_Date : set_bind(String_to_Date)
 | 
						||
	});
 | 
						||
 | 
						||
	library_namespace.set_method(Date.prototype, {
 | 
						||
		to_Excel : set_bind(Date_to_Excel),
 | 
						||
 | 
						||
		age : set_bind(age_of, true),
 | 
						||
		format : set_bind(Date_to_String)
 | 
						||
	});
 | 
						||
 | 
						||
	return (_// JSDT:_module_
 | 
						||
	);
 | 
						||
}
 |