diff --git a/README.md b/README.md index 1fe7a9ba..02a3b8ac 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,23 @@ This isn't a comprehesive list, but it hopefully will hit the high points. * Adding comments to `finish()` in `parser.js` to describe what each condition does * Remove conditions in `finish()` in `parser.js` that are not used by any tests * Fix a couple relative-ish cases like `15th at 3pm` +* Added support for "biasing" toward dates in the past or the future + + +## Biasing ## + +There are many times where your users will be tending to want to specify dates +only in the past or the future, so providing a mechanism where, if it is +`3pm` in the afternoon, specifying `1pm` would give you the next day instead +of the current one. + +To enable biasing, `Date.parse()` now takes an optional object that is used +as the object that is parsed to set defaults. + +To enable biasing, set the `bias` property to either `past` or `future`. For +example: + + Date.parse('3pm', { bias: 'future' }) ## Original README.txt ## diff --git a/src/parser.js b/src/parser.js index 4aa54db9..e0c03e5e 100644 --- a/src/parser.js +++ b/src/parser.js @@ -634,10 +634,10 @@ today = new Date(); } - var expression = !!(this.days && this.days !== null || this.orient || this.operator); + var expression = !!(this.days && this.days !== null || this.orient || this.operator || this.bias); var gap, mod, orient; - orient = ((this.orient == "past" || this.operator == "subtract") ? -1 : 1); + orient = ((this.orient == "past" || this.operator == "subtract" || this.bias == "past") ? -1 : 1); // For parsing: "last second", "next minute", "previous hour", "+5 seconds", // "-5 hours", "5 hours", "7 hours ago" @@ -679,7 +679,7 @@ // For parsing: "last january", "prev march", "next july", "today + 1 month", // "+5 months" - if (expression && (this.month || this.month === 0) && this.unit != "year") { + if ((expression && !this.bias) && (this.month || this.month === 0) && this.unit != "year") { this.unit = "month"; gap = (this.month - today.getMonth()); mod = 12; @@ -687,10 +687,10 @@ this.month = null; } - // For parsing: "Yesterday", "Tomorrow", "last monday", "last friday", - // "previous day", "next week", "next month", "next year", + // For parsing: "last monday", "last friday", "previous day", + // "next week", "next month", "next year", // "today+", "+", "-", "yesterday at 4:00", "last friday at 20:00" - if (!this.value && expression) { + if (!this.value && expression && !this.bias) { this.value = 1; } @@ -700,9 +700,9 @@ } // For parsing: "15th at 20:15", "15th at 8pm" - if (!expression && this.value && (!this.unit || this.unit == "day") && !this.day) { + if ((!expression || this.bias) && this.value && (!this.unit || this.unit == "day") && !this.day) { this.unit = "day"; - this.day = this.value * orient; + this.day = this.value * 1 } // For parsing: "last minute", "+5 hours", "previous month", "1 year ago tomorrow" @@ -741,6 +741,28 @@ today.set(this); + if (this.bias) { + if (this.day) { + this.days = null + } + + if (!this.day) { + if ((this.bias == "past" && today > new Date()) || (this.bias == "future" && today < new Date())) { + this.days = 1 * orient + } + } else if (!this.month && !this.months) { + if ((this.bias == "past" && today > new Date()) || (this.bias == "future" && today < new Date())) { + this.months = 1 * orient + } + } else if (!this.year) { + if ((this.bias == "past" && today > new Date()) || (this.bias == "future" && today < new Date())) { + this.years = 1 * orient + } + } + + expression = true; + } + if (expression) { today.add(this); } @@ -1082,12 +1104,12 @@ } if (!o) { o = {} - } - // try { + } + try { r = $D.Grammar.start.call({}, s.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"), o); - // } catch (e) { - // return null; - // } + } catch (e) { + return null; + } return ((r[1].length === 0) ? r[0] : null); }; diff --git a/test/partial/index.js b/test/partial/index.js index 6f254d1e..9f8fb3c5 100644 --- a/test/partial/index.js +++ b/test/partial/index.js @@ -1104,7 +1104,165 @@ return false; } } + }, + 'Biasing': { + 'biasing to the past, parse 1am': { + run: function() { this.date = Date.parse('12:01am', { bias: 'past' }) }, + assert: function() { return this.date < new Date() } + }, + 'biasing to the past, parse 11pm': { + run: function() { this.date = Date.parse('11:59pm', { bias: 'past' }) }, + assert: function() { return this.date < new Date() } + }, + 'biasing to the past, parse a time an hour ahead of now': { + run: function() { + this.nextHour = new Date().next().hour() + this.date = Date.parse(String(this.nextHour.getHours()) + ":" + String(this.nextHour.getMinutes()), { bias: 'past' }) + }, + assert: function() { + // The date is less than today + return this.date < new Date() && + // and the hour is the hour we told it to be + this.date.getHours() == this.nextHour.getHours() && + // and the day is yesterday + new Date().previous().day().same().day(this.date); + } + }, + + 'biasing to the past, parse a time an hour behind now': { + run: function() { + this.nextHour = new Date().previous().hour() + this.date = Date.parse(String(this.nextHour.getHours()) + ":" + String(this.nextHour.getMinutes()), { bias: 'past' }) + }, + assert: function() { + // The date is less than today + return this.date < new Date() && + // and the hour is the hour we told it to be + this.date.getHours() == this.nextHour.getHours() && + // and the day is yesterday + new Date().same().day(this.date); + } + }, + + 'biasing to the past, parse tomorrows day of the week to last week': { + run: function() { + this.nextDay = new Date().next().day() + this.date = Date.parse(this.nextDay.toString("dddd") + " at 3pm", { bias: 'past' }) + }, + assert: function() { + // The date is less than now + return this.date < new Date() && + // and the hour is 3pm + this.date.getHours() == 15; + } + }, + + 'biasing to the past, parse the day of the month tomorrow to last month': { + run: function() { + this.nextDay = new Date().next().day() + this.date = Date.parse(this.nextDay.toString("dS") + " at 3pm", { bias: 'past' }) + }, + assert: function() { + // The date is less than now + return this.date < new Date() && + // and the hour is 3pm + this.date.getHours() == 15 && + // make sure the day of the month is right + this.date.getDate() == this.nextDay.getDate(); + } + }, + + 'biasing to the past, parse the day and the month of next month to a year ago': { + run: function() { + this.nextMonth = new Date().next().month() + this.date = Date.parse(this.nextMonth.toString("MMMM dS") + " at 3pm", { bias: 'past' }) + }, + assert: function() { + // The date is less than now + return this.date < new Date() && + // and the hour is 3pm + this.date.getHours() == 15 && + // make sure the day of the month is right + this.date.getDate() == this.nextMonth.getDate(); + } + }, + + 'biasing to the future, parse a time an hour behind now': { + run: function() { + this.previousHour = new Date().previous().hour() + this.date = Date.parse(String(this.previousHour.getHours()) + ":" + String(this.previousHour.getMinutes()), { bias: 'future' }) + }, + assert: function() { + // The date is greater than now + return this.date > new Date() && + // and the hour is the hour we told it to be + this.date.getHours() == this.previousHour.getHours() && + // and the day is yesterday + new Date().next().day().same().day(this.date); + } + }, + + 'biasing to the future, parse a time an hour ahead of now': { + run: function() { + this.nextHour = new Date().next().hour() + this.date = Date.parse(String(this.nextHour.getHours()) + ":" + String(this.nextHour.getMinutes()), { bias: 'future' }) + }, + assert: function() { + // The date is greater than now + return this.date > new Date() && + // and the hour is the hour we told it to be + this.date.getHours() == this.nextHour.getHours() && + // and the day is yesterday + new Date().same().day(this.date); + } + }, + + + 'biasing to the future, parse yesterdays day of the week to next week': { + run: function() { + this.previousDay = new Date().previous().day() + this.date = Date.parse(this.previousDay.toString("dddd") + " at 3pm", { bias: 'future' }) + }, + assert: function() { + // The date is greater than now + return this.date > new Date() && + // and the hour is 3pm + this.date.getHours() == 15; + } + }, + + 'biasing to the future, parse the day of the month for yesterday to next month': { + run: function() { + this.previousDay = new Date().previous().day() + this.query = this.previousDay.toString("dS") + " at 3pm" + this.date = Date.parse(this.query, { bias: 'future' }) + }, + assert: function() { + // The date is greater than now + return this.date > new Date() && + // and the hour is 3pm + this.date.getHours() == 15 && + // make sure the day of the month is right + this.date.getDate() == this.previousDay.getDate(); + } + }, + + 'biasing to the future, parse the day and the month of last month to a year from now': { + run: function() { + this.lastMonth = new Date().last().month() + this.query = this.lastMonth.toString("MMMM dS") + " at 3pm" + this.date = Date.parse(this.query, { bias: 'future' }) + }, + assert: function() { + // The date is greater than now + return this.date > new Date() && + // and the hour is 3pm + this.date.getHours() == 15 && + // make sure the day of the month is right + this.date.getDate() == this.lastMonth.getDate(); + } + }, + } - });