Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

About this user

Will Rickards http://willrickards.net/

« Newer Snippets
Older Snippets »
Showing 11-18 of 18 total

Splitting an integer list in TSQL

Revised: 2006-02-23 - fixed various bugs by using new algorithm
Often times I have a list of integers I need to pass to the database to get worked on. Such as checkboxes on a web page or some other list. I needed some TSQL that would take a text string and split it by a separator, in this case a comma. The following is the result of that need.
The way I normally use it is in a stored procedure like the one below with several text type arguments. Sometimes I use a variation designed to split a list of strings separated by a special character sequence. Image two lists, one of the ids and one of the data. You parse the first list to get a table of the ids and you parse the second list to get the data and insert/update as appropriate.

   1  
   2  IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[uspSplitIntegerList]') AND OBJECTPROPERTY(id, N'IsProcedure') = 1)
   3     DROP PROCEDURE [dbo].[uspSplitIntegerList]
   4  GO
   5                                        
   6  SET QUOTED_IDENTIFIER ON 
   7  GO
   8  SET ANSI_NULLS ON 
   9  GO
  10  
  11  
  12  /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
  13  -- uspSplitIntegerList
  14  --
  15  -- Description:
  16  --		splits a comma separated list of integers and returns the integer list
  17  --
  18  -- Arguments:
  19  --		@list_integers					- list of integers
  20  --
  21  -- Notes:
  22  -- 02/22/2006 - WSR : use DATALENGTH instead of LEN throughout because LEN doesn't count trailing blanks
  23  --
  24  -- History:
  25  -- 02/22/2006 - WSR : revised algorithm to account for items crossing 8000 character boundary
  26  --
  27  CREATE PROCEDURE uspSplitIntegerList
  28  	@list_integers			text
  29  AS
  30  
  31  SET NOCOUNT ON
  32  
  33  DECLARE @InputLen			integer			-- input text length
  34  DECLARE @TextPos			integer			-- current position within input text
  35  DECLARE @Chunk				varchar(8000)	-- chunk within input text
  36  DECLARE @ChunkPos			integer			-- current position within chunk
  37  DECLARE @DelimPos			integer			-- position of delimiter
  38  DECLARE @ChunkLen			integer			-- chunk length
  39  DECLARE @DelimLen			integer			-- delimiter length
  40  DECLARE @Delimiter      varchar(3)		-- delimiter
  41  DECLARE @ItemBegPos		integer			-- item starting position in text
  42  DECLARE @ItemOrder		integer			-- item order in list
  43  
  44  -- create table to hold list items
  45  -- actually their positions because we may want to scrub this list eliminating bad entries before substring is applied
  46  CREATE TABLE #list_items ( item_order integer, item_begpos integer, item_endpos integer )
  47  
  48  -- process list
  49  IF @list_integers IS NOT NULL
  50     BEGIN
  51  
  52  	-- initialize
  53     -- notice that this loop assumes a delimiter length of 1
  54     -- if the delimiter is longer we have to deal with stuff like delimiters straddling the chunk boundaries
  55     SET @InputLen = DATALENGTH(@list_integers)
  56     SET @TextPos = 1
  57  	SET @Delimiter = ','
  58  	SET @DelimLen = DATALENGTH(@Delimiter)
  59     SET @ItemBegPos = 1
  60     SET @ItemOrder = 1
  61     SET @ChunkLen = 1
  62  
  63     -- cycle through input processing chunks
  64     WHILE @TextPos <= @InputLen AND @ChunkLen <> 0
  65        BEGIN
  66  
  67        -- get current chunk
  68        SET @Chunk = SUBSTRING(@list_integers, @TextPos, 8000)
  69  
  70        -- setup initial variable values
  71        SET @ChunkPos = 1
  72        SET @ChunkLen = DATALENGTH(@Chunk)
  73        SET @DelimPos = CHARINDEX(@Delimiter, @Chunk, @ChunkPos)
  74  
  75        -- loop over the chunk, until the last delimiter
  76        WHILE @ChunkPos <= @ChunkLen AND @DelimPos <> 0
  77           BEGIN
  78  
  79  			-- insert position
  80           INSERT INTO #list_items (item_order, item_begpos, item_endpos)
  81           VALUES (@ItemOrder, @ItemBegPos, (@TextPos + @DelimPos - 1) - 1)
  82           
  83           -- adjust positions
  84           SET @ItemOrder = @ItemOrder + 1
  85           SET @ItemBegPos = (@TextPos + @DelimPos - 1) + @DelimLen
  86           SET @ChunkPos = @DelimPos + @DelimLen
  87        
  88           -- find next delimiter      
  89           SET @DelimPos = CHARINDEX(@Delimiter, @Chunk, @ChunkPos)
  90  
  91           END
  92  
  93        -- adjust positions
  94        SET @TextPos = @TextPos + @ChunkLen
  95  
  96        END
  97  
  98  	-- handle last item
  99     IF @ItemBegPos <= @InputLen
 100        BEGIN
 101  
 102        -- insert position
 103        INSERT INTO #list_items (item_order, item_begpos, item_endpos)
 104        VALUES (@ItemOrder, @ItemBegPos, @InputLen)
 105  
 106        END
 107  
 108  	-- delete the bad items
 109     DELETE FROM #list_items
 110     WHERE item_endpos < item_begpos
 111  
 112     -- return list items
 113  	SELECT CAST(SUBSTRING(@list_integers, item_begpos, (item_endpos - item_begpos + 1)) AS integer) AS item_integer, item_order, item_begpos, item_endpos
 114     FROM #list_items
 115     WHERE ISNUMERIC(SUBSTRING(@list_integers, item_begpos, (item_endpos - item_begpos + 1))) = 1
 116     ORDER BY item_order
 117  
 118     END
 119  
 120  DROP TABLE #list_items
 121  
 122  RETURN
 123  
 124  /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
 125  
 126  GO
 127  SET QUOTED_IDENTIFIER OFF 
 128  GO
 129  SET ANSI_NULLS ON 
 130  GO

replace in Informix 4GL

Replace algorithm in Informix 4GL so I can find it later.
   1  
   2  DEFINE
   3     str_hitext           CHAR(130),
   4     str_text             CHAR(130),
   5     str_escape           CHAR(1)
   6  
   7     LET str_escape = ASCII 27
   8  
   9  FUNCTION replace_algorithm()
  10  
  11  DEFINE
  12     int_temp2            INTEGER,
  13     int_hipos            INTEGER,
  14     int_hipos_old        INTEGER,
  15     int_pos              INTEGER
  16  
  17     # remove highlight characters from text
  18     LET int_temp2 = LENGTH(str_hitext)
  19     LET int_pos = 1
  20     LET str_text = " "
  21    
  22     # cycle through highlight string looking for highlight strings
  23     LET int_hipos = 1
  24     LET int_hipos_old = 1
  25     WHILE int_hipos <= int_temp2 - 7
  26  
  27        # if we found an escape character (start of highlight sequence)
  28        IF str_hitext[int_hipos, int_hipos] = str_escape THEN
  29  
  30           # if a highlight sequence
  31           IF str_hitext[int_hipos + 1, int_hipos + 7] = "[32703m" OR str_hitext[int_hipos + 1, int_hipos + 7] = "[32723m" THEN
  32   
  33              # if there is previous data to copy over
  34              IF int_hipos - 1 >= int_hipos_old THEN
  35  
  36                 # copy data over
  37                 LET str_text[int_pos, int_pos + int_hipos - 1 - int_hipos_old] = str_hitext[int_hipos_old, int_hipos - 1]
  38                 LET int_pos = int_pos + int_hipos - int_hipos_old
  39  
  40              END IF
  41  
  42              # move source positions - don't copy this over
  43              LET int_hipos = int_hipos + 8
  44              LET int_hipos_old = int_hipos
  45  
  46           # if not a highlight sequence
  47           ELSE
  48  
  49              # move source position
  50              LET int_hipos = int_hipos + 1
  51  
  52           END IF
  53  
  54        # if we didn't find an escape character
  55        ELSE
  56  
  57           # move source position
  58           LET int_hipos = int_hipos + 1
  59  
  60        END IF
  61  
  62     END WHILE
  63  
  64     # if there is previous data to copy over
  65     IF int_temp2 >= int_hipos_old THEN
  66  
  67        # copy data over
  68        LET str_text[int_pos, int_pos + int_temp2 - int_hipos_old] = str_hitext[int_hipos_old, int_temp2]
  69  
  70     END IF 
  71  
  72  END FUNCTION

Date Literals in SQL

Due to international issues, there are many ways of representing a date in SQL code. I prefer to use odbc canonical.

dates
   1  
   2  {d 'yyyy-mm-dd'}
   3  {d '2001-12-31'}


timestamps
   1  
   2  {ts 'yyyy-mm-dd hh:mm:ss'}
   3  {ts '2001-12-31 00:00:00'}


times
   1  
   2  {t 'hh:mm:ss'}


Don't let the ODBC in the name fool you, these work in Microsoft SQL Server, and through ODBC connections.
I've tested it in Query Analyzer, Stored Procedures, ADO code (both OLEDB and ODBC providers), ADO.Net code (both SQLClient and ODBC).


See Microsoft reference:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/odbcjetdate_time_and_timestamp_literals.asp

It is a pity that there isn't some sort of SQL standard for date representation. Or maybe there is and I don't know it?

Javascript IntegerBoxControl

In input forms on web pages you often have to validate input as an integer or more precisely a positive integer greater than zero. Here is a control implementation of sorts.

   1  
   2  // Integer Box Control
   3  // 
   4  // Notes
   5  // To create an integer box control, call the SetupIntegerBoxControl function.
   6  // It should be passed an input element with type of text.
   7  // The onchange event of the input element will invoke the validation function.
   8  // The validation function ensures only positive integer data is entered.
   9  //
  10  // History
  11  // 09/08/2005 - WSR : created based on dateboxcontrol
  12  
  13  // hooks functionality up to given textbox
  14  function SetupIntegerBoxControl( ctlIntegerBox )
  15     {
  16  
  17     // if a valid object was given
  18     if (ctlIntegerBox)
  19        {
  20  
  21        // validate current contents
  22        IntegerBoxControl_Validate( ctlIntegerBox );
  23  
  24        // hook up event handlers
  25        ctlIntegerBox.onchange = function () { IntegerBoxControl_Validate(this); };
  26        
  27        }
  28  
  29     }
  30  
  31  
  32  // validates the input
  33  function IntegerBoxControl_Validate( ctlIntegerBox )
  34     {
  35  
  36     // parse the input as an integer
  37     var intValue = parseInt(ctlIntegerBox.value, 10);
  38  
  39     // if this is not an integer
  40     if (isNaN(intValue))
  41        {
  42  
  43        // clear text box
  44        ctlIntegerBox.value = '';
  45  
  46        }
  47     // if this is an integer
  48     else
  49        {
  50     
  51        switch (true)
  52           {
  53           case (intValue == 0) :
  54  
  55              // clear text box
  56              ctlIntegerBox.value = '';
  57  
  58              break;
  59           case (intValue > 0) :
  60  
  61              // put the parsed integer value in the text box
  62              ctlIntegerBox.value = intValue.toString();
  63  
  64              break;
  65           case (intValue < 0) :
  66  
  67              // put the positive parsed integer value in the text box
  68              ctlIntegerBox.value = (-1 * intValue).toString();
  69  
  70              break;
  71           }
  72        
  73        }
  74  
  75     }


I forgot to mention before how to hook it up in code.
I usually add an attribute bvr-datatype="integer" to the input element. bvr stands for behaviour. I hook up the control in the onload event. Here is a recent example.
   1  
   2  // set window load event handler
   3  window.onload = window_load;
   4  
   5  // ----------------------------------------------------------------------------
   6  // window_load
   7  // Description: event handler for window load event
   8  // Arguments: none
   9  // Dependencies:
  10  //    SetupDateBoxControl (dateboxcontrol.js)
  11  //    SetupIntegerBoxControl (integerboxcontrol.js)
  12  //    frmSearch_submit
  13  //    cmdRequesting_click
  14  //    txtRequesting_change
  15  //    frmRecent_submit
  16  //
  17  function window_load()
  18     {
  19  
  20     // get input elements in document
  21     var arrInputs = document.getElementsByTagName('INPUT');
  22  
  23     // cycle through input elements
  24     for ( var i = 0; i < arrInputs.length; i++ ) 
  25        {
  26  
  27        // if this is a datebox control
  28        if ( 'date' == arrInputs[i].getAttribute('bvr-datatype') && 'text' == arrInputs[i].getAttribute('type') )
  29           {
  30  
  31           // setup the control
  32           SetupDateBoxControl( arrInputs[i] );
  33  
  34           }
  35  
  36        // if this is a integerbox control
  37        if ( 'integer' == arrInputs[i].getAttribute('bvr-datatype') && 'text' == arrInputs[i].getAttribute('type') )
  38           {
  39  
  40           // setup the control
  41           SetupIntegerBoxControl( arrInputs[i] );
  42  
  43           }
  44  
  45        }
  46  
  47     // get reference to search form
  48     var elForm = document.getElementById('frmSearch');
  49     if (elForm)
  50        {
  51  
  52        // set submit event handler
  53        elForm.onsubmit = frmSearch_submit;
  54  
  55        // set cmdTimekeeperLookup click event handler
  56        var elToWire = document.getElementById('cmdTimekeeperLookup');
  57        if (elToWire)
  58           {
  59  
  60           elToWire.onclick = cmdRequesting_click;
  61           elToWire.textbox = document.getElementById('txtRequestingTimekeeper');
  62  
  63           // set textbox change events
  64           if (elToWire.textbox)
  65              {
  66  
  67              elToWire.textbox.onchange = txtRequesting_change;
  68              elToWire.textbox.onblur = txtRequesting_change;
  69              elToWire.textbox.label = document.getElementById('lblTimekeeperName');
  70  
  71              if ( typeof window.strRequesting == 'undefined' )
  72                 window.strRequesting = ''; 
  73  
  74              }
  75  
  76           }
  77  
  78        }
  79  
  80     // get reference to recent form
  81     elForm = document.getElementById('frmRecent');
  82     if (elForm)
  83        {
  84  
  85        // set submit event handler
  86        elForm.onsubmit = frmRecent_submit;
  87  
  88        }
  89  
  90     }
  91  //
  92  // window_load
  93  // ----------------------------------------------------------------------------

JavaScript DateBoxControl

In input forms on web pages you often have to validate dates and users are always entering something off the wall. Simon Incutio came up with a script to parse the dates. I added some stuff and turned it into a control of sorts. It is sort of US centric with date entering but it does accept iso style entries.

   1  
   2  // Date Box Control
   3  // 
   4  // based on:
   5  //  'Magic' date parsing, by Simon Willison (6th October 2003)
   6  //   http://simon.incutio.com/archive/2003/10/06/betterDateInput
   7  // 
   8  // Notes
   9  // To create a date box control, call the SetupDateBoxControl function.
  10  // It should be passed an input element with type of text.
  11  // This will first create a div after the input box.
  12  // Then it will associate the div with the input element.
  13  // The div will use the css classes: DateBoxControlMsg, DateBoxControlErrorMsg.
  14  // Then the div is associated with the input element.
  15  // Then the contents are validated so the div will get populated initially.
  16  // The onchange event of the input element will invoke the validation function.
  17  // The validation function populates the div.
  18  // If a successfully parsed date, the div gets a nicely formatted date.
  19  // If an unsuccessfully parse date, the div gets an error message.
  20  //
  21  // History
  22  // 02/03/2005 - WSR : modified for use as datebox control
  23  // 09/09/2005 - WSR : datebox is not required anymore (blank input is valid)
  24  //                  : added style class to datebox itself
  25  
  26  // hooks functionality up to given textbox
  27  function SetupDateBoxControl( ctlDateBox )
  28     {
  29  
  30     // if a valid object was given
  31     if (ctlDateBox)
  32        {
  33  
  34        // add div after control for messages
  35        var divMessage = document.createElement('div');
  36        divMessage.className = 'DateBoxControlMsg';
  37  
  38        // if there is a next sibling
  39        if (ctlDateBox.nextSibling)
  40           {
  41  
  42           // insert before next sibling
  43  		   ctlDateBox.parentNode.insertBefore( divMessage, ctlDateBox.nextSibling );
  44  
  45           }
  46        // if there is not a next sibling
  47        else
  48           {
  49  
  50           // append child to parent
  51           ctlDateBox.parentNode.appendChild( divMessage );         
  52  
  53           }
  54  
  55        // link message div to textbox for easy script access
  56        ctlDateBox.message = divMessage;
  57  
  58        // validate current contents
  59        DateBoxControl_Validate( ctlDateBox );
  60  
  61        // hook up event handlers
  62        ctlDateBox.onchange = function () { DateBoxControl_Validate(this); };
  63        
  64        }
  65  
  66     }
  67  
  68  // add indexOf function to Array type
  69  // finds the index of the first occurence of item in the array, or -1 if not found
  70  Array.prototype.indexOf = function(item) {
  71      for (var i = 0; i < this.length; i++) {
  72          if (this[i] == item) {
  73              return i;
  74          }
  75      }
  76      return -1;
  77  };
  78  
  79  // add filter function to Array type
  80  // returns an array of items judged true by the passed in test function
  81  Array.prototype.filter = function(test) {
  82      var matches = [];
  83      for (var i = 0; i < this.length; i++) {
  84          if (test(this[i])) {
  85              matches[matches.length] = this[i];
  86          }
  87      }
  88      return matches;
  89  };
  90  
  91  // add right function to String type
  92  // returns the rightmost x characters
  93  String.prototype.right = function( intLength ) {
  94     if (intLength >= this.length)
  95        return this;
  96     else
  97        return this.substr( this.length - intLength, intLength );
  98  };
  99  
 100  // add trim function to String type
 101  // trims leading and trailing whitespace
 102  String.prototype.trim = function() { return this.replace(/^\s+|\s+$/, ''); };
 103  
 104  // arrays for month and weekday names
 105  var monthNames = "January February March April May June July August September October November December".split(" ");
 106  var weekdayNames = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" ");
 107  
 108  /* Takes a string, returns the index of the month matching that string, throws
 109     an error if 0 or more than 1 matches
 110  */
 111  function parseMonth(month) {
 112      var matches = monthNames.filter(function(item) { 
 113          return new RegExp("^" + month, "i").test(item);
 114      });
 115      if (matches.length == 0) {
 116          throw new Error("Invalid month string");
 117      }
 118      if (matches.length < 1) {
 119          throw new Error("Ambiguous month");
 120      }
 121      return monthNames.indexOf(matches[0]);
 122  }
 123  
 124  /* Same as parseMonth but for days of the week */
 125  function parseWeekday(weekday) {
 126      var matches = weekdayNames.filter(function(item) {
 127          return new RegExp("^" + weekday, "i").test(item);
 128      });
 129      if (matches.length == 0) {
 130          throw new Error("Invalid day string");
 131      }
 132      if (matches.length < 1) {
 133          throw new Error("Ambiguous weekday");
 134      }
 135      return weekdayNames.indexOf(matches[0]);
 136  }
 137  
 138  function DateInRange( yyyy, mm, dd )
 139     {
 140  
 141     // if month out of range
 142     if ( mm < 0 || mm > 11 )
 143        throw new Error('Invalid month value.  Valid months values are 1 to 12');
 144  
 145     // get last day in month
 146     var d = (11 == mm) ? new Date(yyyy + 1, 0, 0) : new Date(yyyy, mm + 1, 0);
 147  
 148     // if date out of range
 149     if ( dd < 1 || dd > d.getDate() )
 150        throw new Error('Invalid date value.  Valid date values for ' + monthNames[mm] + ' are 1 to ' + d.getDate().toString());
 151  
 152     return true;
 153  
 154     }
 155  
 156  /* Array of objects, each has 're', a regular expression and 'handler', a 
 157     function for creating a date from something that matches the regular 
 158     expression. Handlers may throw errors if string is unparseable. 
 159  */
 160  var dateParsePatterns = [
 161      // Today
 162      {   re: /^today/i,
 163          handler: function() { 
 164              return new Date();
 165          } 
 166      },
 167      // Tomorrow
 168      {   re: /^tomorrow/i,
 169          handler: function() {
 170              var d = new Date(); 
 171              d.setDate(d.getDate() + 1); 
 172              return d;
 173          }
 174      },
 175      // Yesterday
 176      {   re: /^yesterday/i,
 177          handler: function() {
 178              var d = new Date();
 179              d.setDate(d.getDate() - 1);
 180              return d;
 181          }
 182      },
 183      // mmddyyyy (American style)
 184      {   re: /(\d{2})(\d{2})(\d{4})/,
 185          handler: function(bits) {
 186  
 187              var yyyy = parseInt(bits[3], 10);
 188              var dd = parseInt(bits[2], 10);
 189              var mm = parseInt(bits[1], 10) - 1;
 190  
 191              if ( DateInRange( yyyy, mm, dd ) )
 192                 return new Date(yyyy, mm, dd);
 193  
 194          }
 195      },
 196      // mmddyy (American style) short year
 197      {   re: /(\d{2})(\d{2})(\d{2})/,
 198          handler: function(bits) {
 199  
 200              var d = new Date();
 201              var yyyy = d.getFullYear() - (d.getFullYear() % 100) + parseInt(bits[3], 10);
 202              var dd = parseInt(bits[2], 10);
 203              var mm = parseInt(bits[1], 10) - 1;
 204  
 205              if ( DateInRange(yyyy, mm, dd) )
 206                 return new Date(yyyy, mm, dd);
 207  
 208          }
 209      },
 210      // 4th
 211      {   re: /^(\d{1,2})(st|nd|rd|th)?$/i, 
 212          handler: function(bits) {
 213  
 214              var d = new Date();
 215              var yyyy = d.getFullYear();
 216              var dd = parseInt(bits[1], 10);
 217              var mm = d.getMonth();
 218  
 219              if ( DateInRange( yyyy, mm, dd ) )
 220                 return new Date(yyyy, mm, dd);
 221  
 222          }
 223      },
 224      // 4th Jan
 225      {   re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i, 
 226          handler: function(bits) {
 227  
 228              var d = new Date();
 229              var yyyy = d.getFullYear();
 230              var dd = parseInt(bits[1], 10);
 231              var mm = parseMonth(bits[2]);
 232  
 233              if ( DateInRange( yyyy, mm, dd ) )
 234                 return new Date(yyyy, mm, dd);
 235  
 236          }
 237      },
 238      // 4th Jan 2003
 239      {   re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
 240          handler: function(bits) {
 241  
 242              var yyyy = parseInt(bits[3], 10);
 243              var dd = parseInt(bits[1], 10);
 244              var mm = parseMonth(bits[2]);
 245  
 246              if ( DateInRange( yyyy, mm, dd ) )
 247                 return new Date(yyyy, mm, dd);
 248  
 249          }
 250      },
 251      // Jan 4th
 252      {   re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i, 
 253          handler: function(bits) {
 254  
 255              var d = new Date();
 256              var yyyy = d.getFullYear(); 
 257              var dd = parseInt(bits[2], 10);
 258              var mm = parseMonth(bits[1]);
 259  
 260              if ( DateInRange( yyyy, mm, dd ) )
 261                 return new Date(yyyy, mm, dd);
 262  
 263          }
 264      },
 265      // Jan 4th 2003
 266      {   re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
 267          handler: function(bits) {
 268  
 269              var yyyy = parseInt(bits[3], 10); 
 270              var dd = parseInt(bits[2], 10);
 271              var mm = parseMonth(bits[1]);
 272  
 273              if ( DateInRange( yyyy, mm, dd ) )
 274                 return new Date(yyyy, mm, dd);
 275  
 276          }
 277      },
 278      // next Tuesday - this is suspect due to weird meaning of "next"
 279      {   re: /^next (\w+)$/i,
 280          handler: function(bits) {
 281  
 282              var d = new Date();
 283              var day = d.getDay();
 284              var newDay = parseWeekday(bits[1]);
 285              var addDays = newDay - day;
 286              if (newDay <= day) {
 287                  addDays += 7;
 288              }
 289              d.setDate(d.getDate() + addDays);
 290              return d;
 291  
 292          }
 293      },
 294      // last Tuesday
 295      {   re: /^last (\w+)$/i,
 296          handler: function(bits) {
 297  
 298              var d = new Date();
 299              var wd = d.getDay();
 300              var nwd = parseWeekday(bits[1]);
 301           
 302              // determine the number of days to subtract to get last weekday
 303              // calculates 0 if weekdays are the same so we have to change this to 7
 304              var addDays = (wd == nwd) ? -7 : (-1 * (wd + 7 - nwd)) % 7;
 305              
 306              // adjust date and return
 307              d.setDate(d.getDate() + addDays);
 308              return d;
 309  
 310          }
 311      },
 312      // Tuesday
 313      {   re: /^(\w+)$/i,
 314          handler: function(bits) {
 315  
 316              var d = new Date();
 317              var wd = d.getDay();
 318              var nwd = parseWeekday(bits[1]);
 319           
 320              // if same weekday, return date         
 321              if (nwd == wd)
 322                 return d;
 323