Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
240 lines
8.2 KiB
JavaScript
Executable file
240 lines
8.2 KiB
JavaScript
Executable file
define(
|
|
[
|
|
'module',
|
|
'exports'
|
|
],
|
|
function(
|
|
$module,
|
|
exports
|
|
) {
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Time
|
|
//
|
|
// == JS Dates
|
|
//
|
|
// We primarily deal in UTC timestamps. When we need to talk dates with IMAP
|
|
// (see next section), we need these timestamps to line up with midnight for
|
|
// a given day. We do not need to line up with weeks, months, or years,
|
|
// saving us a lot of complexity.
|
|
//
|
|
// Day algebra is straightforward because JS Date objects have no concept of
|
|
// leap seconds. We don't need to worry that a leap second will cause adding
|
|
// a day to be less than or more than a day. Hooray!
|
|
//
|
|
// == IMAP and Time
|
|
//
|
|
// The stock IMAP SEARCH command's SINCE and BEFORE predicates only operate on
|
|
// whole-dates (and ignore the non-date time parts). Additionally, SINCE is
|
|
// inclusive and BEFORE is exclusive.
|
|
//
|
|
// We use JS millisecond timestamp values throughout, and it's important to us
|
|
// that our date logic is consistent with IMAP's time logic where relevant.
|
|
// All of our IMAP-exposed time-interval related logic operates on day
|
|
// granularities. Our timestamp/date values are always normalized to midnight
|
|
// which happily works out with intuitive range operations.
|
|
//
|
|
// Observe the pretty ASCII art where as you move to the right you are moving
|
|
// forward in time.
|
|
//
|
|
// ________________________________________
|
|
// BEFORE)| midnight (0 millis) ... 11:59:59:999 |
|
|
// ON_OR_BEFORE]
|
|
// [SINCE......................................
|
|
// (AFTER.....................................
|
|
//
|
|
// Our date range comparisons (noting that larger timestamps are 'younger') are:
|
|
// SINCE analog: (testDate >= comparisonDate)
|
|
// testDate is as-recent-as or more-recent-than the comparisonDate.
|
|
// BEFORE analog: (testDate < comparisonDate)
|
|
// testDate is less-recent-than the comparisonDate
|
|
//
|
|
// Because "who is the test date and who is the range value under discussion"
|
|
// can be unclear and the numerical direction of time is not always intuitive,
|
|
// I'm introducing simple BEFORE and SINCE helper functions to try and make
|
|
// the comparison logic ridiculously explicit as well as calling out where we
|
|
// are being consistent with IMAP.
|
|
//
|
|
// Not all of our time logic is consistent with IMAP! Specifically, use of
|
|
// exclusive time bounds without secondary comparison keys means that ranges
|
|
// defined in this way cannot spread messages with the same timestamp over
|
|
// multiple ranges. This allows for pathological data structure situations
|
|
// where there's too much data in a data block, etc.
|
|
// Our date ranges are defined by 'startTS' and 'endTS'. Using math syntax, our
|
|
// IMAP-consistent time ranges end up as: [startTS, endTS). It is always true
|
|
// that BEFORE(startTS, endTS) and SINCE(endTS, startTS) in these cases.
|
|
//
|
|
// As such, I've also created an ON_OR_BEFORE helper that allows equivalence and
|
|
// STRICTLY_AFTER that does not check equivalence to round out all possibilities
|
|
// while still being rather explicit.
|
|
|
|
|
|
/**
|
|
* IMAP-consistent date comparison; read this as "Is `testDate` BEFORE
|
|
* `comparisonDate`"?
|
|
*
|
|
* !BEFORE(a, b) === SINCE(a, b)
|
|
*/
|
|
var BEFORE = exports.BEFORE =
|
|
function BEFORE(testDate, comparisonDate) {
|
|
// testDate is numerically less than comparisonDate, so it is chronologically
|
|
// before it.
|
|
return testDate < comparisonDate;
|
|
};
|
|
|
|
var ON_OR_BEFORE = exports.ON_OR_BEFORE =
|
|
function ON_OR_BEFORE(testDate, comparisonDate) {
|
|
return testDate <= comparisonDate;
|
|
};
|
|
|
|
/**
|
|
* IMAP-consistent date comparison; read this as "Is `testDate` SINCE
|
|
* `comparisonDate`"?
|
|
*
|
|
* !SINCE(a, b) === BEFORE(a, b)
|
|
*/
|
|
var SINCE = exports.SINCE =
|
|
function SINCE(testDate, comparisonDate) {
|
|
// testDate is numerically greater-than-or-equal-to comparisonDate, so it
|
|
// chronologically after/since it.
|
|
return testDate >= comparisonDate;
|
|
};
|
|
|
|
var STRICTLY_AFTER = exports.STRICTLY_AFTER =
|
|
function STRICTLY_AFTER(testDate, comparisonDate) {
|
|
return testDate > comparisonDate;
|
|
};
|
|
|
|
var IN_BS_DATE_RANGE = exports.IN_BS_DATE_RANGE =
|
|
function IN_BS_DATE_RANGE(testDate, startTS, endTS) {
|
|
return testDate >= startTS && testDate < endTS;
|
|
};
|
|
|
|
var PASTWARDS = 1, FUTUREWARDS = -1;
|
|
/**
|
|
* Check if `testDate` is "beyond" the comparison date given the `dir`. If
|
|
* the direction is pastwards, we will return true if testDate happened
|
|
* chronologically before comparisonDate. If the direction is futurewards,
|
|
* we will return true if testDate happened chronologically after
|
|
* comparisonDate.
|
|
*/
|
|
var TIME_DIR_AT_OR_BEYOND = exports.TIME_DIR_AT_OR_BEYOND =
|
|
function TIME_DIR_AT_OR_BEYOND(dir, testDate, comparisonDate) {
|
|
if (dir === PASTWARDS)
|
|
return testDate <= comparisonDate;
|
|
// we use null as a sentinel value for 'the future'/'now'
|
|
else if (comparisonDate === null)
|
|
return testDate >= NOW();
|
|
else // FUTUREWARDS
|
|
return testDate >= comparisonDate;
|
|
};
|
|
/**
|
|
* Compute the delta of the `testDate` relative to the `comparisonDate` where
|
|
* a positive value indicates `testDate` is beyond the `comparisonDate` in
|
|
* the given direction and a negative value indicates it is before it.
|
|
*/
|
|
var TIME_DIR_DELTA = exports.TIME_DIR_DELTA =
|
|
function TIME_DIR_DELTA(dir, testDate, comparisonDate) {
|
|
if (dir === PASTWARDS)
|
|
return testDate - comparisonDate;
|
|
else // FUTUREWARDS
|
|
return comparisonDate - testDate;
|
|
};
|
|
/**
|
|
* Add `time` to the `baseDate` in the given direction. So if the direction
|
|
* is `PASTWARDS`, then we add the date, otherwise we subtract it.
|
|
*/
|
|
var TIME_DIR_ADD = exports.TIME_DIR_ADD =
|
|
function TIME_DIR_ADD(dir, baseDate, time) {
|
|
if (dir === PASTWARDS)
|
|
return baseDate + time;
|
|
else // FUTUREWARDS
|
|
return baseDate - time;
|
|
};
|
|
|
|
//function DATE_RANGES_OVERLAP(A_startTS, A_endTS, B_startTS, B_endTS) {
|
|
//}
|
|
|
|
var HOUR_MILLIS = exports.HOUR_MILLIS = 60 * 60 * 1000;
|
|
var DAY_MILLIS = exports.DAY_MILLIS = 24 * 60 * 60 * 1000;
|
|
|
|
/**
|
|
* Testing override that when present replaces use of Date.now().
|
|
*/
|
|
var TIME_WARPED_NOW = null;
|
|
|
|
/**
|
|
* Pretend that 'now' is actually a fixed point in time for the benefit of
|
|
* unit tests using canned message stores.
|
|
*/
|
|
exports.TEST_LetsDoTheTimewarpAgain = function(fakeNow) {
|
|
if (fakeNow === null) {
|
|
TIME_WARPED_NOW = null;
|
|
return;
|
|
}
|
|
if (typeof(fakeNow) !== 'number')
|
|
fakeNow = fakeNow.valueOf();
|
|
TIME_WARPED_NOW = fakeNow;
|
|
};
|
|
|
|
var NOW = exports.NOW =
|
|
function NOW() {
|
|
return TIME_WARPED_NOW || Date.now();
|
|
};
|
|
|
|
/**
|
|
* Like NOW, but uses performance.now instead. This means it can be reset or
|
|
* changed to a value different than the last window or worker lifetime value.
|
|
* Allows TIME_WARP overrides and falls back to Date.now. So while all the
|
|
* values are in milliseconds, if performance.now() is used, can have a decimal
|
|
* value indicating up to one thousandth of a millisecond.
|
|
*/
|
|
var perfObj = typeof performance !== 'undefined' ? performance : Date;
|
|
exports.PERFNOW = function PERFNOW() {
|
|
return TIME_WARPED_NOW || perfObj.now();
|
|
};
|
|
|
|
/**
|
|
* Make a timestamp some number of days in the past, quantized to midnight of
|
|
* that day. This results in rounding up; if it's noon right now and you
|
|
* ask for 2 days ago, you really get 2.5 days worth of time.
|
|
*/
|
|
var makeDaysAgo = exports.makeDaysAgo =
|
|
function makeDaysAgo(numDays) {
|
|
var past = quantizeDate(TIME_WARPED_NOW || Date.now()) -
|
|
numDays * DAY_MILLIS;
|
|
return past;
|
|
};
|
|
var makeDaysBefore = exports.makeDaysBefore =
|
|
function makeDaysBefore(date, numDaysBefore) {
|
|
if (date === null)
|
|
return makeDaysAgo(numDaysBefore - 1);
|
|
return quantizeDate(date) - numDaysBefore * DAY_MILLIS;
|
|
};
|
|
/**
|
|
* Quantize a date to midnight on that day.
|
|
*/
|
|
var quantizeDate = exports.quantizeDate =
|
|
function quantizeDate(date) {
|
|
if (date === null)
|
|
return null;
|
|
if (typeof(date) === 'number')
|
|
date = new Date(date);
|
|
return date.setUTCHours(0, 0, 0, 0).valueOf();
|
|
};
|
|
|
|
/**
|
|
* If a date is already lined up with midnight of its day, then return that,
|
|
* otherwise round up to the midnight of the next day.
|
|
*/
|
|
var quantizeDateUp = exports.quantizeDateUp =
|
|
function quantizeDateUp(date) {
|
|
if (typeof(date) === 'number')
|
|
date = new Date(date);
|
|
var truncated = date.setUTCHours(0, 0, 0, 0).valueOf();
|
|
if (date.valueOf() === truncated)
|
|
return truncated;
|
|
return truncated + DAY_MILLIS;
|
|
};
|
|
|
|
}); // end define
|