cletus/node_modules/date.js/lib/parser.js
2025-05-09 15:53:19 -05:00

659 lines
12 KiB
JavaScript

/**
* Module Dependencies
*/
var debug = require('debug')('date:parser')
var date = require('./date')
var norm = require('./norm')
/**
* Days
*/
var days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']
var months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september',
'october', 'november', 'december'
]
/**
* Regexs
*/
// 5, 05, 5:30, 5.30, 05:30:10, 05:30.10, 05.30.10, at 5
var rMeridiem = /^(\d{1,2})([:.](\d{1,2}))?([:.](\d{1,2}))?\s*([ap]m)/
var rHourMinute = /^(\d{1,2})([:.](\d{1,2}))([:.](\d{1,2}))?/
var rAtHour = /^at\s?(\d{1,2})$/
var rDays = /\b(sun(day)?|mon(day)?|tues(day)?|wed(nesday)?|thur(sday|s)?|fri(day)?|sat(urday)?)s?\b/
var rMonths = /^((\d{1,2})\s*(st|nd|rd|th))\s(day\s)?(of\s)?(january|february|march|april|may|june|july|august|september|october|november|december)/i
var rPast = /\b(last|yesterday|ago)\b/
var rDayMod = /\b(morning|noon|afternoon|night|evening|midnight)\b/
var rAgo = /^(\d*)\s?\b(second|minute|hour|day|week|month|year)[s]?\b\s?ago$/
/**
* Expose `parser`
*/
module.exports = parser
/**
* Initialize `parser`
*
* @param {String} str
* @return {Date}
* @api publics
*/
function parser (str, offset) {
if (!(this instanceof parser)) return new parser(str, offset)
if (typeof offset == 'string') offset = parser(offset)
// CFG preprocessing into normalized format,
// get {str, tokens, normals}
// !future: return multiple parsed times, some from it
var prepro = norm(str, offset)
// console.log(prepro)
// reset the str to prepro str
str = prepro.str
// if proprocessed doesn't leave any str to be processed (non-date-time) format, check normals
if (!str) {
if (prepro.normals.length) {
// if there's normal date parsed already,
// !return the first
return new Date(prepro.normals[0])
} else {
// otherwise go back to below to return proper Error
str = str
}
}
var d = offset || new Date
this.date = new date(d)
this.original = str
this.str = str.toLowerCase()
this.stash = []
this.tokens = []
while (this.advance() !== 'eos')
debug('tokens %j', this.tokens)
this.nextTime(d)
if (this.date.date == d) throw new Error('Invalid date')
return this.date.date
}
/**
* Advance a token
*/
parser.prototype.advance = function () {
var tok = this.eos()
|| this.space()
|| this._next()
|| this.last()
|| this.dayByName()
|| this.monthByName()
|| this.timeAgo()
|| this.ago()
|| this.yesterday()
|| this.tomorrow()
|| this.noon()
|| this.midnight()
|| this.night()
|| this.evening()
|| this.afternoon()
|| this.morning()
|| this.tonight()
|| this.meridiem()
|| this.hourminute()
|| this.athour()
|| this.week()
|| this.month()
|| this.year()
|| this.second()
|| this.minute()
|| this.hour()
|| this.day()
|| this.number()
|| this.string()
|| this.other()
this.tokens.push(tok)
return tok
}
/**
* Lookahead `n` tokens.
*
* @param {Number} n
* @return {Object}
* @api private
*/
parser.prototype.lookahead = function (n) {
var fetch = n - this.stash.length
if (fetch == 0) return this.lookahead(++n)
while (fetch-- > 0) this.stash.push(this.advance())
return this.stash[--n]
}
/**
* Lookahead a single token.
*
* @return {Token}
* @api private
*/
parser.prototype.peek = function () {
return this.lookahead(1)
}
/**
* Fetch next token including those stashed by peek.
*
* @return {Token}
* @api private
*/
parser.prototype.next = function () {
var tok = this.stashed() || this.advance()
return tok
}
/**
* Return the next possibly stashed token.
*
* @return {Token}
* @api private
*/
parser.prototype.stashed = function () {
var stashed = this.stash.shift()
return stashed
}
/**
* Consume the given `len`.
*
* @param {Number|Array} len
* @api private
*/
parser.prototype.skip = function (len) {
this.str = this.str.substr(Array.isArray(len) ? len[0].length : len)
}
/**
* EOS
*/
parser.prototype.eos = function () {
if (this.str.length) return
return 'eos'
}
/**
* Space
*/
parser.prototype.space = function () {
var captures
if (captures = /^([ \t]+)/.exec(this.str)) {
this.skip(captures)
return this.advance()
}
}
/**
* Second
*/
parser.prototype.second = function () {
var captures
if (captures = /^s(ec|econd)?s?/.exec(this.str)) {
this.skip(captures)
return 'second'
}
}
/**
* Minute
*/
parser.prototype.minute = function () {
var captures
if (captures = /^m(in|inute)?s?/.exec(this.str)) {
this.skip(captures)
return 'minute'
}
}
/**
* Hour
*/
parser.prototype.hour = function () {
var captures
if (captures = /^h(r|our)s?/.exec(this.str)) {
this.skip(captures)
return 'hour'
}
}
/**
* Day
*/
parser.prototype.day = function () {
var captures
if (captures = /^d(ay)?s?/.exec(this.str)) {
this.skip(captures)
return 'day'
}
}
/**
* Day by name
*/
parser.prototype.dayByName = function () {
var captures
var r = new RegExp('^' + rDays.source)
if (captures = r.exec(this.str)) {
var day = captures[1]
this.skip(captures)
this.date[day](1)
return captures[1]
}
}
/**
* Month by name
*/
parser.prototype.monthByName = function () {
var captures
if (captures = rMonths.exec(this.str)) {
var day = captures[2]
var month = captures[6]
this.date.date.setMonth((months.indexOf(month)))
if (day) this.date.date.setDate(parseInt(day))
this.skip(captures)
return captures[0]
}
}
parser.prototype.timeAgo = function () {
var captures
if (captures = rAgo.exec(this.str)) {
var num = captures[1]
var mod = captures[2]
this.date[mod](-num)
this.skip(captures)
return 'timeAgo'
}
}
/**
* Week
*/
parser.prototype.week = function () {
var captures
if (captures = /^w(k|eek)s?/.exec(this.str)) {
this.skip(captures)
return 'week'
}
}
/**
* Month
*/
parser.prototype.month = function () {
var captures
if (captures = /^mon(th)?(es|s)?\b/.exec(this.str)) {
this.skip(captures)
return 'month'
}
}
/**
* Week
*/
parser.prototype.year = function () {
var captures
if (captures = /^y(r|ear)s?/.exec(this.str)) {
this.skip(captures)
return 'year'
}
}
/**
* Meridiem am/pm
*/
parser.prototype.meridiem = function () {
var captures
if (captures = rMeridiem.exec(this.str)) {
this.skip(captures)
this.time(captures[1], captures[3], captures[5], captures[6])
return 'meridiem'
}
}
/**
* Hour Minute (ex. 12:30)
*/
parser.prototype.hourminute = function () {
var captures
if (captures = rHourMinute.exec(this.str)) {
this.skip(captures)
this.time(captures[1], captures[3], captures[5], this._meridiem)
return 'hourminute'
}
}
/**
* At Hour (ex. at 5)
*/
parser.prototype.athour = function () {
var captures
if (captures = rAtHour.exec(this.str)) {
this.skip(captures)
this.time(captures[1], 0, 0, this._meridiem)
this._meridiem = null
return 'athour'
}
}
/**
* Time set helper
*/
parser.prototype.time = function (h, m, s, meridiem) {
var d = this.date
var before = d.clone()
if (meridiem) {
// convert to 24 hour
h = ('pm' == meridiem && 12 > h) ? +h + 12 : h; // 6pm => 18
h = ('am' == meridiem && 12 == h) ? 0 : h; // 12am => 0
}
m = (!m && d.changed('minutes')) ? false : m
s = (!s && d.changed('seconds')) ? false : s
d.time(h, m, s)
}
/**
* Best attempt to pick the next time this date will occur
*
* TODO: place at the end of the parsing
*/
parser.prototype.nextTime = function (before) {
var d = this.date
var orig = this.original
if (before <= d.date || rPast.test(orig)) return this
// If time is in the past, we need to guess at the next time
if (rDays.test(orig)) {
d.day(7)
} else if ((before - d.date) / 1000 > 60) {
// If it is a month in the past, don't add a day
if (rMonths.test(orig)) {
d.day(0)
} else {
d.day(1)
}
}
return this
}
/**
* Yesterday
*/
parser.prototype.yesterday = function () {
var captures
if (captures = /^(yes(terday)?)/.exec(this.str)) {
this.skip(captures)
this.date.day(-1)
return 'yesterday'
}
}
/**
* Tomorrow
*/
parser.prototype.tomorrow = function () {
var captures
if (captures = /^tom(orrow)?/.exec(this.str)) {
this.skip(captures)
this.date.day(1)
return 'tomorrow'
}
}
/**
* Noon
*/
parser.prototype.noon = function () {
var captures
if (captures = /^noon\b/.exec(this.str)) {
this.skip(captures)
var before = this.date.clone()
this.date.date.setHours(12, 0, 0)
return 'noon'
}
}
/**
* Midnight
*/
parser.prototype.midnight = function () {
var captures
if (captures = /^midnight\b/.exec(this.str)) {
this.skip(captures)
var before = this.date.clone()
this.date.date.setHours(0, 0, 0)
return 'midnight'
}
}
/**
* Night (arbitrarily set at 7pm)
*/
parser.prototype.night = function () {
var captures
if (captures = /^night\b/.exec(this.str)) {
this.skip(captures)
this._meridiem = 'pm'
var before = this.date.clone()
this.date.date.setHours(19, 0, 0)
return 'night'
}
}
/**
* Evening (arbitrarily set at 5pm)
*/
parser.prototype.evening = function () {
var captures
if (captures = /^evening\b/.exec(this.str)) {
this.skip(captures)
this._meridiem = 'pm'
var before = this.date.clone()
this.date.date.setHours(17, 0, 0)
return 'evening'
}
}
/**
* Afternoon (arbitrarily set at 2pm)
*/
parser.prototype.afternoon = function () {
var captures
if (captures = /^afternoon\b/.exec(this.str)) {
this.skip(captures)
this._meridiem = 'pm'
var before = this.date.clone()
if (this.date.changed('hours')) return 'afternoon'
this.date.date.setHours(14, 0, 0)
return 'afternoon'
}
}
/**
* Morning (arbitrarily set at 8am)
*/
parser.prototype.morning = function () {
var captures
if (captures = /^morning\b/.exec(this.str)) {
this.skip(captures)
this._meridiem = 'am'
var before = this.date.clone()
if (!this.date.changed('hours')) this.date.date.setHours(8, 0, 0)
return 'morning'
}
}
/**
* Tonight
*/
parser.prototype.tonight = function () {
var captures
if (captures = /^tonight\b/.exec(this.str)) {
this.skip(captures)
this._meridiem = 'pm'
return 'tonight'
}
}
/**
* Next time
*/
parser.prototype._next = function () {
var captures
if (captures = /^next/.exec(this.str)) {
this.skip(captures)
var d = new Date(this.date.date)
var mod = this.peek()
// If we have a defined modifier, then update
if (this.date[mod]) {
this.next()
// slight hack to modify already modified
this.date = date(d)
this.date[mod](1)
} else if (rDayMod.test(mod)) {
this.date.day(1)
}
return 'next'
}
}
/**
* Last time
*/
parser.prototype.last = function () {
var captures
if (captures = /^last/.exec(this.str)) {
this.skip(captures)
var d = new Date(this.date.date)
var mod = this.peek()
// If we have a defined modifier, then update
if (this.date[mod]) {
this.next()
// slight hack to modify already modified
this.date = date(d)
this.date[mod](-1)
} else if (rDayMod.test(mod)) {
this.date.day(-1)
}
return 'last'
}
}
/**
* Ago
*/
parser.prototype.ago = function () {
var captures
if (captures = /^ago\b/.exec(this.str)) {
this.skip(captures)
return 'ago'
}
}
/**
* Number
*/
parser.prototype.number = function () {
var captures
if (captures = /^(\d+)/.exec(this.str)) {
var n = captures[1]
this.skip(captures)
var mod = this.peek()
// If we have a defined modifier, then update
if (this.date[mod]) {
if ('ago' == this.peek()) n = -n
this.date[mod](n)
} else if (this._meridiem) {
// when we don't have meridiem, possibly use context to guess
this.time(n, 0, 0, this._meridiem)
this._meridiem = null
} else if (this.original.indexOf('at') > -1) {
this.time(n, 0, 0, this._meridiem)
this._meridiem = null
}
return 'number'
}
}
/**
* String
*/
parser.prototype.string = function () {
var captures
if (captures = /^\w+/.exec(this.str)) {
this.skip(captures)
return 'string'
}
}
/**
* Other
*/
parser.prototype.other = function () {
var captures
if (captures = /^./.exec(this.str)) {
this.skip(captures)
return 'other'
}
}