mirror of
https://github.com/wassname/fullcalendar.git
synced 2026-07-13 01:00:49 +08:00
break apart date_utils.js
This commit is contained in:
+2
-1
@@ -20,7 +20,8 @@
|
||||
"src/Header.js",
|
||||
"src/EventManager.js",
|
||||
"src/util.js",
|
||||
"src/date_util.js",
|
||||
"src/moment-ext.js",
|
||||
"src/date-formatting.js",
|
||||
"src/basic/MonthView.js",
|
||||
"src/basic/BasicWeekView.js",
|
||||
"src/basic/BasicDayView.js",
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
|
||||
// Single Date Formatting
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// call this if you want Moment's original format method to be used
|
||||
function momentFormat(mom, formatStr) {
|
||||
return moment.fn.format.call(mom, formatStr);
|
||||
}
|
||||
|
||||
|
||||
// Formats `date` with a Moment formatting string, but allow our non-zero areas and
|
||||
// additional token.
|
||||
function formatDate(date, formatStr) {
|
||||
return formatDateWithChunks(date, getFormatStringChunks(formatStr));
|
||||
}
|
||||
|
||||
|
||||
function formatDateWithChunks(date, chunks) {
|
||||
var s = '';
|
||||
var i;
|
||||
|
||||
for (i=0; i<chunks.length; i++) {
|
||||
s += formatDateWithChunk(date, chunks[i]);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
// addition formatting tokens we want recognized
|
||||
var tokenOverrides = {
|
||||
t: function(date) { // "a" or "p"
|
||||
return momentFormat(date, 'a').charAt(0);
|
||||
},
|
||||
T: function(date) { // "A" or "P"
|
||||
return momentFormat(date, 'A').charAt(0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function formatDateWithChunk(date, chunk) {
|
||||
var token;
|
||||
var maybeStr;
|
||||
|
||||
if (typeof chunk === 'string') { // a literal string
|
||||
return chunk;
|
||||
}
|
||||
else if ((token = chunk.token)) { // a token, like "YYYY"
|
||||
if (tokenOverrides[token]) {
|
||||
return tokenOverrides[token](date); // use our custom token
|
||||
}
|
||||
return momentFormat(date, token);
|
||||
}
|
||||
else if (chunk.maybe) { // a grouping of other chunks that must be non-zero
|
||||
maybeStr = formatDateWithChunks(date, chunk.maybe);
|
||||
if (maybeStr.match(/[1-9]/)) {
|
||||
return maybeStr;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
// Date Range Formatting
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// TODO: make it work with timezone offset
|
||||
|
||||
// Using a formatting string meant for a single date, generate a range string, like
|
||||
// "Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ.
|
||||
// If the dates are the same as far as the format string is concerned, just return a single
|
||||
// rendering of one date, without any separator.
|
||||
function formatRange(date1, date2, formatStr, separator, isRTL) {
|
||||
|
||||
// Expand localized format strings, like "LL" -> "MMMM D YYYY"
|
||||
formatStr = date1.lang().longDateFormat(formatStr) || formatStr;
|
||||
// BTW, this is not important for `formatDate` because it is impossible to put custom tokens
|
||||
// or non-zero areas in Moment's localized format strings.
|
||||
|
||||
separator = separator || ' - ';
|
||||
|
||||
return formatRangeWithChunks(
|
||||
date1,
|
||||
date2,
|
||||
getFormatStringChunks(formatStr),
|
||||
separator,
|
||||
isRTL
|
||||
);
|
||||
}
|
||||
fc.formatRange = formatRange; // expose
|
||||
|
||||
|
||||
function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
|
||||
var chunkStr; // the rendering of the chunk
|
||||
var leftI;
|
||||
var leftStr = '';
|
||||
var rightI;
|
||||
var rightStr = '';
|
||||
var middleI;
|
||||
var middleStr1 = '';
|
||||
var middleStr2 = '';
|
||||
var middleStr = '';
|
||||
|
||||
// Start at the leftmost side of the formatting string and continue until you hit a token
|
||||
// that is not the same between dates.
|
||||
for (leftI=0; leftI<chunks.length; leftI++) {
|
||||
chunkStr = formatSimilarChunk(date1, date2, chunks[leftI]);
|
||||
if (chunkStr === false) {
|
||||
break;
|
||||
}
|
||||
leftStr += chunkStr;
|
||||
}
|
||||
|
||||
// Similarly, start at the rightmost side of the formatting string and move left
|
||||
for (rightI=chunks.length-1; rightI>leftI; rightI--) {
|
||||
chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]);
|
||||
if (chunkStr === false) {
|
||||
break;
|
||||
}
|
||||
rightStr = chunkStr + rightStr;
|
||||
}
|
||||
|
||||
// The area in the middle is different for both of the dates.
|
||||
// Collect them distinctly so we can jam them together later.
|
||||
for (middleI=leftI; middleI<=rightI; middleI++) {
|
||||
middleStr1 += formatDateWithChunk(date1, chunks[middleI]);
|
||||
middleStr2 += formatDateWithChunk(date2, chunks[middleI]);
|
||||
}
|
||||
|
||||
if (middleStr1 || middleStr2) {
|
||||
if (isRTL) {
|
||||
middleStr = middleStr2 + separator + middleStr1;
|
||||
}
|
||||
else {
|
||||
middleStr = middleStr1 + separator + middleStr2;
|
||||
}
|
||||
}
|
||||
|
||||
return leftStr + middleStr + rightStr;
|
||||
}
|
||||
|
||||
|
||||
var similarUnitMap = {
|
||||
Y: 'year',
|
||||
M: 'month',
|
||||
D: 'day', // day of month
|
||||
d: 'day' // day of week
|
||||
};
|
||||
// don't go any further than day, because we don't want to break apart times like "12:30:00"
|
||||
// TODO: week maybe?
|
||||
|
||||
|
||||
// Given a formatting chunk, and given that both dates are similar in the regard the
|
||||
// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`.
|
||||
function formatSimilarChunk(date1, date2, chunk) {
|
||||
var token;
|
||||
var unit;
|
||||
|
||||
if (typeof chunk === 'string') { // a literal string
|
||||
return chunk;
|
||||
}
|
||||
else if ((token = chunk.token)) {
|
||||
unit = similarUnitMap[token.charAt(0)];
|
||||
// are the dates the same for this unit of measurement?
|
||||
if (unit && date1.isSame(date2, unit)) {
|
||||
return momentFormat(date1, token); // would be the same if we used `date2`
|
||||
// BTW, don't support custom tokens
|
||||
}
|
||||
}
|
||||
|
||||
return false; // the chunk is NOT the same for the two dates
|
||||
// BTW, don't support splitting on non-zero areas
|
||||
}
|
||||
|
||||
|
||||
// Chunking Utils
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
var formatStringChunkCache = {};
|
||||
|
||||
|
||||
function getFormatStringChunks(formatStr) {
|
||||
if (formatStr in formatStringChunkCache) {
|
||||
return formatStringChunkCache[formatStr];
|
||||
}
|
||||
return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr));
|
||||
}
|
||||
|
||||
|
||||
// Break the formatting string into an array of chunks
|
||||
function chunkFormatString(formatStr) {
|
||||
var chunks = [];
|
||||
var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|((\w)\4*o?T?)|([^\w\[\(]+)/g; // TODO: more descrimination
|
||||
var match;
|
||||
|
||||
while ((match = chunker.exec(formatStr))) {
|
||||
if (match[1]) { // a literal string instead [ ... ]
|
||||
chunks.push(match[1]);
|
||||
}
|
||||
else if (match[2]) { // non-zero formatting inside ( ... )
|
||||
chunks.push({ maybe: chunkFormatString(match[2]) });
|
||||
}
|
||||
else if (match[3]) { // a formatting token
|
||||
chunks.push({ token: match[3] });
|
||||
}
|
||||
else if (match[5]) { // an unenclosed literal string
|
||||
chunks.push(match[5]);
|
||||
}
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
@@ -1,487 +0,0 @@
|
||||
|
||||
var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
||||
var ambigTimeRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))$/;
|
||||
var ambigZoneRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/;
|
||||
var momentCloneMethod = moment.fn.clone;
|
||||
var momentFormatMethod = moment.fn.format;
|
||||
var momentToISOStringMethod = moment.fn.toISOString;
|
||||
|
||||
|
||||
// diffs the two moments into a Duration where full-days are recorded first,
|
||||
// then the remaining time.
|
||||
function dayishDiff(d1, d0) {
|
||||
return moment.duration({
|
||||
days: d1.clone().stripTime().diff(d0.clone().stripTime(), 'days'),
|
||||
ms: d1.time() - d0.time()
|
||||
});
|
||||
}
|
||||
|
||||
function isNativeDate(input) {
|
||||
return Object.prototype.toString.call(input) === '[object Date]' ||
|
||||
input instanceof Date;
|
||||
}
|
||||
|
||||
|
||||
// MOMENT: creating
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// Creates a moment in the local timezone, similar to the vanilla moment(...) constructor,
|
||||
// but with extra features:
|
||||
// - ambiguous times
|
||||
// - enhanced formatting (TODO)
|
||||
fc.moment = function() {
|
||||
return buildMoment(arguments);
|
||||
};
|
||||
|
||||
// Sames as fc.moment, but creates a moment in the UTC timezone.
|
||||
fc.moment.utc = function() {
|
||||
return buildMoment(arguments, true);
|
||||
};
|
||||
|
||||
// Creates a moment and preserves the timezone offset of the ISO8601 string,
|
||||
// allowing for ambigous timezones. If the string is not an ISO8601 string,
|
||||
// the moment is processed in UTC-mode (a departure from moment's method).
|
||||
fc.moment.parseZone = function() {
|
||||
return buildMoment(arguments, true, true);
|
||||
};
|
||||
|
||||
// when parseZone==true, if can't figure it out, fall back to parseUTC
|
||||
function buildMoment(args, parseUTC, parseZone) {
|
||||
var isSingleArg = args.length == 1;
|
||||
var isSingleMoment = isSingleArg && moment.isMoment(args[0]);
|
||||
var isSingleString = isSingleArg && typeof args[0] === 'string';
|
||||
var isSingleArray = isSingleArg && $.isArray(args[0]);
|
||||
var isSingleNativeDate = isSingleArg && isNativeDate(args[0]);
|
||||
var isAmbigTime = isSingleString && ambigTimeRegex.test(args[0]);
|
||||
var isAmbigZone = isAmbigTime || isSingleArray || isSingleString && ambigZoneRegex.test(args[0]);
|
||||
var mom;
|
||||
|
||||
if (parseUTC || parseZone || isAmbigTime) {
|
||||
mom = moment.utc.apply(moment, args);
|
||||
}
|
||||
else {
|
||||
mom = moment.apply(null, args);
|
||||
}
|
||||
|
||||
// if we are essentially cloning a moment, transfer over existing _ambig properties
|
||||
if (isSingleMoment) {
|
||||
transferAmbigs(args[0], mom);
|
||||
}
|
||||
|
||||
if (isAmbigTime) {
|
||||
mom._ambigTime = true;
|
||||
mom._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
|
||||
}
|
||||
|
||||
if (parseZone) {
|
||||
if (isAmbigZone) {
|
||||
mom._ambigZone = true;
|
||||
}
|
||||
else if (isSingleString) {
|
||||
mom.zone(args[0]); // if fails, will set it to 0, which it already was
|
||||
}
|
||||
else if (isSingleNativeDate || args[0] === undefined) {
|
||||
// native Date object?
|
||||
// specified with no arguments?
|
||||
// then consider the moment to be local
|
||||
mom.local();
|
||||
}
|
||||
}
|
||||
|
||||
mom._fc = true; // flag for use other extended functionality (only formatting at this point)
|
||||
|
||||
return mom;
|
||||
}
|
||||
|
||||
// we need to patch moment's clone method because it will not copy our _ambig properties
|
||||
moment.fn.clone = function() {
|
||||
var res = momentCloneMethod.apply(this, arguments);
|
||||
transferAmbigs(this, res);
|
||||
return res;
|
||||
};
|
||||
|
||||
// transfers our internal _ambig properties from one moment to another, but only if true
|
||||
function transferAmbigs(srcMoment, destMoment) {
|
||||
if (srcMoment._ambigTime) {
|
||||
destMoment._ambigTime = true;
|
||||
}
|
||||
if (srcMoment._ambigZone) {
|
||||
destMoment._ambigZone = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MOMENT: time-of-day
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// GETTER
|
||||
// Returns a Duration with the hours/minutes/seconds/ms values of the moment.
|
||||
// If the moment has an ambiguous time, a duration of 00:00 will be returned.
|
||||
//
|
||||
// SETTER
|
||||
// You can supply a Duration, a Moment, or a Duration-like argument.
|
||||
// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous.
|
||||
moment.fn.time = function(time) {
|
||||
if (time === undefined) { // getter
|
||||
return moment.duration({
|
||||
hours: this.hours(),
|
||||
minutes: this.minutes(),
|
||||
seconds: this.seconds(),
|
||||
milliseconds: this.milliseconds()
|
||||
});
|
||||
}
|
||||
else { // setter
|
||||
|
||||
delete this._ambigTime; // mark that the moment now has a time
|
||||
|
||||
if (!moment.isDuration(time) && !moment.isMoment(time)) {
|
||||
time = moment.duration(time);
|
||||
}
|
||||
|
||||
return this.hours(time.hours() + time.days() * 24) // day value will cause overflow (so 24 hours becomes 00:00:00 of next day)
|
||||
.minutes(time.minutes())
|
||||
.seconds(time.seconds())
|
||||
.milliseconds(time.milliseconds());
|
||||
}
|
||||
};
|
||||
|
||||
// Converts the moment to UTC, stripping out its time-of-day and timezone offset,
|
||||
// but preserving its YMD. A moment with a stripped time will display no time
|
||||
// nor timezone offset when .format() is called.
|
||||
moment.fn.stripTime = function() {
|
||||
var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
|
||||
|
||||
this._ambigTime = true;
|
||||
this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
|
||||
|
||||
return this.utc()
|
||||
.year(a[0])
|
||||
.month(a[1])
|
||||
.date(a[2])
|
||||
.hours(0)
|
||||
.minutes(0)
|
||||
.seconds(0)
|
||||
.milliseconds(0);
|
||||
};
|
||||
|
||||
// Returns if the moment has a non-ambiguous time (boolean)
|
||||
moment.fn.hasTime = function() {
|
||||
return !this._ambigTime;
|
||||
};
|
||||
|
||||
|
||||
// MOMENT: timezone offset
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// Converts the moment to UTC, stripping out its timezone offset, but preserving its
|
||||
// YMD and time-of-day. A moment with a stripped timezone offset will display no
|
||||
// timezone offset when .format() is called.
|
||||
moment.fn.stripZone = function() {
|
||||
var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
|
||||
|
||||
this._ambigZone = true;
|
||||
|
||||
return this.utc()
|
||||
.year(a[0])
|
||||
.month(a[1])
|
||||
.date(a[2])
|
||||
.hours(a[3])
|
||||
.minutes(a[4])
|
||||
.seconds(a[5])
|
||||
.milliseconds(a[6]);
|
||||
};
|
||||
|
||||
// Returns of the moment has a non-ambiguous timezone offset (boolean)
|
||||
moment.fn.hasZone = function() {
|
||||
return !this._ambigZone;
|
||||
};
|
||||
|
||||
|
||||
// MOMENT: formatting mods
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
moment.fn.format = function() {
|
||||
if (!arguments[0]) {
|
||||
if (this._ambigTime) {
|
||||
return momentFormat(this, 'YYYY-MM-DD');
|
||||
}
|
||||
if (this._ambigZone) {
|
||||
return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
|
||||
}
|
||||
}
|
||||
if (this._fc) {
|
||||
return formatDate(this, arguments[0]); // our extended formatting
|
||||
}
|
||||
else {
|
||||
return momentFormatMethod.apply(this, arguments); // pass along all arguments
|
||||
}
|
||||
};
|
||||
|
||||
moment.fn.toISOString = function() {
|
||||
if (this._ambigTime) {
|
||||
return momentFormat(this, 'YYYY-MM-DD');
|
||||
}
|
||||
if (this._ambigZone) {
|
||||
return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
|
||||
}
|
||||
return momentToISOStringMethod.apply(this, arguments); // pass along all arguments
|
||||
};
|
||||
|
||||
// call this if you want Moment's original format method to be used
|
||||
function momentFormat(moment, formatStr) {
|
||||
return momentFormatMethod.call(moment, formatStr);
|
||||
}
|
||||
|
||||
|
||||
// MOMENT: misc utils
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// Is the moment within the specified range? `end` is exclusive.
|
||||
// TODO: rename for collision reasons?
|
||||
moment.fn.isWithin = function(start, end) {
|
||||
return this >= moment(start) && this < moment(end);
|
||||
};
|
||||
|
||||
$.each([
|
||||
'isBefore',
|
||||
'isAfter',
|
||||
//'isSame', // nevermind. moment handles normalization to UTC
|
||||
'isWithin'
|
||||
], function(i, methodName) {
|
||||
var origMethod = moment.fn[methodName];
|
||||
var momentCount = methodName == 'isWithin' ? 2 : 1;
|
||||
|
||||
moment.fn[methodName] = function() {
|
||||
var newThis;
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var i;
|
||||
|
||||
if (this._ambigZone) {
|
||||
for (i=0; i<momentCount; i++) {
|
||||
if (typeof args[i] === 'string') {
|
||||
args[i] = fc.moment.parseZone(args[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i<momentCount; i++) {
|
||||
if (moment.isMoment(args[i]) && args[i]._ambigZone !== this._ambigZone) {
|
||||
newThis = newThis || this.clone().stripZone();
|
||||
args[i] = args[i].clone().stripZone();
|
||||
}
|
||||
}
|
||||
|
||||
return origMethod.apply(newThis || this, args);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// Single Date Formatting
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Formats `date` with a Moment formatting string, but allow our non-zero areas and
|
||||
// additional token.
|
||||
function formatDate(date, formatStr) {
|
||||
return formatDateWithChunks(date, getFormatStringChunks(formatStr));
|
||||
}
|
||||
|
||||
|
||||
function formatDateWithChunks(date, chunks) {
|
||||
var s = '';
|
||||
var i;
|
||||
|
||||
for (i=0; i<chunks.length; i++) {
|
||||
s += formatDateWithChunk(date, chunks[i]);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
// addition formatting tokens we want recognized
|
||||
var tokenOverrides = {
|
||||
t: function(date) { // "a" or "p"
|
||||
return momentFormat(date, 'a').charAt(0);
|
||||
},
|
||||
T: function(date) { // "A" or "P"
|
||||
return momentFormat(date, 'A').charAt(0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function formatDateWithChunk(date, chunk) {
|
||||
var token;
|
||||
var maybeStr;
|
||||
|
||||
if (typeof chunk === 'string') { // a literal string
|
||||
return chunk;
|
||||
}
|
||||
else if ((token = chunk.token)) { // a token, like "YYYY"
|
||||
if (tokenOverrides[token]) {
|
||||
return tokenOverrides[token](date); // use our custom token
|
||||
}
|
||||
return momentFormat(date, token);
|
||||
}
|
||||
else if (chunk.maybe) { // a grouping of other chunks that must be non-zero
|
||||
maybeStr = formatDateWithChunks(date, chunk.maybe);
|
||||
if (maybeStr.match(/[1-9]/)) {
|
||||
return maybeStr;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
// Date Range Formatting
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
// TODO: make it work with timezone offset
|
||||
|
||||
// Using a formatting string meant for a single date, generate a range string, like
|
||||
// "Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ.
|
||||
// If the dates are the same as far as the format string is concerned, just return a single
|
||||
// rendering of one date, without any separator.
|
||||
function formatRange(date1, date2, formatStr, separator, isRTL) {
|
||||
|
||||
// Expand localized format strings, like "LL" -> "MMMM D YYYY"
|
||||
formatStr = date1.lang().longDateFormat(formatStr) || formatStr;
|
||||
// BTW, this is not important for `formatDate` because it is impossible to put custom tokens
|
||||
// or non-zero areas in Moment's localized format strings.
|
||||
|
||||
separator = separator || ' - ';
|
||||
|
||||
return formatRangeWithChunks(
|
||||
date1,
|
||||
date2,
|
||||
getFormatStringChunks(formatStr),
|
||||
separator,
|
||||
isRTL
|
||||
);
|
||||
}
|
||||
fc.formatRange = formatRange; // expose
|
||||
|
||||
|
||||
function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
|
||||
var chunkStr; // the rendering of the chunk
|
||||
var leftI;
|
||||
var leftStr = '';
|
||||
var rightI;
|
||||
var rightStr = '';
|
||||
var middleI;
|
||||
var middleStr1 = '';
|
||||
var middleStr2 = '';
|
||||
var middleStr = '';
|
||||
|
||||
// Start at the leftmost side of the formatting string and continue until you hit a token
|
||||
// that is not the same between dates.
|
||||
for (leftI=0; leftI<chunks.length; leftI++) {
|
||||
chunkStr = formatSimilarChunk(date1, date2, chunks[leftI]);
|
||||
if (chunkStr === false) {
|
||||
break;
|
||||
}
|
||||
leftStr += chunkStr;
|
||||
}
|
||||
|
||||
// Similarly, start at the rightmost side of the formatting string and move left
|
||||
for (rightI=chunks.length-1; rightI>leftI; rightI--) {
|
||||
chunkStr = formatSimilarChunk(date1, date2, chunks[rightI]);
|
||||
if (chunkStr === false) {
|
||||
break;
|
||||
}
|
||||
rightStr = chunkStr + rightStr;
|
||||
}
|
||||
|
||||
// The area in the middle is different for both of the dates.
|
||||
// Collect them distinctly so we can jam them together later.
|
||||
for (middleI=leftI; middleI<=rightI; middleI++) {
|
||||
middleStr1 += formatDateWithChunk(date1, chunks[middleI]);
|
||||
middleStr2 += formatDateWithChunk(date2, chunks[middleI]);
|
||||
}
|
||||
|
||||
if (middleStr1 || middleStr2) {
|
||||
if (isRTL) {
|
||||
middleStr = middleStr2 + separator + middleStr1;
|
||||
}
|
||||
else {
|
||||
middleStr = middleStr1 + separator + middleStr2;
|
||||
}
|
||||
}
|
||||
|
||||
return leftStr + middleStr + rightStr;
|
||||
}
|
||||
|
||||
|
||||
var similarUnitMap = {
|
||||
Y: 'year',
|
||||
M: 'month',
|
||||
D: 'day', // day of month
|
||||
d: 'day' // day of week
|
||||
};
|
||||
// don't go any further than day, because we don't want to break apart times like "12:30:00"
|
||||
// TODO: week maybe?
|
||||
|
||||
|
||||
// Given a formatting chunk, and given that both dates are similar in the regard the
|
||||
// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`.
|
||||
function formatSimilarChunk(date1, date2, chunk) {
|
||||
var token;
|
||||
var unit;
|
||||
|
||||
if (typeof chunk === 'string') { // a literal string
|
||||
return chunk;
|
||||
}
|
||||
else if ((token = chunk.token)) {
|
||||
unit = similarUnitMap[token.charAt(0)];
|
||||
// are the dates the same for this unit of measurement?
|
||||
if (unit && date1.isSame(date2, unit)) {
|
||||
return momentFormat(date1, token); // would be the same if we used `date2`
|
||||
// BTW, don't support custom tokens
|
||||
}
|
||||
}
|
||||
|
||||
return false; // the chunk is NOT the same for the two dates
|
||||
// BTW, don't support splitting on non-zero areas
|
||||
}
|
||||
|
||||
|
||||
// Chunking Utils
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
var formatStringChunkCache = {};
|
||||
|
||||
|
||||
function getFormatStringChunks(formatStr) {
|
||||
if (formatStr in formatStringChunkCache) {
|
||||
return formatStringChunkCache[formatStr];
|
||||
}
|
||||
return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr));
|
||||
}
|
||||
|
||||
|
||||
// Break the formatting string into an array of chunks
|
||||
function chunkFormatString(formatStr) {
|
||||
var chunks = [];
|
||||
var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|((\w)\4*o?T?)|([^\w\[\(]+)/g; // TODO: more descrimination
|
||||
var match;
|
||||
|
||||
while ((match = chunker.exec(formatStr))) {
|
||||
if (match[1]) { // a literal string instead [ ... ]
|
||||
chunks.push(match[1]);
|
||||
}
|
||||
else if (match[2]) { // non-zero formatting inside ( ... )
|
||||
chunks.push({ maybe: chunkFormatString(match[2]) });
|
||||
}
|
||||
else if (match[3]) { // a formatting token
|
||||
chunks.push({ token: match[3] });
|
||||
}
|
||||
else if (match[5]) { // an unenclosed literal string
|
||||
chunks.push(match[5]);
|
||||
}
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
|
||||
var ambigTimeRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))$/;
|
||||
var ambigZoneRegex = /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/;
|
||||
|
||||
|
||||
// MOMENT: creating
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// Creates a moment in the local timezone, similar to the vanilla moment(...) constructor,
|
||||
// but with extra features:
|
||||
// - ambiguous times
|
||||
// - enhanced formatting (TODO)
|
||||
fc.moment = function() {
|
||||
return makeMoment(arguments);
|
||||
};
|
||||
|
||||
// Sames as fc.moment, but creates a moment in the UTC timezone.
|
||||
fc.moment.utc = function() {
|
||||
return makeMoment(arguments, true);
|
||||
};
|
||||
|
||||
// Creates a moment and preserves the timezone offset of the ISO8601 string,
|
||||
// allowing for ambigous timezones. If the string is not an ISO8601 string,
|
||||
// the moment is processed in UTC-mode (a departure from moment's method).
|
||||
fc.moment.parseZone = function() {
|
||||
return makeMoment(arguments, true, true);
|
||||
};
|
||||
|
||||
function FCMoment(config) {
|
||||
extend(this, config);
|
||||
}
|
||||
|
||||
FCMoment.prototype = createObject(moment.fn);
|
||||
|
||||
FCMoment.prototype.clone = function() {
|
||||
return makeMoment([ this ]);
|
||||
};
|
||||
|
||||
// when parseZone==true, if can't figure it out, fall back to parseUTC
|
||||
function makeMoment(args, parseUTC, parseZone) {
|
||||
var isSingleArg = args.length == 1;
|
||||
var isSingleMoment = isSingleArg && moment.isMoment(args[0]);
|
||||
var isSingleString = isSingleArg && typeof args[0] === 'string';
|
||||
var isSingleArray = isSingleArg && $.isArray(args[0]);
|
||||
var isSingleNativeDate = isSingleArg && isNativeDate(args[0]);
|
||||
var isAmbigTime = isSingleString && ambigTimeRegex.test(args[0]);
|
||||
var isAmbigZone = isAmbigTime || isSingleArray || isSingleString && ambigZoneRegex.test(args[0]);
|
||||
var mom;
|
||||
|
||||
if (parseUTC || parseZone || isAmbigTime) {
|
||||
mom = moment.utc.apply(moment, args);
|
||||
}
|
||||
else {
|
||||
mom = moment.apply(null, args);
|
||||
}
|
||||
|
||||
if (isSingleMoment) {
|
||||
transferAmbigs(args[0], mom);
|
||||
}
|
||||
|
||||
if (isAmbigTime) {
|
||||
mom._ambigTime = true;
|
||||
mom._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
|
||||
}
|
||||
|
||||
if (parseZone) {
|
||||
if (isAmbigZone) {
|
||||
mom._ambigZone = true;
|
||||
}
|
||||
else if (isSingleString) {
|
||||
mom.zone(args[0]); // if fails, will set it to 0, which it already was
|
||||
}
|
||||
else if (isSingleNativeDate || args[0] === undefined) {
|
||||
// native Date object?
|
||||
// specified with no arguments?
|
||||
// then consider the moment to be local
|
||||
mom.local();
|
||||
}
|
||||
}
|
||||
|
||||
return new FCMoment(mom);
|
||||
}
|
||||
|
||||
// transfers our internal _ambig properties from one moment to another
|
||||
function transferAmbigs(src, dest) {
|
||||
if (src._ambigTime) {
|
||||
dest._ambigTime = true;
|
||||
}
|
||||
else if (dest._ambigTime) {
|
||||
delete dest._ambigTime;
|
||||
}
|
||||
|
||||
if (src._ambigZone) {
|
||||
dest._ambigZone = true;
|
||||
}
|
||||
else if (dest._ambigZone) {
|
||||
delete dest._ambigZone;
|
||||
}
|
||||
}
|
||||
|
||||
function makeMomentAs(input, model) {
|
||||
var output = fc.moment(input);
|
||||
if (model._ambigTime) {
|
||||
output.stripTime();
|
||||
}
|
||||
else if (model._ambigZone) {
|
||||
output.stripZone();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MOMENT: time-of-day
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// GETTER
|
||||
// Returns a Duration with the hours/minutes/seconds/ms values of the moment.
|
||||
// If the moment has an ambiguous time, a duration of 00:00 will be returned.
|
||||
//
|
||||
// SETTER
|
||||
// You can supply a Duration, a Moment, or a Duration-like argument.
|
||||
// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous.
|
||||
FCMoment.prototype.time = function(time) {
|
||||
if (time === undefined) { // getter
|
||||
return moment.duration({
|
||||
hours: this.hours(),
|
||||
minutes: this.minutes(),
|
||||
seconds: this.seconds(),
|
||||
milliseconds: this.milliseconds()
|
||||
});
|
||||
}
|
||||
else { // setter
|
||||
|
||||
delete this._ambigTime; // mark that the moment now has a time
|
||||
|
||||
if (!moment.isDuration(time) && !moment.isMoment(time)) {
|
||||
time = moment.duration(time);
|
||||
}
|
||||
|
||||
return this.hours(time.hours() + time.days() * 24) // day value will cause overflow (so 24 hours becomes 00:00:00 of next day)
|
||||
.minutes(time.minutes())
|
||||
.seconds(time.seconds())
|
||||
.milliseconds(time.milliseconds());
|
||||
}
|
||||
};
|
||||
|
||||
// Converts the moment to UTC, stripping out its time-of-day and timezone offset,
|
||||
// but preserving its YMD. A moment with a stripped time will display no time
|
||||
// nor timezone offset when .format() is called.
|
||||
FCMoment.prototype.stripTime = function() {
|
||||
var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
|
||||
|
||||
this._ambigTime = true;
|
||||
this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
|
||||
|
||||
return this.utc()
|
||||
.year(a[0])
|
||||
.month(a[1])
|
||||
.date(a[2])
|
||||
.hours(0)
|
||||
.minutes(0)
|
||||
.seconds(0)
|
||||
.milliseconds(0);
|
||||
};
|
||||
|
||||
// Returns if the moment has a non-ambiguous time (boolean)
|
||||
FCMoment.prototype.hasTime = function() {
|
||||
return !this._ambigTime;
|
||||
};
|
||||
|
||||
|
||||
// MOMENT: timezone offset
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// Converts the moment to UTC, stripping out its timezone offset, but preserving its
|
||||
// YMD and time-of-day. A moment with a stripped timezone offset will display no
|
||||
// timezone offset when .format() is called.
|
||||
FCMoment.prototype.stripZone = function() {
|
||||
var a = this.toArray(); // year,month,date,hours,minutes,seconds as an array
|
||||
|
||||
this._ambigZone = true;
|
||||
|
||||
return this.utc()
|
||||
.year(a[0])
|
||||
.month(a[1])
|
||||
.date(a[2])
|
||||
.hours(a[3])
|
||||
.minutes(a[4])
|
||||
.seconds(a[5])
|
||||
.milliseconds(a[6]);
|
||||
};
|
||||
|
||||
// Returns of the moment has a non-ambiguous timezone offset (boolean)
|
||||
FCMoment.prototype.hasZone = function() {
|
||||
return !this._ambigZone;
|
||||
};
|
||||
|
||||
FCMoment.prototype.zone = function(tzo) {
|
||||
if (tzo != undefined) {
|
||||
this._ambigZone = false;
|
||||
}
|
||||
return moment.fn.zone.apply(this, arguments);
|
||||
};
|
||||
|
||||
FCMoment.prototype.local = function() {
|
||||
this._ambigZone = false;
|
||||
return moment.fn.local.apply(this, arguments);
|
||||
};
|
||||
|
||||
FCMoment.prototype.utc = function() {
|
||||
this._ambigZone = false;
|
||||
return moment.fn.utc.apply(this, arguments);
|
||||
};
|
||||
|
||||
|
||||
// MOMENT: formatting mods
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
FCMoment.prototype.format = function() {
|
||||
if (arguments[0]) {
|
||||
return formatDate(this, arguments[0]); // our extended formatting
|
||||
}
|
||||
if (this._ambigTime) {
|
||||
return momentFormat(this, 'YYYY-MM-DD');
|
||||
}
|
||||
if (this._ambigZone) {
|
||||
return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
|
||||
}
|
||||
return momentFormat(this); // default moment original formatting
|
||||
};
|
||||
|
||||
FCMoment.prototype.toISOString = function() {
|
||||
if (this._ambigTime) {
|
||||
return momentFormat(this, 'YYYY-MM-DD');
|
||||
}
|
||||
if (this._ambigZone) {
|
||||
return momentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
|
||||
}
|
||||
return moment.fn.toISOString.apply(this, arguments);
|
||||
};
|
||||
|
||||
|
||||
// MOMENT: misc utils
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// Is the moment within the specified range? `end` is exclusive.
|
||||
FCMoment.prototype.isWithin = function(start, end) {
|
||||
return this >= makeMomentAs(start, this) && this < makeMomentAs(end, this);
|
||||
};
|
||||
|
||||
$.each([
|
||||
'isBefore',
|
||||
'isAfter'
|
||||
], function(i, methodName) {
|
||||
FCMoment.prototype[methodName] = function(input, units) {
|
||||
moment.fn[methodName].call(
|
||||
this,
|
||||
makeMomentAs(input, this),
|
||||
units
|
||||
);
|
||||
};
|
||||
});
|
||||
+31
@@ -9,6 +9,37 @@ function createObject(proto) { // like Object.create
|
||||
return new f();
|
||||
}
|
||||
|
||||
function extend(a, b) {
|
||||
for (var i in b) {
|
||||
if (b.hasOwnProperty(i)) {
|
||||
a[i] = b[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Date
|
||||
-----------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
||||
|
||||
|
||||
// diffs the two moments into a Duration where full-days are recorded first,
|
||||
// then the remaining time.
|
||||
function dayishDiff(d1, d0) {
|
||||
return moment.duration({
|
||||
days: d1.clone().stripTime().diff(d0.clone().stripTime(), 'days'),
|
||||
ms: d1.time() - d0.time()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function isNativeDate(input) {
|
||||
return Object.prototype.toString.call(input) === '[object Date]' ||
|
||||
input instanceof Date;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
<script src='../lib/moment/moment.js'></script>
|
||||
<script src='../lib/moment/min/langs.min.js'></script>
|
||||
<script>fc = {}</script>
|
||||
<script src='../src/date_util.js'></script>
|
||||
<script src='../src/util.js'></script>
|
||||
<script src='../src/moment-ext.js'></script>
|
||||
<script src='../src/date-formatting.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user