(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/07:03->10/06: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/choibalsan,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,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/26:02->10/31:24": "0/cairo,egypt",
    "3|n|04/20:04->10/26:02": "2/gaza,2/hebron",
    "3|n|03/31:05->10/27: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/31:04->10/27:03": "8/chisinau,8/tiraspol",
    "3|n|03/31:02->10/26:24": "2/beirut",
    "3|n|03/29:04->10/27: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/31:04->10/27:02": "12/troll",
    "2|s": "0/gaborone,0/harare,0/johannesburg,0/lubumbashi,0/lusaka,0/maputo,0/maseru,0/mbabane",
    "2|n|03/31:04->10/27: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/31:03->10/27: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/07:03->09/29: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/07:03->04/07:02": "11/chatham,nz-chat",
    "11|s|04/07:03->10/06: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/07:03->10/06:04": "11/norfolk",
    "10|s|04/07:03->10/06: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/07:01->10/06:02": "4/lord_howe,4/lhi",
    "0|s|03/10:03->04/14:04": "0/casablanca,0/el_aaiun",
    "0|n|03/31:02->10/27:01": "3/azores",
    "0|n|03/31:01->10/26: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/10:04->11/03:02": "1/adak,1/atka,us/aleutian",
    "-9|n": "11/gambier",
    "-9.5|n": "11/marquesas",
    "-8|n|03/10:04->11/03:02": "1/anchorage,1/juneau,1/metlakatla,1/nome,1/sitka,1/yakutat,us/alaska",
    "-8|n": "11/pitcairn",
    "-7|n|03/10:04->11/03: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/06:22->09/07:24": "11/easter,7/easterisland",
    "-6|n|04/07:02->10/27:02": "1/merida",
    "-6|n|03/10:04->11/03: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/10:04->11/03: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/06:24->09/08:02": "1/santiago,7/continental",
    "-4|s|03/23:24->10/06:02": "1/asuncion",
    "-4|s": "1/campo_grande,1/cuiaba,1/la_paz,1/manaus,5/west",
    "-4|n|03/10:04->11/03: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/10:02->11/03: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/10:04->11/03: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/31:01->10/26:24": "1/nuuk,1/godthab",
    "-2|n|03/10:04->11/03:02": "1/miquelon",
    "-2|n": "1/noronha,3/south_georgia,5/denoronha",
    "-2.5|n|03/10:04->11/03:02": "1/st_johns,6/newfoundland",
    "-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()
    }
    if (tmp.epoch) {
      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.toLowerCase().trim();
        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(1, 'second');
      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;
      // console.log(ms.week+ " "+ skipWeeks);
      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.6.1';

  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,
      };
      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.0';

  /* 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;

}));
