GpsGate Server JavaScript API

Util  1.0.0

GpsGate Server JavaScript API > Util > datetime.js (source view)
Search:
 
Filters
/**
 * 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

Copyright © 2009 Franson Technology AB, Sweden. All rights reserved.