title: Using String Masks in Perl
body: <p>I have a program that allows a user to specify a mask such as MM-DD-YYYY, and compare it to a string. In the string, the <em>MM</em> will be assumed to be a month, <em>DD</em> will be the day of the month, and YYYY will be the year. Everything else must match exactly:</p>
  
  <ul>
  <li>String: 12/31/2010  Mask MM-DD-YYYY: Fail: Must use slashes and not dashes</li>
  <li>String: 12/31/2010  Mask DD/MM/YYYY: Fail: Month must be second and there's no month <em>31</em>.</li>
  <li>String: 12/31-11 Mask: MM/DD-YY: Pass: String matches mask.</li>
  </ul>
  
  <p>Right now, I use <code>index</code> and <code>substr</code> to pull out the month, day, and year, then I use <code>xor</code> to generate a mask for everything else. It seems a bit inelegant, and I was wondering if there's a better way of doing this:</p>
  
  <pre><code>my $self = shift;
  my $date = shift;
  
  my $format = $self-&gt;Format();
  
  my $month;
  my $year;
  my $day;
  
  my $monthIndex;
  my $yearIndex;
  my $dayIndex;
  
  #
  # Pull out Month, Day, and Year
  #
  if (($monthIndex = index($format, "MM")) != -1) {
      $month = substr($date, $monthIndex, 2);
  }
  
  if (($dayIndex = index($format, "DD")) != -1) {
      $day = substr($date, $dayIndex, 2);
  }
  
  if (($yearIndex = index($format, "YYYY")) != -1) {
      $year = substr($date, $yearIndex, 4);
  }
  elsif (($yearIndex = index($format, "YY")) != -1) {
      $year = substr($date, $yearIndex, 2);
      if ($year &lt; 50) {
          $year += 2000;
      }
      else {
          $year += 1900;
      }
  }
  
  #
  # Validate the Rest of Format
  #
  
  (my $restOfFormat = $format) =~ s/[MDY]/./g;    #Month Day and Year can be anything
  if ($date !~ /^$restOfFormat$/) {
      return; #Does not match format
  }
  [...More Stuff before I return a true value...]
  </code></pre>
  
  <p>I'm doing this for a date, time (using <em>HH</em>, <em>MM</em>, <em>SS</em>, and <em>A</em>/*AA*), and IP addresses in my code.</p>
  
  <hr>
  
  <p>BTW, I tried using regular expressions to pull the date from the string, but it's even messier:</p>
  
  <pre><code>#-----------------------------------------------------------------------
  # FIND MONTH
  #
      my $mask = "M" x length($format);  #All M's the length of format string
  
      my $monthMask = ($format ^ $mask);      #Bytes w/ "M" will be "NULL"
      $monthMask =~ s/\x00/\xFF/g;    #Change Null bytes to "FF"
      $monthMask =~ s/[^\xFF]/\x00/g; #Null out other bytes
  
      #
      #   ####Mask created! Apply mask to Date String
      #
  
      $month = ($monthMask &amp; $date);  #Nulls or Month Value
      $month =~ s/\x00//g;            #Remove Null bytes from string
  #
  #-----------------------------------------------------------------------
  </code></pre>
  
  <p>It's a neat programming trick, but it was pretty hard to understand exactly what I was doing and thus would make it hard for someone else to maintain.</p>