(function() {
  var allDayNames = [];
  ["dayNames", "abbreviatedDayNames", "shortestDayNames"].forEach(function(key) {
    Array.prototype.push.apply(allDayNames, Date.CultureInfo[key]);
  });
  Date.CultureInfo.allDayNames = allDayNames;
})();

var features = {
  // If first or last 1 or 2 words are a day of the week, replace with a full date
  "The day of the week is never in the past.": function(input, conversion) {
    var output = input.split(' ');

    if (output.length < 2) {
      return input;
    }

    function isDay(pos) {
      if (pos < 0) pos = output.length + pos;
      return Date.CultureInfo.allDayNames.some(day => 
        day.toUpperCase() === output[pos].toUpperCase()
      );
    }
    function isNext(pos) {
      if (pos < 0) pos = output.length + pos;
      return (output[pos].toUpperCase() in {THIS:1, NEXT:1, ON:1});
    }

    // Remove possible "next/this/on" words, all of which we want to
    // interpret as "the next day excluding today".
    if (isNext(0) && isDay(1)) {
      output.splice(0, 1);
    }
    if (isNext(-2) && isDay(-1)) {
      output.splice(output.length - 2, 1);
    }
    if (!(isDay(0) ^ isDay(-1))) {
      return input; // ambiguous or missing days
    }

    var dayword = output.splice((isDay(0) ? 0: -1), 1);

    // date.js knows "next sun" is always 1-7 days ahead
    var date = Date.parse("next " + dayword); 

    output.unshift(date.toLocaleDateString());
    return output.join(' ');
  },

  // TODO: "Missing month is allowed"
};


// Register all features.
Object.keys(features).forEach(name => {
  var fn = features[name];
  Conversion.steps.push({name: name, fn: fn});
});

@@ -1,16 +1,100 @@
+// TODO: why does %22monday foo%22 work on tuesday but %22mon foo%22 puts it *on* tuesday?%0A%0A
 (function() %7B%0A  
@@ -315,16 +315,42 @@
 DayNames
+.map(w =%3E w.toUpperCase())
 ;%0A%7D)();%0A
@@ -571,593 +571,495 @@
     
-if (output.length %3C 2) %7B%0A      return input;%0A    %7D%0A%0A    function isDay(pos) %7B%0A      if (pos %3C 0) pos = output.length + pos;%0A      return Date.CultureInfo.allDayNames.some(day =%3E %0A        day.toUpperCase() === output%5Bpos%5D.toUpperCase()%0A      );%0A    %7D%0A    function isNext(pos) %7B%0A      if (pos %3C 0) pos = output.length + pos;%0A      return (output%5Bpos%5D.toUpperCase() in %7BTHIS:1, NEXT:1, ON:1%7D);
+var types = %7B DAY: 0, NEXT: 1, OTHER: 2 %7D;%0A    var equals = x =%3E (y =%3E x === y);%0A%0A    var words = output.map(word =%3E %7B%0A      word = word.toUpperCase();%0A      if (Date.CultureInfo.allDayNames.some(equals(word))) %7B%0A        return types.DAY;%0A      %7D else if (word in %7BTHIS:1, NEXT:1, ON:1%7D) %7B%0A        return types.NEXT;%0A      %7D else %7B%0A        return types.OTHER;%0A      %7D
 %0A    %7D
+);
 %0A%0A    
-// Remove possible %22next/this/on%22 words, all of which we want to%0A    // interpret as %22the next day excluding today%22.%0A    if (isNext(0) && isDay(1)) %7B%0A      output.splice(0, 1)
+var dayCount = words.filter(equals(types.DAY)).length;%0A    if (dayCount != 1) %7B%0A      return input
 ;%0A    %7D%0A
     
@@ -1058,238 +1058,238 @@
   %7D%0A
+%0A
     
-if (isNext(-2) && isDay(-1)) %7B%0A      output.splice(output.length - 2
+var dayPos = words.indexOf(types.DAY);%0A    var dayWord = output.splice(dayPos
 , 1);%0A
+%0A
     
-%7D%0A    if (!(isDay(0) %5E isDay(-1))) %7B%0A      return input; // ambiguous or missing days%0A    %7D%0A%0A    var dayword = output.splice((isDay(0) ? 0: -1), 1);
+var hasNEXT = (dayPos %3E 0 && words%5BdayPos - 1%5D === types.NEXT);%0A    if (hasNEXT) %7B%0A      output.splice(dayPos - 1, 1); // delete it%0A    %7D
 %0A%0A  
@@ -1386,15 +1386,14 @@
  day
-w
+W
 ord);
- 
 %0A%0A  

Allow day of week anywhere in phrase