Files
rappaurio-sae501_502/app/node_modules/cejs/data/date.js
2023-09-25 09:41:55 +02:00

4112 lines
132 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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 之 JDNUTC+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
* 112
* @param {Natural}date
* 131
* @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: 12月, a=0: 312月
// 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
// 37月: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 09999。
// [ 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., 'MayJune 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: 099 的年份會加上此年份。
&& (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;
}
// 099 的年份會加上此年份 (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: 011, date: 128/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 BCE2200 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) {
// 因為加至當年沒有閏日的 Gregorian2/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-32006 支持將符號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 WoMaya: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: 131
// count=2: 115, 1631
// count=3: 110, 1120, 2131
// count=4: 17, 814, 1521, 2231
// count=5: 16, ...
/**
* 將`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_
);
}