/**
* @fileOverview
* <p>Dateオブジェクトを拡張して、PHPのdate関数と同様の書式でフォーマット出来るようにする。</p>
* <p>他、おまけメソッド色々付き。<a href="http://rewish.org/javascript/php_date">詳細</a>。</p>
* <pre>
* The MIT License
*
* Copyright (c) 2009-2011 Hiroshi Hoaki &lt;rewish.org@gmail.com&gt;
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* </pre>
*
* @name PHP Date
* @version 0.3.1
* @author rew &lt;<a href="mailto:rewish.org@gmail.com">rewish.org@gmail.com</a>&gt;
*/
(function() {

/**
* Y-m-d\\TH:i:sP
* @constant
* @return {String}
*/
Date.ATOM = 'Y-m-d\\TH:i:sP';
/**
* l, d-M-y H:i:s T
* @constant
* @return {String}
*/
Date.COOKIE = 'l, d-M-y H:i:s T';
/**
* Y-m-d\\TH:i:sP
* @constant
* @return {String}
*/
Date.ISO8601 = 'Y-m-d\\TH:i:sO';
/**
* D, d M y H:i:s O
* @constant
* @return {String}
*/
Date.RFC822 = 'D, d M y H:i:s O';
/**
* l, d-M-y H:i:s T
* @constant
* @return {String}
*/
Date.RFC850 = 'l, d-M-y H:i:s T';
/**
* D, d M y H:i:s O
* @constant
* @return {String}
*/
Date.RFC1036 = 'D, d M y H:i:s O';
/**
* D, d M Y H:i:s O
* @constant
* @return {String}
*/
Date.RFC1123 = 'D, d M Y H:i:s O';
/**
* D, d M Y H:i:s O
* @constant
* @return {String}
*/
Date.RFC2822 = 'D, d M Y H:i:s O';
/**
* Y-m-d\\TH:i:sP
* @constant
* @return {String}
*/
Date.RFC3339 = 'Y-m-d\\TH:i:sP';
/**
* D, d M Y H:i:s O
* @constant
* @return {String}
*/
Date.RSS = 'D, d M Y H:i:s O';
/**
* Y-m-d\\TH:i:sP
* @constant
* @return {String}
*/
Date.W3C = 'Y-m-d\\TH:i:sP';

/**
* Month full names
* @private
*/
var monthFullNames = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
/**
* Month short names
* @private
*/
var monthShortNames = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
/**
* Day full names
* @private
*/
var dayFullNames = [
'Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday'
];
/**
* Day short names
* @private
*/
var dayShortNames = [
'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'
];
/**
* Timezone data
* @private
*/
var timezone = {
id : 'Asia/Tokyo',
abbr: 'JST'
};
/**
* Date.parse Original
* @private
*/
var DateParse = Date.parse;
/**
* Date.parse patterns
* @private
*/
var parsePattern = [
// [2009-08-01T01:02:03Z] or [2009-08-01T01:02:03+09:00]
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([\+|\-]{1}\d{2}:\d{2})?/,
// [Monday, 01-Aug-09 01:02:03 JST] or [Mon, 01 Aug 2009 01:02:03 +0900]
/^[a-z]+, (\d{2,})[\s\-]([a-z]{3})[\s\-](\d{2}) (\d{2}):(\d{2}):(\d{2})\s*(.+)?/i
];
/**
* is Opera
* @private
*/
var isOpera = typeof window.opera !== 'undefined';
/**
* inArray
* @private
*/
function inArray(array, value) {
if (array.indexOf) {
return array.indexOf(value);
}
for (var i = 0, length = array.length; i < length; ++i) {
if (array[i] === value) {
return i;
}
}
return -1;
}
/**
* zero padding
* @private
*/
function zp(value, digit) {
digit = digit || 2;
for (var i = 0; i < digit; ++i) {
value = '0' + value;
}
return value.slice(-digit);
}

/**
* Date.parse及び定数で扱えるフォーマットをパースしてUTCベースのUNIXタイムスタンプを返す
*
* @param {String} dateString 規格に沿った日付文字列
* @param {Number} [baseYear=2000] 年が二桁の時ベースになる年
* @return {Number} UNIXタイムスタンプ
*/
Date.parse = function(dateString, baseYear) {
var m, time;

// OperaはDate.parseが変なので取り敢えずNaNにする
time = isOpera ? NaN : DateParse(dateString);

if (!isNaN(time)) {
return time;
}

// [2009-08-01T01:02:03Z] or [2009-08-01T01:02:03+09:00]
if (m = dateString.match(parsePattern[0])) {
time = Date.UTC(m[1], m[2] - 1, m[3], m[4], m[5], m[6]);
return m[7] ? Date.applyDiffTime(time, m[7]) : time;
}

// [Monday, 01-Aug-09 01:02:03 JST] or [Mon, 01 Aug 2009 01:02:03 +0900]
if (m = dateString.match(parsePattern[1])) {
time = Date.UTC(+m[3] + (baseYear || 2000), inArray(monthShortNames, m[2]), m[1], m[4], m[5], m[6]);
return m[7] ? Date.applyDiffTime(time, m[7]) : time;
}

// OperaかつtimeがNaNならDate.parseを通す
return isOpera && isNaN(time) ? DateParse(dateString) : time;
};

/**
* 時差を適用する
*
* @param {Number} time ミリ秒単位のUNIXタイムスタンプ
* @param {String} diff 時差文字列 (+0900 or +09:00 or JST)
* @return {Number} 時差を適用したUNIXタイムスタンプ
*/
Date.applyDiffTime = function(time, diff) {
diff = diff.replace('JST', '+0900').replace('UTC', '+0000');
var diffTime = diff.match(/\d{2}/g);
diffTime = ((Math.abs(diffTime[0]) * 60) + Math.abs(diffTime[1])) * 60 * 1000;
return diff.match(/^./)[0] === '-' ? time + diffTime : time - diffTime;
};

/**
* ISO-8601 月曜日に始まる年単位の週番号を返す<br>
* ※1月4日及びその年の最初の木曜日が含まれる週が開始週番号
*
* @param {Number} [year=this.getFullYear()] 対象年
* @param {Number} [month=this.getMonth()] 対象月
* @param {Number} [date=this.getDate()] 対象日
* @return {Number} 週番号 (1-53)
*/
Date.prototype.getISOWeekNumber = function(year, month, date) {
var _doy, doy;
year = year || this.getFullYear();
month = month || this.getMonth();
date = date || this.getDate();
// 今年の1月4日の曜日番号から1日2日3日の合計3日を減算
_doy = (new Date(year, 0, 4).getDay() || 7) - 3;
// 上記で算出した週番号開始日を経過日数に加算
doy = _doy + this.getElapseDays(year);
// 経過日数が0以下の場合、前年から続く週番号
if (doy <= 0) {
year = year - 1;
doy = _doy + (new Date(year, 11, 31).getElapseDays(year));
}
// 12月29日より前はここで終了
if (month < 11 || date < 29) {
// 算出された日数を一週間分の7で割って小数点以下切り上げ
return Math.ceil(doy / 7);
}
// 12月29日～12月31日の曜日がそれぞれ月、月or火、月～水なら次月の週番号開始
if ((this.getDay() || 7) <= (3 - (31 - date))) {
return 1;
}
// 算出された日数を一週間分の7で割って小数点以下切り上げ
return Math.ceil(doy / 7);
};

/**
* ISO-8601 週番号に属する年を返す
*
* @param {Number} [year=this.getFullYear()] 対象年
* @param {Number} [month=this.getMonth()] 対象月
* @param {Number} [date=this.getDate()] 対象日
* @return {Number} 年
*/
Date.prototype.getISOYear = function(year, month, date) {
year = year || this.getFullYear();
month = month || this.getMonth();
date = date || this.getDate();
weekNumber = this.getISOWeekNumber(year, month, date);
return date <= 3 && weekNumber >= 52 ? year - 1
: date >= 29 && weekNumber == 1 ? year + 1
: year;
};

/**
* getTimeの数値から時差を引いたミリ秒単位の数値を返す
*
* @return {Number} ミリ秒単位の数値
*/
Date.prototype.getUTCTime = function() {
return this.getTime() + (this.getTimezoneOffset() * 60 * 1000);
};

/**
* 対象年がうるう年かどうか
*
* @param {Number} [year=this.getFullYear()] 対象年
* @return {Boolean} うるう年ならtrue、平年ならfalse
*/
Date.prototype.isLeapYear = function(year) {
year = year || this.getFullYear();
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
};

/**
* 現在のインターネットタイムを返す
*
* @param {Number} [hour=GMT Hour] 対象時
* @param {Number} [min=GMT Min] 対象分
* @param {Number} [sec=GMT Sec] 対象秒
* @return {String} インターネットタイム
*/
Date.prototype.getInternetTime = function(hour, min, sec) {
var beat, gmt = this.toGMTString().split(' ')[4].split(':');
hour = hour || +gmt[0];
min = min || +gmt[1];
sec = sec || +gmt[2];
beat = (hour * 3600 + min * 60 + sec + 3600) / 86.4;
return zp(Math.floor(beat >= 1000 ? beat - 1000 : beat), 3);
};

/**
* 対象日の序数を表すサフィックスを返す
*
* @param {Number} [date=this.getDate()] 対象日
* @return {String} "st" or "nd" or "rd" or "th"
*/
Date.prototype.getSuffix = function(date) {
date = '' + (date || this.getDate());
var last = date.slice(-1);
return last === '1' && date !== '11' ? 'st'
: last === '2' && date !== '12' ? 'nd'
: last === '3' && date !== '13' ? 'rd'
: 'th';
};

/**
* 対象年月日からの経過日数を返す
*
* @param {Number} [year=this.getFullYear()] 対象年
* @param {Number} [month=0] 対象月
* @param {Number} [date=1] 対象日
* @return {Number} 経過日数
*/
Date.prototype.getElapseDays = function(year, month, date) {
var start = new Date(year || this.getFullYear(), month || 0, date || 1),
now = new Date(this.getFullYear(), this.getMonth(), this.getDate());
return Math.floor((now - start) / 60 / 60 / 24 / 1000);
};

/**
* 対象月の全日数を返す
*
* @param {Number} [year=this.getFullYear()] 対象年
* @param {Number} [month=this.getMonth()] 対象月
* @return {Number} 対象月の全日数
*/
Date.prototype.getMonthTotalDays = function(year, month) {
year = year || this.getFullYear();
month = month || this.getMonth();
return new Date(year, month + 1, 0).getDate();
};

/**
* 12時間単位の時間を返す
*
* @param {Number} [hour=this.getHours()] 対象の時間
* @return {Number} 12時間単位の時間
*/
Date.prototype.getHalfHours = function(hour) {
hour = hour || this.getHours();
return hour > 12 ? hour - 12 : hour === 0 ? 12 : hour;
};

/**
* グリニッジ標準時 (GMT) との時差を返す
*
* @param {Boolean} [colon] trueなら時間と分をコロンで区切る
* @return {String} 時差文字列 (+0900 or +09:00)
*/
Date.prototype.getGMTDiff = function(colon) {
var offset = this.getTimezoneOffset() / 60;
return (offset > 0 ? '-' : '+') + zp(Math.abs(offset)) + (colon ? ':' : '') + '00';
};

/**
* PHPのdate関数と同様の書式で日付をフォーマット
*
* @example
* var date = new Date();
* date.format('Y-m-d H:i:s'); // 1970-01-01 00:00:00
* @param {String} format フォーマット文字列
* @param {Number|String} [timestamp] UNIXタイムスタンプ または Date.parseでパース出来る日付
* @return {String} フォーマット文字列にしたがってフォーマットされた日付
* @see <a href="http://php.net/manual/ja/function.date.php">PHP: date - Manual</a>
*/
Date.prototype.format = function(format, timestamp) {
if (!timestamp) {
return _formatter.call(this, format);
}
if (typeof timestamp !== 'number') {
timestamp = Date.parse(timestamp);
}
var _timestamp = this.getTime();
this.setTime(timestamp);
var ret = _formatter.call(this, format);
this.setTime(_timestamp);
return ret;
};

function _formatter(format) {
// toString
format = format + '';
// Result
var result = [];
for (var i = 0, str; str = format.charAt(i); ++i) {
if (str === '\\') {
result[++i] = format.charAt(i);
continue;
}
result[i]
// [Day] 01 to 31
= str === 'd' ? zp(this.getDate())
// [Day] Mon through Sun
: str === 'D' ? dayShortNames[this.getDay()]
// [Day] 1 to 31
: str === 'j' ? this.getDate()
// [Day] Monday through Sunday
: str === 'l' ? dayFullNames[this.getDay()]
// [Day] 1 (for Monday) through 7 (for Sunday)
: str === 'N' ? this.getDay() === 0 ? 7 : this.getDay()
// [Day] st, nd, rd or th. Works well with j
: str === 'S' ? this.getSuffix(this.getDate())
// [Day] 0 (for Sunday) through 6 (for Saturday)
: str === 'w' ? this.getDay()
// [Day] 0 through 365
: str === 'z' ? this.getElapseDays()

// [Week] Example: 42 (the 42nd week in the year)
: str === 'W' ? zp(this.getISOWeekNumber())

// [Month] January through December
: str === 'F' ? monthFullNames[this.getMonth()]
// [Month] 01 through 12
: str === 'm' ? zp(this.getMonth() + 1)
// [Month] Jan through Dec
: str === 'M' ? monthShortNames[this.getMonth()]
// [Month] 1 through 12
: str === 'n' ? this.getMonth() + 1
// [Month] 28 through 31
: str === 't' ? this.getMonthTotalDays()

// [Year] 1 if it is a leap year, 0 otherwise.
: str === 'L' ? this.isLeapYear() ? 1 : 0
// [Year] Examples: 1999 or 2003 (ISO8601)
: str === 'o' ? this.getISOYear()
// [Year] Examples: 1999 or 2003
: str === 'Y' ? this.getFullYear()
// [Year] Examples: 99 or 03
: str === 'y' ? (this.getFullYear() + '').slice(-2)

// [Time] am or pm
: str === 'a' ? this.getHours() < 12 ? 'am' : 'pm'
// [Time] AM or PM
: str === 'A' ? this.getHours() < 12 ? 'AM' : 'PM'
// [Time] 000 through 999
: str === 'B' ? this.getInternetTime()
// [Time] 1 through 12
: str === 'g' ? this.getHalfHours()
// [Time] 0 through 23
: str === 'G' ? this.getHours()
// [Time] 01 through 12
: str === 'h' ? zp(this.getHalfHours())
// [Time] 00 through 23
: str === 'H' ? zp(this.getHours())
// [Time] 00 to 59
: str === 'i' ? zp(this.getMinutes())
// [Time] 00 through 59
: str === 's' ? zp(this.getSeconds())
// [Time] Example: 654321
: str === 'u' ? zp(this.getMilliseconds(), 3) + '000'

// [Timezone] Examples: UTC, GMT, Atlantic/Azores
: str === 'e' ? timezone['id']
// [Timezone] 1 if Daylight Saving Time, 0 otherwise.
: str === 'I' ? 0
// [Timezone] Example: +0900
: str === 'O' ? this.getGMTDiff()
// [Timezone] Example: +09:00
: str === 'P' ? this.getGMTDiff(true)
// [Timezone] Examples: EST, MDT ...
: str === 'T' ? timezone['abbr']
// [Timezone] -43200 through 50400
: str === 'Z' ? (this.getTimezoneOffset() > 0 ? '-' : '')
+ Math.abs(this.getTimezoneOffset() * 60)

// [Full Date/Time] 2004-02-12T15:19:21+00:00
// Date.ISO8601
: str === 'c' ? arguments.callee.call(this, Date.ATOM)
// [Full Date/Time] Example: Thu, 21 Dec 2000 16:01:07 +0200
: str === 'r' ? arguments.callee.call(this, Date.RFC2822)
// [Full Date/Time] Unix timestamp
: str === 'U' ? (this.getTime() + '').slice(0, -3)

// [NoMatch]
: str;
}
return result.join('');
}

})();


