Sun, 05 Jun 2005

Credit Card Expiration Date Maxim

Steve Friedl has long maintained the "No Dashes Or Spaces" Hall of Shame which catalogs web sites that require visitors to enter their credit card number without spaces as dashes. As he points out, it's trivial to strip out the extraneous characters server-side so there's no reason to have this stupid restriction in web applications.

I would like to proffer an additional maxim of credit card entry. Credit card expiration dates should never be entered using the name of the month. While I don't have an extensive list of offenders like Steve, I would estimate that there are thousands of web sites that request your credit card expiration date with two pull-down menus, one for the month, January, February, etc., and one for the year. As far as I know, every credit card in existence has the expiration printed as either MM/YY or, less commonly, MM-YY.

When somebody is making a purchase online with a credit card, they simply need to copy the card number (hopefully, without stripping out the whitespace) and the expiration date into a form. The customer shouldn't need to mentally convert the format of the expiration date on the card to the format required on the web site. Additionally, there's no reason to use pull-downs to enter the expiration date. A small text box is the perfect format for entering a 5 digit string.

I suggest the following regex be used to validate expiration dates:
^((0?[1-9])|(1[0-2]))[/-]?(2[01])?\d\d$
This is probably even more liberal than necessary. It allows a one- or two-digit month and a two- or four-digit year, optionally separated by either a slash or a dash. Here's a PHP function to validate such a date string and normalize it to MM/YYYY:

   define('MPE_CREDIT_CARD_EXPIRATION', '~^((0?[1-9])|(1[0-2]))[/-]?(2[01])?\d\d$~');

   /**
   *
   * Check whether a string is a valid expiration date.  Returns the
   * normalized string if it is valid; else returns false.
   *
   * @return   mixed
   * @access   public
   */
   function validateExpiration($expiration, $asOf = null, $expiredOK = false) {
      if (is_null($asOf)) {
         $yearMonth = date('Ym');
      } elseif (is_numeric($asOf)) {
         // unix timestamp
         $yearMonth = date('Ym', $asOf);
      } else {
         // assume mysql datetime (YYYY-MM-DD HH:MM:SS)
         $yearMonth = substr(str_replace('-', '', $asOf), 0, 6);
      }
      $expiration = preg_replace('/\s/', '', $expiration);
      if (! preg_match(MPE_CREDIT_CARD_EXPIRATION, $expiration)) {
         return false;
      }
      if (strlen($expiration) <= 4) {
         // [M]MYY => MM/YY
         $expiration = sprintf('%02d/%02d', substr($expiration, 0, -2), substr($expiration, -2));
      } elseif (! preg_match('~[/-]~', $expiration)) {
         // [M]MYYYY => MM/YYYY
         $expiration = sprintf('%02d/%02d', substr($expiration, 0, -4), substr($expiration, -4));
      }
      // [M]M-[YY]YY => [M]M/[YY]YY
      $expiration = str_replace('-', '/', $expiration);
      list($month, $year) = explode('/', $expiration);
      strlen($year) < 4 && $year += 2000;
      $month = sprintf('%02d', $month);
      if (! $expiredOK && $year . $month < $yearMonth) {
         return false;
      }
      return sprintf('%02d/%04d', $month, $year);
   }

It will also verify that the expiration date is in the future, as of the current date or an optional date passed as a second parameter. If the third parameter is true, dates in the past will not be rejected.

tech | Permanent Link

Great Beer and Great Art in Los Angeles

I went down to the Scala Galleria on Montana in Santa Monica yesterday to check out their art by Carrie Graber. They had about 10 pieces of hers including a couple small original paintings and a number of giclees on canvas. Her paintings are amazing.

I had never checked out that area of Santa Monica before, so I wandered down Montana towards the beach. It's mostly little boutiques, but there are also a number of coffee cafes and restaurants. I stopped at the bluffs and read for bit. On the way back, I stopped in a little pub called Father's Office. From the "Beer" sign outside, I thought it would be a dive, but it was actually a trendy little place.

It reminds me a bit of City Pub in Redwood City. They have 36 beers on tap, mostly California microbrews and a few international beers. I had a great IPA, Bear Republic Racer 5, and an even hoppier double IPA, Russian River Brewing's Pliny the Elder. Both were fantastic beers. Surprisingly, they had Greene King Abbot Ale. I've never seen Greene King in the states. I asked if they ever get the IPA, which is probably my favorite English beer, but the manager said that it doesn't travel well, which is kind of weird considering IPA's were originally made for export.

The only disappointment was the food. They have an absurd policy of "No substitutions, modification, alterations or deletions. Yes, really." When did beer snobs become food snobs? Since I couldn't get a burger without cheese, I ordered some chorizo which wasn't very good.

culture | Permanent Link

The state is that great fiction by which everyone tries to live at the expense of everyone else. - Frederic Bastiat