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