/**
* Copyright Franson Technology AB, Sweden, 2009
* http://gpsgate.com
*
* author Fredrik Blomqvist
*
* Date & Time helper methods
* @module DateTime
*
*/
var Franson = Franson || {};
/**
* namespace
* @class Franson.DateTime
* @static
*/
Franson.DateTime = Franson.DateTime || {};
/**
* @method get24hTime
* @param {Date} [date=now]
* @return {integer} number of milliseconds passed since 00:00 (current 24h) for the current date
*/
Franson.DateTime.get24hTime = function(date)
{
date = date || new Date();
return (date.getHours()*3600 + date.getMinutes()*60 + date.getSeconds())*1000 + date.getMilliseconds();
};
/**
* comparator for the 24h part of the clock
* @method compare24hTime
* @param {Date} a
* @param {Date} b
* @return {integer} [-1, 0, +1]
*/
Franson.DateTime.compare24hTime = function(a, b)
{
// experiments for best speed.. (used on large data-volumes in the tablecontrol for example)
// return MochiKit.Base.compare(Franson.DateTime.get24hTime(a), Franson.DateTime.get24hTime(b)); // slower? todo: profile
// return Franson.Util.lexicoCmp(
// [Franson.DateTime.get24hTime(a)],
// [Franson.DateTime.get24hTime(b)]
// );
// return MochiKit.Base.compare(
// return Franson.Util.lexicoCmp(
// [a.getHours(), a.getMinutes(), a.getSeconds()],
// [b.getHours(), b.getMinutes(), b.getSeconds()]
// );
// should be faster
var aH = a.getHours();
var bH = b.getHours();
if (aH < bH)
return -1;
else
if (aH > bH)
return 1;
else
{
var aM = a.getMinutes();
var bM = b.getMinutes();
if (aM < bM)
return -1;
else
if (aM > bM)
return 1;
else
{
var aS = a.getSeconds();
var bS = b.getSeconds();
if (aS < bS)
return -1;
else
if (aS > bS)
return 1;
}
}
return 0;
// test using the new lazy-cmp
// return Franson.Util.lazyLexicoCmp(
// [ method(a, a.getHours), method(a, a.getMinutes), method(a, a.getSeconds) ],
// [ method(b, b.getHours), method(b, b.getMinutes), method(b, b.getSeconds) ]
// );
};
/**
* @method getDaysInMonth
* @param {integer} year
* @param {integer} month
* @return {integer}
*/
Franson.DateTime.getDaysInMonth = function(year, month)
{
return 32 - new Date(year, month, 32).getDate();
};
/**
* @method getFirstDayofMonth
* @param {integer} year
* @param {integer} month
* @return {integer}
*/
Franson.DateTime.getFirstDayofMonth = function(year, month)
{
return new Date(year, month, 0).getDay();
};
/**
* example: <code>var isToday = isSameDay(new Date(), date);</code>
* @method isSameDay
* @param {Date} dateA
* @param {Date} dateB
* @return {boolean}
*/
Franson.DateTime.isSameDay = function(dateA, dateB)
{
return (
dateA.getDate() == dateB.getDate() &&
dateA.getMonth() == dateB.getMonth() &&
dateA.getFullYear() == dateB.getFullYear()
);
};
// isSameWeek, isSameMonth ?
/**
* Guesses the century for two-digit years. <br />
* according to the X/Open CAE specifications. 0->68 20th century, 69->99 21th century
* todo: hmm, MySQL uses 1969 and others 1970, MS Excel 1929, MSSql Server 1949, OLE 1930, 38 etc..
* @method guessCentury
* @param {integer} year
* @param {integer} [cutoffYear=(19)68]
* @return {integer} four-digit year
*/
Franson.DateTime.guessCentury = function(year, cutoffYear)
{
// todo: return error if < 0 etc?
cutoffYear = Franson.Util.valueOrDefault(cutoffYear, 68);
if (0 <= year && year <= cutoffYear)
return 2000 + year;
if (cutoffYear < year && year <= 99)
return 1900 + year;
return year;
};
// todo: getWeekNumber
/**
* @method isValidDate
* @param {integer} year uses <a href="#method_guessCentury">guessCentury()</a> if only a two digit number is given.
* @param {integer} month note: validated as 1-based
* @param {integer} [day=1]
* @param {integer} [hours=0]
* @param {integer} [minutes=0]
* @param {integer} [seconds=0]
* @return {boolean}
*/
Franson.DateTime.isValidDate = function(year, month, day, hours, minutes, seconds)
{
hours = hours || 0;
minutes = minutes || 0;
seconds = seconds || 0;
day = Franson.Util.valueOrDefault(day, 1); // can't use || here, since 0 || 1 == 1 ..
year = Franson.DateTime.guessCentury(year);
month -= 1; // JS uses 0-based _month_ index(!)
var date = new Date(year, month, day, hours, minutes, seconds);
return (
date.getFullYear() == year &&
date.getMonth() == month &&
date.getDate() == day &&
date.getHours() == hours &&
date.getMinutes() == minutes &&
date.getSeconds() == seconds
);
};
/**
* parses 24h time in either <tt>"hh:mm"</tt> or <tt>"hh:mm:ss"</tt> format
* todo: support AM/PM format
* @method fuzzyTimeParse
* @param {string} strTime
* @return {integer[3]} [hh, mm, ss] or null if format error
*/
Franson.DateTime.fuzzyTimeParse = function(strTime)
{
// var re = /\s{0,}(\d{1,2}):(\d{2})\s{0,}/; // ok?
var hms = strTime.split(':');
if (hms.length < 2 || hms.length > 3) // need atleast a "hh:mm" format
return null;
for (var i = 0; i < hms.length; ++i)
{
// or are these checks too strict?
if (!Franson.Util.isDecimalNumber(hms[i]))
return null;
if (i === 0 && (hms[i].length < 1 || hms[i].length > 3)) // allow hour to be only one digit (not ISO)
return null;
if (i > 0 && hms[i].length != 2) // min & sec must always be two digits
return null;
}
var h = Franson.Util.valueOrDefault(parseInt(hms[0], 10), -1);
var m = Franson.Util.valueOrDefault(parseInt(hms[1], 10), -1);
var s = Franson.Util.valueOrDefault(parseInt(hms[2], 10), 0); // allow seconds to be skipped
// todo: hmm, allow 24:00? (make optional?)
if (!(0 <= h && h < 24))
return null;
if (!(0 <= m && m < 60))
return null;
if (!(0 <= s && s < 60))
return null;
return [h, m, s];
};
/**
* parses <tt>"YYYY-MM-DD"</tt>, <tt>"YY-MM-DD"</tt> or <tt>"MM-DD"</tt> format. <tt>"MM-DD"</tt> assumes current year.
* note: doesn't validate the actual date based on days in month or leapyears etc (add as optional flag?)
* todo: settings param?
* todo: allow '/' and '\' also?
* @method fuzzyDateParse
* @param {string} strDate
* @return {integer[3]} <code>[yyyy, mm, dd], null</code> if format error. note:! month is 1..12 Not 0..11 as JS Date uses!
*/
Franson.DateTime.fuzzyDateParse = function(strDate)
{
var ymd = strDate.split('-');
if (!(2 <= ymd.length && ymd.length <= 3)) // need atleast a "MM-DD" format
return null;
// needed, since parseInt apparently allows "7x" to be interpreted as 7..
for (var i = 0; i < ymd.length; ++i)
{
if (!Franson.Util.isDecimalNumber(ymd[i]))
return null;
}
var i = ymd.length == 3 ? 0 : -1; // used to skip year in the "MM-DD" case
var now = new Date();
var y = Franson.Util.valueOrDefault(parseInt(ymd[0 + i], 10), now.getFullYear()); // if no year set we assume current year
var m = Franson.Util.valueOrDefault(parseInt(ymd[1 + i], 10), -1);
var d = Franson.Util.valueOrDefault(parseInt(ymd[2 + i], 10), -1);
// todo: better date-validation (leap years etc) (use isValidDate?)
if (!(0 <= y))
return null;
y = Franson.DateTime.guessCentury(y);
// todo: hmm, either skip this or add a thorough check?
if (!(1 <= m && m <= 12))
return null;
if (!(1 <= d && d <= 31))
return null;
return [y, m, d]; // hmm, or change to { 'year': y, 'month': m, 'day': d } instead?
};
/**
* parses human-friendly datetime strings.<br />
* parses same formats as <a href="#method_fuzzyTimeParse">fuzzyTimeParse()</a> and <a href="#method_fuzzyDateParse">fuzzyDateParse()</a> but in same
* string and in any order.
* todo: add optional setting params.
* todo: allow special cased "today", "tomorrow", "+10 min" etc input? :) (cron style?)
* @method fuzzyDateTimeParse
* @param {string} dateTime
* @return {Date}
*/
Franson.DateTime.fuzzyDateTimeParse = function(dateTime)
{
var dateTimeSplit = Franson.String.splitOnWhitespace(dateTime);
if (dateTimeSplit.length == 2)
{
// we assume "date time" or "time date" input
var tmpDate0 = Franson.DateTime.fuzzyDateParse(dateTimeSplit[0]);
var tmpTime0 = Franson.DateTime.fuzzyTimeParse(dateTimeSplit[1]);
var tmpDate1 = Franson.DateTime.fuzzyDateParse(dateTimeSplit[1]);
var tmpTime1 = Franson.DateTime.fuzzyTimeParse(dateTimeSplit[0]);
var dateBeforeTime = tmpDate0 !== null && tmpTime0 !== null;
var timeBeforeDate = tmpDate1 !== null && tmpTime1 !== null;
// XOR logic
var date = null;
var time = null;
if (dateBeforeTime && !timeBeforeDate)
{
date = tmpDate0;
time = tmpTime0;
}
else
if (timeBeforeDate && !dateBeforeTime)
{
date = tmpDate1;
time = tmpTime1;
}
else
{
return null;
}
return new Date(
date[0], date[1] - 1, date[2], // JS month is 0-based!
time[0], time[1], time[2]
);
}
else
if (dateTimeSplit.length == 1)
{
// we assume "date" or "time" input
var tmpDate = Franson.DateTime.fuzzyDateParse(dateTimeSplit[0]);
var tmpTime = Franson.DateTime.fuzzyTimeParse(dateTimeSplit[0]);
if (tmpDate !== null && tmpTime === null)
{
return new Date(tmpDate[0], tmpDate[1] - 1, tmpDate[2]); // time = 00:00:00 (JS month is 0-based!)
}
else
if (tmpTime !== null && tmpDate === null)
{
var now = new Date();
return new Date(
now.getFullYear(), now.getMonth(), now.getDate(),
tmpTime[0], tmpTime[1], tmpTime[2]
);
}
// else error..
}
else
{
// error..
}
return null;
};
/**
* note: hours is not bound to 24h (since we're not extracting months, days etc)
* @method getHourMinSecDiff
* @param {Date} dateA
* @param {Date} dateB
* @return {integer[3]} <code>[hour, min, sec]</code> absolute difference.
*/
Franson.DateTime.getHourMinSecDiff = function(dateA, dateB)
{
var diff = Math.abs(dateA.getTime() - dateB.getTime()); // todo: trim away everything above hours?
var dt = diff / 1000; // ms -> s
var dh = Math.floor(dt / 3600); // s -> h
dt -= dh * 3600;
var dm = Math.floor(dt / 60); // h -> m
dt -= dm * 60;
var ds = Math.round(dt); // remainder is seconds
return [dh, dm, ds];
};
/**
* todo: support stopping (rounding) on for example hours or minutes (i.e no ms..)
* todo: this is currently not that general, more or less tailored for VT fatpoint display.. (only shortformat is "ok")
* @see Franson.DateTime.getDifference / getHourMinSecDiff
* @method formatDuration
* @param {integer[7]} duration (<code>[y, m, d .. ]</code>) output from getDifference
* @param {boolean} [shortFormat=false]
* @return {string}
* @private (for now)
*/
Franson.DateTime.formatDuration = function(duration, shortFormat) // fuzzyFormatDuration?
{
shortFormat = shortFormat || false;
function plural(str, v)
{
if (shortFormat)
return str;
return str + (v > 1 ? 's' : ''); // todo: should use DURATION_*_PLURAL localization keys
}
// todo: cache this
var fields = shortFormat ?
[
localize('DURATION_HOUR_YEAR_SHORT'),
localize('DURATION_HOUR_MONTH_SHORT'),
localize('DURATION_HOUR_DAY_SHORT'),
localize('DURATION_HOUR_SHORT'),
localize('DURATION_MINUTE_SHORT'),
localize('DURATION_SECOND_SHORT'),
localize('DURATION_MILLISECOND_SHORT')
] : [
localize('DURATION_YEAR'),
localize('DURATION_MONTH'),
localize('DURATION_DAY'),
localize('DURATION_HOUR'),
localize('DURATION_MINUTE'),
localize('DURATION_SECOND'),
localize('DURATION_MILLISECOND')
];
var parts = [];
for (var i = 0; i < fields.length; ++i)
{
if (Math.abs(duration[i]) > 0)
{
parts.push(duration[i] + ' ' + fields[i] /*plural(fields[i], duration[i])*/ );
}
}
return parts.join(' ');
};
// todo: format Date & Clock YY/MM/DD, dd/mm/yy, 24h, am/pm etc