cejs
Version:
A JavaScript module framework that is simple to use.
1,594 lines (1,437 loc) • 136 kB
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.<br />
* .search_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.repla