Files
2026-07-13 12:48:55 +08:00

9010 lines
247 KiB
JavaScript

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.compromiseDates = factory());
})(this, (function () { 'use strict';
// chop things up into bite-size pieces
const split = function (dates) {
let m = null;
// don't split anything if it looks like a range
if (dates.has('^(between|within) #Date')) {
return dates
}
if (dates.has('#Month')) {
// 'june 5, june 10'
m = dates.match('[#Month #Value] and? #Month', 0).ifNo('@hasDash$');
if (m.found) {
dates = dates.splitAfter(m);
}
// '5 june, 10 june'
m = dates.match('[#Value #Month] and? #Value #Month', 0);
if (m.found) {
dates = dates.splitAfter(m);
}
// 'june, august'
m = dates.match('^[#Month] and? #Month #Ordinal?$', 0);
if (m.found) {
dates = dates.splitAfter(m);
}
// 'june 5th, june 10th'
m = dates.match('[#Month #Value] #Month', 0).ifNo('@hasDash$');
if (m.found) {
dates = dates.splitAfter(m);
}
}
if (dates.has('#WeekDay')) {
// 'tuesday, wednesday'
m = dates.match('^[#WeekDay] and? #WeekDay$', 0).ifNo('@hasDash$');
if (m.found) {
dates = dates.splitAfter(m);
}
// 'tuesday, wednesday, and friday'
m = dates.match('#WeekDay #WeekDay and? #WeekDay');
if (m.found) {
dates = dates.splitOn('#WeekDay');
}
// monday, wednesday
m = dates.match('[#WeekDay] (and|or|this|next)? #WeekDay', 0).ifNo('@hasDash$');
if (m.found) {
dates = dates.splitAfter('#WeekDay');
}
}
// next week tomorrow
m = dates.match('(this|next) #Duration [(today|tomorrow|yesterday)]', 0);
if (m.found) {
dates = dates.splitBefore(m);
}
// tomorrow 15 march
m = dates.match('[(today|tomorrow|yesterday)] #Value #Month', 0);
if (m.found) {
dates = dates.splitAfter(m);
}
// tomorrow yesterday
m = dates.match('[(today|tomorrow|yesterday)] (today|tomorrow|yesterday|#WeekDay)', 0).ifNo('@hasDash$');
if (m.found) {
dates = dates.splitAfter(m);
}
//1998 and 1999
m = dates.match('#Year [and] #Year', 0);
if (m.found) {
dates = dates.splitAfter(m);
}
// cleanup any splits
dates = dates.not('^and');
dates = dates.not('and$');
return dates
};
const findDate = function (doc) {
let dates = doc.match('#Date+');
// ignore only-durations like '20 minutes'
dates = dates.filter(m => {
let isDuration = m.has('^#Duration+$') || m.has('^#Value #Duration+$');
// allow 'q4', etc
if (isDuration === true && m.has('(#FinancialQuarter|quarter)')) {
return true
}
return isDuration === false
});
// 30 minutes on tuesday
let m = dates.match('[#Cardinal #Duration (in|on|this|next|during|for)] #Date', 0);
if (m.found) {
dates = dates.not(m);
}
// 30 minutes tuesday
m = dates.match('[#Cardinal #Duration] #WeekDay', 0);
if (m.found) {
dates = dates.not(m);
}
// tuesday for 30 mins
m = dates.match('#Date [for #Value #Duration]$', 0);
if (m.found) {
dates = dates.not(m);
}
// '20 minutes june 5th'
m = dates.match('[#Cardinal #Duration] #Date', 0); //but allow '20 minutes ago'
if (m.found && !dates.has('#Cardinal #Duration] (ago|from|before|after|back)')) {
dates = dates.not(m);
}
// for 20 minutes
m = dates.match('for #Cardinal #Duration');
if (m.found) {
dates = dates.not(m);
}
// 'one saturday'
dates = dates.notIf('^one (#WeekDay|#Month)$');
// tokenize the dates
dates = split(dates);
// $5 an hour
dates = dates.notIf('(#Money|#Percentage)');
dates = dates.notIf('^per #Duration');
return dates
};
const knownUnits = {
second: true,
minute: true,
hour: true,
day: true,
week: true,
weekend: true,
month: true,
season: true,
quarter: true,
year: true,
};
const aliases$2 = {
wk: 'week',
min: 'minute',
sec: 'second',
weekend: 'week', //for now...
};
const parseUnit = function (m) {
let unit = m.match('#Duration').text('normal');
unit = unit.replace(/s$/, '');
// support shorthands like 'min'
if (aliases$2.hasOwnProperty(unit)) {
unit = aliases$2[unit];
}
return unit
};
//turn '5 weeks before' to {weeks:5}
const parseShift = function (doc) {
let result = {};
let m = doc.none();
let shift = doc.match('#DateShift+');
if (shift.found === false) {
return { res: result, m }
}
// '5 weeks'
shift.match('#Cardinal #Duration').forEach((ts) => {
let num = ts.match('#Cardinal').numbers().get()[0];
if (num && typeof num === 'number') {
let unit = parseUnit(ts);
if (knownUnits[unit] === true) {
result[unit] = num;
}
}
});
//is it 2 weeks ago? → -2
if (shift.has('(before|ago|hence|back)$') === true) {
Object.keys(result).forEach((k) => (result[k] *= -1));
}
m = shift.match('#Cardinal #Duration');
shift = shift.not(m);
// supoprt '1 day after tomorrow'
m = shift.match('[<unit>#Duration] [<dir>(after|before)]');
if (m.found) {
let unit = m.groups('unit').text('reduced');
// unit = unit.replace(/s$/, '')
let dir = m.groups('dir').text('reduced');
if (dir === 'after') {
result[unit] = 1;
} else if (dir === 'before') {
result[unit] = -1;
}
}
// in half an hour
m = shift.match('half (a|an) [#Duration]', 0);
if (m.found) {
let unit = parseUnit(m);
result[unit] = 0.5;
}
// a couple years
m = shift.match('a (few|couple) [#Duration]', 0);
if (m.found) {
let unit = parseUnit(m);
result[unit] = m.has('few') ? 3 : 2;
}
// finally, remove it from our text
m = doc.match('#DateShift+');
return { result, m }
};
/*
a 'counter' is a Unit determined after a point
* first hour of x
* 7th week in x
* last year in x
*
unlike a shift, like "2 weeks after x"
*/
const oneBased = {
minute: true,
};
const getCounter = function (doc) {
// 7th week of
let m = doc.match('[<num>#Value] [<unit>#Duration+] (of|in)');
if (m.found) {
let obj = m.groups();
let num = obj.num.numbers().get()[0];
let unit = obj.unit.text('reduced');
let result = {
unit: unit,
num: Number(num) || 0,
};
// 0-based or 1-based units
if (!oneBased[unit]) {
result.num -= 1;
}
return { result, m }
}
// first week of
m = doc.match('[<dir>(first|initial|last|final)] [<unit>#Duration+] (of|in)');
if (m.found) {
let obj = m.groups();
let dir = obj.dir.text('reduced');
let unit = obj.unit.text('reduced');
if (dir === 'initial') {
dir = 'first';
}
if (dir === 'final') {
dir = 'last';
}
let result = {
unit: unit,
dir: dir,
};
return { result, m }
}
return { result: null, m: doc.none() }
};
const MSEC_IN_HOUR = 60 * 60 * 1000;
//convert our local date syntax a javascript UTC date
const toUtc = (dstChange, offset, year) => {
const [month, rest] = dstChange.split('/');
const [day, hour] = rest.split(':');
return Date.UTC(year, month - 1, day, hour) - (offset * MSEC_IN_HOUR)
};
// compare epoch with dst change events (in utc)
const inSummerTime = (epoch, start, end, summerOffset, winterOffset) => {
const year = new Date(epoch).getUTCFullYear();
const startUtc = toUtc(start, winterOffset, year);
const endUtc = toUtc(end, summerOffset, year);
// simple number comparison now
return epoch >= startUtc && epoch < endUtc
};
/* eslint-disable no-console */
// this method avoids having to do a full dst-calculation on every operation
// it reproduces some things in ./index.js, but speeds up spacetime considerably
const quickOffset = s => {
let zones = s.timezones;
let obj = zones[s.tz];
if (obj === undefined) {
console.warn("Warning: couldn't find timezone " + s.tz);
return 0
}
if (obj.dst === undefined) {
return obj.offset
}
//get our two possible offsets
let jul = obj.offset;
let dec = obj.offset + 1; // assume it's the same for now
if (obj.hem === 'n') {
dec = jul - 1;
}
let split = obj.dst.split('->');
let inSummer = inSummerTime(s.epoch, split[0], split[1], jul, dec);
if (inSummer === true) {
return jul
}
return dec
};
var data = {
"9|s": "2/dili,2/jayapura",
"9|n": "2/chita,2/khandyga,2/pyongyang,2/seoul,2/tokyo,2/yakutsk,11/palau,japan,rok",
"9.5|s|04/06:03->10/05:04": "4/adelaide,4/broken_hill,4/south,4/yancowinna",
"9.5|s": "4/darwin,4/north",
"8|s|03/13:01->10/02:00": "12/casey",
"8|s": "2/kuala_lumpur,2/makassar,2/singapore,4/perth,2/ujung_pandang,4/west,singapore",
"8|n": "2/brunei,2/hong_kong,2/irkutsk,2/kuching,2/macau,2/manila,2/shanghai,2/taipei,2/ulaanbaatar,2/chongqing,2/chungking,2/harbin,2/macao,2/ulan_bator,2/choibalsan,hongkong,prc,roc",
"8.75|s": "4/eucla",
"7|s": "12/davis,2/jakarta,9/christmas",
"7|n": "2/bangkok,2/barnaul,2/hovd,2/krasnoyarsk,2/novokuznetsk,2/novosibirsk,2/phnom_penh,2/pontianak,2/ho_chi_minh,2/tomsk,2/vientiane,2/saigon",
"6|s": "12/vostok",
"6|n": "2/almaty,2/bishkek,2/dhaka,2/omsk,2/qyzylorda,2/qostanay,2/thimphu,2/urumqi,9/chagos,2/dacca,2/kashgar,2/thimbu",
"6.5|n": "2/yangon,9/cocos,2/rangoon",
"5|s": "12/mawson,9/kerguelen",
"5|n": "2/aqtau,2/aqtobe,2/ashgabat,2/atyrau,2/dushanbe,2/karachi,2/oral,2/samarkand,2/tashkent,2/yekaterinburg,9/maldives,2/ashkhabad",
"5.75|n": "2/kathmandu,2/katmandu",
"5.5|n": "2/kolkata,2/colombo,2/calcutta",
"4|s": "9/reunion",
"4|n": "2/baku,2/dubai,2/muscat,2/tbilisi,2/yerevan,8/astrakhan,8/samara,8/saratov,8/ulyanovsk,8/volgograd,9/mahe,9/mauritius,2/volgograd",
"4.5|n": "2/kabul",
"3|s": "12/syowa,9/antananarivo",
"3|n|04/25:02->10/30:24": "0/cairo,egypt",
"3|n|04/12:04->10/25:02": "2/gaza,2/hebron",
"3|n|03/30:05->10/26:04": "2/famagusta,2/nicosia,8/athens,8/bucharest,8/helsinki,8/kyiv,8/mariehamn,8/riga,8/sofia,8/tallinn,8/uzhgorod,8/vilnius,8/zaporozhye,8/nicosia,8/kiev,eet",
"3|n|03/30:04->10/26:03": "8/chisinau,8/tiraspol",
"3|n|03/30:02->10/25:24": "2/beirut",
"3|n|03/28:04->10/26:02": "2/jerusalem,2/tel_aviv,israel",
"3|n": "0/addis_ababa,0/asmara,0/asmera,0/dar_es_salaam,0/djibouti,0/juba,0/kampala,0/mogadishu,0/nairobi,2/aden,2/amman,2/baghdad,2/bahrain,2/damascus,2/kuwait,2/qatar,2/riyadh,8/istanbul,8/kirov,8/minsk,8/moscow,8/simferopol,9/comoro,9/mayotte,2/istanbul,turkey,w-su",
"3.5|n": "2/tehran,iran",
"2|s|03/30:04->10/26:02": "12/troll",
"2|s": "0/gaborone,0/harare,0/johannesburg,0/lubumbashi,0/lusaka,0/maputo,0/maseru,0/mbabane",
"2|n|03/30:04->10/26:03": "0/ceuta,arctic/longyearbyen,8/amsterdam,8/andorra,8/belgrade,8/berlin,8/bratislava,8/brussels,8/budapest,8/busingen,8/copenhagen,8/gibraltar,8/ljubljana,8/luxembourg,8/madrid,8/malta,8/monaco,8/oslo,8/paris,8/podgorica,8/prague,8/rome,8/san_marino,8/sarajevo,8/skopje,8/stockholm,8/tirane,8/vaduz,8/vatican,8/vienna,8/warsaw,8/zagreb,8/zurich,3/jan_mayen,poland,cet,met",
"2|n": "0/blantyre,0/bujumbura,0/khartoum,0/kigali,0/tripoli,8/kaliningrad,libya",
"1|s": "0/brazzaville,0/kinshasa,0/luanda,0/windhoek",
"1|n|03/30:03->10/26:02": "3/canary,3/faroe,3/madeira,8/dublin,8/guernsey,8/isle_of_man,8/jersey,8/lisbon,8/london,3/faeroe,eire,8/belfast,gb-eire,gb,portugal,wet",
"1|n": "0/algiers,0/bangui,0/douala,0/lagos,0/libreville,0/malabo,0/ndjamena,0/niamey,0/porto-novo,0/tunis",
"14|n": "11/kiritimati",
"13|s": "11/apia,11/tongatapu",
"13|n": "11/enderbury,11/kanton,11/fakaofo",
"12|s|04/06:03->09/28:04": "12/mcmurdo,11/auckland,12/south_pole,nz",
"12|s": "11/fiji",
"12|n": "2/anadyr,2/kamchatka,2/srednekolymsk,11/funafuti,11/kwajalein,11/majuro,11/nauru,11/tarawa,11/wake,11/wallis,kwajalein",
"12.75|s|04/06:03->04/06:02": "11/chatham,nz-chat",
"11|s|04/06:03->10/05:04": "12/macquarie",
"11|s": "11/bougainville",
"11|n": "2/magadan,2/sakhalin,11/efate,11/guadalcanal,11/kosrae,11/noumea,11/pohnpei,11/ponape",
"11.5|n|04/06:03->10/05:04": "11/norfolk",
"10|s|04/06:03->10/05:04": "4/currie,4/hobart,4/melbourne,4/sydney,4/act,4/canberra,4/nsw,4/tasmania,4/victoria",
"10|s": "12/dumontdurville,4/brisbane,4/lindeman,11/port_moresby,4/queensland",
"10|n": "2/ust-nera,2/vladivostok,11/guam,11/saipan,11/chuuk,11/truk,11/yap",
"10.5|s|04/06:01->10/05:02": "4/lord_howe,4/lhi",
"0|s|02/23:03->04/06:04": "0/casablanca,0/el_aaiun",
"0|n|03/30:02->10/26:01": "3/azores",
"0|n|03/30:01->10/25:24": "1/scoresbysund",
"0|n": "0/abidjan,0/accra,0/bamako,0/banjul,0/bissau,0/conakry,0/dakar,0/freetown,0/lome,0/monrovia,0/nouakchott,0/ouagadougou,0/sao_tome,1/danmarkshavn,3/reykjavik,3/st_helena,13/gmt,13/utc,0/timbuktu,13/greenwich,13/uct,13/universal,13/zulu,gmt-0,gmt+0,gmt0,greenwich,iceland,uct,universal,utc,zulu,13/unknown,factory",
"-9|n|03/09:04->11/02:02": "1/adak,1/atka,us/aleutian",
"-9|n": "11/gambier",
"-9.5|n": "11/marquesas",
"-8|n|03/09:04->11/02:02": "1/anchorage,1/juneau,1/metlakatla,1/nome,1/sitka,1/yakutat,us/alaska",
"-8|n": "11/pitcairn",
"-7|n|03/09:04->11/02:02": "1/los_angeles,1/santa_isabel,1/tijuana,1/vancouver,1/ensenada,6/pacific,10/bajanorte,us/pacific-new,us/pacific",
"-7|n": "1/creston,1/dawson,1/dawson_creek,1/fort_nelson,1/hermosillo,1/mazatlan,1/phoenix,1/whitehorse,6/yukon,10/bajasur,us/arizona,mst",
"-6|s|04/05:22->09/06:24": "11/easter,7/easterisland",
"-6|n|04/07:02->10/27:02": "1/merida",
"-6|n|03/09:04->11/02:02": "1/boise,1/cambridge_bay,1/denver,1/edmonton,1/inuvik,1/north_dakota,1/ojinaga,1/ciudad_juarez,1/yellowknife,1/shiprock,6/mountain,navajo,us/mountain",
"-6|n": "1/bahia_banderas,1/belize,1/chihuahua,1/costa_rica,1/el_salvador,1/guatemala,1/managua,1/mexico_city,1/monterrey,1/regina,1/swift_current,1/tegucigalpa,11/galapagos,6/east-saskatchewan,6/saskatchewan,10/general",
"-5|s": "1/lima,1/rio_branco,1/porto_acre,5/acre",
"-5|n|03/09:04->11/02:02": "1/chicago,1/matamoros,1/menominee,1/rainy_river,1/rankin_inlet,1/resolute,1/winnipeg,1/indiana/knox,1/indiana/tell_city,1/north_dakota/beulah,1/north_dakota/center,1/north_dakota/new_salem,1/knox_in,6/central,us/central,us/indiana-starke",
"-5|n": "1/bogota,1/cancun,1/cayman,1/coral_harbour,1/eirunepe,1/guayaquil,1/jamaica,1/panama,1/atikokan,jamaica,est",
"-4|s|04/05:24->09/07:02": "1/santiago,7/continental",
"-4|s|03/22:24->10/05:02": "1/asuncion",
"-4|s": "1/campo_grande,1/cuiaba,1/la_paz,1/manaus,5/west",
"-4|n|03/09:04->11/02:02": "1/detroit,1/grand_turk,1/indiana,1/indianapolis,1/iqaluit,1/kentucky,1/louisville,1/montreal,1/nassau,1/new_york,1/nipigon,1/pangnirtung,1/port-au-prince,1/thunder_bay,1/toronto,1/indiana/marengo,1/indiana/petersburg,1/indiana/vevay,1/indiana/vincennes,1/indiana/winamac,1/kentucky/monticello,1/fort_wayne,1/indiana/indianapolis,1/kentucky/louisville,6/eastern,us/east-indiana,us/eastern,us/michigan",
"-4|n|03/09:02->11/02:01": "1/havana,cuba",
"-4|n": "1/anguilla,1/antigua,1/aruba,1/barbados,1/blanc-sablon,1/boa_vista,1/caracas,1/curacao,1/dominica,1/grenada,1/guadeloupe,1/guyana,1/kralendijk,1/lower_princes,1/marigot,1/martinique,1/montserrat,1/port_of_spain,1/porto_velho,1/puerto_rico,1/santo_domingo,1/st_barthelemy,1/st_kitts,1/st_lucia,1/st_thomas,1/st_vincent,1/tortola,1/virgin",
"-3|s": "1/argentina,1/buenos_aires,1/catamarca,1/cordoba,1/fortaleza,1/jujuy,1/mendoza,1/montevideo,1/punta_arenas,1/sao_paulo,12/palmer,12/rothera,3/stanley,1/argentina/la_rioja,1/argentina/rio_gallegos,1/argentina/salta,1/argentina/san_juan,1/argentina/san_luis,1/argentina/tucuman,1/argentina/ushuaia,1/argentina/comodrivadavia,1/argentina/buenos_aires,1/argentina/catamarca,1/argentina/cordoba,1/argentina/jujuy,1/argentina/mendoza,1/argentina/rosario,1/rosario,5/east",
"-3|n|03/09:04->11/02:02": "1/glace_bay,1/goose_bay,1/halifax,1/moncton,1/thule,3/bermuda,6/atlantic",
"-3|n": "1/araguaina,1/bahia,1/belem,1/cayenne,1/maceio,1/paramaribo,1/recife,1/santarem",
"-2|n|03/09:04->11/02:02": "1/miquelon",
"-2|n": "1/noronha,3/south_georgia,5/denoronha",
"-2.5|n|03/09:04->11/02:02": "1/st_johns,6/newfoundland",
"-1|n|03/30:01->10/25:24": "1/nuuk,1/godthab",
"-1|n": "3/cape_verde",
"-11|n": "11/midway,11/niue,11/pago_pago,11/samoa,us/samoa",
"-10|n": "11/honolulu,11/johnston,11/rarotonga,11/tahiti,us/hawaii,hst"
};
//prefixes for iana names..
var prefixes = [
'africa',
'america',
'asia',
'atlantic',
'australia',
'brazil',
'canada',
'chile',
'europe',
'indian',
'mexico',
'pacific',
'antarctica',
'etc'
];
let all = {};
Object.keys(data).forEach((k) => {
let split = k.split('|');
let obj = {
offset: Number(split[0]),
hem: split[1]
};
if (split[2]) {
obj.dst = split[2];
}
let names = data[k].split(',');
names.forEach((str) => {
str = str.replace(/(^[0-9]+)\//, (before, num) => {
num = Number(num);
return prefixes[num] + '/'
});
all[str] = obj;
});
});
all.utc = {
offset: 0,
hem: 'n' //default to northern hemisphere - (sorry!)
};
//add etc/gmt+n
for (let i = -14; i <= 14; i += 0.5) {
let num = i;
if (num > 0) {
num = '+' + num;
}
let name = 'etc/gmt' + num;
all[name] = {
offset: i * -1, //they're negative!
hem: 'n' //(sorry)
};
name = 'utc/gmt' + num; //this one too, why not.
all[name] = {
offset: i * -1,
hem: 'n'
};
}
//find the implicit iana code for this machine.
//safely query the Intl object
//based on - https://bitbucket.org/pellepim/jstimezonedetect/src
const fallbackTZ = 'utc'; //
//this Intl object is not supported often, yet
const safeIntl = () => {
if (typeof Intl === 'undefined' || typeof Intl.DateTimeFormat === 'undefined') {
return null
}
let format = Intl.DateTimeFormat();
if (typeof format === 'undefined' || typeof format.resolvedOptions === 'undefined') {
return null
}
let timezone = format.resolvedOptions().timeZone;
if (!timezone) {
return null
}
return timezone.toLowerCase()
};
const guessTz = () => {
let timezone = safeIntl();
if (timezone === null) {
return fallbackTZ
}
return timezone
};
const isOffset$1 = /(-?[0-9]+)h(rs)?/i;
const isNumber$1 = /(-?[0-9]+)/;
const utcOffset$1 = /utc([\-+]?[0-9]+)/i;
const gmtOffset$1 = /gmt([\-+]?[0-9]+)/i;
const toIana$1 = function (num) {
num = Number(num);
if (num >= -13 && num <= 13) {
num = num * -1; //it's opposite!
num = (num > 0 ? '+' : '') + num; //add plus sign
return 'etc/gmt' + num
}
return null
};
const parseOffset$2 = function (tz) {
// '+5hrs'
let m = tz.match(isOffset$1);
if (m !== null) {
return toIana$1(m[1])
}
// 'utc+5'
m = tz.match(utcOffset$1);
if (m !== null) {
return toIana$1(m[1])
}
// 'GMT-5' (not opposite)
m = tz.match(gmtOffset$1);
if (m !== null) {
let num = Number(m[1]) * -1;
return toIana$1(num)
}
// '+5'
m = tz.match(isNumber$1);
if (m !== null) {
return toIana$1(m[1])
}
return null
};
/* eslint-disable no-console */
let local = guessTz();
//add all the city names by themselves
const cities = Object.keys(all).reduce((h, k) => {
let city = k.split('/')[1] || '';
city = city.replace(/_/g, ' ');
h[city] = k;
return h
}, {});
//try to match these against iana form
const normalize$4 = (tz) => {
tz = tz.replace(/ time/g, '');
tz = tz.replace(/ (standard|daylight|summer)/g, '');
tz = tz.replace(/\b(east|west|north|south)ern/g, '$1');
tz = tz.replace(/\b(africa|america|australia)n/g, '$1');
tz = tz.replace(/\beuropean/g, 'europe');
tz = tz.replace(/islands/g, 'island');
return tz
};
// try our best to reconcile the timzone to this given string
const lookupTz = (str, zones) => {
if (!str) {
// guard if Intl response is unsupported (#397)
if (!zones.hasOwnProperty(local)) {
console.warn(`Unrecognized IANA id '${local}'. Setting fallback tz to UTC.`);
local = 'utc';
}
return local
}
if (typeof str !== 'string') {
console.error("Timezone must be a string - recieved: '", str, "'\n");
}
let tz = str.trim();
// let split = str.split('/')
//support long timezones like 'America/Argentina/Rio_Gallegos'
// if (split.length > 2 && zones.hasOwnProperty(tz) === false) {
// tz = split[0] + '/' + split[1]
// }
tz = tz.toLowerCase();
if (zones.hasOwnProperty(tz) === true) {
return tz
}
//lookup more loosely..
tz = normalize$4(tz);
if (zones.hasOwnProperty(tz) === true) {
return tz
}
//try city-names
if (cities.hasOwnProperty(tz) === true) {
return cities[tz]
}
// //try to parse '-5h'
if (/[0-9]/.test(tz) === true) {
let id = parseOffset$2(tz);
if (id) {
return id
}
}
throw new Error(
"Spacetime: Cannot find timezone named: '" + str + "'. Please enter an IANA timezone id."
)
};
//git:blame @JuliasCaesar https://www.timeanddate.com/date/leapyear.html
function isLeapYear(year) { return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0 }
// unsurprisingly-nasty `typeof date` call
function isDate(d) { return Object.prototype.toString.call(d) === '[object Date]' && !isNaN(d.valueOf()) }
function isArray$1(input) { return Object.prototype.toString.call(input) === '[object Array]' }
function isObject(input) { return Object.prototype.toString.call(input) === '[object Object]' }
function isBoolean(input) { return Object.prototype.toString.call(input) === '[object Boolean]' }
function zeroPad(str, len = 2) {
let pad = '0';
str = str + '';
return str.length >= len ? str : new Array(len - str.length + 1).join(pad) + str
}
function titleCase$1(str) {
if (!str) {
return ''
}
return str[0].toUpperCase() + str.substr(1)
}
function ordinal(i) {
let j = i % 10;
let k = i % 100;
if (j === 1 && k !== 11) {
return i + 'st'
}
if (j === 2 && k !== 12) {
return i + 'nd'
}
if (j === 3 && k !== 13) {
return i + 'rd'
}
return i + 'th'
}
//strip 'st' off '1st'..
function toCardinal(str) {
str = String(str);
str = str.replace(/([0-9])(st|nd|rd|th)$/i, '$1');
return parseInt(str, 10)
}
//used mostly for cleanup of unit names, like 'months'
function normalize$3(str = '') {
str = str.toLowerCase().trim();
str = str.replace(/ies$/, 'y'); //'centuries'
str = str.replace(/s$/, '');
str = str.replace(/-/g, '');
if (str === 'day' || str === 'days') {
return 'date'
}
if (str === 'min' || str === 'mins') {
return 'minute'
}
return str
}
function getEpoch(tmp) {
//support epoch
if (typeof tmp === 'number') {
return tmp
}
//suport date objects
if (isDate(tmp)) {
return tmp.getTime()
}
// support spacetime objects
if (tmp.epoch || tmp.epoch === 0) {
return tmp.epoch
}
return null
}
//make sure this input is a spacetime obj
function beADate(d, s) {
if (isObject(d) === false) {
return s.clone().set(d)
}
return d
}
function formatTimezone(offset, delimiter = '') {
const sign = offset > 0 ? '+' : '-';
const absOffset = Math.abs(offset);
const hours = zeroPad(parseInt('' + absOffset, 10));
const minutes = zeroPad((absOffset % 1) * 60);
return `${sign}${hours}${delimiter}${minutes}`
}
/* eslint-disable no-console */
const defaults$1 = {
year: new Date().getFullYear(),
month: 0,
date: 1
};
//support [2016, 03, 01] format
const parseArray$1 = (s, arr, today) => {
if (arr.length === 0) {
return s
}
let order = ['year', 'month', 'date', 'hour', 'minute', 'second', 'millisecond'];
for (let i = 0; i < order.length; i++) {
let num = arr[i] || today[order[i]] || defaults$1[order[i]] || 0;
s = s[order[i]](num);
}
return s
};
//support {year:2016, month:3} format
const parseObject$1 = (s, obj, today) => {
// if obj is empty, do nothing
if (Object.keys(obj).length === 0) {
return s
}
obj = Object.assign({}, defaults$1, today, obj);
let keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
let unit = keys[i];
//make sure we have this method
if (s[unit] === undefined || typeof s[unit] !== 'function') {
continue
}
//make sure the value is a number
if (obj[unit] === null || obj[unit] === undefined || obj[unit] === '') {
continue
}
let num = obj[unit] || today[unit] || defaults$1[unit] || 0;
s = s[unit](num);
}
return s
};
// this may seem like an arbitrary number, but it's 'within jan 1970'
// this is only really ambiguous until 2054 or so
const parseNumber$1 = function (s, input) {
const minimumEpoch = 2500000000;
// if the given epoch is really small, they've probably given seconds and not milliseconds
// anything below this number is likely (but not necessarily) a mistaken input.
if (input > 0 && input < minimumEpoch && s.silent === false) {
console.warn(' - Warning: You are setting the date to January 1970.');
console.warn(' - did input seconds instead of milliseconds?');
}
s.epoch = input;
return s
};
var fns = {
parseArray: parseArray$1,
parseObject: parseObject$1,
parseNumber: parseNumber$1
};
// pull in 'today' data for the baseline moment
const getNow = function (s) {
s.epoch = Date.now();
Object.keys(s._today || {}).forEach((k) => {
if (typeof s[k] === 'function') {
s = s[k](s._today[k]);
}
});
return s
};
const dates$4 = {
now: (s) => {
return getNow(s)
},
today: (s) => {
return getNow(s)
},
tonight: (s) => {
s = getNow(s);
s = s.hour(18); //6pm
return s
},
tomorrow: (s) => {
s = getNow(s);
s = s.add(1, 'day');
s = s.startOf('day');
return s
},
yesterday: (s) => {
s = getNow(s);
s = s.subtract(1, 'day');
s = s.startOf('day');
return s
},
christmas: (s) => {
let year = getNow(s).year();
s = s.set([year, 11, 25, 18, 0, 0]); // Dec 25
return s
},
'new years': (s) => {
let year = getNow(s).year();
s = s.set([year, 11, 31, 18, 0, 0]); // Dec 31
return s
}
};
dates$4['new years eve'] = dates$4['new years'];
//little cleanup..
const normalize$2 = function (str) {
// remove all day-names
str = str.replace(/\b(mon|tues?|wed|wednes|thur?s?|fri|sat|satur|sun)(day)?\b/i, '');
//remove ordinal ending
str = str.replace(/([0-9])(th|rd|st|nd)/, '$1');
str = str.replace(/,/g, '');
str = str.replace(/ +/g, ' ').trim();
return str
};
let o = {
millisecond: 1
};
o.second = 1000;
o.minute = 60000;
o.hour = 3.6e6; // dst is supported post-hoc
o.day = 8.64e7; //
o.date = o.day;
o.month = 8.64e7 * 29.5; //(average)
o.week = 6.048e8;
o.year = 3.154e10; // leap-years are supported post-hoc
//add plurals
Object.keys(o).forEach(k => {
o[k + 's'] = o[k];
});
/* eslint-disable no-console */
//basically, step-forward/backward until js Date object says we're there.
const walk = (s, n, fn, unit, previous) => {
let current = s.d[fn]();
if (current === n) {
return //already there
}
let startUnit = previous === null ? null : s.d[previous]();
let original = s.epoch;
//try to get it as close as we can
let diff = n - current;
s.epoch += o[unit] * diff;
//DST edge-case: if we are going many days, be a little conservative
// console.log(unit, diff)
if (unit === 'day') {
// s.epoch -= ms.minute
//but don't push it over a month
if (Math.abs(diff) > 28 && n < 28) {
s.epoch += o.hour;
}
}
// 1st time: oops, did we change previous unit? revert it.
if (previous !== null && startUnit !== s.d[previous]()) {
// console.warn('spacetime warning: missed setting ' + unit)
s.epoch = original;
// s.epoch += ms[unit] * diff * 0.89 // maybe try and make it close...?
}
//repair it if we've gone too far or something
//(go by half-steps, just in case)
const halfStep = o[unit] / 2;
while (s.d[fn]() < n) {
s.epoch += halfStep;
}
while (s.d[fn]() > n) {
s.epoch -= halfStep;
}
// 2nd time: did we change previous unit? revert it.
if (previous !== null && startUnit !== s.d[previous]()) {
// console.warn('spacetime warning: missed setting ' + unit)
s.epoch = original;
}
};
//find the desired date by a increment/check while loop
const units$5 = {
year: {
valid: (n) => n > -4000 && n < 4000,
walkTo: (s, n) => walk(s, n, 'getFullYear', 'year', null)
},
month: {
valid: (n) => n >= 0 && n <= 11,
walkTo: (s, n) => {
let d = s.d;
let current = d.getMonth();
let original = s.epoch;
let startUnit = d.getFullYear();
if (current === n) {
return
}
//try to get it as close as we can..
let diff = n - current;
s.epoch += o.day * (diff * 28); //special case
//oops, did we change the year? revert it.
if (startUnit !== s.d.getFullYear()) {
s.epoch = original;
}
//increment by day
while (s.d.getMonth() < n) {
s.epoch += o.day;
}
while (s.d.getMonth() > n) {
s.epoch -= o.day;
}
}
},
date: {
valid: (n) => n > 0 && n <= 31,
walkTo: (s, n) => walk(s, n, 'getDate', 'day', 'getMonth')
},
hour: {
valid: (n) => n >= 0 && n < 24,
walkTo: (s, n) => walk(s, n, 'getHours', 'hour', 'getDate')
},
minute: {
valid: (n) => n >= 0 && n < 60,
walkTo: (s, n) => walk(s, n, 'getMinutes', 'minute', 'getHours')
},
second: {
valid: (n) => n >= 0 && n < 60,
walkTo: (s, n) => {
//do this one directly
s.epoch = s.seconds(n).epoch;
}
},
millisecond: {
valid: (n) => n >= 0 && n < 1000,
walkTo: (s, n) => {
//do this one directly
s.epoch = s.milliseconds(n).epoch;
}
}
};
const walkTo = (s, wants) => {
let keys = Object.keys(units$5);
let old = s.clone();
for (let i = 0; i < keys.length; i++) {
let k = keys[i];
let n = wants[k];
if (n === undefined) {
n = old[k]();
}
if (typeof n === 'string') {
n = parseInt(n, 10);
}
//make-sure it's valid
if (!units$5[k].valid(n)) {
s.epoch = null;
if (s.silent === false) {
console.warn('invalid ' + k + ': ' + n);
}
return
}
units$5[k].walkTo(s, n);
}
return
};
const monthLengths = [
31, // January - 31 days
28, // February - 28 days in a common year and 29 days in leap years
31, // March - 31 days
30, // April - 30 days
31, // May - 31 days
30, // June - 30 days
31, // July - 31 days
31, // August - 31 days
30, // September - 30 days
31, // October - 31 days
30, // November - 30 days
31 // December - 31 days
];
// 28 - feb
// 30 - april, june, sept, nov
// 31 - jan, march, may, july, aug, oct, dec
let shortMonths = [
'jan',
'feb',
'mar',
'apr',
'may',
'jun',
'jul',
'aug',
'sep',
'oct',
'nov',
'dec'
];
let longMonths = [
'january',
'february',
'march',
'april',
'may',
'june',
'july',
'august',
'september',
'october',
'november',
'december'
];
function buildMapping() {
const obj = {
sep: 8 //support this format
};
for (let i = 0; i < shortMonths.length; i++) {
obj[shortMonths[i]] = i;
}
for (let i = 0; i < longMonths.length; i++) {
obj[longMonths[i]] = i;
}
return obj
}
function short$1() { return shortMonths }
function long$1() { return longMonths }
function mapping$3() { return buildMapping() }
function set$5(i18n) {
shortMonths = i18n.short || shortMonths;
longMonths = i18n.long || longMonths;
}
//pull-apart ISO offsets, like "+0100"
const parseOffset$1 = (s, offset) => {
if (!offset) {
return s
}
offset = offset.trim().toLowerCase();
// according to ISO8601, tz could be hh:mm, hhmm or hh
// so need few more steps before the calculation.
let num = 0;
// for (+-)hh:mm
if (/^[+-]?[0-9]{2}:[0-9]{2}$/.test(offset)) {
//support "+01:00"
if (/:00/.test(offset) === true) {
offset = offset.replace(/:00/, '');
}
//support "+01:30"
if (/:30/.test(offset) === true) {
offset = offset.replace(/:30/, '.5');
}
}
// for (+-)hhmm
if (/^[+-]?[0-9]{4}$/.test(offset)) {
offset = offset.replace(/30$/, '.5');
}
num = parseFloat(offset);
//divide by 100 or 10 - , "+0100", "+01"
if (Math.abs(num) > 100) {
num = num / 100;
}
//this is a fancy-move
if (num === 0 || offset === 'Z' || offset === 'z') {
s.tz = 'etc/gmt';
return s
}
//okay, try to match it to a utc timezone
//remember - this is opposite! a -5 offset maps to Etc/GMT+5 ¯\_(:/)_/¯
//https://askubuntu.com/questions/519550/why-is-the-8-timezone-called-gmt-8-in-the-filesystem
num *= -1;
if (num >= 0) {
num = '+' + num;
}
let tz = 'etc/gmt' + num;
let zones = s.timezones;
if (zones[tz]) {
// log a warning if we're over-writing a given timezone?
// console.log('changing timezone to: ' + tz)
s.tz = tz;
}
return s
};
// truncate any sub-millisecond values
const parseMs = function (str = '') {
str = String(str);
//js does not support sub-millisecond values
// so truncate these - 2021-11-02T19:55:30.087772
if (str.length > 3) {
str = str.substring(0, 3);
} else if (str.length === 1) {
// assume ms are zero-padded on the left
// but maybe not on the right.
// turn '.10' into '.100'
str = str + '00';
} else if (str.length === 2) {
str = str + '0';
}
return Number(str) || 0
};
const parseTime$1 = (s, str = '') => {
// remove all whitespace
str = str.replace(/^\s+/, '').toLowerCase();
//formal time format - 04:30.23
let arr = str.match(/([0-9]{1,2}):([0-9]{1,2}):?([0-9]{1,2})?[:.]?([0-9]{1,4})?/);
if (arr !== null) {
let [, h, m, sec, ms] = arr;
//validate it a little
h = Number(h);
if (h < 0 || h > 24) {
return s.startOf('day')
}
m = Number(m); //don't accept '5:3pm'
if (arr[2].length < 2 || m < 0 || m > 59) {
return s.startOf('day')
}
s = s.hour(h);
s = s.minute(m);
s = s.seconds(sec || 0);
s = s.millisecond(parseMs(ms));
//parse-out am/pm
let ampm = str.match(/[0-9] ?(am|pm)\b/);
if (ampm !== null && ampm[1]) {
s = s.ampm(ampm[1]);
}
return s
}
//try an informal form - 5pm (no minutes)
arr = str.match(/([0-9]+) ?(am|pm)/);
if (arr !== null && arr[1]) {
let h = Number(arr[1]);
//validate it a little..
if (h > 12 || h < 1) {
return s.startOf('day')
}
s = s.hour(arr[1] || 0);
s = s.ampm(arr[2]);
s = s.startOf('hour');
return s
}
//no time info found, use start-of-day
s = s.startOf('day');
return s
};
let months$1 = mapping$3();
//given a month, return whether day number exists in it
const validate$1 = (obj) => {
//invalid values
if (monthLengths.hasOwnProperty(obj.month) !== true) {
return false
}
//support leap-year in february
if (obj.month === 1) {
if (isLeapYear(obj.year) && obj.date <= 29) {
return true
} else {
return obj.date <= 28
}
}
//is this date too-big for this month?
let max = monthLengths[obj.month] || 0;
if (obj.date <= max) {
return true
}
return false
};
const parseYear = (str = '', today) => {
str = str.trim();
// parse '86 shorthand
if (/^'[0-9][0-9]$/.test(str) === true) {
let num = Number(str.replace(/'/, ''));
if (num > 50) {
return 1900 + num
}
return 2000 + num
}
let year = parseInt(str, 10);
// use a given year from options.today
if (!year && today) {
year = today.year;
}
// fallback to this year
year = year || new Date().getFullYear();
return year
};
const parseMonth = function (str) {
str = str.toLowerCase().trim();
if (str === 'sept') {
return months$1.sep
}
return months$1[str]
};
var ymd = [
// =====
// y-m-d
// =====
//iso-this 1998-05-30T22:00:00:000Z, iso-that 2017-04-03T08:00:00-0700
{
reg: /^(-?0{0,2}[0-9]{3,4})-([0-9]{1,2})-([0-9]{1,2})[T| ]([0-9.:]+)(Z|[0-9-+:]+)?$/i,
parse: (s, m) => {
let obj = {
year: m[1],
month: parseInt(m[2], 10) - 1,
date: m[3]
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
parseOffset$1(s, m[5]);
walkTo(s, obj);
s = parseTime$1(s, m[4]);
return s
}
},
//short-iso "2015-03-25" or "2015/03/25" or "2015/03/25 12:26:14 PM"
{
reg: /^([0-9]{4})[\-/. ]([0-9]{1,2})[\-/. ]([0-9]{1,2})( [0-9]{1,2}(:[0-9]{0,2})?(:[0-9]{0,3})? ?(am|pm)?)?$/i,
parse: (s, m) => {
let obj = {
year: m[1],
month: parseInt(m[2], 10) - 1,
date: parseInt(m[3], 10)
};
if (obj.month >= 12) {
//support yyyy/dd/mm (weird, but ok)
obj.date = parseInt(m[2], 10);
obj.month = parseInt(m[3], 10) - 1;
}
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s, m[4]);
return s
}
},
//text-month "2015-feb-25"
{
reg: /^([0-9]{4})[\-/. ]([a-z]+)[\-/. ]([0-9]{1,2})( [0-9]{1,2}(:[0-9]{0,2})?(:[0-9]{0,3})? ?(am|pm)?)?$/i,
parse: (s, m) => {
let obj = {
year: parseYear(m[1], s._today),
month: parseMonth(m[2]),
date: toCardinal(m[3] || '')
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s, m[4]);
return s
}
}
];
var mdy = [
// =====
// m-d-y
// =====
//mm/dd/yyyy - uk/canada "6/28/2019, 12:26:14 PM"
{
reg: /^([0-9]{1,2})[-/.]([0-9]{1,2})[\-/.]?([0-9]{4})?( [0-9]{1,2}:[0-9]{2}:?[0-9]{0,2} ?(am|pm|gmt))?$/i,
parse: (s, arr) => {
let month = parseInt(arr[1], 10) - 1;
let date = parseInt(arr[2], 10);
//support dd/mm/yyy
if (s.british || month >= 12) {
date = parseInt(arr[1], 10);
month = parseInt(arr[2], 10) - 1;
}
let obj = {
date,
month,
year: parseYear(arr[3], s._today) || new Date().getFullYear()
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s, arr[4]);
return s
}
},
//alt short format - "feb-25-2015"
{
reg: /^([a-z]+)[\-/. ]([0-9]{1,2})[\-/. ]?([0-9]{4}|'[0-9]{2})?( [0-9]{1,2}(:[0-9]{0,2})?(:[0-9]{0,3})? ?(am|pm)?)?$/i,
parse: (s, arr) => {
let obj = {
year: parseYear(arr[3], s._today),
month: parseMonth(arr[1]),
date: toCardinal(arr[2] || '')
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s, arr[4]);
return s
}
},
//Long "Mar 25 2015"
//February 22, 2017 15:30:00
{
reg: /^([a-z]+) ([0-9]{1,2})( [0-9]{4})?( ([0-9:]+( ?am| ?pm| ?gmt)?))?$/i,
parse: (s, arr) => {
let obj = {
year: parseYear(arr[3], s._today),
month: parseMonth(arr[1]),
date: toCardinal(arr[2] || '')
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s, arr[4]);
return s
}
},
// 'Sun Mar 14 15:09:48 +0000 2021'
{
reg: /^([a-z]+) ([0-9]{1,2}) ([0-9]{1,2}:[0-9]{2}:?[0-9]{0,2})( \+[0-9]{4})?( [0-9]{4})?$/i,
parse: (s, arr) => {
let [, month, date, time, tz, year] = arr;
let obj = {
year: parseYear(year, s._today),
month: parseMonth(month),
date: toCardinal(date || '')
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseOffset$1(s, tz);
s = parseTime$1(s, time);
return s
}
}
];
var dmy = [
// =====
// d-m-y
// =====
//common british format - "25-feb-2015"
{
reg: /^([0-9]{1,2})[-/]([a-z]+)[\-/]?([0-9]{4})?$/i,
parse: (s, m) => {
let obj = {
year: parseYear(m[3], s._today),
month: parseMonth(m[2]),
date: toCardinal(m[1] || '')
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s, m[4]);
return s
}
},
// "25 Mar 2015"
{
reg: /^([0-9]{1,2})( [a-z]+)( [0-9]{4}| '[0-9]{2})? ?([0-9]{1,2}:[0-9]{2}:?[0-9]{0,2} ?(am|pm|gmt))?$/i,
parse: (s, m) => {
let obj = {
year: parseYear(m[3], s._today),
month: parseMonth(m[2]),
date: toCardinal(m[1])
};
if (!obj.month || validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s, m[4]);
return s
}
},
// 01-jan-2020
{
reg: /^([0-9]{1,2})[. \-/]([a-z]+)[. \-/]([0-9]{4})?( [0-9]{1,2}(:[0-9]{0,2})?(:[0-9]{0,3})? ?(am|pm)?)?$/i,
parse: (s, m) => {
let obj = {
date: Number(m[1]),
month: parseMonth(m[2]),
year: Number(m[3])
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = s.startOf('day');
s = parseTime$1(s, m[4]);
return s
}
}
];
var misc = [
// =====
// no dates
// =====
// '2012-06' month-only
{
reg: /^([0-9]{4})[\-/]([0-9]{2})$/,
parse: (s, m) => {
let obj = {
year: m[1],
month: parseInt(m[2], 10) - 1,
date: 1
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s, m[4]);
return s
}
},
//February 2017 (implied date)
{
reg: /^([a-z]+) ([0-9]{4})$/i,
parse: (s, arr) => {
let obj = {
year: parseYear(arr[2], s._today),
month: parseMonth(arr[1]),
date: s._today.date || 1
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s, arr[4]);
return s
}
},
{
// 'q2 2002'
reg: /^(q[0-9])( of)?( [0-9]{4})?/i,
parse: (s, arr) => {
let quarter = arr[1] || '';
s = s.quarter(quarter);
let year = arr[3] || '';
if (year) {
year = year.trim();
s = s.year(year);
}
return s
}
},
{
// 'summer 2002'
reg: /^(spring|summer|winter|fall|autumn)( of)?( [0-9]{4})?/i,
parse: (s, arr) => {
let season = arr[1] || '';
s = s.season(season);
let year = arr[3] || '';
if (year) {
year = year.trim();
s = s.year(year);
}
return s
}
},
{
// '200bc'
reg: /^[0-9,]+ ?b\.?c\.?$/i,
parse: (s, arr) => {
let str = arr[0] || '';
//make year-negative
str = str.replace(/^([0-9,]+) ?b\.?c\.?$/i, '-$1');
let d = new Date();
let obj = {
year: parseInt(str.trim(), 10),
month: d.getMonth(),
date: d.getDate()
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s);
return s
}
},
{
// '200ad'
reg: /^[0-9,]+ ?(a\.?d\.?|c\.?e\.?)$/i,
parse: (s, arr) => {
let str = arr[0] || '';
//remove commas
str = str.replace(/,/g, '');
let d = new Date();
let obj = {
year: parseInt(str.trim(), 10),
month: d.getMonth(),
date: d.getDate()
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s);
return s
}
},
{
// '1992'
reg: /^[0-9]{4}( ?a\.?d\.?)?$/i,
parse: (s, arr) => {
let today = s._today;
// using today's date, but a new month is awkward.
if (today.month && !today.date) {
today.date = 1;
}
let d = new Date();
let obj = {
year: parseYear(arr[0], today),
month: today.month || d.getMonth(),
date: today.date || d.getDate()
};
if (validate$1(obj) === false) {
s.epoch = null;
return s
}
walkTo(s, obj);
s = parseTime$1(s);
return s
}
}
];
var parsers = [].concat(ymd, mdy, dmy, misc);
/* eslint-disable no-console */
const parseString = function (s, input, givenTz) {
// let parsers = s.parsers || []
//try each text-parse template, use the first good result
for (let i = 0; i < parsers.length; i++) {
let m = input.match(parsers[i].reg);
if (m) {
let res = parsers[i].parse(s, m, givenTz);
if (res !== null && res.isValid()) {
return res
}
}
}
if (s.silent === false) {
console.warn("Warning: couldn't parse date-string: '" + input + "'");
}
s.epoch = null;
return s
};
const { parseArray, parseObject, parseNumber } = fns;
//we have to actually parse these inputs ourselves
// - can't use built-in js parser ;(
//=========================================
// ISO Date "2015-03-25"
// Short Date "03/25/2015" or "2015/03/25"
// Long Date "Mar 25 2015" or "25 Mar 2015"
// Full Date "Wednesday March 25 2015"
//=========================================
const defaults = {
year: new Date().getFullYear(),
month: 0,
date: 1
};
//find the epoch from different input styles
const parseInput = (s, input) => {
let today = s._today || defaults;
//if we've been given a epoch number, it's easy
if (typeof input === 'number') {
return parseNumber(s, input)
}
//set tmp time
s.epoch = Date.now();
// overwrite tmp time with 'today' value, if exists
if (s._today && isObject(s._today) && Object.keys(s._today).length > 0) {
let res = parseObject(s, today, defaults);
if (res.isValid()) {
s.epoch = res.epoch;
}
}
// null input means 'now'
if (input === null || input === undefined || input === '') {
return s //k, we're good.
}
//support input of Date() object
if (isDate(input) === true) {
s.epoch = input.getTime();
return s
}
//support [2016, 03, 01] format
if (isArray$1(input) === true) {
s = parseArray(s, input, today);
return s
}
//support {year:2016, month:3} format
if (isObject(input) === true) {
//support spacetime object as input
if (input.epoch) {
s.epoch = input.epoch;
s.tz = input.tz;
return s
}
s = parseObject(s, input, today);
return s
}
//input as a string..
if (typeof input !== 'string') {
return s
}
//little cleanup..
input = normalize$2(input);
//try some known-words, like 'now'
if (dates$4.hasOwnProperty(input) === true) {
s = dates$4[input](s);
return s
}
//try each text-parse template, use the first good result
return parseString(s, input)
};
let shortDays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
let longDays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
function short() { return shortDays }
function long() { return longDays }
function set$4(i18n) {
shortDays = i18n.short || shortDays;
longDays = i18n.long || longDays;
}
const aliases$1 = {
mo: 1,
tu: 2,
we: 3,
th: 4,
fr: 5,
sa: 6,
su: 7,
tues: 2,
weds: 3,
wedn: 3,
thur: 4,
thurs: 4
};
let titleCaseEnabled = true;
function useTitleCase() {
return titleCaseEnabled
}
function set$3(val) {
titleCaseEnabled = val;
}
// create the timezone offset part of an iso timestamp
// it's kind of nuts how involved this is
// "+01:00", "+0100", or simply "+01"
const isoOffset = s => {
let offset = s.timezone().current.offset;
return !offset ? 'Z' : formatTimezone(offset, ':')
};
const applyCaseFormat = (str) => {
if (useTitleCase()) {
return titleCase$1(str)
}
return str
};
// iso-year padding
const padYear = (num) => {
if (num >= 0) {
return zeroPad(num, 4)
} else {
num = Math.abs(num);
return '-' + zeroPad(num, 4)
}
};
const format = {
day: (s) => applyCaseFormat(s.dayName()),
'day-short': (s) => applyCaseFormat(short()[s.day()]),
'day-number': (s) => s.day(),
'day-ordinal': (s) => ordinal(s.day()),
'day-pad': (s) => zeroPad(s.day()),
date: (s) => s.date(),
'date-ordinal': (s) => ordinal(s.date()),
'date-pad': (s) => zeroPad(s.date()),
month: (s) => applyCaseFormat(s.monthName()),
'month-short': (s) => applyCaseFormat(short$1()[s.month()]),
'month-number': (s) => s.month(),
'month-ordinal': (s) => ordinal(s.month()),
'month-pad': (s) => zeroPad(s.month()),
'iso-month': (s) => zeroPad(s.month() + 1), //1-based months
year: (s) => {
let year = s.year();
if (year > 0) {
return year
}
year = Math.abs(year);
return year + ' BC'
},
'year-short': (s) => {
let year = s.year();
if (year > 0) {
return `'${String(s.year()).substr(2, 4)}`
}
year = Math.abs(year);
return year + ' BC'
},
'iso-year': (s) => {
let year = s.year();
let isNegative = year < 0;
let str = zeroPad(Math.abs(year), 4); //0-padded
if (isNegative) {
//negative years are for some reason 6-digits ('-00008')
str = zeroPad(str, 6);
str = '-' + str;
}
return str
},
time: (s) => s.time(),
'time-24': (s) => `${s.hour24()}:${zeroPad(s.minute())}`,
hour: (s) => s.hour12(),
'hour-pad': (s) => zeroPad(s.hour12()),
'hour-24': (s) => s.hour24(),
'hour-24-pad': (s) => zeroPad(s.hour24()),
minute: (s) => s.minute(),
'minute-pad': (s) => zeroPad(s.minute()),
second: (s) => s.second(),
'second-pad': (s) => zeroPad(s.second()),
millisecond: (s) => s.millisecond(),
'millisecond-pad': (s) => zeroPad(s.millisecond(), 3),
ampm: (s) => s.ampm(),
AMPM: (s) => s.ampm().toUpperCase(),
quarter: (s) => 'Q' + s.quarter(),
season: (s) => s.season(),
era: (s) => s.era(),
json: (s) => s.json(),
timezone: (s) => s.timezone().name,
offset: (s) => isoOffset(s),
numeric: (s) => `${s.year()}/${zeroPad(s.month() + 1)}/${zeroPad(s.date())}`, // yyyy/mm/dd
'numeric-us': (s) => `${zeroPad(s.month() + 1)}/${zeroPad(s.date())}/${s.year()}`, // mm/dd/yyyy
'numeric-uk': (s) => `${zeroPad(s.date())}/${zeroPad(s.month() + 1)}/${s.year()}`, //dd/mm/yyyy
'mm/dd': (s) => `${zeroPad(s.month() + 1)}/${zeroPad(s.date())}`, //mm/dd
// ... https://en.wikipedia.org/wiki/ISO_8601 ;(((
iso: (s) => {
let year = s.format('iso-year');
let month = zeroPad(s.month() + 1); //1-based months
let date = zeroPad(s.date());
let hour = zeroPad(s.h24());
let minute = zeroPad(s.minute());
let second = zeroPad(s.second());
let ms = zeroPad(s.millisecond(), 3);
let offset = isoOffset(s);
return `${year}-${month}-${date}T${hour}:${minute}:${second}.${ms}${offset}` //2018-03-09T08:50:00.000-05:00
},
'iso-short': (s) => {
let month = zeroPad(s.month() + 1); //1-based months
let date = zeroPad(s.date());
let year = padYear(s.year());
return `${year}-${month}-${date}` //2017-02-15
},
'iso-utc': (s) => {
return new Date(s.epoch).toISOString() //2017-03-08T19:45:28.367Z
},
//i made these up
nice: (s) => `${short$1()[s.month()]} ${ordinal(s.date())}, ${s.time()}`,
'nice-24': (s) =>
`${short$1()[s.month()]} ${ordinal(s.date())}, ${s.hour24()}:${zeroPad(
s.minute()
)}`,
'nice-year': (s) => `${short$1()[s.month()]} ${ordinal(s.date())}, ${s.year()}`,
'nice-day': (s) =>
`${short()[s.day()]} ${applyCaseFormat(short$1()[s.month()])} ${ordinal(
s.date()
)}`,
'nice-full': (s) =>
`${s.dayName()} ${applyCaseFormat(s.monthName())} ${ordinal(s.date())}, ${s.time()}`,
'nice-full-24': (s) =>
`${s.dayName()} ${applyCaseFormat(s.monthName())} ${ordinal(
s.date()
)}, ${s.hour24()}:${zeroPad(s.minute())}`
};
//aliases
const aliases = {
'day-name': 'day',
'month-name': 'month',
'iso 8601': 'iso',
'time-h24': 'time-24',
'time-12': 'time',
'time-h12': 'time',
tz: 'timezone',
'day-num': 'day-number',
'month-num': 'month-number',
'month-iso': 'iso-month',
'year-iso': 'iso-year',
'nice-short': 'nice',
'nice-short-24': 'nice-24',
mdy: 'numeric-us',
dmy: 'numeric-uk',
ymd: 'numeric',
'yyyy/mm/dd': 'numeric',
'mm/dd/yyyy': 'numeric-us',
'dd/mm/yyyy': 'numeric-us',
'little-endian': 'numeric-uk',
'big-endian': 'numeric',
'day-nice': 'nice-day'
};
Object.keys(aliases).forEach((k) => (format[k] = format[aliases[k]]));
const printFormat = (s, str = '') => {
//don't print anything if it's an invalid date
if (s.isValid() !== true) {
return ''
}
//support .format('month')
if (format.hasOwnProperty(str)) {
let out = format[str](s) || '';
if (str !== 'json') {
out = String(out);
if (str.toLowerCase() !== 'ampm') {
out = applyCaseFormat(out);
}
}
return out
}
//support '{hour}:{minute}' notation
if (str.indexOf('{') !== -1) {
let sections = /\{(.+?)\}/g;
str = str.replace(sections, (_, fmt) => {
fmt = fmt.trim();
if (fmt !== 'AMPM') {
fmt = fmt.toLowerCase();
}
if (format.hasOwnProperty(fmt)) {
let out = String(format[fmt](s));
if (fmt.toLowerCase() !== 'ampm') {
return applyCaseFormat(out)
}
return out
}
return ''
});
return str
}
return s.format('iso-short')
};
//parse this insane unix-time-templating thing, from the 19th century
//http://unicode.org/reports/tr35/tr35-25.html#Date_Format_Patterns
//time-symbols we support
const mapping$2 = {
G: (s) => s.era(),
GG: (s) => s.era(),
GGG: (s) => s.era(),
GGGG: (s) => (s.era() === 'AD' ? 'Anno Domini' : 'Before Christ'),
//year
y: (s) => s.year(),
yy: (s) => {
//last two chars
return zeroPad(Number(String(s.year()).substr(2, 4)))
},
yyy: (s) => s.year(),
yyyy: (s) => s.year(),
yyyyy: (s) => '0' + s.year(),
// u: (s) => {},//extended non-gregorian years
//quarter
Q: (s) => s.quarter(),
QQ: (s) => s.quarter(),
QQQ: (s) => s.quarter(),
QQQQ: (s) => s.quarter(),
//month
M: (s) => s.month() + 1,
MM: (s) => zeroPad(s.month() + 1),
MMM: (s) => s.format('month-short'),
MMMM: (s) => s.format('month'),
//week
w: (s) => s.week(),
ww: (s) => zeroPad(s.week()),
//week of month
// W: (s) => s.week(),
//date of month
d: (s) => s.date(),
dd: (s) => zeroPad(s.date()),
//date of year
D: (s) => s.dayOfYear(),
DD: (s) => zeroPad(s.dayOfYear()),
DDD: (s) => zeroPad(s.dayOfYear(), 3),
// F: (s) => {},//date of week in month
// g: (s) => {},//modified julian day
//day
E: (s) => s.format('day-short'),
EE: (s) => s.format('day-short'),
EEE: (s) => s.format('day-short'),
EEEE: (s) => s.format('day'),
EEEEE: (s) => s.format('day')[0],
e: (s) => s.day(),
ee: (s) => s.day(),
eee: (s) => s.format('day-short'),
eeee: (s) => s.format('day'),
eeeee: (s) => s.format('day')[0],
//am/pm
a: (s) => s.ampm().toUpperCase(),
aa: (s) => s.ampm().toUpperCase(),
aaa: (s) => s.ampm().toUpperCase(),
aaaa: (s) => s.ampm().toUpperCase(),
//hour
h: (s) => s.h12(),
hh: (s) => zeroPad(s.h12()),
H: (s) => s.hour(),
HH: (s) => zeroPad(s.hour()),
// j: (s) => {},//weird hour format
m: (s) => s.minute(),
mm: (s) => zeroPad(s.minute()),
s: (s) => s.second(),
ss: (s) => zeroPad(s.second()),
//milliseconds
SSS: (s) => zeroPad(s.millisecond(), 3),
//milliseconds in the day
A: (s) => s.epoch - s.startOf('day').epoch,
//timezone
z: (s) => s.timezone().name,
zz: (s) => s.timezone().name,
zzz: (s) => s.timezone().name,
zzzz: (s) => s.timezone().name,
Z: (s) => formatTimezone(s.timezone().current.offset),
ZZ: (s) => formatTimezone(s.timezone().current.offset),
ZZZ: (s) => formatTimezone(s.timezone().current.offset),
ZZZZ: (s) => formatTimezone(s.timezone().current.offset, ':')
};
const addAlias = (char, to, n) => {
let name = char;
let toName = to;
for (let i = 0; i < n; i += 1) {
mapping$2[name] = mapping$2[toName];
name += char;
toName += to;
}
};
addAlias('q', 'Q', 4);
addAlias('L', 'M', 4);
addAlias('Y', 'y', 4);
addAlias('c', 'e', 4);
addAlias('k', 'H', 2);
addAlias('K', 'h', 2);
addAlias('S', 's', 2);
addAlias('v', 'z', 4);
addAlias('V', 'Z', 4);
// support unix-style escaping with ' character
const escapeChars = function (arr) {
for (let i = 0; i < arr.length; i += 1) {
if (arr[i] === `'`) {
// greedy-search for next apostrophe
for (let o = i + 1; o < arr.length; o += 1) {
if (arr[o]) {
arr[i] += arr[o];
}
if (arr[o] === `'`) {
arr[o] = null;
break
}
arr[o] = null;
}
}
}
return arr.filter((ch) => ch)
};
//combine consecutive chars, like 'yyyy' as one.
const combineRepeated = function (arr) {
for (let i = 0; i < arr.length; i += 1) {
let c = arr[i];
// greedy-forward
for (let o = i + 1; o < arr.length; o += 1) {
if (arr[o] === c) {
arr[i] += arr[o];
arr[o] = null;
} else {
break
}
}
}
// '' means one apostrophe
arr = arr.filter((ch) => ch);
arr = arr.map((str) => {
if (str === `''`) {
str = `'`;
}
return str
});
return arr
};
const unixFmt = (s, str) => {
let arr = str.split('');
// support character escaping
arr = escapeChars(arr);
//combine 'yyyy' as string.
arr = combineRepeated(arr);
return arr.reduce((txt, c) => {
if (mapping$2[c] !== undefined) {
txt += mapping$2[c](s) || '';
} else {
// 'unescape'
if (/^'.+'$/.test(c)) {
c = c.replace(/'/g, '');
}
txt += c;
}
return txt
}, '')
};
const units$4 = ['year', 'season', 'quarter', 'month', 'week', 'day', 'quarterHour', 'hour', 'minute'];
const doUnit = function (s, k) {
let start = s.clone().startOf(k);
let end = s.clone().endOf(k);
let duration = end.epoch - start.epoch;
let percent = (s.epoch - start.epoch) / duration;
return parseFloat(percent.toFixed(2))
};
//how far it is along, from 0-1
const progress = (s, unit) => {
if (unit) {
unit = normalize$3(unit);
return doUnit(s, unit)
}
let obj = {};
units$4.forEach(k => {
obj[k] = doUnit(s, k);
});
return obj
};
/* eslint-disable no-console */
//round to either current, or +1 of this unit
const nearest = (s, unit) => {
//how far have we gone?
let prog = s.progress();
unit = normalize$3(unit);
//fix camel-case for this one
if (unit === 'quarterhour') {
unit = 'quarterHour';
}
if (prog[unit] !== undefined) {
// go forward one?
if (prog[unit] > 0.5) {
s = s.add(1, unit);
}
// go to start
s = s.startOf(unit);
} else if (s.silent === false) {
console.warn("no known unit '" + unit + "'");
}
return s
};
//increment until dates are the same
const climb = (a, b, unit) => {
let i = 0;
a = a.clone();
while (a.isBefore(b)) {
//do proper, expensive increment to catch all-the-tricks
a = a.add(1, unit);
i += 1;
}
//oops, we went too-far..
if (a.isAfter(b, unit)) {
i -= 1;
}
return i
};
// do a thurough +=1 on the unit, until they match
// for speed-reasons, only used on day, month, week.
const diffOne = (a, b, unit) => {
if (a.isBefore(b)) {
return climb(a, b, unit)
} else {
return climb(b, a, unit) * -1 //reverse it
}
};
// don't do anything too fancy here.
// 2020 - 2019 may be 1 year, or 0 years
// - '1 year difference' means 366 days during a leap year
const fastYear = (a, b) => {
let years = b.year() - a.year();
// should we decrement it by 1?
a = a.year(b.year());
if (a.isAfter(b)) {
years -= 1;
}
return years
};
// use a waterfall-method for computing a diff of any 'pre-knowable' units
// compute years, then compute months, etc..
// ... then ms-math for any very-small units
const diff = function (a, b) {
// an hour is always the same # of milliseconds
// so these units can be 'pre-calculated'
let msDiff = b.epoch - a.epoch;
let obj = {
milliseconds: msDiff,
seconds: parseInt(msDiff / 1000, 10)
};
obj.minutes = parseInt(obj.seconds / 60, 10);
obj.hours = parseInt(obj.minutes / 60, 10);
//do the year
let tmp = a.clone();
obj.years = fastYear(tmp, b);
tmp = a.add(obj.years, 'year');
//there's always 12 months in a year...
obj.months = obj.years * 12;
tmp = a.add(obj.months, 'month');
obj.months += diffOne(tmp, b, 'month');
// there's always 4 quarters in a year...
obj.quarters = obj.years * 4;
obj.quarters += parseInt((obj.months % 12) / 3, 10);
// there's always atleast 52 weeks in a year..
// (month * 4) isn't as close
obj.weeks = obj.years * 52;
tmp = a.add(obj.weeks, 'week');
obj.weeks += diffOne(tmp, b, 'week');
// there's always atleast 7 days in a week
obj.days = obj.weeks * 7;
tmp = a.add(obj.days, 'day');
obj.days += diffOne(tmp, b, 'day');
return obj
};
const reverseDiff = function (obj) {
Object.keys(obj).forEach((k) => {
obj[k] *= -1;
});
return obj
};
// this method counts a total # of each unit, between a, b.
// '1 month' means 28 days in february
// '1 year' means 366 days in a leap year
const main$1 = function (a, b, unit) {
b = beADate(b, a);
//reverse values, if necessary
let reversed = false;
if (a.isAfter(b)) {
let tmp = a;
a = b;
b = tmp;
reversed = true;
}
//compute them all (i know!)
let obj = diff(a, b);
if (reversed) {
obj = reverseDiff(obj);
}
//return just the requested unit
if (unit) {
//make sure it's plural-form
unit = normalize$3(unit);
if (/s$/.test(unit) !== true) {
unit += 's';
}
if (unit === 'dates') {
unit = 'days';
}
return obj[unit]
}
return obj
};
/*
ISO 8601 duration format
// https://en.wikipedia.org/wiki/ISO_8601#Durations
"P3Y6M4DT12H30M5S"
P the start of the duration representation.
Y the number of years.
M the number of months.
W the number of weeks.
D the number of days.
T of the representation.
H the number of hours.
M the number of minutes.
S the number of seconds.
*/
const fmt$1 = (n) => Math.abs(n) || 0;
const toISO = function (diff) {
let iso = 'P';
iso += fmt$1(diff.years) + 'Y';
iso += fmt$1(diff.months) + 'M';
iso += fmt$1(diff.days) + 'DT';
iso += fmt$1(diff.hours) + 'H';
iso += fmt$1(diff.minutes) + 'M';
iso += fmt$1(diff.seconds) + 'S';
return iso
};
//get number of hours/minutes... between the two dates
function getDiff(a, b) {
const isBefore = a.isBefore(b);
const later = isBefore ? b : a;
let earlier = isBefore ? a : b;
earlier = earlier.clone();
const diff = {
years: 0,
months: 0,
days: 0,
hours: 0,
minutes: 0,
seconds: 0
};
Object.keys(diff).forEach((unit) => {
if (earlier.isSame(later, unit)) {
return
}
let max = earlier.diff(later, unit);
earlier = earlier.add(max, unit);
diff[unit] = max;
});
//reverse it, if necessary
if (isBefore) {
Object.keys(diff).forEach((u) => {
if (diff[u] !== 0) {
diff[u] *= -1;
}
});
}
return diff
}
let units$3 = {
second: 'second',
seconds: 'seconds',
minute: 'minute',
minutes: 'minutes',
hour: 'hour',
hours: 'hours',
day: 'day',
days: 'days',
month: 'month',
months: 'months',
year: 'year',
years: 'years',
};
function unitsString(unit) {
return units$3[unit] || '';
}
function set$2(i18n = {}) {
units$3 = {
second: i18n.second || units$3.second,
seconds: i18n.seconds || units$3.seconds,
minute: i18n.minute || units$3.minute,
minutes: i18n.minutes || units$3.minutes,
hour: i18n.hour || units$3.hour,
hours: i18n.hours || units$3.hours,
day: i18n.day || units$3.day,
days: i18n.days || units$3.days,
month: i18n.month || units$3.month,
months: i18n.months || units$3.months,
year: i18n.year || units$3.year,
years: i18n.years || units$3.years,
};
}
let past = 'past';
let future = 'future';
let present = 'present';
let now = 'now';
let almost = 'almost';
let over = 'over';
let pastDistance = (value) => `${value} ago`;
let futureDistance = (value) => `in ${value}`;
function pastDistanceString(value) { return pastDistance(value) }
function futureDistanceString(value) { return futureDistance(value) }
function pastString() { return past }
function futureString() { return future }
function presentString() { return present }
function nowString() { return now }
function almostString() { return almost }
function overString() { return over }
function set$1(i18n) {
pastDistance = i18n.pastDistance || pastDistance;
futureDistance = i18n.futureDistance || futureDistance;
past = i18n.past || past;
future = i18n.future || future;
present = i18n.present || present;
now = i18n.now || now;
almost = i18n.almost || almost;
over = i18n.over || over;
}
//our conceptual 'break-points' for each unit
const qualifiers = {
months: {
almost: 10,
over: 4
},
days: {
almost: 25,
over: 10
},
hours: {
almost: 20,
over: 8
},
minutes: {
almost: 50,
over: 20
},
seconds: {
almost: 50,
over: 20
}
};
// Expects a plural unit arg
function pluralize(value, unit) {
if (value === 1) {
return value + ' ' + unitsString(unit.slice(0, -1))
}
return value + ' ' + unitsString(unit)
}
const toSoft = function (diff) {
let rounded = null;
let qualified = null;
let abbreviated = [];
let englishValues = [];
//go through each value and create its text-representation
Object.keys(diff).forEach((unit, i, units) => {
const value = Math.abs(diff[unit]);
if (value === 0) {
return
}
abbreviated.push(value + unit[0]);
const englishValue = pluralize(value, unit);
englishValues.push(englishValue);
if (!rounded) {
rounded = englishValue;
qualified = englishValue;
if (i > 4) {
return
}
//is it a 'almost' something, etc?
const nextUnit = units[i + 1];
const nextValue = Math.abs(diff[nextUnit]);
if (nextValue > qualifiers[nextUnit].almost) {
rounded = pluralize(value + 1, unit);
qualified = almostString() + ' ' + rounded;
} else if (nextValue > qualifiers[nextUnit].over) {
qualified = overString() + ' ' + englishValue;
}
}
});
return { qualified, rounded, abbreviated, englishValues }
};
//by spencermountain + Shaun Grady
//create the human-readable diff between the two dates
const since = (start, end) => {
end = beADate(end, start);
const diff = getDiff(start, end);
const isNow = Object.keys(diff).every((u) => !diff[u]);
if (isNow === true) {
return {
diff,
rounded: nowString(),
qualified: nowString(),
precise: nowString(),
abbreviated: [],
iso: 'P0Y0M0DT0H0M0S',
direction: presentString(),
}
}
let precise;
let direction = futureString();
let { rounded, qualified, englishValues, abbreviated } = toSoft(diff);
//make them into a string
precise = englishValues.splice(0, 2).join(', ');
//handle before/after logic
if (start.isAfter(end) === true) {
rounded = pastDistanceString(rounded);
qualified = pastDistanceString(qualified);
precise = pastDistanceString(precise);
direction = pastString();
} else {
rounded = futureDistanceString(rounded);
qualified = futureDistanceString(qualified);
precise = futureDistanceString(precise);
}
// https://en.wikipedia.org/wiki/ISO_8601#Durations
// P[n]Y[n]M[n]DT[n]H[n]M[n]S
let iso = toISO(diff);
return {
diff,
rounded,
qualified,
precise,
abbreviated,
iso,
direction,
}
};
//https://www.timeanddate.com/calendar/aboutseasons.html
// Spring - from March 1 to May 31;
// Summer - from June 1 to August 31;
// Fall (autumn) - from September 1 to November 30; and,
// Winter - from December 1 to February 28 (February 29 in a leap year).
const north = [
['spring', 2, 1],
['summer', 5, 1],
['fall', 8, 1],
['autumn', 8, 1],
['winter', 11, 1] //dec 1
];
const south = [
['fall', 2, 1],
['autumn', 2, 1],
['winter', 5, 1],
['spring', 8, 1],
['summer', 11, 1] //dec 1
];
var seasons$1 = { north, south };
var quarters = [
null,
[0, 1], //jan 1
[3, 1], //apr 1
[6, 1], //july 1
[9, 1] //oct 1
];
const units$2 = {
second: (s) => {
walkTo(s, {
millisecond: 0
});
return s
},
minute: (s) => {
walkTo(s, {
second: 0,
millisecond: 0
});
return s
},
quarterhour: (s) => {
let minute = s.minutes();
if (minute >= 45) {
s = s.minutes(45);
} else if (minute >= 30) {
s = s.minutes(30);
} else if (minute >= 15) {
s = s.minutes(15);
} else {
s = s.minutes(0);
}
walkTo(s, {
second: 0,
millisecond: 0
});
return s
},
hour: (s) => {
walkTo(s, {
minute: 0,
second: 0,
millisecond: 0
});
return s
},
day: (s) => {
walkTo(s, {
hour: 0,
minute: 0,
second: 0,
millisecond: 0
});
return s
},
week: (s) => {
let original = s.clone();
s = s.day(s._weekStart); //monday
if (s.isAfter(original)) {
s = s.subtract(1, 'week');
}
walkTo(s, {
hour: 0,
minute: 0,
second: 0,
millisecond: 0
});
return s
},
month: (s) => {
walkTo(s, {
date: 1,
hour: 0,
minute: 0,
second: 0,
millisecond: 0
});
return s
},
quarter: (s) => {
let q = s.quarter();
if (quarters[q]) {
walkTo(s, {
month: quarters[q][0],
date: quarters[q][1],
hour: 0,
minute: 0,
second: 0,
millisecond: 0
});
}
return s
},
season: (s) => {
let current = s.season();
let hem = 'north';
if (s.hemisphere() === 'South') {
hem = 'south';
}
for (let i = 0; i < seasons$1[hem].length; i++) {
if (seasons$1[hem][i][0] === current) {
//winter goes between years
let year = s.year();
if (current === 'winter' && s.month() < 3) {
year -= 1;
}
walkTo(s, {
year,
month: seasons$1[hem][i][1],
date: seasons$1[hem][i][2],
hour: 0,
minute: 0,
second: 0,
millisecond: 0
});
return s
}
}
return s
},
year: (s) => {
walkTo(s, {
month: 0,
date: 1,
hour: 0,
minute: 0,
second: 0,
millisecond: 0
});
return s
},
decade: (s) => {
s = s.startOf('year');
let year = s.year();
let decade = parseInt(year / 10, 10) * 10;
s = s.year(decade);
return s
},
century: (s) => {
s = s.startOf('year');
let year = s.year();
// near 0AD goes '-1 | +1'
let decade = parseInt(year / 100, 10) * 100;
s = s.year(decade);
return s
}
};
units$2.date = units$2.day;
const startOf = (a, unit) => {
let s = a.clone();
unit = normalize$3(unit);
if (units$2[unit]) {
return units$2[unit](s)
}
if (unit === 'summer' || unit === 'winter') {
s = s.season(unit);
return units$2.season(s)
}
return s
};
//piggy-backs off startOf
const endOf = (a, unit) => {
let s = a.clone();
unit = normalize$3(unit);
if (units$2[unit]) {
// go to beginning, go to next one, step back 1ms
s = units$2[unit](s); // startof
s = s.add(1, unit);
s = s.subtract(1, 'millisecond');
return s
}
return s
};
//is it 'wednesday'?
const isDay = function (unit) {
if (short().find((s) => s === unit)) {
return true
}
if (long().find((s) => s === unit)) {
return true
}
return false
};
// return a list of the weeks/months/days between a -> b
// returns spacetime objects in the timezone of the input
const every = function (start, unit, end, stepCount = 1) {
if (!unit || !end) {
return []
}
//cleanup unit param
unit = normalize$3(unit);
//cleanup to param
end = start.clone().set(end);
//swap them, if they're backwards
if (start.isAfter(end)) {
let tmp = start;
start = end;
end = tmp;
}
//prevent going beyond end if unit/stepCount > than the range
if (start.diff(end, unit) < stepCount) {
return []
}
//support 'every wednesday'
let d = start.clone();
if (isDay(unit)) {
d = d.next(unit);
unit = 'week';
} else {
let first = d.startOf(unit);
if (first.isBefore(start)) {
d = d.next(unit);
}
}
//okay, actually start doing it
let result = [];
while (d.isBefore(end)) {
result.push(d);
d = d.add(stepCount, unit);
}
return result
};
/* eslint-disable no-console */
const parseDst = dst => {
if (!dst) {
return []
}
return dst.split('->')
};
//iana codes are case-sensitive, technically
const titleCase = str => {
str = str[0].toUpperCase() + str.substr(1);
str = str.replace(/[/_-]([a-z])/gi, s => {
return s.toUpperCase()
});
str = str.replace(/_(of|es)_/i, (s) => s.toLowerCase());
str = str.replace(/\/gmt/i, '/GMT');
str = str.replace(/\/Dumontdurville$/i, '/DumontDUrville');
str = str.replace(/\/Mcmurdo$/i, '/McMurdo');
str = str.replace(/\/Port-au-prince$/i, '/Port-au-Prince');
return str
};
//get metadata about this timezone
const timezone = s => {
let zones = s.timezones;
let tz = s.tz;
if (zones.hasOwnProperty(tz) === false) {
tz = lookupTz(s.tz, zones);
}
if (tz === null) {
if (s.silent === false) {
console.warn("Warn: could not find given or local timezone - '" + s.tz + "'");
}
return {
current: {
epochShift: 0
}
}
}
let found = zones[tz];
let result = {
name: titleCase(tz),
hasDst: Boolean(found.dst),
default_offset: found.offset,
//do north-hemisphere version as default (sorry!)
hemisphere: found.hem === 's' ? 'South' : 'North',
current: {}
};
if (result.hasDst) {
let arr = parseDst(found.dst);
result.change = {
start: arr[0],
back: arr[1]
};
}
//find the offsets for summer/winter times
//(these variable names are north-centric)
let summer = found.offset; // (july)
let winter = summer; // (january) assume it's the same for now
if (result.hasDst === true) {
if (result.hemisphere === 'North') {
winter = summer - 1;
} else {
//southern hemisphere
winter = found.offset + 1;
}
}
//find out which offset to use right now
//use 'summer' time july-time
if (result.hasDst === false) {
result.current.offset = summer;
result.current.isDST = false;
} else if (inSummerTime(s.epoch, result.change.start, result.change.back, summer, winter) === true) {
result.current.offset = summer;
result.current.isDST = result.hemisphere === 'North'; //dst 'on' in winter in north
} else {
//use 'winter' january-time
result.current.offset = winter;
result.current.isDST = result.hemisphere === 'South'; //dst 'on' in summer in south
}
return result
};
/* eslint-disable no-console */
const units$1 = [
'century',
'decade',
'year',
'month',
'date',
'day',
'hour',
'minute',
'second',
'millisecond'
];
//the spacetime instance methods (also, the API)
const methods$4 = {
set: function (input, tz) {
let s = this.clone();
s = parseInput(s, input);
if (tz) {
this.tz = lookupTz(tz);
}
return s
},
timezone: function () {
return timezone(this)
},
isDST: function () {
return timezone(this).current.isDST
},
hasDST: function () {
return timezone(this).hasDst
},
offset: function () {
return timezone(this).current.offset * 60
},
hemisphere: function () {
return timezone(this).hemisphere
},
format: function (fmt) {
return printFormat(this, fmt)
},
unixFmt: function (fmt) {
return unixFmt(this, fmt)
},
startOf: function (unit) {
return startOf(this, unit)
},
endOf: function (unit) {
return endOf(this, unit)
},
leapYear: function () {
let year = this.year();
return isLeapYear(year)
},
progress: function (unit) {
return progress(this, unit)
},
nearest: function (unit) {
return nearest(this, unit)
},
diff: function (d, unit) {
return main$1(this, d, unit)
},
since: function (d) {
if (!d) {
d = this.clone().set();
}
return since(this, d)
},
next: function (unit) {
let s = this.add(1, unit);
return s.startOf(unit)
},
//the start of the previous year/week/century
last: function (unit) {
let s = this.subtract(1, unit);
return s.startOf(unit)
},
isValid: function () {
//null/undefined epochs
if (!this.epoch && this.epoch !== 0) {
return false
}
return !isNaN(this.d.getTime())
},
//travel to this timezone
goto: function (tz) {
let s = this.clone();
s.tz = lookupTz(tz, s.timezones); //science!
return s
},
//get each week/month/day between a -> b
every: function (unit, to, stepCount) {
// allow swapping these params:
if (typeof unit === 'object' && typeof to === 'string') {
let tmp = to;
to = unit;
unit = tmp;
}
return every(this, unit, to, stepCount)
},
isAwake: function () {
let hour = this.hour();
//10pm -> 8am
if (hour < 8 || hour > 22) {
return false
}
return true
},
isAsleep: function () {
return !this.isAwake()
},
daysInMonth: function () {
switch (this.month()) {
case 0:
return 31
case 1:
return this.leapYear() ? 29 : 28
case 2:
return 31
case 3:
return 30
case 4:
return 31
case 5:
return 30
case 6:
return 31
case 7:
return 31
case 8:
return 30
case 9:
return 31
case 10:
return 30
case 11:
return 31
default:
throw new Error('Invalid Month state.')
}
},
//pretty-printing
log: function () {
console.log('');
console.log(printFormat(this, 'nice-short'));
return this
},
logYear: function () {
console.log('');
console.log(printFormat(this, 'full-short'));
return this
},
json: function () {
return units$1.reduce((h, unit) => {
h[unit] = this[unit]();
return h
}, {})
},
debug: function () {
let tz = this.timezone();
let date = this.format('MM') + ' ' + this.format('date-ordinal') + ' ' + this.year();
date += '\n - ' + this.format('time');
console.log('\n\n', date + '\n - ' + tz.name + ' (' + tz.current.offset + ')');
return this
},
//alias of 'since' but opposite - like moment.js
from: function (d) {
d = this.clone().set(d);
return d.since(this)
},
fromNow: function () {
let d = this.clone().set(Date.now());
return d.since(this)
},
weekStart: function (input) {
//accept a number directly
if (typeof input === 'number') {
this._weekStart = input;
return this
}
if (typeof input === 'string') {
// accept 'wednesday'
input = input.toLowerCase().trim();
let num = short().indexOf(input);
if (num === -1) {
num = long().indexOf(input);
}
if (num === -1) {
num = 1; //go back to default
}
this._weekStart = num;
} else {
console.warn('Spacetime Error: Cannot understand .weekStart() input:', input);
}
return this
}
};
// aliases
methods$4.inDST = methods$4.isDST;
methods$4.round = methods$4.nearest;
methods$4.each = methods$4.every;
// javascript setX methods like setDate() can't be used because of the local bias
//these methods wrap around them.
const validate = (n) => {
//handle number as a string
if (typeof n === 'string') {
n = parseInt(n, 10);
}
return n
};
const order$1 = ['year', 'month', 'date', 'hour', 'minute', 'second', 'millisecond'];
//reduce hostile micro-changes when moving dates by millisecond
const confirm = (s, tmp, unit) => {
let n = order$1.indexOf(unit);
let arr = order$1.slice(n, order$1.length);
for (let i = 0; i < arr.length; i++) {
let want = tmp[arr[i]]();
s[arr[i]](want);
}
return s
};
// allow specifying setter direction
const fwdBkwd = function (s, old, goFwd, unit) {
if (goFwd === true && s.isBefore(old)) {
s = s.add(1, unit);
} else if (goFwd === false && s.isAfter(old)) {
s = s.minus(1, unit);
}
return s
};
const milliseconds = function (s, n) {
n = validate(n);
let current = s.millisecond();
let diff = current - n; //milliseconds to shift by
return s.epoch - diff
};
const seconds = function (s, n, goFwd) {
n = validate(n);
let old = s.clone();
let diff = s.second() - n;
let shift = diff * o.second;
s.epoch = s.epoch - shift;
s = fwdBkwd(s, old, goFwd, 'minute'); // specify direction
return s.epoch
};
const minutes = function (s, n, goFwd) {
n = validate(n);
let old = s.clone();
let diff = s.minute() - n;
let shift = diff * o.minute;
s.epoch -= shift;
confirm(s, old, 'second');
s = fwdBkwd(s, old, goFwd, 'hour'); // specify direction
return s.epoch
};
const hours = function (s, n, goFwd) {
n = validate(n);
if (n >= 24) {
n = 24;
} else if (n < 0) {
n = 0;
}
let old = s.clone();
let diff = s.hour() - n;
let shift = diff * o.hour;
s.epoch -= shift;
// oops, did we change the day?
if (s.date() !== old.date()) {
s = old.clone();
if (diff > 1) {
diff -= 1;
}
if (diff < 1) {
diff += 1;
}
shift = diff * o.hour;
s.epoch -= shift;
}
walkTo(s, {
hour: n
});
confirm(s, old, 'minute');
s = fwdBkwd(s, old, goFwd, 'day'); // specify direction
return s.epoch
};
const time = function (s, str, goFwd) {
let m = str.match(/([0-9]{1,2})[:h]([0-9]{1,2})(:[0-9]{1,2})? ?(am|pm)?/);
if (!m) {
//fallback to support just '2am'
m = str.match(/([0-9]{1,2}) ?(am|pm)/);
if (!m) {
return s.epoch
}
m.splice(2, 0, '0'); //add implicit 0 minutes
m.splice(3, 0, ''); //add implicit seconds
}
let h24 = false;
let hour = parseInt(m[1], 10);
let minute = parseInt(m[2], 10);
if (minute >= 60) {
minute = 59;
}
if (hour > 12) {
h24 = true;
}
//make the hour into proper 24h time
if (h24 === false) {
if (m[4] === 'am' && hour === 12) {
//12am is midnight
hour = 0;
}
if (m[4] === 'pm' && hour < 12) {
//12pm is noon
hour += 12;
}
}
// handle seconds
m[3] = m[3] || '';
m[3] = m[3].replace(/:/, '');
let sec = parseInt(m[3], 10) || 0;
let old = s.clone();
s = s.hour(hour);
s = s.minute(minute);
s = s.second(sec);
s = s.millisecond(0);
s = fwdBkwd(s, old, goFwd, 'day'); // specify direction
return s.epoch
};
const date = function (s, n, goFwd) {
n = validate(n);
//avoid setting february 31st
if (n > 28) {
let month = s.month();
let max = monthLengths[month];
// support leap day in february
if (month === 1 && n === 29 && isLeapYear(s.year())) {
max = 29;
}
if (n > max) {
n = max;
}
}
//avoid setting < 0
if (n <= 0) {
n = 1;
}
let old = s.clone();
walkTo(s, {
date: n
});
s = fwdBkwd(s, old, goFwd, 'month'); // specify direction
return s.epoch
};
const month = function (s, n, goFwd) {
if (typeof n === 'string') {
if (n === 'sept') {
n = 'sep';
}
n = mapping$3()[n.toLowerCase()];
}
n = validate(n);
//don't go past december
if (n >= 12) {
n = 11;
}
if (n <= 0) {
n = 0;
}
let d = s.date();
//there's no 30th of february, etc.
if (d > monthLengths[n]) {
//make it as close as we can..
d = monthLengths[n];
}
let old = s.clone();
walkTo(s, {
month: n,
d
});
s = fwdBkwd(s, old, goFwd, 'year'); // specify direction
return s.epoch
};
const year = function (s, n) {
// support '97
if (typeof n === 'string' && /^'[0-9]{2}$/.test(n)) {
n = n.replace(/'/, '').trim();
n = Number(n);
// '89 is 1989
if (n > 30) {
//change this in 10y
n = 1900 + n;
} else {
// '12 is 2012
n = 2000 + n;
}
}
n = validate(n);
walkTo(s, {
year: n
});
return s.epoch
};
const week = function (s, n, goFwd) {
let old = s.clone();
n = validate(n);
s = s.month(0);
s = s.date(1);
s = s.day('monday');
//first week starts first Thurs in Jan
// so mon dec 28th is 1st week
// so mon dec 29th is not the week
if (s.monthName() === 'december' && s.date() >= 28) {
s = s.add(1, 'week');
}
n -= 1; //1-based
s = s.add(n, 'weeks');
s = fwdBkwd(s, old, goFwd, 'year'); // specify direction
return s.epoch
};
const dayOfYear = function (s, n, goFwd) {
n = validate(n);
let old = s.clone();
n -= 1; //days are 1-based
if (n <= 0) {
n = 0;
} else if (n >= 365) {
if (isLeapYear(s.year())) {
n = 365;
} else {
n = 364;
}
}
s = s.startOf('year');
s = s.add(n, 'day');
confirm(s, old, 'hour');
s = fwdBkwd(s, old, goFwd, 'year'); // specify direction
return s.epoch
};
let morning = 'am';
let evening = 'pm';
function am() { return morning }
function pm() { return evening }
function set(i18n) {
morning = i18n.am || morning;
evening = i18n.pm || evening;
}
const methods$3 = {
millisecond: function (num) {
if (num !== undefined) {
let s = this.clone();
s.epoch = milliseconds(s, num);
return s
}
return this.d.getMilliseconds()
},
second: function (num, goFwd) {
if (num !== undefined) {
let s = this.clone();
s.epoch = seconds(s, num, goFwd);
return s
}
return this.d.getSeconds()
},
minute: function (num, goFwd) {
if (num !== undefined) {
let s = this.clone();
s.epoch = minutes(s, num, goFwd);
return s
}
return this.d.getMinutes()
},
hour: function (num, goFwd) {
let d = this.d;
if (num !== undefined) {
let s = this.clone();
s.epoch = hours(s, num, goFwd);
return s
}
return d.getHours()
},
//'3:30' is 3.5
hourFloat: function (num, goFwd) {
if (num !== undefined) {
let s = this.clone();
let minute = num % 1;
minute = minute * 60;
let hour = parseInt(num, 10);
s.epoch = hours(s, hour, goFwd);
s.epoch = minutes(s, minute, goFwd);
return s
}
let d = this.d;
let hour = d.getHours();
let minute = d.getMinutes();
minute = minute / 60;
return hour + minute
},
// hour in 12h format
hour12: function (str, goFwd) {
let d = this.d;
if (str !== undefined) {
let s = this.clone();
str = '' + str;
let m = str.match(/^([0-9]+)(am|pm)$/);
if (m) {
let hour = parseInt(m[1], 10);
if (m[2] === 'pm') {
hour += 12;
}
s.epoch = hours(s, hour, goFwd);
}
return s
}
//get the hour
let hour12 = d.getHours();
if (hour12 > 12) {
hour12 = hour12 - 12;
}
if (hour12 === 0) {
hour12 = 12;
}
return hour12
},
//some ambiguity here with 12/24h
time: function (str, goFwd) {
if (str !== undefined) {
let s = this.clone();
str = str.toLowerCase().trim();
s.epoch = time(s, str, goFwd);
return s
}
return `${this.h12()}:${zeroPad(this.minute())}${this.ampm()}`
},
// either 'am' or 'pm'
ampm: function (input, goFwd) {
// let which = 'am'
let which = am();
let hour = this.hour();
if (hour >= 12) {
// which = 'pm'
which = pm();
}
if (typeof input !== 'string') {
return which
}
//okay, we're doing a setter
let s = this.clone();
input = input.toLowerCase().trim();
//ampm should never change the day
// - so use `.hour(n)` instead of `.minus(12,'hour')`
if (hour >= 12 && input === 'am') {
//noon is 12pm
hour -= 12;
return s.hour(hour, goFwd)
}
if (hour < 12 && input === 'pm') {
hour += 12;
return s.hour(hour, goFwd)
}
return s
},
//some hard-coded times of day, like 'noon'
dayTime: function (str, goFwd) {
if (str !== undefined) {
const times = {
morning: '7:00',
breakfast: '7:00',
noon: '12:00',
lunch: '12:00',
afternoon: '14:00',
evening: '18:00',
dinner: '18:00',
night: '23:00',
midnight: '00:00'
};
let s = this.clone();
str = str || '';
str = str.toLowerCase();
if (times.hasOwnProperty(str) === true) {
s = s.time(times[str], goFwd);
}
return s
}
let h = this.hour();
if (h < 6) {
return 'night'
}
if (h < 12) {
//until noon
return 'morning'
}
if (h < 17) {
//until 5pm
return 'afternoon'
}
if (h < 22) {
//until 10pm
return 'evening'
}
return 'night'
},
//parse a proper iso string
iso: function (num) {
if (num !== undefined) {
return this.set(num)
}
return this.format('iso')
}
};
const methods$2 = {
// # day in the month
date: function (num, goFwd) {
if (num !== undefined) {
let s = this.clone();
num = parseInt(num, 10);
if (num) {
s.epoch = date(s, num, goFwd);
}
return s
}
return this.d.getDate()
},
//like 'wednesday' (hard!)
day: function (input, goFwd) {
if (input === undefined) {
return this.d.getDay()
}
let original = this.clone();
let want = input;
// accept 'wednesday'
if (typeof input === 'string') {
input = input.toLowerCase();
if (aliases$1.hasOwnProperty(input)) {
want = aliases$1[input];
} else {
want = short().indexOf(input);
if (want === -1) {
want = long().indexOf(input);
}
}
}
//move approx
let day = this.d.getDay();
let diff = day - want;
if (goFwd === true && diff > 0) {
diff = diff - 7;
}
if (goFwd === false && diff < 0) {
diff = diff + 7;
}
let s = this.subtract(diff, 'days');
//tighten it back up
walkTo(s, {
hour: original.hour(),
minute: original.minute(),
second: original.second()
});
return s
},
//these are helpful name-wrappers
dayName: function (input, goFwd) {
if (input === undefined) {
return long()[this.day()]
}
let s = this.clone();
s = s.day(input, goFwd);
return s
}
};
/* eslint-disable no-console */
const clearMinutes = (s) => {
s = s.minute(0);
s = s.second(0);
s = s.millisecond(1);
return s
};
const methods$1 = {
// day 0-366
dayOfYear: function (num, goFwd) {
if (num !== undefined) {
let s = this.clone();
s.epoch = dayOfYear(s, num, goFwd);
return s
}
//days since newyears - jan 1st is 1, jan 2nd is 2...
let sum = 0;
let month = this.d.getMonth();
let tmp;
//count the num days in each month
for (let i = 1; i <= month; i++) {
tmp = new Date();
tmp.setDate(1);
tmp.setFullYear(this.d.getFullYear()); //the year matters, because leap-years
tmp.setHours(1);
tmp.setMinutes(1);
tmp.setMonth(i);
tmp.setHours(-2); //the last day of the month
sum += tmp.getDate();
}
return sum + this.d.getDate()
},
//since the start of the year
week: function (num, goFwd) {
// week-setter
if (num !== undefined) {
let s = this.clone();
s.epoch = week(this, num, goFwd);
s = clearMinutes(s);
return s
}
//find-out which week it is
let tmp = this.clone();
tmp = tmp.month(0);
tmp = tmp.date(1);
tmp = clearMinutes(tmp);
tmp = tmp.day('monday');
//don't go into last-year
if (tmp.month() === 11 && tmp.date() >= 25) {
tmp = tmp.add(1, 'week');
}
// is first monday the 1st?
let toAdd = 1;
if (tmp.date() === 1) {
toAdd = 0;
}
tmp = tmp.minus(2, 'hours');
const thisOne = this.epoch;
//if the week technically hasn't started yet
if (tmp.epoch > thisOne) {
return 1
}
//speed it up, if we can
let i = 0;
let skipWeeks = this.month() * 4;
tmp.epoch += o.week * skipWeeks;
i += skipWeeks;
for (; i <= 52; i++) {
if (tmp.epoch > thisOne) {
return i + toAdd
}
tmp = tmp.add(1, 'week');
}
return 52
},
//either name or number
month: function (input, goFwd) {
if (input !== undefined) {
let s = this.clone();
s.epoch = month(s, input, goFwd);
return s
}
return this.d.getMonth()
},
//'january'
monthName: function (input, goFwd) {
if (input !== undefined) {
let s = this.clone();
s = s.month(input, goFwd);
return s
}
return long$1()[this.month()]
},
//q1, q2, q3, q4
quarter: function (num, goFwd) {
if (num !== undefined) {
if (typeof num === 'string') {
num = num.replace(/^q/i, '');
num = parseInt(num, 10);
}
if (quarters[num]) {
let s = this.clone();
let month = quarters[num][0];
s = s.month(month, goFwd);
s = s.date(1, goFwd);
s = s.startOf('day');
return s
}
}
let month = this.d.getMonth();
for (let i = 1; i < quarters.length; i++) {
if (month < quarters[i][0]) {
return i - 1
}
}
return 4
},
//spring, summer, winter, fall
season: function (input, goFwd) {
let hem = 'north';
if (this.hemisphere() === 'South') {
hem = 'south';
}
if (input !== undefined) { // setter
let s = this.clone();
for (let i = 0; i < seasons$1[hem].length; i++) {
if (input === seasons$1[hem][i][0]) {
s = s.month(seasons$1[hem][i][1], goFwd);
s = s.date(1);
s = s.startOf('day');
}
}
return s
}
let month = this.d.getMonth();
for (let i = 0; i < seasons$1[hem].length - 1; i++) {
if (month >= seasons$1[hem][i][1] && month < seasons$1[hem][i + 1][1]) {
return seasons$1[hem][i][0]
}
}
return hem === 'north' ? 'winter' : 'summer'
},
//the year number
year: function (num) {
if (num !== undefined) {
let s = this.clone();
s.epoch = year(s, num);
return s
}
return this.d.getFullYear()
},
//bc/ad years
era: function (str) {
if (str !== undefined) {
let s = this.clone();
str = str.toLowerCase();
//TODO: there is no year-0AD i think. may have off-by-1 error here
let year$1 = s.d.getFullYear();
//make '1992' into 1992bc..
if (str === 'bc' && year$1 > 0) {
s.epoch = year(s, year$1 * -1);
}
//make '1992bc' into '1992'
if (str === 'ad' && year$1 < 0) {
s.epoch = year(s, year$1 * -1);
}
return s
}
if (this.d.getFullYear() < 0) {
return 'BC'
}
return 'AD'
},
// 2019 -> 2010
decade: function (input) {
if (input !== undefined) {
input = String(input);
input = input.replace(/([0-9])'?s$/, '$1'); //1950's
input = input.replace(/([0-9])(th|rd|st|nd)/, '$1'); //fix ordinals
if (!input) {
console.warn('Spacetime: Invalid decade input');
return this
}
// assume 20th century?? for '70s'.
if (input.length === 2 && /[0-9][0-9]/.test(input)) {
input = '19' + input;
}
let year = Number(input);
if (isNaN(year)) {
return this
}
// round it down to the decade
year = Math.floor(year / 10) * 10;
return this.year(year) //.startOf('decade')
}
return this.startOf('decade').year()
},
// 1950 -> 19+1
century: function (input) {
if (input !== undefined) {
if (typeof input === 'string') {
input = input.replace(/([0-9])(th|rd|st|nd)/, '$1'); //fix ordinals
input = input.replace(/([0-9]+) ?(b\.?c\.?|a\.?d\.?)/i, (a, b, c) => {
if (c.match(/b\.?c\.?/i)) {
b = '-' + b;
}
return b
});
input = input.replace(/c$/, ''); //20thC
}
let year = Number(input);
if (isNaN(input)) {
console.warn('Spacetime: Invalid century input');
return this
}
// there is no century 0
if (year === 0) {
year = 1;
}
if (year >= 0) {
year = (year - 1) * 100;
} else {
year = (year + 1) * 100;
}
return this.year(year)
}
// century getter
let num = this.startOf('century').year();
num = Math.floor(num / 100);
if (num < 0) {
return num - 1
}
return num + 1
},
// 2019 -> 2+1
millenium: function (input) {
if (input !== undefined) {
if (typeof input === 'string') {
input = input.replace(/([0-9])(th|rd|st|nd)/, '$1'); //fix ordinals
input = Number(input);
if (isNaN(input)) {
console.warn('Spacetime: Invalid millenium input');
return this
}
}
if (input > 0) {
input -= 1;
}
let year = input * 1000;
// there is no year 0
if (year === 0) {
year = 1;
}
return this.year(year)
}
// get the current millenium
let num = Math.floor(this.year() / 1000);
if (num >= 0) {
num += 1;
}
return num
}
};
const methods = Object.assign({}, methods$3, methods$2, methods$1);
//aliases
methods.milliseconds = methods.millisecond;
methods.seconds = methods.second;
methods.minutes = methods.minute;
methods.hours = methods.hour;
methods.hour24 = methods.hour;
methods.h12 = methods.hour12;
methods.h24 = methods.hour24;
methods.days = methods.day;
const addMethods$4 = Space => {
//hook the methods into prototype
Object.keys(methods).forEach(k => {
Space.prototype[k] = methods[k];
});
};
const getMonthLength = function (month, year) {
if (month === 1 && isLeapYear(year)) {
return 29
}
return monthLengths[month]
};
//month is the one thing we 'model/compute'
//- because ms-shifting can be off by enough
const rollMonth = (want, old) => {
//increment year
if (want.month > 0) {
let years = parseInt(want.month / 12, 10);
want.year = old.year() + years;
want.month = want.month % 12;
} else if (want.month < 0) {
let m = Math.abs(want.month);
let years = parseInt(m / 12, 10);
if (m % 12 !== 0) {
years += 1;
}
want.year = old.year() - years;
//ignore extras
want.month = want.month % 12;
want.month = want.month + 12;
if (want.month === 12) {
want.month = 0;
}
}
return want
};
// briefly support day=-2 (this does not need to be perfect.)
const rollDaysDown = (want, old, sum) => {
want.year = old.year();
want.month = old.month();
let date = old.date();
want.date = date - Math.abs(sum);
while (want.date < 1) {
want.month -= 1;
if (want.month < 0) {
want.month = 11;
want.year -= 1;
}
let max = getMonthLength(want.month, want.year);
want.date += max;
}
return want
};
// briefly support day=33 (this does not need to be perfect.)
const rollDaysUp = (want, old, sum) => {
let year = old.year();
let month = old.month();
let max = getMonthLength(month, year);
while (sum > max) {
sum -= max;
month += 1;
if (month >= 12) {
month -= 12;
year += 1;
}
max = getMonthLength(month, year);
}
want.month = month;
want.date = sum;
return want
};
const months = rollMonth;
const days = rollDaysUp;
const daysBack = rollDaysDown;
// this logic is a bit of a mess,
// but briefly:
// millisecond-math, and some post-processing covers most-things
// we 'model' the calendar here only a little bit
// and that usually works-out...
const order = ['millisecond', 'second', 'minute', 'hour', 'date', 'month'];
let keep = {
second: order.slice(0, 1),
minute: order.slice(0, 2),
quarterhour: order.slice(0, 2),
hour: order.slice(0, 3),
date: order.slice(0, 4),
month: order.slice(0, 4),
quarter: order.slice(0, 4),
season: order.slice(0, 4),
year: order,
decade: order,
century: order
};
keep.week = keep.hour;
keep.season = keep.date;
keep.quarter = keep.date;
// Units need to be dst adjuested
const dstAwareUnits = {
year: true,
quarter: true,
season: true,
month: true,
week: true,
date: true
};
const keepDate = {
month: true,
quarter: true,
season: true,
year: true
};
const addMethods$3 = (SpaceTime) => {
SpaceTime.prototype.add = function (num, unit) {
let s = this.clone();
if (!unit || num === 0) {
return s //don't bother
}
let old = this.clone();
unit = normalize$3(unit);
if (unit === 'millisecond') {
s.epoch += num;
return s
}
// support 'fortnight' alias
if (unit === 'fortnight') {
num *= 2;
unit = 'week';
}
//move forward by the estimated milliseconds (rough)
if (o[unit]) {
s.epoch += o[unit] * num;
} else if (unit === 'week' || unit === 'weekend') {
s.epoch += o.day * (num * 7);
} else if (unit === 'quarter' || unit === 'season') {
s.epoch += o.month * (num * 3);
} else if (unit === 'quarterhour') {
s.epoch += o.minute * 15 * num;
}
//now ensure our milliseconds/etc are in-line
let want = {};
if (keep[unit]) {
keep[unit].forEach((u) => {
want[u] = old[u]();
});
}
if (dstAwareUnits[unit]) {
const diff = old.timezone().current.offset - s.timezone().current.offset;
s.epoch += diff * 3600 * 1000;
}
//ensure month/year has ticked-over
if (unit === 'month') {
want.month = old.month() + num;
//month is the one unit we 'model' directly
want = months(want, old);
}
//support coercing a week, too
if (unit === 'week') {
let sum = old.date() + (num * 7);
if (sum <= 28 && sum > 1) {
want.date = sum;
}
}
if (unit === 'weekend' && s.dayName() !== 'saturday') {
s = s.day('saturday', true); //ensure it's saturday
}
//support 25-hour day-changes on dst-changes
else if (unit === 'date') {
if (num < 0) {
want = daysBack(want, old, num);
} else {
//specify a naive date number, if it's easy to do...
let sum = old.date() + num;
// ok, model this one too
want = days(want, old, sum);
}
//manually punt it if we haven't moved at all..
if (num !== 0 && old.isSame(s, 'day')) {
want.date = old.date() + num;
}
}
// ensure a quarter is 3 months over
else if (unit === 'quarter') {
want.month = old.month() + (num * 3);
want.year = old.year();
// handle rollover
if (want.month < 0) {
let years = Math.floor(want.month / 12);
let remainder = want.month + (Math.abs(years) * 12);
want.month = remainder;
want.year += years;
} else if (want.month >= 12) {
let years = Math.floor(want.month / 12);
want.month = want.month % 12;
want.year += years;
}
want.date = old.date();
}
//ensure year has changed (leap-years)
else if (unit === 'year') {
let wantYear = old.year() + num;
let haveYear = s.year();
if (haveYear < wantYear) {
let toAdd = Math.floor(num / 4) || 1; //approx num of leap-days
s.epoch += Math.abs(o.day * toAdd);
} else if (haveYear > wantYear) {
let toAdd = Math.floor(num / 4) || 1; //approx num of leap-days
s.epoch += o.day * toAdd;
}
}
//these are easier
else if (unit === 'decade') {
want.year = s.year() + 10;
} else if (unit === 'century') {
want.year = s.year() + 100;
}
//keep current date, unless the month doesn't have it.
if (keepDate[unit]) {
let max = monthLengths[want.month];
want.date = old.date();
if (want.date > max) {
want.date = max;
}
}
if (Object.keys(want).length > 1) {
walkTo(s, want);
}
return s
};
//subtract is only add *-1
SpaceTime.prototype.subtract = function (num, unit) {
let s = this.clone();
return s.add(num * -1, unit)
};
//add aliases
SpaceTime.prototype.minus = SpaceTime.prototype.subtract;
SpaceTime.prototype.plus = SpaceTime.prototype.add;
};
//make a string, for easy comparison between dates
const print = {
millisecond: (s) => {
return s.epoch
},
second: (s) => {
return [s.year(), s.month(), s.date(), s.hour(), s.minute(), s.second()].join('-')
},
minute: (s) => {
return [s.year(), s.month(), s.date(), s.hour(), s.minute()].join('-')
},
hour: (s) => {
return [s.year(), s.month(), s.date(), s.hour()].join('-')
},
day: (s) => {
return [s.year(), s.month(), s.date()].join('-')
},
week: (s) => {
return [s.year(), s.week()].join('-')
},
month: (s) => {
return [s.year(), s.month()].join('-')
},
quarter: (s) => {
return [s.year(), s.quarter()].join('-')
},
year: (s) => {
return s.year()
}
};
print.date = print.day;
const addMethods$2 = (SpaceTime) => {
SpaceTime.prototype.isSame = function (b, unit, tzAware = true) {
let a = this;
if (!unit) {
return null
}
// support swapped params
if (typeof b === 'string' && typeof unit === 'object') {
let tmp = b;
b = unit;
unit = tmp;
}
if (typeof b === 'string' || typeof b === 'number') {
b = new SpaceTime(b, this.timezone.name);
}
//support 'seconds' aswell as 'second'
unit = unit.replace(/s$/, '');
// make them the same timezone for proper comparison
if (tzAware === true && a.tz !== b.tz) {
b = b.clone();
b.tz = a.tz;
}
if (print[unit]) {
return print[unit](a) === print[unit](b)
}
return null
};
};
const addMethods$1 = SpaceTime => {
const methods = {
isAfter: function (d) {
d = beADate(d, this);
let epoch = getEpoch(d);
if (epoch === null) {
return null
}
return this.epoch > epoch
},
isBefore: function (d) {
d = beADate(d, this);
let epoch = getEpoch(d);
if (epoch === null) {
return null
}
return this.epoch < epoch
},
isEqual: function (d) {
d = beADate(d, this);
let epoch = getEpoch(d);
if (epoch === null) {
return null
}
return this.epoch === epoch
},
isBetween: function (start, end, isInclusive = false) {
start = beADate(start, this);
end = beADate(end, this);
let startEpoch = getEpoch(start);
if (startEpoch === null) {
return null
}
let endEpoch = getEpoch(end);
if (endEpoch === null) {
return null
}
if (isInclusive) {
return this.isBetween(start, end) || this.isEqual(start) || this.isEqual(end);
}
return startEpoch < this.epoch && this.epoch < endEpoch
}
};
//hook them into proto
Object.keys(methods).forEach(k => {
SpaceTime.prototype[k] = methods[k];
});
};
const addMethods = SpaceTime => {
const methods = {
i18n: function (data) {
//change the day names
if (isObject(data.days)) {
set$4(data.days);
}
//change the month names
if (isObject(data.months)) {
set$5(data.months);
}
//change the display style of the month / day names
if (isBoolean(data.useTitleCase)) {
set$3(data.useTitleCase);
}
//change am and pm strings
if (isObject(data.ampm)) {
set(data.ampm);
}
//change distance strings
if(isObject(data.distance)){
set$1(data.distance);
}
//change units strings
if(isObject(data.units)){
set$2(data.units);
}
return this
}
};
//hook them into proto
Object.keys(methods).forEach(k => {
SpaceTime.prototype[k] = methods[k];
});
};
let timezones$1 = all;
// fake timezone-support, for fakers (es5 class)
const SpaceTime = function (input, tz, options = {}) {
// the holy moment
this.epoch = null;
// the shift for the given timezone
this.tz = lookupTz(tz, timezones$1);
// whether to output warnings to console
this.silent = typeof options.silent !== 'undefined' ? options.silent : true;
// favour british interpretation of 02/02/2018, etc
this.british = options.dmy || options.british;
// does the week start on sunday, or monday:
this._weekStart = 1; // default to monday
if (options.weekStart !== undefined) {
this._weekStart = options.weekStart;
}
// the reference today date object, (for testing)
this._today = {};
if (options.today !== undefined) {
this._today = options.today;
}
// dunno if this is a good idea, or not
// Object.defineProperty(this, 'parsers', {
// enumerable: false,
// writable: true,
// value: parsers
// })
// add getter/setters
Object.defineProperty(this, 'd', {
// return a js date object
get: function () {
let offset = quickOffset(this);
// every computer is somewhere- get this computer's built-in offset
let bias = new Date(this.epoch).getTimezoneOffset() || 0;
// movement
let shift = bias + (offset * 60); //in minutes
shift = shift * 60 * 1000; //in ms
// remove this computer's offset
let epoch = this.epoch + shift;
let d = new Date(epoch);
return d
}
});
// add this data on the object, to allow adding new timezones
Object.defineProperty(this, 'timezones', {
get: () => timezones$1,
set: (obj) => {
timezones$1 = obj;
return obj
}
});
// parse the various formats
let tmp = parseInput(this, input);
this.epoch = tmp.epoch;
if (tmp.tz) {
this.tz = tmp.tz;
}
};
// (add instance methods to prototype)
Object.keys(methods$4).forEach((k) => {
SpaceTime.prototype[k] = methods$4[k];
});
// ¯\_(ツ)_/¯
SpaceTime.prototype.clone = function () {
return new SpaceTime(this.epoch, this.tz, {
silent: this.silent,
weekStart: this._weekStart,
today: this._today,
parsers: this.parsers
})
};
/**
* @deprecated use toNativeDate()
* @returns native date object at the same epoch
*/
SpaceTime.prototype.toLocalDate = function () {
return this.toNativeDate()
};
/**
* @returns native date object at the same epoch
*/
SpaceTime.prototype.toNativeDate = function () {
return new Date(this.epoch)
};
// append more methods
addMethods$4(SpaceTime);
addMethods$3(SpaceTime);
addMethods$2(SpaceTime);
addMethods$1(SpaceTime);
addMethods(SpaceTime);
// const timezones = require('../data');
const whereIts = (a, b) => {
let start = new SpaceTime(null);
let end = new SpaceTime(null);
start = start.time(a);
//if b is undefined, use as 'within one hour'
if (b) {
end = end.time(b);
} else {
end = start.add(59, 'minutes');
}
let startHour = start.hour();
let endHour = end.hour();
let tzs = Object.keys(start.timezones).filter((tz) => {
if (tz.indexOf('/') === -1) {
return false
}
let m = new SpaceTime(null, tz);
let hour = m.hour();
//do 'calendar-compare' not real-time-compare
if (hour >= startHour && hour <= endHour) {
//test minutes too, if applicable
if (hour === startHour && m.minute() < start.minute()) {
return false
}
if (hour === endHour && m.minute() > end.minute()) {
return false
}
return true
}
return false
});
return tzs
};
var version$1 = '7.7.0';
const main = (input, tz, options) => new SpaceTime(input, tz, options);
// set all properties of a given 'today' object
const setToday = function (s) {
let today = s._today || {};
Object.keys(today).forEach((k) => {
s = s[k](today[k]);
});
return s
};
//some helper functions on the main method
main.now = (tz, options) => {
let s = new SpaceTime(new Date().getTime(), tz, options);
s = setToday(s);
return s
};
main.today = (tz, options) => {
let s = new SpaceTime(new Date().getTime(), tz, options);
s = setToday(s);
return s.startOf('day')
};
main.tomorrow = (tz, options) => {
let s = new SpaceTime(new Date().getTime(), tz, options);
s = setToday(s);
return s.add(1, 'day').startOf('day')
};
main.yesterday = (tz, options) => {
let s = new SpaceTime(new Date().getTime(), tz, options);
s = setToday(s);
return s.subtract(1, 'day').startOf('day')
};
main.extend = function (obj = {}) {
Object.keys(obj).forEach((k) => {
SpaceTime.prototype[k] = obj[k];
});
return this
};
main.timezones = function () {
let s = new SpaceTime();
return s.timezones
};
main.max = function (tz, options) {
let s = new SpaceTime(null, tz, options);
s.epoch = 8640000000000000;
return s
};
main.min = function (tz, options) {
let s = new SpaceTime(null, tz, options);
s.epoch = -8640000000000000;
return s
};
//find tz by time
main.whereIts = whereIts;
main.version = version$1;
//aliases:
main.plugin = main.extend;
// these should be added to model
const hardCoded = {
daybreak: '7:00am', //ergh
breakfast: '8:00am',
morning: '9:00am',
noon: '12:00pm',
midday: '12:00pm',
afternoon: '2:00pm',
lunchtime: '12:00pm',
evening: '6:00pm',
dinnertime: '6:00pm',
night: '8:00pm',
eod: '10:00pm',
midnight: '12:00am',
am: '9:00am', //tomorow am
pm: '5:00pm',
'early day': '8:00am',
'late at night': '11:00pm'
};
const minMap = {
quarter: 15,
half: 30,
};
// choose ambiguous ampm
const ampmChooser = function (s) {
let early = s.time('6:00am');
if (s.isBefore(early)) {
return s.ampm('pm')
}
return s
};
// parse 'twenty past 2'
const halfPast = function (m, s) {
let hour = m.match('#Cardinal$');
let punt = m.not(hour).match('(half|quarter|25|20|15|10|5)');
// get the mins, and the hour
hour = hour.text('reduced');
let mins = punt.text('reduced');
// support 'quarter'
if (minMap.hasOwnProperty(mins)) {
mins = minMap[mins];
}
let behind = m.has('to');
// apply it
s = s.hour(hour);
s = s.startOf('hour');
// assume 'half past 5' is 5pm
if (hour < 6) {
s = s.ampm('pm');
}
if (behind) {
s = s.subtract(mins, 'minutes');
} else {
s = s.add(mins, 'minutes');
}
return s
};
const parseTime = function (doc, context) {
let time = doc.match('(at|by|for|before|this|after)? #Time+');
// get the main part of the time
time = time.not('^(at|by|for|before|this|after)');
time = time.not('sharp');
time = time.not('on the dot');
let s = main.now(context.timezone);
let now = s.clone();
// check for known-times (like 'today')
let timeStr = time.not('in? the').text('reduced');
timeStr = timeStr.replace(/^@/, '');//@4pm
if (hardCoded.hasOwnProperty(timeStr)) {
return { result: hardCoded[timeStr], m: time }
}
// '5 oclock'
let m = time.match('^#Cardinal oclock (am|pm)?');
if (m.found) {
s = s.hour(m.text('reduced'));
s = s.startOf('hour');
if (s.isValid() && !s.isEqual(now)) {
let ampm = m.match('(am|pm)');
if (ampm.found) {
s = s.ampm(ampm.text('reduced'));
} else {
s = ampmChooser(s);
}
return { result: s.time(), m }
}
}
// 'quarter to two'
m = time.match('(half|quarter|25|20|15|10|5) (past|after|to) #Cardinal');
if (m.found) {
s = halfPast(m, s);
if (s.isValid() && !s.isEqual(now)) {
// choose ambiguous ampm
s = ampmChooser(s);
return { result: s.time(), m }
}
}
// 'twenty past'
m = time.match('[<min>(half|quarter|25|20|15|10|5)] (past|after)');
if (m.found) {
let min = m.groups('min').text('reduced');
let d = main(context.today);
// support 'quarter', etc.
if (minMap.hasOwnProperty(min)) {
min = minMap[min];
}
d = d.next('hour').startOf('hour').minute(min);
if (d.isValid() && !d.isEqual(now)) {
return { result: d.time(), m }
}
}
// 'ten to'
m = time.match('[<min>(half|quarter|25|20|15|10|5)] to');
if (m.found) {
let min = m.groups('min').text('reduced');
let d = main(context.today);
// support 'quarter', etc.
if (minMap.hasOwnProperty(min)) {
min = minMap[min];
}
d = d.next('hour').startOf('hour').minus(min, 'minutes');
if (d.isValid() && !d.isEqual(now)) {
return { result: d.time(), m }
}
}
// '4 in the evening'
m = time.match('[<time>#Time] (in|at) the? [<desc>(morning|evening|night|nighttime)]');
if (m.found) {
let str = m.groups('time').text('normal');
if (/^[0-9]{1,2}$/.test(str)) {
s = s.hour(str); //3 in the morning
s = s.startOf('hour');
} else {
s = s.time(str); // 3:30 in the morning
}
if (s.isValid() && !s.isEqual(now)) {
let desc = m.groups('desc').text('reduced');
if (desc === 'evening' || desc === 'night') {
s = s.ampm('pm');
}
return { result: s.time(), m }
}
}
// 'this morning at 4'
m = time.match('this? [<desc>(morning|evening|tonight)] at [<time>(#Cardinal|#Time)]');
if (m.found) {
let g = m.groups();
let str = g.time.text('reduced');
if (/^[0-9]{1,2}$/.test(str)) {
s = s.hour(str); //3
s = s.startOf('hour');
} else {
s = s.time(str); // 3:30
}
if (s.isValid() && !s.isEqual(now)) {
let desc = g.desc.text('reduced');
if (desc === 'morning') {
s = s.ampm('am');
}
if (desc === 'evening' || desc === 'tonight') {
s = s.ampm('pm');
}
return { result: s.time(), m }
}
}
// 'at 4' -> '4'
m = time.match('^#Cardinal$');
if (m.found) {
let str = m.text('reduced');
s = s.hour(str);
s = s.startOf('hour');
if (s.isValid() && !s.isEqual(now)) {
// choose ambiguous ampm
if (/(am|pm)/i.test(str) === false) {
s = ampmChooser(s);
}
return { result: s.time(), m }
}
}
// parse random a time like '4:54pm'
let str = time.text('reduced');
s = s.time(str);
if (s.isValid() && !s.isEqual(now)) {
// choose ambiguous ampm
if (/(am|pm)/i.test(str) === false) {
s = ampmChooser(s);
}
return { result: s.time(), m: time }
}
// should we fallback to a dayStart default?
if (context.dayStart) {
return { result: context.dayStart, m: doc.none() }
}
return { result: null, m: doc.none() }
};
// interpret 'this halloween' or 'next june'
const parseRelative = function (doc) {
// avoid parsing 'day after next'
if (doc.has('(next|last|this)$')) {
return { result: null, m: doc.none() }
}
// next monday
let m = doc.match('^this? (next|upcoming|coming)');
if (m.found) {
return { result: 'next', m }
}
// 'this past monday' is not-always 'last monday'
m = doc.match('^this? (past)');
if (m.found) {
return { result: 'this-past', m }
}
// last monday
m = doc.match('^this? (last|previous)');
if (m.found) {
return { result: 'last', m }
}
// this monday
m = doc.match('^(this|current)');
if (m.found) {
return { result: 'this', m }
}
return { result: null, m: doc.none() }
};
// 'start of october', 'middle of june 1st'
const parseSection = function (doc) {
// start of 2019
let m = doc.match('[(start|beginning) of] .', 0);
if (m.found) {
return { result: 'start', m }
}
// end of 2019
m = doc.match('[end of] .', 0);
if (m.found) {
return { result: 'end', m }
}
// middle of 2019
m = doc.match('[(middle|midpoint|center) of] .', 0);
if (m.found) {
return { result: 'middle', m }
}
return { result: null, m }
};
// some opinionated-but-common-sense timezone abbreviations
// these timezone abbreviations are wholly made-up by me, Spencer Kelly, with no expertise in geography
// generated humbly from https://github.com/spencermountain/spacetime-informal
const america$1 = 'America/';
const asia$1 = 'Asia/';
const europe$1 = 'Europe/';
const africa$1 = 'Africa/';
const aus$1 = 'Australia/';
const pac$1 = 'Pacific/';
const informal$1 = {
//europe
'british summer time': europe$1 + 'London',
bst: europe$1 + 'London',
'british time': europe$1 + 'London',
'britain time': europe$1 + 'London',
'irish summer time': europe$1 + 'Dublin',
'irish time': europe$1 + 'Dublin',
ireland: europe$1 + 'Dublin',
'central european time': europe$1 + 'Berlin',
cet: europe$1 + 'Berlin',
'central european summer time': europe$1 + 'Berlin',
cest: europe$1 + 'Berlin',
'central europe': europe$1 + 'Berlin',
'eastern european time': europe$1 + 'Riga',
eet: europe$1 + 'Riga',
'eastern european summer time': europe$1 + 'Riga',
eest: europe$1 + 'Riga',
'eastern europe time': europe$1 + 'Riga',
'western european time': europe$1 + 'Lisbon',
// wet: europe+'Lisbon',
'western european summer time': europe$1 + 'Lisbon',
// west: europe+'Lisbon',
'western europe': europe$1 + 'Lisbon',
'turkey standard time': europe$1 + 'Istanbul',
trt: europe$1 + 'Istanbul',
'turkish time': europe$1 + 'Istanbul',
//africa
etc: africa$1 + 'Freetown',
utc: africa$1 + 'Freetown',
'greenwich standard time': africa$1 + 'Freetown',
gmt: africa$1 + 'Freetown',
'east africa time': africa$1 + 'Nairobi',
// eat: africa+'Nairobi',
'east african time': africa$1 + 'Nairobi',
'eastern africa time': africa$1 + 'Nairobi',
'central africa time': africa$1 + 'Khartoum',
// cat: africa+'Khartoum',
'central african time': africa$1 + 'Khartoum',
'south africa standard time': africa$1 + 'Johannesburg',
sast: africa$1 + 'Johannesburg',
'southern africa': africa$1 + 'Johannesburg',
'south african': africa$1 + 'Johannesburg',
'west africa standard time': africa$1 + 'Lagos',
// wat: africa+'Lagos',
'western africa time': africa$1 + 'Lagos',
'west african time': africa$1 + 'Lagos',
'australian central standard time': aus$1 + 'Adelaide',
acst: aus$1 + 'Adelaide',
'australian central daylight time': aus$1 + 'Adelaide',
acdt: aus$1 + 'Adelaide',
'australia central': aus$1 + 'Adelaide',
'australian eastern standard time': aus$1 + 'Brisbane',
aest: aus$1 + 'Brisbane',
'australian eastern daylight time': aus$1 + 'Brisbane',
aedt: aus$1 + 'Brisbane',
'australia east': aus$1 + 'Brisbane',
'australian western standard time': aus$1 + 'Perth',
awst: aus$1 + 'Perth',
'australian western daylight time': aus$1 + 'Perth',
awdt: aus$1 + 'Perth',
'australia west': aus$1 + 'Perth',
'australian central western standard time': aus$1 + 'Eucla',
acwst: aus$1 + 'Eucla',
'australia central west': aus$1 + 'Eucla',
'lord howe standard time': aus$1 + 'Lord_Howe',
lhst: aus$1 + 'Lord_Howe',
'lord howe daylight time': aus$1 + 'Lord_Howe',
lhdt: aus$1 + 'Lord_Howe',
'russian standard time': europe$1 + 'Moscow',
msk: europe$1 + 'Moscow',
russian: europe$1 + 'Moscow',
//america
'central standard time': america$1 + 'Chicago',
'central time': america$1 + 'Chicago',
cst: america$1 + 'Havana',
'central daylight time': america$1 + 'Chicago',
cdt: america$1 + 'Havana',
'mountain standard time': america$1 + 'Denver',
'mountain time': america$1 + 'Denver',
mst: america$1 + 'Denver',
'mountain daylight time': america$1 + 'Denver',
mdt: america$1 + 'Denver',
'atlantic standard time': america$1 + 'Halifax',
'atlantic time': america$1 + 'Halifax',
ast: asia$1 + 'Baghdad',
'atlantic daylight time': america$1 + 'Halifax',
adt: america$1 + 'Halifax',
'eastern standard time': america$1 + 'New_York',
'eastern': america$1 + 'New_York',
'eastern time': america$1 + 'New_York',
est: america$1 + 'New_York',
'eastern daylight time': america$1 + 'New_York',
edt: america$1 + 'New_York',
'pacific time': america$1 + 'Los_Angeles',
'pacific standard time': america$1 + 'Los_Angeles',
pst: america$1 + 'Los_Angeles',
'pacific daylight time': america$1 + 'Los_Angeles',
pdt: america$1 + 'Los_Angeles',
'alaskan standard time': america$1 + 'Anchorage',
'alaskan time': america$1 + 'Anchorage',
ahst: america$1 + 'Anchorage',
'alaskan daylight time': america$1 + 'Anchorage',
ahdt: america$1 + 'Anchorage',
'hawaiian standard time': pac$1 + 'Honolulu',
'hawaiian time': pac$1 + 'Honolulu',
hst: pac$1 + 'Honolulu',
'aleutian time': pac$1 + 'Honolulu',
'hawaii time': pac$1 + 'Honolulu',
'newfoundland standard time': america$1 + 'St_Johns',
'newfoundland time': america$1 + 'St_Johns',
nst: america$1 + 'St_Johns',
'newfoundland daylight time': america$1 + 'St_Johns',
ndt: america$1 + 'St_Johns',
'brazil time': america$1 + 'Sao_Paulo',
brt: america$1 + 'Sao_Paulo',
'brasília': america$1 + 'Sao_Paulo',
brasilia: america$1 + 'Sao_Paulo',
'brazilian time': america$1 + 'Sao_Paulo',
'argentina time': america$1 + 'Buenos_Aires',
// art: a+'Buenos_Aires',
'argentinian time': america$1 + 'Buenos_Aires',
'amazon time': america$1 + 'Manaus',
amt: america$1 + 'Manaus',
'amazonian time': america$1 + 'Manaus',
'easter island standard time': 'Chile/Easterisland',
east: 'Chile/Easterisland',
'easter island summer time': 'Chile/Easterisland',
easst: 'Chile/Easterisland',
'venezuelan standard time': america$1 + 'Caracas',
'venezuelan time': america$1 + 'Caracas',
vet: america$1 + 'Caracas',
'venezuela time': america$1 + 'Caracas',
'paraguay time': america$1 + 'Asuncion',
pyt: america$1 + 'Asuncion',
'paraguay summer time': america$1 + 'Asuncion',
pyst: america$1 + 'Asuncion',
'cuba standard time': america$1 + 'Havana',
'cuba time': america$1 + 'Havana',
'cuba daylight time': america$1 + 'Havana',
'cuban time': america$1 + 'Havana',
'bolivia time': america$1 + 'La_Paz',
// bot: a+'La_Paz',
'bolivian time': america$1 + 'La_Paz',
'colombia time': america$1 + 'Bogota',
cot: america$1 + 'Bogota',
'colombian time': america$1 + 'Bogota',
'acre time': america$1 + 'Eirunepe',
// act: a+'Eirunepe',
'peru time': america$1 + 'Lima',
// pet: a+'Lima',
'chile standard time': america$1 + 'Punta_Arenas',
'chile time': america$1 + 'Punta_Arenas',
clst: america$1 + 'Punta_Arenas',
'chile summer time': america$1 + 'Punta_Arenas',
cldt: america$1 + 'Punta_Arenas',
'uruguay time': america$1 + 'Montevideo',
uyt: america$1 + 'Montevideo',
//asia
ist: asia$1 + 'Jerusalem',
'arabic standard time': asia$1 + 'Baghdad',
'arabic time': asia$1 + 'Baghdad',
'arab time': asia$1 + 'Baghdad',
'iran standard time': asia$1 + 'Tehran',
'iran time': asia$1 + 'Tehran',
irst: asia$1 + 'Tehran',
'iran daylight time': asia$1 + 'Tehran',
irdt: asia$1 + 'Tehran',
iranian: asia$1 + 'Tehran',
'pakistan standard time': asia$1 + 'Karachi',
'pakistan time': asia$1 + 'Karachi',
pkt: asia$1 + 'Karachi',
'india standard time': asia$1 + 'Kolkata',
'indian time': asia$1 + 'Kolkata',
'indochina time': asia$1 + 'Bangkok',
ict: asia$1 + 'Bangkok',
'south east asia': asia$1 + 'Bangkok',
'china standard time': asia$1 + 'Shanghai',
ct: asia$1 + 'Shanghai',
'chinese time': asia$1 + 'Shanghai',
'alma-ata time': asia$1 + 'Almaty',
almt: asia$1 + 'Almaty',
'oral time': asia$1 + 'Oral',
'orat time': asia$1 + 'Oral',
'yakutsk time': asia$1 + 'Yakutsk',
yakt: asia$1 + 'Yakutsk',
'gulf standard time': asia$1 + 'Dubai',
'gulf time': asia$1 + 'Dubai',
gst: asia$1 + 'Dubai',
uae: asia$1 + 'Dubai',
'hong kong time': asia$1 + 'Hong_Kong',
hkt: asia$1 + 'Hong_Kong',
'western indonesian time': asia$1 + 'Jakarta',
wib: asia$1 + 'Jakarta',
'indonesia time': asia$1 + 'Jakarta',
'central indonesian time': asia$1 + 'Makassar',
wita: asia$1 + 'Makassar',
'israel daylight time': asia$1 + 'Jerusalem',
idt: asia$1 + 'Jerusalem',
'israel standard time': asia$1 + 'Jerusalem',
'israel time': asia$1 + 'Jerusalem',
israeli: asia$1 + 'Jerusalem',
'krasnoyarsk time': asia$1 + 'Krasnoyarsk',
krat: asia$1 + 'Krasnoyarsk',
'malaysia time': asia$1 + 'Kuala_Lumpur',
myt: asia$1 + 'Kuala_Lumpur',
'singapore time': asia$1 + 'Singapore',
sgt: asia$1 + 'Singapore',
'korea standard time': asia$1 + 'Seoul',
'korea time': asia$1 + 'Seoul',
kst: asia$1 + 'Seoul',
'korean time': asia$1 + 'Seoul',
'uzbekistan time': asia$1 + 'Samarkand',
uzt: asia$1 + 'Samarkand',
'vladivostok time': asia$1 + 'Vladivostok',
vlat: asia$1 + 'Vladivostok',
//indian
'maldives time': 'Indian/Maldives',
mvt: 'Indian/Maldives',
'mauritius time': 'Indian/Mauritius',
mut: 'Indian/Mauritius',
// pacific
'marshall islands time': pac$1 + 'Kwajalein',
mht: pac$1 + 'Kwajalein',
'samoa standard time': pac$1 + 'Midway',
sst: pac$1 + 'Midway',
'somoan time': pac$1 + 'Midway',
'chamorro standard time': pac$1 + 'Guam',
chst: pac$1 + 'Guam',
'papua new guinea time': pac$1 + 'Bougainville',
pgt: pac$1 + 'Bougainville',
};
//add the official iana zonefile names
let iana$1 = main().timezones;
let formal$1 = Object.keys(iana$1).reduce((h, k) => {
h[k] = k;
return h
}, {});
var informal$2 = Object.assign({}, informal$1, formal$1);
const isOffset = /(-?[0-9]+)h(rs)?/i;
const isNumber = /(-?[0-9]+)/;
const utcOffset = /utc([-+]?[0-9]+)/i;
const gmtOffset = /gmt([-+]?[0-9]+)/i;
const toIana = function (num) {
num = Number(num);
if (num > -13 && num < 13) {
num = num * -1; //it's opposite!
num = (num > 0 ? '+' : '') + num; //add plus sign
return 'Etc/GMT' + num
}
return null
};
const parseOffset = function (tz) {
// '+5hrs'
let m = tz.match(isOffset);
if (m !== null) {
return toIana(m[1])
}
// 'utc+5'
m = tz.match(utcOffset);
if (m !== null) {
return toIana(m[1])
}
// 'GMT-5' (not opposite)
m = tz.match(gmtOffset);
if (m !== null) {
let num = Number(m[1]) * -1;
return toIana(num)
}
// '+5'
m = tz.match(isNumber);
if (m !== null) {
return toIana(m[1])
}
return null
};
const parseTimezone = function (doc) {
let m = doc.match('#Timezone+');
//remove prepositions
m = m.not('(in|for|by|near|at)');
let str = m.text('reduced');
// check our list of informal tz names
if (informal$2.hasOwnProperty(str)) {
return { result: informal$2[str], m }
}
let tz = parseOffset(str);
if (tz) {
return { result: tz, m }
}
return { result: null, m: doc.none() }
};
// pull-out 'thurs' from 'thurs next week'
const parseWeekday = function (doc) {
let day = doc.match('#WeekDay');
if (day.found && !doc.has('^#WeekDay$')) {
// handle relative-day logic elsewhere.
if (doc.has('(this|next|last) (next|upcoming|coming|past)? #WeekDay')) {
return { result: null, m: doc.none() }
}
return { result: day.text('reduced'), m: day }
}
return { result: null, m: doc.none() }
};
const cleanup = function (doc) {
// 'the fifth week ..'
doc = doc.not('[^the] !#Value', 0); // keep 'the 17th'
//
doc = doc.not('#Preposition$');
doc = doc.not('#Conjunction$');
doc = doc.not('sharp');
doc = doc.not('on the dot');
doc = doc.not('^(on|of)');
doc = doc.not('(next|last|this)$');
return doc
};
const tokenize = function (doc, context) {
// parse 'two weeks after'
let res = parseShift(doc);
let shift = res.result;
doc = doc.not(res.m);
// parse 'nth week of june'
res = getCounter(doc);
let counter = res.result;
doc = doc.not(res.m);
// parse 'eastern time'
res = parseTimezone(doc);
let tz = res.result;
doc = doc.not(res.m);
// parse '2pm'
res = parseTime(doc, context);
let time = res.result;
doc = doc.not(res.m);
// parse 'tuesday'
res = parseWeekday(doc);
let weekDay = res.result;
doc = doc.not(res.m);
// parse 'start of x'
res = parseSection(doc);
let section = res.result;
doc = doc.not(res.m);
// parse 'next x'
res = parseRelative(doc);
let rel = res.result;
doc = doc.not(res.m);
// cleanup remaining doc object
doc = cleanup(doc);
return {
shift,
counter,
tz,
time,
weekDay,
section,
rel,
doc
}
};
class Unit {
constructor(input, unit, context) {
this.unit = unit || 'day';
this.setTime = false;
context = context || {};
let today = {};
if (context.today) {
today = {
date: context.today.date(),
month: context.today.month(),
year: context.today.year(),
};
}
if (input && input.month === 'sept') {
input.month = 'sep';
}
// set it to the beginning of the given unit
let d = main(input, context.timezone, { today: today, dmy: context.dmy });
Object.defineProperty(this, 'd', {
enumerable: false,
writable: true,
value: d,
});
Object.defineProperty(this, 'context', {
enumerable: false,
writable: true,
value: context,
});
}
// make a new one
clone() {
let d = new Unit(this.d, this.unit, this.context);
d.setTime = this.setTime;
return d
}
log() {
console.log('--');//eslint-disable-line
this.d.log();
console.log('\n');//eslint-disable-line
return this
}
applyShift(obj = {}) {
Object.keys(obj).forEach((unit) => {
this.d = this.d.add(obj[unit], unit);
if (unit === 'hour' || unit === 'minute') {
this.setTime = true;
}
});
return this
}
applyTime(str) {
if (str) {
let justHour = /^[0-9]{1,2}$/;
if (justHour.test(str)) {
this.d = this.d.hour(str);
} else {
this.d = this.d.time(str);
}
// wiggle the best am/pm
let amPM = /[ap]m/.test(str);
if (!amPM) {
let tooEarly = this.d.time('6:00am');
if (this.d.isBefore(tooEarly)) {
this.d = this.d.ampm('pm');
}
let tooLate = this.d.time('10:00pm');
if (this.d.isAfter(tooLate)) {
this.d = this.d.ampm('am');
}
}
} else {
this.d = this.d.startOf('day'); //zero-out time
}
this.setTime = true;
return this
}
applyWeekDay(day) {
if (day) {
let epoch = this.d.epoch;
this.d = this.d.day(day);
if (this.d.epoch < epoch) {
this.d = this.d.add(1, 'week');
}
}
return this
}
applyRel(rel) {
if (rel === 'next') {
return this.next()
}
if (rel === 'last' || rel === 'this-past') {
// special 'this past' logic is handled in WeekDay
return this.last()
}
return this
}
applySection(section) {
if (section === 'start') {
return this.start()
}
if (section === 'end') {
return this.end()
}
if (section === 'middle') {
return this.middle()
}
return this
}
format(fmt) {
return this.d.format(fmt)
}
start() {
this.d = this.d.startOf(this.unit);
// do we have a custom day-start?
if (this.context.dayStart) {
this.d = this.d.time(this.context.dayStart);
}
return this
}
end() {
// do we have a custom day-end?
this.d = this.d.endOf(this.unit);
if (this.context.dayEnd) {
this.d = this.d.startOf('day');
let dayEnd = this.d.time(this.context.dayEnd);
if (dayEnd.isAfter(this.d)) {
this.d = dayEnd;
return this
}
}
return this
}
middle() {
let diff = this.d.diff(this.d.endOf(this.unit));
let minutes = Math.round(diff.minutes / 2);
this.d = this.d.add(minutes, 'minutes');
return this
}
// move it to 3/4s through
beforeEnd() {
let diff = this.d.startOf(this.unit).diff(this.d.endOf(this.unit));
let mins = Math.round(diff.minutes / 4);
this.d = this.d.endOf(this.unit);
this.d = this.d.minus(mins, 'minutes');
if (this.context.dayStart) {
this.d = this.d.time(this.context.dayStart);
}
return this
}
// the millescond before
before() {
this.d = this.d.minus(1, this.unit);
this.d = this.d.endOf(this.unit);
if (this.context.dayEnd) {
this.d = this.d.time(this.context.dayEnd);
}
return this
}
// 'after 2019'
after() {
this.d = this.d.add(1, this.unit);
this.d = this.d.startOf(this.unit);
return this
}
// tricky: 'next june' 'next tuesday'
next() {
this.d = this.d.add(1, this.unit);
this.d = this.d.startOf(this.unit);
return this
}
// tricky: 'last june' 'last tuesday'
last() {
this.d = this.d.minus(1, this.unit);
this.d = this.d.startOf(this.unit);
return this
}
}
class Day extends Unit {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'day';
if (this.d.isValid()) {
this.d = this.d.startOf('day');
}
}
middle() {
this.d = this.d.time('10am');
return this
}
beforeEnd() {
this.d = this.d.time('2pm');
return this
}
}
// like 'feb 2'
class CalendarDate extends Day {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'day';
if (this.d.isValid()) {
this.d = this.d.startOf('day');
}
}
next() {
this.d = this.d.add(1, 'year');
return this
}
last() {
this.d = this.d.minus(1, 'year');
return this
}
}
class WeekDay extends Day {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'day';
this.isWeekDay = true; //cool.
// is the input just a weekday?
if (typeof input === 'string') {
this.d = main(context.today, context.timezone);
this.d = this.d.day(input);
// assume a wednesday in the future
if (this.d.isBefore(context.today)) {
this.d = this.d.add(7, 'days');
}
} else {
this.d = input;
}
this.weekDay = this.d.dayName();
if (this.d.isValid()) {
this.d = this.d.startOf('day');
}
}
// clone() {
// return new WeekDay(this.d, this.unit, this.context)
// }
next() {
this.d = this.d.add(7, 'days');
this.d = this.d.day(this.weekDay);
return this
}
last() {
this.d = this.d.minus(7, 'days');
this.d = this.d.day(this.weekDay);
return this
}
// the millescond before
before() {
this.d = this.d.minus(1, 'day');
this.d = this.d.endOf('day');
if (this.context.dayEnd) {
this.d = this.d.time(this.context.dayEnd);
}
return this
}
applyRel(rel) {
if (rel === 'next') {
let tooFar = this.context.today.endOf('week').add(1, 'week');
this.next();
// did we go too-far?
if (this.d.isAfter(tooFar)) {
this.last(); // go back
}
return this
}
// the closest-one backwards
if (rel === 'this-past') {
return this.last()
}
if (rel === 'last') {
let start = this.context.today.startOf('week');
this.last();
// are we still in 'this week' though?
if (this.d.isBefore(start) === false) {
this.last(); // do it again
}
return this
}
return this
}
}
// like 'haloween'
class Holiday extends CalendarDate {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'day';
if (this.d.isValid()) {
this.d = this.d.startOf('day');
}
}
}
class Hour extends Unit {
constructor(input, unit, context) {
super(input, unit, context, true);
this.unit = 'hour';
if (this.d.isValid()) {
this.d = this.d.startOf('hour');
}
}
}
class Minute extends Unit {
constructor(input, unit, context) {
super(input, unit, context, true);
this.unit = 'minute';
if (this.d.isValid()) {
this.d = this.d.startOf('minute');
}
}
}
class Moment extends Unit {
constructor(input, unit, context) {
super(input, unit, context, true);
this.unit = 'millisecond';
}
}
// a specific month, like 'March'
class AnyMonth extends Unit {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'month';
// set to beginning
if (this.d.isValid()) {
this.d = this.d.startOf(this.unit);
}
}
}
// a specific month, like 'March'
class Month extends Unit {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'month';
// set to beginning
if (this.d.isValid()) {
this.d = this.d.startOf(this.unit);
}
}
next() {
this.d = this.d.add(1, 'year');
this.d = this.d.startOf('month');
return this
}
last() {
this.d = this.d.minus(1, 'year');
this.d = this.d.startOf('month');
return this
}
middle() {
this.d = this.d.add(15, 'days');
this.d = this.d.startOf('day');
return this
}
}
class AnyQuarter extends Unit {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'quarter';
// set to beginning
if (this.d.isValid()) {
this.d = this.d.startOf(this.unit);
}
}
last() {
this.d = this.d.minus(1, 'quarter');
this.d = this.d.startOf(this.unit);
return this
}
}
class Quarter extends Unit {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'quarter';
// set to beginning
if (this.d.isValid()) {
this.d = this.d.startOf(this.unit);
}
}
next() {
this.d = this.d.add(1, 'year');
this.d = this.d.startOf(this.unit);
return this
}
last() {
this.d = this.d.minus(1, 'year');
this.d = this.d.startOf(this.unit);
return this
}
}
class Season extends Unit {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'season';
// set to beginning
if (this.d.isValid()) {
this.d = this.d.startOf(this.unit);
}
}
next() {
this.d = this.d.add(1, 'year');
this.d = this.d.startOf(this.unit);
return this
}
last() {
this.d = this.d.minus(1, 'year');
this.d = this.d.startOf(this.unit);
return this
}
}
class Year extends Unit {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'year';
if (this.d.isValid()) {
this.d = this.d.startOf('year');
}
}
}
class Week extends Unit {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'week';
if (this.d.isValid()) {
this.d = this.d.startOf('week');
}
}
clone() {
return new Week(this.d, this.unit, this.context)
}
middle() {
this.d = this.d.add(2, 'days'); //wednesday
return this
}
// move it to 3/4s through
beforeEnd() {
this.d = this.d.day('friday');
return this
}
}
//may need some work
class WeekEnd extends Unit {
constructor(input, unit, context) {
super(input, unit, context);
this.unit = 'week';
if (this.d.isValid()) {
this.d = this.d.day('saturday');
this.d = this.d.startOf('day');
}
}
start() {
this.d = this.d.day('saturday').startOf('day');
return this
}
// end() {
// this.d = this.d.day('sunday').endOf('day')
// return this
// }
next() {
this.d = this.d.add(1, this.unit);
this.d = this.d.startOf('weekend');
return this
}
last() {
this.d = this.d.minus(1, this.unit);
this.d = this.d.startOf('weekend');
return this
}
}
const knownWord = {
today: (context) => {
return new Day(context.today, null, context)
},
yesterday: (context) => {
return new Day(context.today.minus(1, 'day'), null, context)
},
tomorrow: (context) => {
return new Day(context.today.plus(1, 'day'), null, context)
},
eom: (context) => {
let d = context.today.endOf('month');
d = d.startOf('day');
return new Day(d, null, context)
},
// eod: (context) => {
// let d = context.today.endOf('day')
// d = d.startOf('hour').minus(4, 'hours') //rough
// return new Hour(d, null, context)
// },
eoy: (context) => {
let d = context.today.endOf('year');
d = d.startOf('day');
return new Day(d, null, context)
},
now: (context) => {
return new Moment(context.today, null, context) // should we set the current hour?
},
};
knownWord.tommorrow = knownWord.tomorrow;
knownWord.tmrw = knownWord.tomorrow;
knownWord.anytime = knownWord.today;
knownWord.sometime = knownWord.today;
const today = function (doc, context, parts) {
let unit = null;
// is it empty?
if (doc.found === false) {
// do we have just a time?
if (parts.time !== null) {
unit = new Moment(context.today, null, context); // choose today
}
//do we just have a shift?
if (parts.shift && Object.keys(parts.shift).length > 0) {
if (parts.shift.hour || parts.shift.minute) {
unit = new Moment(context.today, null, context); // choose now
} else {
unit = new Day(context.today, null, context); // choose today
}
}
}
// today, yesterday, tomorrow
let str = doc.text('reduced');
if (knownWord.hasOwnProperty(str) === true) {
return knownWord[str](context)
}
// day after next
if (str === 'next' && parts.shift && Object.keys(parts.shift).length > 0) {
return knownWord.tomorrow(context)
}
return unit
};
//yep,
const jan$1 = 'january';
const feb$1 = 'february';
const mar$1 = 'march';
const apr = 'april';
const may$1 = 'may';
const jun$1 = 'june';
const jul = 'july';
const aug = 'august';
const sep$1 = 'september';
const oct$1 = 'october';
const nov$1 = 'november';
const dec = 'december';
var fixed = {
'new years eve': [dec, 31],
'new years': [jan$1, 1],
'new years day': [jan$1, 1],
'inauguration day': [jan$1, 20],
'australia day': [jan$1, 26],
'national freedom day': [feb$1, 1],
'groundhog day': [feb$1, 2],
'rosa parks day': [feb$1, 4],
'valentines day': [feb$1, 14],
'saint valentines day': [feb$1, 14],
'st valentines day ': [feb$1, 14],
'saint patricks day': [mar$1, 17],
'st patricks day': [mar$1, 17],
'april fools': [apr, 1],
'april fools day': [apr, 1],
'emancipation day': [apr, 16],
'tax day': [apr, 15], //US
'labour day': [may$1, 1],
'cinco de mayo': [may$1, 5],
'national nurses day': [may$1, 6],
'harvey milk day': [may$1, 22],
'victoria day': [may$1, 24],
juneteenth: [jun$1, 19],
'canada day': [jul, 1],
'independence day': [jul, 4],
'independents day': [jul, 4],
'bastille day': [jul, 14],
'purple heart day': [aug, 7],
'womens equality day': [aug, 26],
'16 de septiembre': [sep$1, 16],
'dieciseis de septiembre': [sep$1, 16],
'grito de dolores': [sep$1, 16],
halloween: [oct$1, 31],
'all hallows eve': [oct$1, 31],
'day of the dead': [oct$1, 31], // Ranged holiday [nov, 2],
'dia de muertos': [oct$1, 31], // Ranged holiday [nov, 2],
'veterans day': [nov$1, 11],
'st andrews day': [nov$1, 30],
'saint andrews day': [nov$1, 30],
'all saints day': [nov$1, 1],
'all sts day': [nov$1, 1],
'armistice day': [nov$1, 11],
'rememberance day': [nov$1, 11],
'christmas eve': [dec, 24],
christmas: [dec, 25],
xmas: [dec, 25],
'boxing day': [dec, 26],
'st stephens day': [dec, 26],
'saint stephens day': [dec, 26],
// Fixed religious and cultural holidays
// Catholic + Christian
epiphany: [jan$1, 6],
'orthodox christmas day': [jan$1, 7],
'orthodox new year': [jan$1, 14],
'assumption of mary': [aug, 15],
'all souls day': [nov$1, 2],
'feast of the immaculate conception': [dec, 8],
'feast of our lady of guadalupe': [dec, 12],
// Kwanzaa
kwanzaa: [dec, 26], // Ranged holiday [jan, 1],
// Pagan / metal 🤘
imbolc: [feb$1, 2],
beltaine: [may$1, 1],
lughnassadh: [aug, 1],
samhain: [oct$1, 31]
};
// holidays that are the same date every year
const fixedDates$1 = function (str, normal, year, tz) {
if (fixed.hasOwnProperty(str) || fixed.hasOwnProperty(normal)) {
let arr = fixed[str] || fixed[normal] || [];
let s = main.now(tz);
s = s.year(year);
s = s.startOf('year');
s = s.month(arr[0]);
s = s.date(arr[1]);
if (s.isValid()) {
return s
}
}
return null
};
var fixedDates$2 = fixedDates$1;
//these are holidays on the 'nth weekday of month'
const jan = 'january';
const feb = 'february';
const mar = 'march';
// const apr = 'april'
const may = 'may';
const jun = 'june';
// const jul = 'july'
// const aug = 'august'
const sep = 'september';
const oct = 'october';
const nov = 'november';
// const dec = 'december'
const mon = 'monday';
// const tues = 'tuesday'
// const wed = 'wednesday'
const thurs = 'thursday';
const fri = 'friday';
// const sat = 'saturday'
const sun = 'sunday';
let holidays$3 = {
'martin luther king day': [3, mon, jan], //[third monday in january],
'presidents day': [3, mon, feb], //[third monday in february],
'commonwealth day': [2, mon, mar], //[second monday in march],
'mothers day': [2, sun, may], //[second Sunday in May],
'fathers day': [3, sun, jun], //[third Sunday in June],
'labor day': [1, mon, sep], //[first monday in september],
'columbus day': [2, mon, oct], //[second monday in october],
'canadian thanksgiving': [2, mon, oct], //[second monday in october],
thanksgiving: [4, thurs, nov], // [fourth Thursday in November],
'black friday': [4, fri, nov] //[fourth friday in november],
// 'memorial day': [may], //[last monday in may],
// 'us election': [nov], // [Tuesday following the first Monday in November],
// 'cyber monday': [nov]
// 'advent': [] // fourth Sunday before Christmas
};
// add aliases
holidays$3['turday day'] = holidays$3.thanksgiving;
holidays$3['indigenous peoples day'] = holidays$3['columbus day'];
holidays$3['mlk day'] = holidays$3['martin luther king day'];
var calendar = holidays$3;
// holidays that are the same date every year
const fixedDates = function (str, normal, year, tz) {
if (calendar.hasOwnProperty(str) || calendar.hasOwnProperty(normal)) {
let arr = calendar[str] || calendar[normal] || [];
let s = main.now(tz);
s = s.year(year);
// [3rd, 'monday', 'january']
s = s.month(arr[2]);
s = s.startOf('month');
// make it january
let month = s.month();
// make it the 1st monday
s = s.day(arr[1]);
if (s.month() !== month) {
s = s.add(1, 'week');
}
// make it nth monday
if (arr[0] > 1) {
s = s.add(arr[0] - 1, 'week');
}
if (s.isValid()) {
return s
}
}
return null
};
var nthWeekday = fixedDates;
// https://www.timeanddate.com/calendar/determining-easter-date.html
let dates$2 = {
easter: 0,
'ash wednesday': -46, // (46 days before easter)
'palm sunday': 7, // (1 week before easter)
'maundy thursday': -3, // (3 days before easter)
'good friday': -2, // (2 days before easter)
'holy saturday': -1, // (1 days before easter)
'easter saturday': -1, // (1 day before easter)
'easter monday': 1, // (1 day after easter)
'ascension day': 39, // (39 days after easter)
'whit sunday': 49, // / pentecost (49 days after easter)
'whit monday': 50, // (50 days after easter)
'trinity sunday': 65, // (56 days after easter)
'corpus christi': 60, // (60 days after easter)
'mardi gras': -47 //(47 days before easter)
};
dates$2['easter sunday'] = dates$2.easter;
dates$2.pentecost = dates$2['whit sunday'];
dates$2.whitsun = dates$2['whit sunday'];
var holidays$2 = dates$2;
// by John Dyer
// based on the algorithm by Oudin (1940) from http://www.tondering.dk/claus/cal/easter.php
const calcEaster = function (year) {
let f = Math.floor,
// Golden Number - 1
G = year % 19,
C = f(year / 100),
// related to Epact
H = (C - f(C / 4) - f((8 * C + 13) / 25) + 19 * G + 15) % 30,
// number of days from 21 March to the Paschal full moon
I = H - f(H / 28) * (1 - f(29 / (H + 1)) * f((21 - G) / 11)),
// weekday for the Paschal full moon
J = (year + f(year / 4) + I + 2 - C + f(C / 4)) % 7,
// number of days from 21 March to the Sunday on or before the Paschal full moon
L = I - J,
month = 3 + f((L + 40) / 44),
date = L + 28 - 31 * f(month / 4);
month = month === 4 ? 'April' : 'March';
return month + ' ' + date
};
var calcEaster$1 = calcEaster;
//calculate any holidays based on easter
const easterDates = function (str, normal, year, tz) {
if (holidays$2.hasOwnProperty(str) || holidays$2.hasOwnProperty(normal)) {
let days = holidays$2[str] || holidays$2[normal] || [];
let date = calcEaster$1(year);
if (!date) {
return null //no easter for this year
}
let e = main(date, tz);
e = e.year(year);
let s = e.add(days, 'day');
if (s.isValid()) {
return s
}
}
return null
};
var easterDates$1 = easterDates;
// http://www.astropixels.com/ephemeris/soleq2001.html
// years 2000-2100
const exceptions = {
spring: [
2003,
2007,
2044,
2048,
2052,
2056,
2060,
2064,
2068,
2072,
2076,
2077,
2080,
2081,
2084,
2085,
2088,
2089,
2092,
2093,
2096,
2097
],
summer: [
2021,
2016,
2020,
2024,
2028,
2032,
2036,
2040,
2041,
2044,
2045,
2048,
2049,
2052,
2053,
2056,
2057,
2060,
2061,
2064,
2065,
2068,
2069,
2070,
2072,
2073,
2074,
2076,
2077,
2078,
2080,
2081,
2082,
2084,
2085,
2086,
2088,
2089,
2090,
2092,
2093,
2094,
2096,
2097,
2098,
2099
],
fall: [
2002,
2003,
2004,
2006,
2007,
2010,
2011,
2014,
2015,
2018,
2019,
2022,
2023,
2026,
2027,
2031,
2035,
2039,
2043,
2047,
2051,
2055,
2059,
2092,
2096
],
winter: [
2002,
2003,
2006,
2007,
2011,
2015,
2019,
2023,
2027,
2031,
2035,
2039,
2043,
2080,
2084,
2088,
2092,
2096
]
};
const winter20th = [2080, 2084, 2088, 2092, 2096];
const calcSeasons = function (year) {
// most common defaults
let res = {
spring: 'March 20 ' + year,
summer: 'June 21 ' + year,
fall: 'Sept 22 ' + year,
winter: 'Dec 21 ' + year
};
if (exceptions.spring.indexOf(year) !== -1) {
res.spring = 'March 19 ' + year;
}
if (exceptions.summer.indexOf(year) !== -1) {
res.summer = 'June 20 ' + year;
}
if (exceptions.fall.indexOf(year) !== -1) {
res.fall = 'Sept 21 ' + year;
}
// winter can be 20th, 21st, or 22nd
if (exceptions.winter.indexOf(year) !== -1) {
res.winter = 'Dec 22 ' + year;
}
if (winter20th.indexOf(year) !== -1) {
res.winter = 'Dec 20 ' + year;
}
return res
};
var calcSeasons$1 = calcSeasons;
// these are properly calculated in ./lib/seasons
let dates$1 = {
'spring equinox': 'spring',
'summer solistice': 'summer',
'fall equinox': 'fall',
'winter solstice': 'winter'
};
// aliases
dates$1['march equinox'] = dates$1['spring equinox'];
dates$1['vernal equinox'] = dates$1['spring equinox'];
dates$1['ostara'] = dates$1['spring equinox'];
dates$1['june solstice'] = dates$1['summer solistice'];
dates$1['litha'] = dates$1['summer solistice'];
dates$1['autumn equinox'] = dates$1['fall equinox'];
dates$1['autumnal equinox'] = dates$1['fall equinox'];
dates$1['september equinox'] = dates$1['fall equinox'];
dates$1['sept equinox'] = dates$1['fall equinox'];
dates$1['mabon'] = dates$1['fall equinox'];
dates$1['december solstice'] = dates$1['winter solistice'];
dates$1['dec solstice'] = dates$1['winter solistice'];
dates$1['yule'] = dates$1['winter solistice'];
var holidays$1 = dates$1;
const astroDates = function (str, normal, year, tz) {
if (holidays$1.hasOwnProperty(str) || holidays$1.hasOwnProperty(normal)) {
let season = holidays$1[str] || holidays$1[normal];
let seasons = calcSeasons$1(year);
if (!season || !seasons || !seasons[season]) {
return null // couldn't figure it out
}
let s = main(seasons[season], tz);
if (s.isValid()) {
return s
}
}
return null
};
var astroDates$1 = astroDates;
let dates$3 = {
// Muslim holidays
'isra and miraj': 'april 13',
'lailat al-qadr': 'june 10',
'eid al-fitr': 'june 15',
'id al-Fitr': 'june 15',
'eid ul-Fitr': 'june 15',
ramadan: 'may 16', // Range holiday
'eid al-adha': 'sep 22',
muharram: 'sep 12',
'prophets birthday': 'nov 21'
};
var holidays$4 = dates$3;
// (lunar year is 354.36 days)
const dayDiff = -10.64;
const lunarDates = function (str, normal, year, tz) {
if (holidays$4.hasOwnProperty(str) || holidays$4.hasOwnProperty(normal)) {
let date = holidays$4[str] || holidays$4[normal] || [];
if (!date) {
return null
}
// start at 2018
let s = main(date + ' 2018', tz);
let diff = year - 2018;
let toAdd = diff * dayDiff;
s = s.add(toAdd, 'day');
s = s.startOf('day');
// now set the correct year
s = s.year(year);
if (s.isValid()) {
return s
}
}
return null
};
var lunarDates$1 = lunarDates;
const nowYear = main.now().year();
const spacetimeHoliday = function (str, year, tz) {
year = year || nowYear;
str = str || '';
str = String(str);
str = str.trim().toLowerCase();
str = str.replace(/'s/, 's'); // 'mother's day'
let normal = str.replace(/ day$/, '');
normal = normal.replace(/^the /, '');
normal = normal.replace(/^orthodox /, ''); //orthodox good friday
// try easier, unmoving holidays
let s = fixedDates$2(str, normal, year, tz);
if (s !== null) {
return s
}
// try 'nth monday' holidays
s = nthWeekday(str, normal, year, tz);
if (s !== null) {
return s
}
// easter-based holidays
s = easterDates$1(str, normal, year, tz);
if (s !== null) {
return s
}
// solar-based holidays
s = astroDates$1(str, normal, year, tz);
if (s !== null) {
return s
}
// mostly muslim holidays
s = lunarDates$1(str, normal, year, tz);
if (s !== null) {
return s
}
return null
};
const parseHoliday = function (doc, context) {
let unit = null;
let m = doc.match('[<holiday>#Holiday+] [<year>#Year?]');
let year = context.today.year();
if (m.groups('year').found) {
year = Number(m.groups('year').text('reduced')) || year;
}
let str = m.groups('holiday').text('reduced');
let s = spacetimeHoliday(str, year, context.timezone);
if (s !== null) {
// assume the year in the future..
if (s.isBefore(context.today) && year === context.today.year()) {
s = spacetimeHoliday(str, year + 1, context.timezone);
}
unit = new Holiday(s, null, context);
}
return unit
};
const mapping$1 = {
day: Day,
hour: Hour,
evening: Hour,
second: Moment,
milliscond: Moment,
instant: Moment,
minute: Minute,
week: Week,
weekend: WeekEnd,
month: AnyMonth,
quarter: AnyQuarter,
year: Year,
season: Season,
// set aliases
yr: Year,
qtr: AnyQuarter,
wk: Week,
sec: Moment,
hr: Hour,
};
let matchStr = `^(${Object.keys(mapping$1).join('|')})$`;
// when a unit of time is spoken of as 'this month' - instead of 'february'
const nextLast = function (doc, context) {
//this month, last quarter, next year
let m = doc.match(matchStr);
if (m.found === true) {
let str = m.text('reduced');
if (mapping$1.hasOwnProperty(str)) {
let Model = mapping$1[str];
if (!Model) {
return null
}
let unit = new Model(null, str, context);
return unit
}
}
//'next friday, last thursday'
m = doc.match('^#WeekDay$');
if (m.found === true) {
let str = m.text('reduced');
let unit = new WeekDay(str, null, context);
return unit
}
// tuesday next week
// m = doc.match('^#WeekDay (this|next)')
// if (m.found === true) {
// let str = m.text('reduced')
// let unit = new WeekDay(str, null, context)
// return unit
// }
return null
};
const fmtToday = function (context) {
return {
date: context.today.date(),
month: context.today.month(),
year: context.today.year(),
}
};
const parseYearly = function (doc, context) {
// support 'summer 2002'
let m = doc.match('(spring|summer|winter|fall|autumn) [<year>#Year?]');
if (m.found) {
let str = doc.text('reduced');
let s = main(str, context.timezone, { today: fmtToday(context) });
let unit = new Season(s, null, context);
if (unit.d.isValid() === true) {
return unit
}
}
// support 'q4 2020'
m = doc.match('[<q>#FinancialQuarter] [<year>#Year?]');
if (m.found) {
let str = m.groups('q').text('reduced');
let s = main(str, context.timezone, { today: fmtToday(context) });
if (m.groups('year')) {
let year = Number(m.groups('year').text()) || context.today.year();
s = s.year(year);
}
let unit = new Quarter(s, null, context);
if (unit.d.isValid() === true) {
return unit
}
}
// support '4th quarter 2020'
m = doc.match('[<q>#Value] quarter (of|in)? [<year>#Year?]');
if (m.found) {
let q = m.groups('q').text('reduced');
let s = main(`q${q}`, context.timezone, { today: fmtToday(context) });
if (m.groups('year')) {
let year = Number(m.groups('year').text()) || context.today.year();
s = s.year(year);
}
let unit = new Quarter(s, null, context);
if (unit.d.isValid() === true) {
return unit
}
}
// support '2020'
m = doc.match('^#Year$');
if (m.found) {
let str = doc.text('reduced');
let s = main(null, context.timezone, { today: fmtToday(context) });
s = s.year(str);
let unit = new Year(s, null, context);
if (unit.d.isValid() === true) {
return unit
}
}
return null
};
// parse things like 'june 5th 2019'
// most of this is done in spacetime
const parseExplicit = function (doc, context) {
let impliedYear = context.today.year();
// 'fifth of june 1992'
// 'june the fifth 1992'
let m = doc.match('[<date>#Value] of? [<month>#Month] [<year>#Year]');
if (!m.found) {
m = doc.match('[<month>#Month] the? [<date>#Value] [<year>#Year]');
}
if (m.found) {
let obj = {
month: m.groups('month').text('reduced'),
date: m.groups('date').text('reduced'),
year: m.groups('year').text() || impliedYear,
};
let unit = new CalendarDate(obj, null, context);
if (unit.d.isValid() === true) {
return unit
}
}
// 'march 1992'
m = doc.match('[<month>#Month] of? [<year>#Year]');
if (m.found) {
let obj = {
month: m.groups('month').text('reduced'),
year: m.groups('year').text('reduced') || impliedYear,
};
let unit = new Month(obj, null, context);
if (unit.d.isValid() === true) {
return unit
}
}
// 'march 5th next year'
m = doc.match('[<month>#Month] [<date>#Value+]? of? the? [<rel>(this|next|last|current)] year');
if (m.found) {
let rel = m.groups('rel').text('reduced');
let year = impliedYear;
if (rel === 'next') {
year += 1;
} else if (rel === 'last') {
year -= 1;
}
let obj = {
month: m.groups('month').text('reduced'),
date: m.groups('date').numbers(0).get()[0],
year,
};
if (obj.date === undefined) {
obj.date = 1;
let unit = new Month(obj, null, context);
if (unit.d.isValid() === true) {
return unit
}
}
let unit = new CalendarDate(obj, null, context);
if (unit.d.isValid() === true) {
return unit
}
}
// '5th of next month'
m = doc.match('^the? [<date>#Value+]? of? [<rel>(this|next|last|current)] month');
if (m.found) {
let month = context.today.month();
let rel = m.groups('rel').text('reduced');
if (rel === 'next') {
month += 1;
} else if (rel === 'last') {
month -= 1;
}
let obj = {
month,
date: m.groups('date').numbers(0).get()[0],
};
let unit = new CalendarDate(obj, null, context);
if (unit.d.isValid() === true) {
return unit
}
}
//no-years
// 'fifth of june'
m = doc.match('[<date>#Value] of? [<month>#Month]');
// 'june the fifth'
if (!m.found) {
m = doc.match('[<month>#Month] the? [<date>#Value]');
}
if (m.found) {
let obj = {
month: m.groups('month').text('reduced'),
date: m.groups('date').text('reduced'),
year: context.today.year(),
};
let unit = new CalendarDate(obj, null, context);
// assume 'feb' in the future
if (unit.d.month() < context.today.month()) {
obj.year += 1;
unit = new CalendarDate(obj, null, context);
}
if (unit.d.isValid() === true) {
return unit
}
}
// support 'december'
if (doc.has('#Month')) {
let obj = {
month: doc.match('#Month').text('reduced'),
date: 1, //assume 1st
year: context.today.year(),
};
let unit = new Month(obj, null, context);
// assume 'february' is always in the future
if (unit.d.month() < context.today.month()) {
obj.year += 1;
unit = new Month(obj, null, context);
}
if (unit.d.isValid() === true) {
return unit
}
}
// support 'thursday 21st'
m = doc.match('#WeekDay [<date>#Value]');
if (m.found) {
let obj = {
month: context.today.month(),
date: m.groups('date').text('reduced'),
year: context.today.year(),
};
let unit = new CalendarDate(obj, null, context);
if (unit.d.isValid() === true) {
return unit
}
}
// support date-only 'the 21st'
m = doc.match('the [<date>#Value]');
if (m.found) {
let obj = {
month: context.today.month(),
date: m.groups('date').text('reduced'),
year: context.today.year(),
};
let unit = new CalendarDate(obj, null, context);
if (unit.d.isValid() === true) {
// assume it's forward
if (unit.d.isBefore(context.today)) {
unit.d = unit.d.add(1, 'month');
}
return unit
}
}
// parse ISO as a Moment
m = doc.match('/[0-9]{4}-[0-9]{2}-[0-9]{2}t[0-9]{2}:/');
if (m.found) {
let str = doc.text('reduced');
let unit = new Moment(str, null, context);
if (unit.d.isValid() === true) {
return unit
}
}
let str = doc.text('reduced');
if (!str) {
return new Moment(context.today, null, context)
}
// punt it to spacetime, for the heavy-lifting
let unit = new Day(str, null, context);
// console.log(str, unit, context.today.year())
// did we find a date?
if (unit.d.isValid() === false) {
return null
}
return unit
};
const parse$3 = function (doc, context, parts) {
let unit = null;
//'in two days'
unit = unit || today(doc, context, parts);
// 'this haloween'
unit = unit || parseHoliday(doc, context);
// 'this month'
unit = unit || nextLast(doc, context);
// 'q2 2002'
unit = unit || parseYearly(doc, context);
// 'this june 2nd'
unit = unit || parseExplicit(doc, context);
return unit
};
const units = {
day: Day,
week: Week,
weekend: WeekEnd,
month: Month,
quarter: Quarter,
season: Season,
hour: Hour,
minute: Minute,
};
const applyCounter = function (unit, counter = {}) {
let Unit = units[counter.unit];
if (!Unit) {
return unit
}
let d = unit.d;
// support 'first' or 0th
if (counter.dir === 'first' || counter.num === 0) {
d = unit.start().d;
d = d.startOf(counter.unit);
} else if (counter.dir === 'last') {
d = d.endOf(unit.unit);
if (counter.unit === 'weekend') {
d = d.day('saturday', false);
} else {
d = d.startOf(counter.unit);
}
} else if (counter.num) {
if (counter.unit === 'weekend') {
d = d.day('saturday', true).add(1, 'day'); //fix bug
}
// support 'nth week', eg.
d = d.add(counter.num, counter.unit);
}
let u = new Unit(d, null, unit.context);
if (u.d.isValid() === true) {
return u
}
return unit //fallback
};
// apply all parsed {parts} to our unit date
const transform = function (unit, context, parts) {
if (!unit && parts.weekDay) {
unit = new WeekDay(parts.weekDay, null, context);
parts.weekDay = null;
}
// just don't bother
if (!unit) {
return null
}
// 2 days after..
if (parts.shift) {
let shift = parts.shift;
unit.applyShift(shift);
// allow shift to change our unit size
if (shift.hour || shift.minute) {
unit = new Moment(unit.d, null, unit.context);
} else if (shift.week || shift.day || shift.month) {
unit = new Day(unit.d, null, unit.context);
}
}
// wednesday next week
if (parts.weekDay && unit.unit !== 'day') {
unit.applyWeekDay(parts.weekDay);
unit = new WeekDay(unit.d, null, unit.context);
}
// this/next/last
if (parts.rel) {
unit.applyRel(parts.rel);
}
// end of
if (parts.section) {
unit.applySection(parts.section);
}
// at 5:40pm
if (parts.time) {
unit.applyTime(parts.time);
// unit = new Minute(unit.d, null, unit.context)
}
// apply counter
if (parts.counter && parts.counter.unit) {
unit = applyCounter(unit, parts.counter);
}
return unit
};
// import spacetime from 'spacetime'
const env$1 = typeof process === 'undefined' || !process.env ? self.env || {} : process.env;
const log$1 = parts => {
if (env$1.DEBUG_DATE) {
// console.log(parts)// eslint-disable-line
console.log(`\n==== '${parts.doc.text()}' =====`); // eslint-disable-line
Object.keys(parts).forEach(k => {
if (k !== 'doc' && parts[k]) {
console.log(k, parts[k]); // eslint-disable-line
}
});
parts.doc.debug(); // allow
}
};
const parseDate = function (doc, context) {
//parse-out any sections
let parts = tokenize(doc, context);
doc = parts.doc;
// logger
log$1(parts);
//apply our given timezone
if (parts.tz) {
context = Object.assign({}, context, { timezone: parts.tz });
// set timezone on any 'today' value, too
let iso = context.today.format('iso-short');
context.today = context.today.goto(context.timezone).set(iso);
}
// decide on a root date object
let unit = parse$3(doc, context, parts);
// apply all our parts
unit = transform(unit, context, parts);
return unit
};
const dayNames = {
mon: 'monday',
tue: 'tuesday',
tues: 'wednesday',
wed: 'wednesday',
thu: 'thursday',
fri: 'friday',
sat: 'saturday',
sun: 'sunday',
monday: 'monday',
tuesday: 'tuesday',
wednesday: 'wednesday',
thursday: 'thursday',
friday: 'friday',
saturday: 'saturday',
sunday: 'sunday',
};
// 'any tuesday' vs 'every tuesday'
const parseLogic = function (m) {
if (m.match('(every|each)').found) {
return 'AND'
}
if (m.match('(any|a)').found) {
return 'OR'
}
return null
};
// parse repeating dates, like 'every week'
const parseIntervals = function (doc, context) {
// 'every week'
let m = doc.match('[<logic>(every|any|each)] [<skip>other?] [<unit>#Duration] (starting|beginning|commencing)?');
if (m.found) {
let repeat = { interval: {} };
let unit = m.groups('unit').text('reduced');
repeat.interval[unit] = 1;
repeat.choose = parseLogic(m);
// 'every other week'
if (m.groups('skip').found) {
repeat.interval[unit] = 2;
}
doc = doc.remove(m);
return { repeat: repeat }
}
// 'every two weeks'
m = doc.match('[<logic>(every|any|each)] [<num>#Value] [<unit>#Duration] (starting|beginning|commencing)?');
if (m.found) {
let repeat = { interval: {} };
let units = m.groups('unit');
units.nouns().toSingular();
let unit = units.text('reduced');
repeat.interval[unit] = m.groups('num').numbers().get()[0];
repeat.choose = parseLogic(m);
doc = doc.remove(m);
return { repeat: repeat }
}
// 'every friday'
m = doc.match('[<logic>(every|any|each|a)] [<skip>other?] [<day>#WeekDay+] (starting|beginning|commencing)?');
if (m.found) {
let repeat = { interval: { day: 1 }, filter: { weekDays: {} } };
let str = m.groups('day').text('reduced');
str = dayNames[str]; //normalize it
if (str) {
repeat.filter.weekDays[str] = true;
repeat.choose = parseLogic(m);
doc = doc.remove(m);
return { repeat: repeat }
}
}
// 'every weekday'
m = doc.match(
'[<logic>(every|any|each|a)] [<day>(weekday|week day|weekend|weekend day)] (starting|beginning|commencing)?'
);
if (m.found) {
let repeat = { interval: { day: 1 }, filter: { weekDays: {} } };
let day = m.groups('day');
if (day.has('(weekday|week day)')) {
repeat.filter.weekDays = {
monday: true,
tuesday: true,
wednesday: true,
thursday: true,
friday: true,
};
} else if (day.has('(weekend|weekend day)')) {
repeat.filter.weekDays = {
saturday: true,
sunday: true,
};
}
repeat.choose = parseLogic(m);
doc = doc.remove(m);
return { repeat: repeat }
}
// mondays
m = doc.match(
'[<day>(mondays|tuesdays|wednesdays|thursdays|fridays|saturdays|sundays)] (at|near|after)? [<time>#Time+?]'
);
if (m.found) {
let repeat = { interval: { day: 1 }, filter: { weekDays: {} } };
let str = m.groups('day').text('reduced');
str = str.replace(/s$/, '');
str = dayNames[str]; //normalize it
if (str) {
repeat.filter.weekDays[str] = true;
repeat.choose = 'OR';
doc = doc.remove(m);
let time = m.groups('time');
if (time.found) {
repeat.time = parseTime(time, context);
}
return { repeat: repeat }
}
}
return null
};
// somewhat-intellegent response to end-before-start situations
const reverseMaybe = function (obj) {
let start = obj.start;
let end = obj.end;
if (start.d.isAfter(end.d)) {
// wednesday to sunday -> move end up a week
if (start.isWeekDay && end.isWeekDay) {
obj.end.next();
return obj
}
// else, reverse them
let tmp = start;
obj.start = end;
obj.end = tmp;
}
return obj
};
const moveToPM = function (obj) {
let start = obj.start;
let end = obj.end;
if (start.d.isAfter(end.d)) {
if (end.d.hour() < 10) {
end.d = end.d.ampm('pm');
}
}
return obj
};
var doTwoTimes = [
{
// '3pm to 4pm january 5th'
match: '[<from>#Time+] (to|until|upto|through|thru|and) [<to>#Time+ #Date+]',
desc: '3pm to 4pm january 5th',
parse: (m, context) => {
let from = m.groups('from');
let to = m.groups('to');
let end = parseDate(to, context);
if (end) {
let start = end.clone();
start.applyTime(from.text('implicit'));
if (start) {
let obj = {
start: start,
end: end,
unit: 'time',
};
if (/(am|pm)/.test(to) === false) {
obj = moveToPM(obj);
}
obj = reverseMaybe(obj);
return obj
}
}
return null
},
},
{
// 'january from 3pm to 4pm'
match: '[<main>#Date+] from [<a>#Time] (to|until|upto|through|thru|and) [<b>#Time+]',
desc: 'january from 3pm to 4pm',
parse: (m, context) => {
let main = m.groups('main');
let a = m.groups('a');
let b = m.groups('b');
main = parseDate(main, context);
if (main) {
main.applyTime(a.text('implicit'));
let end = main.clone();
end.applyTime(b.text('implicit'));
if (end) {
let obj = {
start: main,
end: end,
unit: 'time',
};
if (/(am|pm)/.test(b.text()) === false) {
obj = moveToPM(obj);
}
obj = reverseMaybe(obj);
return obj
}
}
return null
},
},
{
// 'january 5th 3pm to 4pm'
match: '[<from>#Date+] (to|until|upto|through|thru|and) [<to>#Time+]',
desc: 'january from 3pm to 4pm',
parse: (m, context) => {
let from = m.groups('from');
let to = m.groups('to');
from = parseDate(from, context);
if (from) {
let end = from.clone();
end.applyTime(to.text('implicit'));
if (end) {
let obj = {
start: from,
end: end,
unit: 'time',
};
if (/(am|pm)/.test(to.text()) === false) {
obj = moveToPM(obj);
}
obj = reverseMaybe(obj);
return obj
}
}
return null
},
},
];
// these are dates that produce two seperate dates,
// and not a start-end range.
var doCombos = [
{
// 'jan, or march 1999'
match: '^during? #Month+ (or|and) #Month [<year>#Year]?',
desc: 'march or june',
parse: (m, context) => {
let before = m.match('^during? [#Month]', 0);
m = m.not('(or|and)');
let start = parseDate(before, context);
if (start) {
let result = [
{
start: start,
end: start.clone().end(),
unit: start.unit,
},
];
// add more run-on numbers?
let more = m.not(before);
if (more.found) {
more.match('#Month').forEach((month) => {
let s = parseDate(month, context);
// s.d = s.d.month(month.text('reduced'))
result.push({
start: s,
end: s.clone().end(),
unit: s.unit,
});
});
}
// apply the year
let year = m.match('#Year$');
if (year.found) {
year = year.text('reduced');
result.forEach((o) => {
o.start.d = o.start.d.year(year);
o.end.d = o.end.d.year(year);
});
}
return result
}
return null
},
},
{
// 'jan 5 or 8' - (one month, shared dates)
match: '^#Month #Value+ (or|and)? #Value$',
desc: 'jan 5 or 8',
parse: (m, context) => {
m = m.not('(or|and)');
let before = m.match('^#Month #Value');
let start = parseDate(before, context);
if (start) {
let result = [
{
start: start,
end: start.clone().end(),
unit: start.unit,
},
];
// add more run-on numbers?
let more = m.not(before);
if (more.found) {
more.match('#Value').forEach((v) => {
let s = start.clone();
s.d = s.d.date(v.text('reduced'));
result.push({
start: s,
end: s.clone().end(),
unit: s.unit,
});
});
}
return result
}
return null
},
},
{
// 'jan 5, 8' - (similar to above)
match: '^#Month+ #Value #Value+$',
desc: 'jan 5 8',
parse: (m, context) => {
let month = m.match('#Month');
let year = m.match('#Year');
m = m.not('#Year');
let results = [];
m.match('#Value').forEach((val) => {
val = val.clone();
let d = val.prepend(month.text());
if (year.found) {
d.append(year);
}
let start = parseDate(d, context);
if (start) {
results.push({
start: start,
end: start.clone().end(),
unit: start.unit,
});
}
});
return results
},
},
{
// '5 or 8 of jan' - (one month, shared dates)
match: '^#Value+ (or|and)? #Value of #Month #Year?$',
desc: '5 or 8 of Jan',
parse: (m, context) => {
let month = m.match('#Month');
let year = m.match('#Year');
m = m.not('#Year');
let results = [];
m.match('#Value').forEach((val) => {
let d = val.append(month);
if (year.found) {
d.append(year);
}
let start = parseDate(d, context);
if (start) {
results.push({
start: start,
end: start.clone().end(),
unit: start.unit,
});
}
});
return results
},
},
{
// 'june or july 2019'
match: '^!(between|from|during)? [<from>#Date+] (and|or) [<to>#Date+]$',
desc: 'A or B',
parse: (m, context) => {
let fromDoc = m.groups('from');
let toDoc = m.groups('to');
let from = parseDate(fromDoc, context);
let to = parseDate(toDoc, context);
if (from && to) {
return [
{
start: from,
end: from.clone().end(),
},
{
start: to,
end: to.clone().end(),
},
]
}
return null
},
},
];
var doDateRange = [
{
// month-ranges have some folksy rules
match: 'between [<start>#Month] and [<end>#Month] [<year>#Year?]',
desc: 'between march and jan',
parse: (m, context) => {
let { start, end, year } = m.groups();
let y = year && year.found ? Number(year.text('reduced')) : context.today.year();
let obj = { month: start.text('reduced'), year: y };
let left = new Month(obj, null, context).start();
obj = { month: end.text('reduced'), year: y };
let right = new Month(obj, null, context).start();
if (left.d.isAfter(right.d)) {
return {
start: right,
end: left,
}
}
return {
start: left,
end: right,
}
},
},
{
// two explicit dates - 'between friday and sunday'
match: 'between [<start>.+] and [<end>.+]',
desc: 'between friday and sunday',
parse: (m, context) => {
let start = m.groups('start');
start = parseDate(start, context);
let end = m.groups('end');
end = parseDate(end, context);
if (start && end) {
end = end.before();
return {
start: start,
end: end,
}
}
return null
},
},
{
// two months, no year - 'june 5 to june 7'
match: '[<from>#Month #Value] (to|through|thru) [<to>#Month #Value] [<year>#Year?]',
desc: 'june 5 to june 7',
parse: (m, context) => {
let res = m.groups();
let start = res.from;
if (res.year) {
start = start.append(res.year);
}
start = parseDate(start, context);
if (start) {
let end = res.to;
if (res.year) {
end = end.append(res.year);
}
end = parseDate(end, context);
if (end) {
// assume end is after start
if (start.d.isAfter(end.d)) {
end.d = end.d.add(1, 'year');
}
let obj = {
start: start,
end: end.end(),
};
return obj
}
}
return null
},
},
{
// one month, one year, first form - 'january 5 to 7 1998'
match: '[<month>#Month] [<from>#Value] (to|through|thru) [<to>#Value] of? [<year>#Year]',
desc: 'january 5 to 7 1998',
parse: (m, context) => {
let { month, from, to, year } = m.groups();
let year2 = year.clone();
let start = from.prepend(month).append(year);
start = parseDate(start, context);
if (start) {
let end = to.prepend(month).append(year2);
end = parseDate(end, context);
return {
start: start,
end: end.end(),
}
}
return null
},
},
{
// one month, one year, second form - '5 to 7 of january 1998'
match: '[<from>#Value] (to|through|thru) [<to>#Value of? #Month #Date+?]',
desc: '5 to 7 of january 1998',
parse: (m, context) => {
let to = m.groups('to');
to = parseDate(to, context);
if (to) {
let fromDate = m.groups('from');
let from = to.clone();
from.d = from.d.date(fromDate.text('implicit'));
return {
start: from,
end: to.end(),
}
}
return null
},
},
{
// one month, no year - 'january 5 to 7'
match: '[<from>#Month #Value] (to|through|thru) [<to>#Value]',
desc: 'january 5 to 7',
parse: (m, context) => {
let from = m.groups('from');
from = parseDate(from, context);
if (from) {
let toDate = m.groups('to');
let to = from.clone();
to.d = to.d.date(toDate.text('implicit'));
return {
start: from,
end: to.end(),
}
}
return null
},
},
{
// 'january to may 2020'
match: 'from? [<from>#Month] (to|until|upto|through|thru) [<to>#Month] [<year>#Year]',
desc: 'january to may 2020',
parse: (m, context) => {
let from = m.groups('from');
let year = m.groups('year').numbers().get()[0];
let to = m.groups('to');
from = parseDate(from, context);
to = parseDate(to, context);
from.d = from.d.year(year);
to.d = to.d.year(year);
if (from && to) {
let obj = {
start: from,
end: to.end(),
};
// reverse the order?
obj = reverseMaybe(obj);
return obj
}
return null
},
},
{
// in 2 to 4 weeks
match: '^in [<min>#Value] to [<max>#Value] [<unit>(days|weeks|months|years)]',
desc: 'in 2 to 4 weeks',
parse: (m, context) => {
const { min, max, unit } = m.groups();
let start = new CalendarDate(context.today, null, context);
let end = start.clone();
const duration = unit.text('implicit');
start = start.applyShift({ [duration]: min.numbers().get()[0] });
end = end.applyShift({ [duration]: max.numbers().get()[0] });
return {
start: start,
end: end.end(),
}
},
},
{
// 2 to 4 weeks ago
match:
'[<min>#Value] to [<max>#Value] [<unit>(days|weeks|months|years)] (ago|before|earlier|prior)',
desc: '2 to 4 weeks ago',
parse: (m, context) => {
const { min, max, unit } = m.groups();
let start = new CalendarDate(context.today, null, context);
let end = start.clone();
const duration = unit.text('implicit');
start = start.applyShift({ [duration]: -max.numbers().get()[0] });
end = end.applyShift({ [duration]: -min.numbers().get()[0] });
return {
start: start,
end: end.end(),
}
},
},
{
// implicit range
match: '^until [<to>#Date+]',
desc: 'until christmas',
parse: (m, context) => {
let to = m.groups('to');
to = parseDate(to, context);
if (to) {
let start = new CalendarDate(context.today, null, context);
return {
start: start,
end: to.start(),
}
}
return null
},
},
{
// second half of march
match: '[<part>(1st|initial|2nd|latter)] half of [<month>#Month] [<year>#Year?]',
desc: 'second half of march',
parse: (m, context) => {
const { part, month, year } = m.groups();
let obj = {
month: month.text('reduced'),
date: 1, //assume 1st
year: year && year.found ? year.text('reduced') : context.today.year()
};
let unit = new Month(obj, null, context);
if (part.has('(1st|initial)')) {
return {
start: unit.start(),
end: unit.clone().middle(),
}
}
return {
start: unit.middle(),
end: unit.clone().end(),
}
},
},
];
const punt = function (unit, context) {
unit = unit.applyShift(context.punt);
return unit
};
var doOneDate = [
{
// 'from A to B'
match: 'from? [<from>.+] (to|until|upto|through|thru) [<to>.+]',
desc: 'from A to B',
parse: (m, context) => {
let from = m.groups('from');
let to = m.groups('to');
from = parseDate(from, context);
to = parseDate(to, context);
if (from && to) {
let obj = {
start: from,
end: to.end(),
};
obj = reverseMaybe(obj);
return obj
}
return null
},
},
{
// 'before june'
match: '^due? (by|before) [.+]',
desc: 'before june',
parse: (m, context) => {
m = m.group(0);
let unit = parseDate(m, context);
if (unit) {
let start = new Unit(context.today, null, context);
if (start.d.isAfter(unit.d)) {
start = unit.clone().applyShift({ weeks: -2 });
}
// end the night before
let end = unit.clone().applyShift({ day: -1 });
return {
start: start,
end: end.end(),
}
}
return null
},
},
{
// 'in june'
match: '^(on|in|at|@|during) [.+]',
desc: 'in june',
parse: (m, context) => {
m = m.group(0);
let unit = parseDate(m, context);
if (unit) {
return { start: unit, end: unit.clone().end(), unit: unit.unit }
}
return null
},
},
{
// 'after june'
match: '^(after|following) [.+]',
desc: 'after june',
parse: (m, context) => {
m = m.group(0);
let unit = parseDate(m, context);
if (unit) {
unit = unit.after();
return {
start: unit.clone(),
end: punt(unit.clone(), context),
}
}
return null
},
},
{
// 'middle of'
match: '^(middle|center|midpoint) of [.+]',
desc: 'middle of',
parse: (m, context) => {
m = m.group(0);
let unit = parseDate(m, context);
let start = unit.clone().middle();
let end = unit.beforeEnd();
if (unit) {
return {
start: start,
end: end,
}
}
return null
},
},
{
// 'tuesday after 5pm'
match: '.+ after #Time+$',
desc: 'tuesday after 5pm',
parse: (m, context) => {
let unit = parseDate(m, context);
if (unit) {
let start = unit.clone();
let end = unit.end();
return {
start: start,
end: end,
unit: 'time',
}
}
return null
},
},
{
// 'tuesday before noon'
match: '.+ before #Time+$',
desc: 'tuesday before noon',
parse: (m, context) => {
let unit = parseDate(m, context);
if (unit) {
let end = unit.clone();
let start = unit.start();
if (unit) {
return {
start: start,
end: end,
unit: 'time',
}
}
}
return null
},
},
];
const ranges = [].concat(doTwoTimes, doCombos, doDateRange, doOneDate);
const env = typeof process === 'undefined' || !process.env ? self.env || {} : process.env;
const log = msg => {
if (env.DEBUG_DATE) {
console.log(`\n \x1b[32m ${msg} \x1b[0m`); // eslint-disable-line
}
};
const isArray = function (arr) {
return Object.prototype.toString.call(arr) === '[object Array]'
};
//else, try whole thing, non ranges
const tryFull = function (doc, context) {
let res = {
start: null,
end: null,
};
if (!doc.found) {
return res
}
let unit = parseDate(doc, context);
if (unit) {
let end = unit.clone().end();
res = {
start: unit,
end: end,
unit: unit.setTime ? 'time' : unit.unit,
};
}
return res
};
const tryRanges = function (doc, context) {
// try each template in order
for (let i = 0; i < ranges.length; i += 1) {
let fmt = ranges[i];
let m = doc.match(fmt.match);
if (m.found) {
log(` ---[${fmt.desc}]---`);
let res = fmt.parse(m, context);
if (res !== null) {
// did it return more than one date?
if (!isArray(res)) {
res = [res];
}
return res
}
}
}
return null
};
// loop thru each range template
const parseRanges = function (m, context) {
// parse-out 'every week ..'
let repeats = parseIntervals(m, context) || {};
// try picking-apart ranges
let found = tryRanges(m, context);
if (!found) {
found = [tryFull(m, context)];
}
// add the repeat info to each date
found = found.map((o) => Object.assign({}, repeats, o));
// ensure start is not after end
found.forEach((res) => {
if (res.start && res.end && res.start.d.epoch > res.end.d.epoch) {
res.start = res.start.start();
}
});
return found
};
const normalize$1 = function (doc) {
if (!doc.numbers) {
console.warn(`\nCompromise warning: compromise/three must be used with compromise-dates plugin\n`); // eslint-disable-line
}
// normalize doc
doc = doc.clone();
doc.numbers().toNumber();
// expand 'aug 20-21'
doc.contractions().expand();
// 'week-end'
doc.replace('week end', 'weekend', true).tag('Date');
// 'a up to b'
doc.replace('up to', 'upto', true).tag('Date');
// 'a year ago'
if (doc.has('once (a|an) #Duration') === false) {
doc.match('[(a|an)] #Duration', 0).replaceWith('1', { tags: true }).compute('lexicon');
}
// jan - feb
doc.match('@hasDash').insertAfter('to').tag('Date');
return doc
};
const parse$2 = function (doc, context) {
// normalize context
context = context || {};
if (context.timezone === false) {
context.timezone = 'UTC';
}
context.today = context.today || main.now(context.timezone);
context.today = main(context.today, context.timezone);
doc = normalize$1(doc);
let res = parseRanges(doc, context);
return res
};
const getDuration = function (range) {
let end = range.end.d.add(1, 'millisecond');
let diff = end.since(range.start.d).diff;
delete diff.milliseconds;
delete diff.seconds;
return diff
};
const toJSON = function (range) {
if (!range.start) {
return {
start: null,
end: null,
timezone: null,
duration: {},
// range: null
}
}
let diff = range.end ? getDuration(range) : {};
return {
start: range.start.format('iso'),
end: range.end ? range.end.format('iso') : null,
timezone: range.start.d.format('timezone'),
duration: diff,
// range: getRange(diff)
}
};
const quickDate = function (view, str) {
let tmp = view.fromText(str);
let found = parse$2(tmp, view.opts)[0];
if (!found || !found.start || !found.start.d) {
return null
}
return found.start.d
};
const api$2 = function (View) {
class Dates extends View {
constructor(document, pointer, groups, opts = {}) {
super(document, pointer, groups);
this.viewType = 'Dates';
this.opts = Object.assign({}, opts);
}
get(n) {
let all = [];
this.forEach(m => {
parse$2(m, this.opts).forEach(res => {
let json = toJSON(res);
if (json.start) {
all.push(json);
}
});
});
if (typeof n === 'number') {
return all[n]
}
return all
}
json(opts = {}) {
return this.map(m => {
let json = m.toView().json(opts)[0] || {};
if (opts && opts.dates !== false) {
let parsed = parse$2(m, this.opts);
if (parsed.length > 0) {
json.dates = toJSON(parsed[0]);
}
}
return json
}, [])
}
/** replace date terms with a formatted date */
format(fmt) {
let found = this;
let res = found.map(m => {
let obj = parse$2(m, this.opts)[0] || {};
if (obj.start) {
let start = obj.start.d;
let str = start.format(fmt);
if (obj.end) {
let end = obj.end.d;
if (start.isSame(end, 'day') === false) {
str += ' to ' + end.format(fmt);
}
}
m.replaceWith(str);
}
return m
});
return new Dates(this.document, res.pointer, null, this.opts)
}
/** return only dates occuring before a given date */
isBefore(iso) {
let pivot = quickDate(this, iso);
return this.filter(m => {
let obj = parse$2(m, this.opts)[0] || {};
return obj.start && obj.start.d && obj.start.d.isBefore(pivot)
})
}
/** return only dates occuring after a given date */
isAfter(iso) {
let pivot = quickDate(this, iso);
return this.filter(m => {
let obj = parse$2(m, this.opts)[0] || {};
return obj.start && obj.start.d && obj.start.d.isAfter(pivot)
})
}
/** return only dates occuring after a given date */
isSame(unit, iso) {
let pivot = quickDate(this, iso);
return this.filter(m => {
let obj = parse$2(m, this.opts)[0] || {};
return obj.start && obj.start.d && obj.start.d.isSame(pivot, unit)
})
}
}
View.prototype.dates = function (opts) {
let m = findDate(this);
return new Dates(this.document, m.pointer, null, opts)
};
};
const normalize = function (doc) {
doc = doc.clone();
// 'four thirty' -> 4:30
let m = doc.match('#Time+').match('[<hour>#Cardinal] [<min>(thirty|fifteen)]');
if (m.found) {
let hour = m.groups('hour');
let min = m.groups('min');
let num = hour.values().get()[0];
if (num > 0 && num <= 12) {
let mins = min.values().get()[0];
let str = `${num}:${mins}`;
m.replaceWith(str);
}
}
if (!doc.numbers) {
console.warn(`Warning: compromise .numbers() not loaded.\n This plugin requires >= v14`); //eslint-disable-line
} else {
// doc.numbers().normalize()
// convert 'two' to 2
let num = doc.numbers();
num.toNumber();
num.toCardinal(false);
}
// expand 'aug 20-21'
if (doc.contractions) {
doc.contractions().expand();
}
// remove adverbs
doc.adverbs().remove();
// 'week-end'
doc.replace('week end', 'weekend', true).tag('Date');
// 'a up to b'
doc.replace('up to', 'upto', true).tag('Date');
// 'a year ago'
if (doc.has('once (a|an) #Duration') === false) {
doc.match('[(a|an)] #Duration', 0).replaceWith('1').compute('lexicon');
// tagger(doc)
}
// 'in a few years'
// m = doc.match('in [a few] #Duration')
// if (m.found) {
// m.groups('0').replaceWith('2')
// tagger(doc)
// }
// jan - feb
doc.match('@hasDash').insertAfter('to').tag('Date');
return doc
};
const find = function (doc) {
return doc.match('#Time+ (am|pm)?')
};
const parse$1 = function (m, context = {}) {
m = normalize(m);
let res = parseTime(m, context);
if (!res.result) {
return { time: null, '24h': null }
}
let s = main.now().time(res.result);
return {
time: res.result,
'24h': s.format('time-24'),
hour: s.hour(),
minute: s.minute(),
}
};
const getNth = (doc, n) => (typeof n === 'number' ? doc.eq(n) : doc);
const api$1 = function (View) {
class Times extends View {
constructor(document, pointer, groups, opts) {
super(document, pointer, groups);
this.viewType = 'Times';
this.opts = opts || {};
}
format(fmt) {
let found = this;
let res = found.map(m => {
let obj = parse$1(m) || {};
if (obj.time) {
let s = main.now().time(obj.time);
let str = obj.time;
if (fmt === '24h') {
str = s.format('time-24');
} else {
str = s.format(fmt);
}
m = m.not('#Preposition');
m.replaceWith(str);
}
return m
});
return new Times(this.document, res.pointer, null, this.opts)
}
get(n) {
return getNth(this, n).map(parse$1)
}
json(opts = {}) {
return this.map(m => {
let json = m.toView().json(opts)[0] || {};
if (opts && opts.time !== false) {
json.time = parse$1(m);
}
return json
}, [])
}
}
View.prototype.times = function (n) {
let m = find(this);
m = getNth(m, n);
return new Times(this.document, m.pointer)
};
};
const known = {
century: true,
day: true,
decade: true,
hour: true,
millisecond: true,
minute: true,
month: true,
second: true,
weekend: true,
week: true,
year: true,
quarter: true,
season: true,
};
let mapping = {
m: 'minute',
h: 'hour',
hr: 'hour',
min: 'minute',
sec: 'second',
'week end': 'weekend',
wk: 'week',
yr: 'year',
qtr: 'quarter',
};
// add plurals
Object.keys(mapping).forEach((k) => {
mapping[k + 's'] = mapping[k];
});
const parse = function (doc) {
let duration = {};
//parse '8 minutes'
let twoWord = doc.match('#Value+ #Duration');
if (twoWord.found) {
twoWord.forEach((m) => {
let num = m.numbers().get()[0];
let unit = m.terms().last().text('reduced');
unit = unit.replace(/ies$/, 'y');
unit = unit.replace(/s$/, '');
// turn 'mins' into 'minute'
if (mapping.hasOwnProperty(unit)) {
unit = mapping[unit];
}
if (known.hasOwnProperty(unit) && num !== null) {
duration[unit] = num;
}
});
} else {
let oneWord = doc.match('(#Duration && /[0-9][a-z]+$/)');
if (oneWord.found) {
let str = doc.text();
let num = str.match(/([0-9]+)/);
let unit = str.match(/([a-z]+)/);
if (num && unit) {
num = num[0] || null;
unit = unit[0] || null;
if (mapping.hasOwnProperty(unit)) {
unit = mapping[unit];
}
if (known.hasOwnProperty(unit) && num !== null) {
duration[unit] = Number(num);
}
}
}
}
return duration
};
const addDurations = function (View) {
/** phrases like '2 months', or '2mins' */
class Durations extends View {
constructor(document, pointer, groups) {
super(document, pointer, groups);
this.context = {};
}
/** overload the original json with duration information */
json(opts = {}) {
return this.map(m => {
let json = m.toView().json(opts)[0] || {};
if (opts && opts.duration !== false) {
json.duration = parse(m);
}
return json
}, [])
}
/** easy getter for the time */
get(options) {
let arr = [];
this.forEach(doc => {
let res = parse(doc);
arr.push(res);
});
if (typeof options === 'number') {
return arr[options]
}
return arr
}
}
/** phrases like '2 months' */
View.prototype.durations = function (n) {
let m = this.match('#Value+ #Duration (and? #Value+ #Duration)?');
// not 'in 20 minutes'
m = m.notIf('#DateShift');
if (typeof n === 'number') {
m = m.eq(n);
}
return new Durations(this.document, m.pointer)
};
};
const api = function (View) {
api$2(View);
api$1(View);
addDurations(View);
};
//ambiguous 'may' and 'march'
// const preps = '(in|by|before|during|on|until|after|of|within|all)' //6
// const thisNext = '(last|next|this|previous|current|upcoming|coming)' //2
const sections$1 = '(start|end|middle|starting|ending|midpoint|beginning)'; //2
// const seasons = '(spring|summer|winter|fall|autumn)'
//ensure a year is approximately typical for common years
//please change in one thousand years
const tagYear = (m, reason) => {
if (m.found !== true) {
return
}
m.forEach((p) => {
let str = p.text('reduced');
let num = parseInt(str, 10);
if (num && num > 1000 && num < 3000) {
p.tag('Year', reason);
}
});
};
//same, but for less-confident values
const tagYearSafe = (m, reason) => {
if (m.found !== true) {
return
}
m.forEach((p) => {
let str = p.text('reduced');
let num = parseInt(str, 10);
if (num && num > 1900 && num < 2030) {
p.tag('Year', reason);
}
});
};
const tagDates = function (doc) {
doc
.match('(march|april|may) (and|to|or|through|until)? (march|april|may)')
.tag('Date')
.match('(march|april|may)')
.tag('Month', 'march|april|may');
// april should almost-always be a date
// doc.match('[april] !#LastName?', 0).tag('Month', 'april')
//year/cardinal tagging
let cardinal = doc.if('#Cardinal');
if (cardinal.found === true) {
let v = cardinal.match(`#Date #Value [#Cardinal]`, 0);
tagYear(v, 'date-value-year');
//scoops up a bunch
v = cardinal.match(`#Date [#Cardinal]`, 0);
tagYearSafe(v, 'date-year');
//middle of 1999
v = cardinal.match(`${sections$1} of [#Cardinal]`);
tagYearSafe(v, 'section-year');
//feb 8 2018
v = cardinal.match(`#Month #Value [#Cardinal]`, 0);
tagYear(v, 'month-value-year');
//feb 8 to 10th 2018
v = cardinal.match(`#Month #Value to #Value [#Cardinal]`, 0);
tagYear(v, 'month-range-year');
//in 1998
v = cardinal.match(`(in|of|by|during|before|starting|ending|for|year|since) [#Cardinal]`, 0);
tagYear(v, 'in-year-1');
//q2 2009
v = cardinal.match('(q1|q2|q3|q4) [#Cardinal]', 0);
tagYear(v, 'in-year-2');
//2nd quarter 2009
v = cardinal.match('#Ordinal quarter of? [#Cardinal]', 0);
tagYear(v, 'in-year-3');
//in the year 1998
v = cardinal.match('the year [#Cardinal]', 0);
tagYear(v, 'in-year-4');
//it was 1998
v = cardinal.match('it (is|was) [#Cardinal]', 0);
tagYearSafe(v, 'in-year-5');
// re-tag this part
cardinal.match(`${sections$1} of #Year`).tag('Date');
//between 1999 and 1998
let m = cardinal.match('between [#Cardinal] and [#Cardinal]');
tagYear(m.groups('0'), 'between-year-and-year-1');
tagYear(m.groups('1'), 'between-year-and-year-2');
}
//'2020' bare input
let m = doc.match('^/^20[012][0-9]$/$');
tagYearSafe(m, '2020-ish');
return doc
};
// 3-4 can be a time-range, sometimes
const tagTimeRange = function (m, reason) {
if (m.found) {
m.tag('Date', reason);
let nums = m.numbers().lessThan(31).ifNo('#Year');
nums.tag('#Time', reason);
}
};
//
const timeTagger = function (doc) {
let date = doc.if('#Date');
if (date.found) {
// ==time-ranges=
// --number-ranges--
let range = date.if('#NumberRange');
if (range.found) {
// 3-4 on tuesday
let m = range.match('[#NumberRange+] (on|by|at)? #WeekDay', 0);
tagTimeRange(m, '3-4-tuesday');
// 3-4 on march 2nd
m = range.match('[#NumberRange+] (on|by|at)? #Month #Value', 0);
tagTimeRange(m, '3-4 mar 3');
// 3-4pm
m = range.match('[#NumberRange] to (#NumberRange && #Time)', 0);
tagTimeRange(m, '3-4pm');
// 3pm-5
m = range.match('(#NumberRange && #Time) to [#NumberRange]', 0);
tagTimeRange(m, '3pm-4');
}
// from 4 to 5 tomorrow
let m = date.match('(from|between) #Cardinal and #Cardinal (in|on)? (#WeekDay|tomorrow|yesterday)');
tagTimeRange(m, 'from 9-5 tues');
// 9 to 5 tomorrow
m = doc.match('#Cardinal to #Cardinal (#WeekDay|tomorrow|yesterday)');
tagTimeRange(m, '9-5 tues');
// from 4 to 5pm
m = date.match('(from|between) [#NumericValue] (to|and) #Time', 0).tag('Time', '4-to-5pm');
tagTimeRange(m, 'from 9-5pm');
// wed from 3 to 4
m = date.match('(#WeekDay|tomorrow|yesterday) (from|between)? (#Cardinal|#Time) (and|to) (#Cardinal|#Time)');
tagTimeRange(m, 'tues 3-5');
// june 5 from 3 to 4
m = date.match('#Month #Value+ (from|between) [<time>(#Cardinal|#Time) (and|to) (#Cardinal|#Time)]').group('time');
tagTimeRange(m, 'sep 4 from 9-5');
// 3pm to 4 on wednesday
m = date.match('#Time to #Cardinal on? #Date');
tagTimeRange(m, '3pm-4 wed');
// 3 to 4pm on wednesday
m = date.match('#Cardinal to #Time on? #Date');
tagTimeRange(m, '3-4pm wed');
// 3 to 4 on wednesday
m = date.match('#Cardinal to #Cardinal on? (#WeekDay|#Month #Value)');
tagTimeRange(m, '3-4 wed');
// 3 to 4 pm
// m = date.match('^#Cardinal to #Time')
// tagTimeRange(m, '3 to 4pm')
}
return doc
};
// timezone abbreviations
// (from spencermountain/timezone-soft)
const zones = [
'act',
'aft',
'akst',
'anat',
'art',
'azot',
'azt',
'bnt',
'bot',
'bt',
'cast',
'cat',
'cct',
'chast',
'chut',
'ckt',
'cvt',
'cxt',
'davt',
'eat',
'ect',
'fjt',
'fkst',
'fnt',
'gamt',
'get',
'gft',
'gilt',
'gyt',
'hast',
'hncu',
'hneg',
'hnnomx',
'hnog',
'hnpm',
'hnpmx',
'hntn',
'hovt',
'iot',
'irkt',
'jst',
'kgt',
'kost',
'lint',
'magt',
'mart',
'mawt',
'mmt',
'nct',
'nft',
'novt',
'npt',
'nrt',
'nut',
'nzst',
'omst',
'pet',
'pett',
'phot',
'phst',
'pont',
'pwt',
'ret',
'sakt',
'samt',
'sbt',
'sct',
'sret',
'srt',
'syot',
'taht',
'tft',
'tjt',
'tkt',
'tlt',
'tmt',
'tot',
'tvt',
'ulat',
'vut',
'wakt',
'wat',
'wet',
'wft',
'wit',
'wst',
'yekt',
].reduce((h, str) => {
h[str] = true;
return h
}, {});
const tagTz = function (doc) {
// 4pm PST
let m = doc.match('#Time [#Acronym]', 0);
if (m.found) {
let str = m.text('reduced');
if (zones[str] === true) {
m.tag('Timezone', 'tz-abbr');
}
}
};
const here = 'fix-tagger';
//
const fixUp = function (doc) {
//fixups
if (doc.has('#Date')) {
//first day by monday
let oops = doc.match('#Date+ by #Date+');
if (oops.found && !oops.has('^due')) {
oops.match('^#Date+').unTag('Date', 'by-monday');
}
let d = doc.match('#Date+');
//between june
if (d.has('^between') && !d.has('and .')) {
d.unTag('Date', here);
}
// log the hours
if (d.has('(minutes|seconds|weeks|hours|days|months)') && !d.has('#Value #Duration')) {
d.match('(minutes|seconds|weeks|hours|days|months)').unTag('Date', 'log-hours');
}
// about thanksgiving
if (d.has('about #Holiday')) {
d.match('about').unTag('#Date', 'about-thanksgiving');
}
// the day after next
d.match('#Date+').match('^the').unTag('Date');
}
return doc
};
const preps = '(in|by|before|during|on|until|after|of|within|all)'; //6
const thisNext = '(last|next|this|previous|current|upcoming|coming)'; //2
const sections = '(start|end|middle|starting|ending|midpoint|beginning|mid)'; //2
const seasons = '(spring|summer|winter|fall|autumn)';
const knownDate = '(yesterday|today|tomorrow)';
// { match: '', tag: '', reason:'' },
let matches = [
// in the evening
{ match: 'in the (night|evening|morning|afternoon|day|daytime)', tag: 'Time', reason: 'in-the-night' },
// 8 pm
{ match: '(#Value|#Time) (am|pm)', tag: 'Time', reason: 'value-ampm' },
// 2012-06
// { match: '/^[0-9]{4}-[0-9]{2}$/', tag: 'Date', reason: '2012-06' },
// 30mins
// { match: '/^[0-9]+(min|sec|hr|d)s?$/', tag: 'Duration', reason: '30min' },
// misc weekday words
{ match: '(tue|thu)', tag: 'WeekDay', reason: 'misc-weekday' },
//June 5-7th
{ match: `#Month #Date+`, tag: 'Date', reason: 'correction-numberRange' },
//5th of March
{ match: '#Value of #Month', tag: 'Date', unTag: 'Time', reason: 'value-of-month' },
//5 March
{ match: '#Cardinal #Month', tag: 'Date', reason: 'cardinal-month' },
//march 5 to 7
{ match: '#Month #Value (and|or|to)? #Value+', tag: 'Date', reason: 'value-to-value' },
//march the 12th
{ match: '#Month the #Value', tag: 'Date', reason: 'month-the-value' },
// march to april
{ match: '[(march|may)] to? #Date', group: 0, tag: 'Month', reason: 'march-to' },
// 'march'
{ match: '^(march|may)$', tag: 'Month', reason: 'single-march' },
//March or June
{ match: '#Month or #Month', tag: 'Date', reason: 'month-or-month' },
//june 7
{ match: '(#WeekDay|#Month) #Value', ifNo: '#Money', tag: 'Date', reason: 'date-value' },
//7 june
{ match: '#Value (#WeekDay|#Month)', ifNo: '#Money', tag: 'Date', reason: 'value-date' },
//may twenty five
// { match: '#TextValue #TextValue', if: '#Date', tag: '#Date', reason: 'textvalue-date' },
//two thursdays back
{ match: '#Value (#WeekDay|#Duration) back', tag: '#Date', reason: '3-back' },
//for 4 months
{ match: 'for #Value #Duration', tag: 'Date', reason: 'for-x-duration' },
//two days before
{ match: '#Value #Duration (before|ago|hence|back)', tag: 'Date', reason: 'val-duration-past' },
//for four days
{ match: `${preps}? #Value #Duration`, tag: 'Date', reason: 'value-duration' },
// 6-8 months
{ match: 'in? #Value to #Value #Duration time?', tag: 'Date', reason: '6-to-8-years' },
//two years old
{ match: '#Value #Duration old', unTag: 'Date', reason: 'val-years-old' },
//
{ match: `${preps}? ${thisNext} ${seasons}`, tag: 'Date', reason: 'thisNext-season' },
//
{ match: `the? ${sections} of ${seasons}`, tag: 'Date', reason: 'section-season' },
//
{ match: `${seasons} ${preps}? #Cardinal`, tag: 'Date', reason: 'season-year' },
//june the 5th
{ match: '#Date the? #Ordinal', tag: 'Date', reason: 'correction' },
//last month
{ match: `${thisNext} #Date`, tag: 'Date', reason: 'thisNext-date' },
//by 5 March
{ match: 'due? (by|before|after|until) #Date', tag: 'Date', reason: 'by' },
//next feb
{ match: '(last|next|this|previous|current|upcoming|coming|the) #Date', tag: 'Date', reason: 'next-feb' },
//start of june
{ match: `#Preposition? the? ${sections} of #Date`, tag: 'Date', reason: 'section-of' },
//fifth week in 1998
{ match: '#Ordinal #Duration in #Date', tag: 'Date', reason: 'duration-in' },
//early in june
{ match: '(early|late) (at|in)? the? #Date', tag: 'Time', reason: 'early-evening' },
//tomorrow before 3
{ match: '#Date [(by|before|after|at|@|about) #Cardinal]', group: 0, tag: 'Time', reason: 'date-before-Cardinal' },
//feb to june
{ match: '#Date (#Preposition|to) #Date', ifNo: '#Duration', tag: 'Date', reason: 'date-prep-date' },
//by 6pm
{ match: '(by|before|after|at|@|about) #Time', tag: 'Time', reason: 'preposition-time' },
// in 20mins
{ match: '(in|after) /^[0-9]+(min|sec|wk)s?/', tag: 'Date', reason: 'shift-units' },
//tuesday night
{ match: '#Date [(now|night|sometime)]', group: 0, tag: 'Time', reason: 'date-now' },
// 4 days from now
{ match: '(from|starting|until|by) now', tag: 'Date', reason: 'for-now' },
// every night
{ match: '(each|every) night', tag: 'Date', reason: 'for-now' },
//saturday am
{ match: '#Date [(am|pm)]', group: 0, tag: 'Time', reason: 'date-am' },
// mid-august
{ match: `[${sections}] #Date`, group: 0, tag: 'Date', reason: 'mid-sept' },
//june 5 to 7th
{ match: '#Month #Value to #Value of? #Year?', tag: 'Date', reason: 'june 5 to 7th' },
//5 to 7th june
{ match: '#Value to #Value of? #Month #Year?', tag: 'Date', reason: '5 to 7th june' },
//third week of may
{ match: '#Value #Duration of #Date', tag: 'Date', reason: 'third week of may' },
//two days after
{ match: '#Value+ #Duration (after|before|into|later|afterwards|ago)?', tag: 'Date', reason: 'two days after' },
//two days
{ match: '#Value #Date', tag: 'Date', reason: 'two days' },
//june 5th
{ match: '#Date #Value', tag: 'Date', reason: 'june 5th' },
//tuesday at 5
{ match: '#Date #Preposition #Value', tag: 'Date', reason: 'tuesday at 5' },
//tomorrow before 3
{ match: '#Date (after|before|during|on|in) #Value', tag: 'Date', reason: 'tomorrow before 3' },
//a year and a half
{ match: '#Value (year|month|week|day) and a half', tag: 'Date', reason: 'a year and a half' },
//5 and a half years
{ match: '#Value and a half (years|months|weeks|days)', tag: 'Date', reason: '5 and a half years' },
//on the fifth
{ match: 'on the #Ordinal', tag: 'Date', reason: 'on the fifth' },
// 'jan 5 or 8'
{ match: '#Month #Value+ (and|or) #Value', tag: 'Date', reason: 'date-or-date' },
// 5 or 8 of jan
{ match: '#Value+ (and|or) #Value of #Month ', tag: 'Date', reason: 'date-and-date' },
{ match: '(spring|summer|winter|fall|autumn|springtime|wintertime|summertime)', tag: 'Season', reason: 'date-tag1' },
{ match: '(q1|q2|q3|q4)', tag: 'FinancialQuarter', reason: 'date-tag2' },
{ match: '(this|next|last|current) quarter', tag: 'FinancialQuarter', reason: 'date-tag3' },
{ match: '(this|next|last|current) season', tag: 'Season', reason: 'date-tag4' },
//friday to sunday
{ match: '#Date #Preposition #Date', tag: 'Date', reason: 'friday to sunday' },
//once a day..
{ match: '(once|twice) (a|an|each) #Date', tag: 'Date', reason: 'once a day' },
//a year after..
{ match: 'a #Duration', tag: 'Date', reason: 'a year' },
//between x and y
{ match: '(between|from) #Date', tag: 'Date', reason: 'between x and y' },
{ match: '(to|until|upto) #Date', tag: 'Date', reason: 'between x and y2' },
{ match: 'between #Date+ and #Date+', tag: 'Date', reason: 'between x and y3' },
{ match: '#Month and #Month #Year', tag: 'Date', reason: 'x and y4' },
//day after next
{ match: 'the? #Date after next one?', tag: 'Date', reason: 'day after next' },
//approximately...
{ match: '(about|approx|approximately|around) #Date', tag: 'Date', reason: 'approximately june' },
// until june
{
match: '(by|until|on|in|at|during|over|every|each|due) the? #Date',
ifNo: '#PhrasalVerb',
tag: 'Date',
reason: 'until june',
},
// until last june
{
match: '(by|until|after|before|during|on|in|following|since) (next|this|last)? #Date',
ifNo: '#PhrasalVerb',
tag: 'Date',
reason: 'until last june',
},
//next september
{
match: 'this? (last|next|past|this|previous|current|upcoming|coming|the) #Date',
tag: 'Date',
reason: 'next september',
},
//starting this june
{ match: '(starting|beginning|ending) #Date', tag: 'Date', reason: 'starting this june' },
//start of june
{ match: 'the? (start|end|middle|beginning) of (last|next|this|the) #Date', tag: 'Date', reason: 'start of june' },
//this coming june
{ match: '(the|this) #Date', tag: 'Date', reason: 'this coming june' },
//january up to june
{ match: '#Date up to #Date', tag: 'Date', reason: 'january up to june' },
// 2 oclock
{ match: '#Cardinal oclock', tag: 'Time', reason: '2 oclock' },
// // 13h30
// { match: '/^[0-9]{2}h[0-9]{2}$/', tag: 'Time', reason: '13h30' },
// // 03/02
// { match: '/^[0-9]{2}/[0-9]{2}/', tag: 'Date', unTag: 'Value', reason: '03/02' },
// 3 in the morning
{ match: '#Value (in|at) the? (morning|evening|night|nighttime)', tag: 'Time', reason: '3 in the morning' },
// ten to seven
{
match: '(5|10|15|20|five|ten|fifteen|quarter|twenty|half) (after|past) #Cardinal',
tag: 'Time',
reason: 'ten to seven',
}, //add check for 1 to 1 etc.
// at 10 past
{
match: '(at|by|before) (5|10|15|20|five|ten|fifteen|twenty|quarter|half) (after|past|to)',
tag: 'Time',
reason: 'at-20-past',
},
// iso (2020-03-02T00:00:00.000Z)
// { match: '/^[0-9]{4}[:-][0-9]{2}[:-][0-9]{2}T[0-9]/', tag: 'Time', reason: 'iso-time-tag' },
// tuesday at 4
{ match: '#Date [at #Cardinal]', group: 0, ifNo: '#Year', tag: 'Time', reason: ' tuesday at 4' },
// half an hour
{ match: 'half an (hour|minute|second)', tag: 'Date', reason: 'half an hour' },
// in eastern time
{ match: '(in|for|by|near|at) #Timezone', tag: 'Date', reason: 'in eastern time' },
// 3pm to 4pm
{ match: '#Time to #Time', tag: 'Date', reason: '3pm to 4pm' },
// 4pm sharp
{ match: '#Time [(sharp|on the dot)]', group: 0, tag: 'Time', reason: '4pm sharp' },
// around four thirty
{
match: '(at|around|near|#Date) [#Cardinal (thirty|fifteen) (am|pm)?]',
group: 0,
tag: 'Time',
reason: 'around four thirty',
},
// four thirty am
{ match: '#Cardinal (thirty|fifteen) (am|pm)', tag: 'Time', reason: 'four thirty am' },
// four thirty tomorrow
{ match: '[#Cardinal (thirty|fifteen)] #Date', group: 0, tag: 'Time', reason: 'four thirty tomorrow' },
//anytime around 3
{ match: '(anytime|sometime) (before|after|near) [#Cardinal]', group: 0, tag: 'Time', reason: 'antime-after-3' },
//'two days before'/ 'nine weeks frow now'
{
match: '(#Cardinal|a|an) #Duration (before|after|ago|from|hence|back)',
tag: 'DateShift',
reason: 'nine weeks frow now',
},
// in two weeks
{ match: 'in (around|about|maybe|perhaps)? #Cardinal #Duration', tag: 'DateShift', reason: 'in two weeks' },
{ match: 'in (a|an) #Duration', tag: 'DateShift', reason: 'in a week' },
// an hour from now
{ match: '[(a|an) #Duration from] #Date', group: 0, tag: 'DateShift', reason: 'an hour from now' },
// a month ago
{ match: '(a|an) #Duration ago', tag: 'DateShift', reason: 'a month ago' },
// in half an hour
{ match: 'in half (a|an) #Duration', tag: 'DateShift', reason: 'in half an hour' },
// in a few weeks
{ match: 'in a (few|couple) of? #Duration', tag: 'DateShift', reason: 'in a few weeks' },
//two weeks and three days before
// { match: '#Cardinal #Duration and? #DateShift', tag: 'DateShift', reason: 'three days before' },
// { match: '#DateShift and #Cardinal #Duration', tag: 'DateShift', reason: 'date-shift' },
// 'day after tomorrow'
{ match: '[#Duration (after|before)] #Date', group: 0, tag: 'DateShift', reason: 'day after tomorrow' },
// july 3rd and 4th
{ match: '#Month #Ordinal and #Ordinal', tag: 'Date', reason: 'ord-and-ord' },
// every other week
{ match: 'every other #Duration', tag: 'Date', reason: 'every-other' },
// every weekend
{ match: '(every|any|each|a) (day|weekday|week day|weekend|weekend day)', tag: 'Date', reason: 'any-weekday' },
// any-wednesday
{ match: '(every|any|each|a) (#WeekDay)', tag: 'Date', reason: 'any-wednesday' },
// any week
{ match: '(every|any|each|a) (#Duration)', tag: 'Date', reason: 'any-week' },
// wed nov
{ match: '[(wed|sat)] (#Month|#Year|on|between|during|from)', group: 0, tag: 'WeekDay', reason: 'wed' },
//'spa day'
{ match: '^day$', unTag: 'Date', reason: 'spa-day' },
// tomorrow's meeting
{ match: '(in|of|by|for)? (#Possessive && #Date)', unTag: 'Date', reason: 'tomorrows meeting' },
//yesterday 7
{ match: `${knownDate} [#Value]$`, unTag: 'Date', group: 0, reason: 'yesterday-7' },
//7 yesterday
{ match: `^[#Value] ${knownDate}$`, group: 0, unTag: 'Date', reason: '7 yesterday' },
//friday yesterday
// { match: `#WeekDay+ ${knownDate}$`, unTag: 'Date').lastTerm(, tag:'Date', reason: 'fri-yesterday'},
//tomorrow on 5
{ match: `on #Cardinal$`, unTag: 'Date', reason: 'on 5' },
//this tomorrow
{ match: `[this] tomorrow`, group: 0, unTag: 'Date', reason: 'this-tomorrow' },
//q2 2019
{ match: `(q1|q2|q3|q4) #Year`, tag: 'Date', reason: 'q2 2016' },
//5 next week
{ match: `^[#Value] (this|next|last)`, group: 0, unTag: 'Date', reason: '4 next' },
//this month 7
{ match: `(last|this|next) #Duration [#Value]`, group: 0, unTag: 'Date', reason: 'this month 7' },
//7 this month
{ match: `[!#Month] #Value (last|this|next) #Date`, group: 0, unTag: 'Date', reason: '7 this month' },
// over the years
{ match: '(in|over) the #Duration #Date+?', unTag: 'Date', reason: 'over-the-duration' },
// second quarter of 2020
{ match: '#Ordinal quarter of? #Year', unTag: 'Fraction' },
// a month from now
{ match: '(from|by|before) now', unTag: 'Time', tag: 'Date' },
// 18th next month
{ match: '#Value of? (this|next|last) #Date', tag: 'Date' },
// first half of march
{ match: '(first|initial|second|latter) half of #Month', tag: 'Date' },
];
let net = null;
const doMatches = function (view) {
let { world } = view;
net = net || world.methods.one.buildNet(matches, world);
view.sweep(net);
};
// run each of the taggers
const compute = function (view) {
view.cache();
doMatches(view);
doMatches(view); // do it twice
tagDates(view);
timeTagger(view);
tagTz(view);
fixUp(view);
view.uncache();
// sorry, one more
view.match('#Cardinal #Duration and? #DateShift').tag('DateShift', 'three days before');
view.match('#DateShift and #Cardinal #Duration').tag('DateShift', 'three days and two weeks');
view.match('#Time [(sharp|on the dot)]').tag('Time', '4pm sharp');
// view.match('in #Adverb #DateShift').tag('Date', 'in-around-2-weeks')
return view
};
var compute$1 = {
dates: compute
};
var tags = {
FinancialQuarter: {
is: 'Date',
not: ['Fraction'],
},
// 'summer'
Season: {
is: 'Date',
},
// '1982'
Year: {
is: 'Date',
not: ['RomanNumeral'],
},
// 'easter'
Holiday: {
is: 'Date',
also: 'Noun',
},
// 'two weeks before'
DateShift: {
is: 'Date',
not: ['Timezone', 'Holiday'],
},
};
const america = 'America/';
const asia = 'Asia/';
const europe = 'Europe/';
const africa = 'Africa/';
const aus = 'Australia/';
const pac = 'Pacific/';
const informal = {
//europe
'british summer time': europe + 'London',
bst: europe + 'London',
'british time': europe + 'London',
'britain time': europe + 'London',
'irish summer time': europe + 'Dublin',
'irish time': europe + 'Dublin',
'central european time': europe + 'Berlin',
cet: europe + 'Berlin',
'central european summer time': europe + 'Berlin',
cest: europe + 'Berlin',
'central europe': europe + 'Berlin',
'eastern european time': europe + 'Riga',
eet: europe + 'Riga',
'eastern european summer time': europe + 'Riga',
eest: europe + 'Riga',
'eastern europe time': europe + 'Riga',
'western european time': europe + 'Lisbon',
'western european summer time': europe + 'Lisbon',
'turkey standard time': europe + 'Istanbul',
'turkish time': europe + 'Istanbul',
utc: africa + 'Freetown',
'greenwich standard time': africa + 'Freetown',
gmt: africa + 'Freetown',
//africa
'east africa time': africa + 'Nairobi',
'east african time': africa + 'Nairobi',
'eastern africa time': africa + 'Nairobi',
'central africa time': africa + 'Khartoum',
'central african time': africa + 'Khartoum',
'south africa standard time': africa + 'Johannesburg',
'west africa standard time': africa + 'Lagos',
'western africa time': africa + 'Lagos',
'west african time': africa + 'Lagos',
// australia
'australian central standard time': aus + 'Adelaide',
acst: aus + 'Adelaide',
'australian central daylight time': aus + 'Adelaide',
acdt: aus + 'Adelaide',
'australian eastern standard time': aus + 'Brisbane',
aest: aus + 'Brisbane',
'australian eastern daylight time': aus + 'Brisbane',
aedt: aus + 'Brisbane',
'australian western standard time': aus + 'Perth',
awst: aus + 'Perth',
'australian western daylight time': aus + 'Perth',
awdt: aus + 'Perth',
'australian central western standard time': aus + 'Eucla',
acwst: aus + 'Eucla',
'lord howe standard time': aus + 'Lord_Howe',
lhst: aus + 'Lord_Howe',
'lord howe daylight time': aus + 'Lord_Howe',
lhdt: aus + 'Lord_Howe',
'russian standard time': europe + 'Moscow',
msk: europe + 'Moscow',
//america
'central standard time': america + 'Chicago',
'central time': america + 'Chicago',
cst: america + 'Havana',
'central daylight time': america + 'Chicago',
cdt: america + 'Havana',
'mountain standard time': america + 'Denver',
'mountain time': america + 'Denver',
mst: america + 'Denver',
'mountain daylight time': america + 'Denver',
mdt: america + 'Denver',
'atlantic standard time': america + 'Halifax',
'atlantic time': america + 'Halifax',
ast: asia + 'Baghdad',
'atlantic daylight time': america + 'Halifax',
adt: america + 'Halifax',
'eastern standard time': america + 'New_York',
'eastern time': america + 'New_York',
est: america + 'New_York',
'eastern daylight time': america + 'New_York',
edt: america + 'New_York',
'pacific time': america + 'Los_Angeles',
'pacific standard time': america + 'Los_Angeles',
pst: america + 'Los_Angeles',
'pacific daylight time': america + 'Los_Angeles',
pdt: america + 'Los_Angeles',
'alaskan standard time': america + 'Anchorage',
'alaskan time': america + 'Anchorage',
ahst: america + 'Anchorage',
'alaskan daylight time': america + 'Anchorage',
ahdt: america + 'Anchorage',
'hawaiian standard time': pac + 'Honolulu',
'hawaiian time': pac + 'Honolulu',
hst: pac + 'Honolulu',
'aleutian time': pac + 'Honolulu',
'hawaii time': pac + 'Honolulu',
'newfoundland standard time': america + 'St_Johns',
'newfoundland time': america + 'St_Johns',
'newfoundland daylight time': america + 'St_Johns',
'brazil time': america + 'Sao_Paulo',
'brazilian time': america + 'Sao_Paulo',
'argentina time': america + 'Buenos_Aires',
'argentinian time': america + 'Buenos_Aires',
'amazon time': america + 'Manaus',
'amazonian time': america + 'Manaus',
'easter island standard time': 'Chile/Easterisland',
'easter island summer time': 'Chile/Easterisland',
easst: 'Chile/Easterisland',
'venezuelan standard time': america + 'Caracas',
'venezuelan time': america + 'Caracas',
'venezuela time': america + 'Caracas',
'paraguay time': america + 'Asuncion',
'paraguay summer time': america + 'Asuncion',
'cuba standard time': america + 'Havana',
'cuba time': america + 'Havana',
'cuba daylight time': america + 'Havana',
'cuban time': america + 'Havana',
'bolivia time': america + 'La_Paz',
'bolivian time': america + 'La_Paz',
'colombia time': america + 'Bogota',
'colombian time': america + 'Bogota',
'acre time': america + 'Eirunepe',
'peru time': america + 'Lima',
'chile standard time': america + 'Punta_Arenas',
'chile time': america + 'Punta_Arenas',
clst: america + 'Punta_Arenas',
'chile summer time': america + 'Punta_Arenas',
cldt: america + 'Punta_Arenas',
'uruguay time': america + 'Montevideo',
uyt: america + 'Montevideo',
//asia
'arabic standard time': asia + 'Baghdad',
'iran standard time': asia + 'Tehran',
'iran time': asia + 'Tehran',
'iran daylight time': asia + 'Tehran',
'pakistan standard time': asia + 'Karachi',
'pakistan time': asia + 'Karachi',
'india standard time': asia + 'Kolkata',
'indian time': asia + 'Kolkata',
'indochina time': asia + 'Bangkok',
'china standard time': asia + 'Shanghai',
'alma-ata time': asia + 'Almaty',
'oral time': asia + 'Oral',
'orat time': asia + 'Oral',
'yakutsk time': asia + 'Yakutsk',
yakt: asia + 'Yakutsk',
'gulf standard time': asia + 'Dubai',
'gulf time': asia + 'Dubai',
'hong kong time': asia + 'Hong_Kong',
'western indonesian time': asia + 'Jakarta',
'indonesia time': asia + 'Jakarta',
'central indonesian time': asia + 'Makassar',
'israel daylight time': asia + 'Jerusalem',
'israel standard time': asia + 'Jerusalem',
'israel time': asia + 'Jerusalem',
'krasnoyarsk time': asia + 'Krasnoyarsk',
'malaysia time': asia + 'Kuala_Lumpur',
'singapore time': asia + 'Singapore',
'korea standard time': asia + 'Seoul',
'korea time': asia + 'Seoul',
kst: asia + 'Seoul',
'korean time': asia + 'Seoul',
'uzbekistan time': asia + 'Samarkand',
'vladivostok time': asia + 'Vladivostok',
//indian
'maldives time': 'Indian/Maldives',
'mauritius time': 'Indian/Mauritius',
'marshall islands time': pac + 'Kwajalein',
'samoa standard time': pac + 'Midway',
'somoan time': pac + 'Midway',
'chamorro standard time': pac + 'Guam',
'papua new guinea time': pac + 'Bougainville',
};
//add the official iana zonefile names
let iana = main().timezones;
let formal = Object.keys(iana).reduce((h, k) => {
h[k] = k;
return h
}, {});
var timezones = Object.assign({}, informal, formal);
var dates = [
'weekday',
'summer',
'winter',
'autumn',
// 'some day',
// 'one day',
'all day',
// 'some point',
'eod',
'eom',
'eoy',
'standard time',
'daylight time',
'tommorrow',
];
var durations = [
'centuries',
'century',
'day',
'days',
'decade',
'decades',
'hour',
'hours',
'hr',
'hrs',
'millisecond',
'milliseconds',
'minute',
'minutes',
'min',
'mins',
'month',
'months',
'seconds',
'sec',
'secs',
'week end',
'week ends',
'weekend',
'weekends',
'week',
'weeks',
'wk',
'wks',
'year',
'years',
'yr',
'yrs',
'quarter',
// 'quarters',
'qtr',
'qtrs',
'season',
'seasons',
];
var holidays = [
'all hallows eve',
'all saints day',
'all sts day',
'april fools',
'armistice day',
'australia day',
'bastille day',
'boxing day',
'canada day',
'christmas eve',
'christmas',
'cinco de mayo',
'day of the dead',
'dia de muertos',
'dieciseis de septiembre',
'emancipation day',
'grito de dolores',
'groundhog day',
'halloween',
'harvey milk day',
'inauguration day',
'independence day',
'independents day',
'juneteenth',
'labour day',
'national freedom day',
'national nurses day',
'new years eve',
'new years',
'purple heart day',
'rememberance day',
'rosa parks day',
'saint andrews day',
'saint patricks day',
'saint stephens day',
'saint valentines day',
'st andrews day',
'st patricks day',
'st stephens day',
'st valentines day ',
'valentines day',
'valentines',
'veterans day',
'victoria day',
'womens equality day',
'xmas',
// Fixed religious and cultural holidays
// Catholic + Christian
'epiphany',
'orthodox christmas day',
'orthodox new year',
'assumption of mary',
'all souls day',
'feast of the immaculate conception',
'feast of our lady of guadalupe',
// Kwanzaa
'kwanzaa',
// Pagan / metal 🤘
'imbolc',
'beltaine',
'lughnassadh',
'samhain',
'martin luther king day',
'mlk day',
'presidents day',
'mardi gras',
'tax day',
'commonwealth day',
'mothers day',
'memorial day',
'fathers day',
'columbus day',
'indigenous peoples day',
'canadian thanksgiving',
'election day',
'thanksgiving',
't-day',
'turkey day',
'black friday',
'cyber monday',
// Astronomical religious and cultural holidays
'ash wednesday',
'palm sunday',
'maundy thursday',
'good friday',
'holy saturday',
'easter',
'easter sunday',
'easter monday',
'orthodox good friday',
'orthodox holy saturday',
'orthodox easter',
'orthodox easter monday',
'ascension day',
'pentecost',
'whitsunday',
'whit sunday',
'whit monday',
'trinity sunday',
'corpus christi',
'advent',
// Jewish
'tu bishvat',
'tu bshevat',
'purim',
'passover',
'yom hashoah',
'lag baomer',
'shavuot',
'tisha bav',
'rosh hashana',
'yom kippur',
'sukkot',
'shmini atzeret',
'simchat torah',
'chanukah',
'hanukkah',
// Muslim
'isra and miraj',
'lailat al-qadr',
'eid al-fitr',
'id al-Fitr',
'eid ul-Fitr',
'ramadan',
'eid al-adha',
'muharram',
'the prophets birthday',
'ostara',
'march equinox',
'vernal equinox',
'litha',
'june solistice',
'summer solistice',
'mabon',
'september equinox',
'fall equinox',
'autumnal equinox',
'yule',
'december solstice',
'winter solstice',
// Additional important holidays
'chinese new year',
'diwali',
];
var times = [
'noon',
'midnight',
'morning',
'tonight',
'evening',
'afternoon',
'breakfast time',
'lunchtime',
'dinnertime',
'midday',
'eod',
'oclock',
'oclock',
'at night',
// 'now',
// 'night',
// 'sometime',
// 'all day',
];
let lex = {
'a couple': 'Value',
thur: 'WeekDay',
thurs: 'WeekDay',
};
const add = function (arr, tag) {
arr.forEach(str => {
lex[str] = tag;
});
};
add(Object.keys(timezones), 'Timezone');
add(dates, 'Date');
add(durations, 'Duration');
add(holidays, 'Holiday');
add(times, 'Time');
var regex = [
// 30sec
[/^[0-9]+(min|sec|hr|d)s?$/i, 'Duration', '30min'],
// 2012-06
[/^[0-9]{4}-[0-9]{2}$/, 'Date', '2012-06'],
// 13h30
[/^[0-9]{2}h[0-9]{2}$/i, 'Time', '13h30'],
// @4:30
[/^@[0-9]+:[0-9]{2}$/, 'Time', '@5:30'],
// @4pm
[/^@[1-9]+(am|pm)$/, 'Time', '@5pm'],
// 03/02
[/^(?:0[1-9]|[12]\d|3[01])\/(?:0[1-9]|[12]\d|3[01])$/, 'Date', '03/02'],
// iso-time
// [/^[0-9]{4}[:-][0-9]{2}[:-][0-9]{2}T[0-9]/i, 'Time', 'iso-time-tag']
];
var version = '3.7.1';
/* eslint-disable no-console */
const fmt = iso => (iso ? main(iso).format('{nice-day} {year}') : '-');
// https://stackoverflow.com/questions/9781218/how-to-change-node-jss-console-font-color
const reset = '\x1b[0m';
//cheaper than requiring chalk
const cli = {
magenta: str => '\x1b[35m' + str + reset,
cyan: str => '\x1b[36m' + str + reset,
yellow: str => '\x1b[33m' + str + reset,
dim: str => '\x1b[2m' + str + reset,
i: str => '\x1b[3m' + str + reset,
};
const debug = function (view) {
view.dates().forEach(m => {
let res = m.dates().get()[0];
console.log('\n────────');
m.debug('highlight');
let msg = '';
if (res && res.start) {
msg = ' ' + cli.magenta(fmt(res.start));
}
if (res && res.end) {
msg += cli.dim(' → ') + cli.cyan(fmt(res.end));
}
console.log(msg + '\n');
});
};
var plugin = {
tags,
words: lex,
compute: compute$1,
api,
mutate: world => {
// add our regexes
world.model.two.regexText = world.model.two.regexText || [];
world.model.two.regexText = world.model.two.regexText.concat(regex);
// add our debug('dates') method
world.methods.one.debug = world.methods.one.debug || {};
world.methods.one.debug.dates = debug;
},
hooks: ['dates'],
version,
};
return plugin;
}));