diff -rup location-3.1/location.inc location/location.inc
--- location-3.1/location.inc	2010-08-14 01:54:27.000000000 +0200
+++ location/location.inc	2010-08-10 14:05:16.000000000 +0200
@@ -78,6 +78,8 @@ function location_map_link($location = a
  *     'province'       => the province code defined in the country-specific include file
  *     'country'        => the lower-case of the two-letter ISO code (REQUIRED)
  *     'postal_code'    => the postal-code (REQUIRED)
+ * @param $geocode_in_db
+ *   Boolean. If TRUE, the coordinates for the zipcode may be geocoded, and cached in the database.
  *
  * @return
  *   Array or NULL. NULL if the delegated-to function that does the
@@ -88,18 +90,55 @@ function location_map_link($location = a
  *
  * @ingroup Location
  */
-function location_get_postalcode_data($location = array()) {
+function location_get_postalcode_data($location = array(), $geocode_in_db = FALSE) {
   $location['country'] = isset($location['country']) ? trim($location['country']) : NULL;
   $location['postal_code'] = isset($location['postal_code']) ? trim($location['postal_code']) : NULL;
   if (is_null($location['postal_code']) || is_null($location['country']) || empty($location['country']) || empty($location['postal_code']) || $location['postal_code'] == 'xx') {
     return NULL;
   }
+
+  // Normalize postalcode data
+  if (!location_standardize_postalcode($location['postal_code'], $location['country'])) {
+    return NULL;
+  }
   location_load_country($location['country']);
   $country_specific_function = 'location_get_postalcode_data_'. $location['country'];
   if (function_exists($country_specific_function)) {
     return $country_specific_function($location);
   }
   else {
+    
+    // Check whether postalcode data is present in zipcode table
+    $result = db_query("SELECT * FROM {zipcodes} WHERE country = '%s' AND zip = '%s'", $location['country'], $location['postal_code']);
+    if ($row = db_fetch_object($result)) {
+      // We can't be absolutely sure that city/province are filled in the database, but some callers use it
+      return array('lat' => $row->latitude, 'lon' => $row->longitude, 'city' => $row->city, 'province' => $row->state, 'country' => $row->country);
+    }
+    elseif ($geocode_in_db) {
+
+      // Try to get exact location. If it succeeds, cache it in zipcode table
+      if ($data = location_latlon_exact($location)) {
+        $location['latitude'] = $data['lat'];
+        $location['longitude'] = $data['lon'];
+
+        // Try to see whether we can get timezone/dst data based on location. If not, we'll insert zeroes.
+        $country_specific_function = 'location_timezone_data_'. $location['country'];
+        if (function_exists($country_specific_function)) {
+          $tzdata = $country_specific_function($location);
+        }
+        else {
+          $tzdata = array('timezone' => 0, 'dst' => 0);
+        }
+        // Make sure values have the right keys and write record to database.
+        if (isset($location['province'])) {
+          $tzdata['state'] = $location['province'];
+        }
+        $tzdata['zip'] = $location['postal_code'];
+        drupal_write_record('zipcodes', array_merge($location, $tzdata));
+
+        return array('lat' => $location['latitude'], 'lon' => $location['longitude']);
+      }
+    }
     return NULL;
   }
 }
@@ -491,6 +530,26 @@ function location_province_code($country
 }
 
 // @@@ New in 3.x, document.
+/**
+ * Canonicalize a postal code.
+ */
+function location_standardize_postalcode(&$postalcode, $country = 'us') {
+
+  // Standard things go here
+  $postalcode = trim($postalcode);
+
+  // Country-custom stuff
+  location_load_country($country);
+  $country_specific_function = 'location_standardize_postalcode_'. $country;
+  if (function_exists($country_specific_function)) {
+    return $country_specific_function($postalcode);
+  }
+
+  // If no custom functions yielded 'invalid', the postal code is OK
+  return TRUE;
+}
+
+// @@@ New in 3.x, document.
 /**
  * Canonicalize a country code.
  */
diff -rup location-3.1/location.views.inc location/location.views.inc
--- location-3.1/location.views.inc	2010-08-14 01:54:27.000000000 +0200
+++ location/location.views.inc	2010-08-10 20:42:29.000000000 +0200
@@ -497,7 +497,7 @@ function location_views_proximity_get_re
       }
       // Zip code lookup.
       if (!empty($options['postal_code']) && !empty($options['country'])) {
-        $coords = location_latlon_rough($options);
+        $coords = location_get_postalcode_data($options, TRUE);
         if ($coords) {
           $coordinates['latitude'] = (float) $coords['lat'];
           $coordinates['longitude'] = (float) $coords['lon'];
diff -rup location-3.1/supported/location.ca.inc location/supported/location.ca.inc
--- location-3.1/supported/location.ca.inc	2010-06-03 04:27:53.000000000 +0200
+++ location/supported/location.ca.inc	2009-12-26 20:50:41.000000000 +0100
@@ -387,3 +387,24 @@ function location_geocode_ca_geocoder($l
     }
   }
 }
+
+function location_standardize_postalcode_ca(&$postalcode) {
+  if (!is_string($postalcode)) {
+    return FALSE;
+  }
+
+  $code = strtoupper(str_replace(' ','',$postalcode));
+  if (preg_match("/^[A-Z]{1,2}[0-9]{2,3}[A-Z]{2}$/", $code)
+    || preg_match("/^[A-Z]{1,2}[0-9]{1}[A-Z]{1}[0-9]{1}[A-Z]{2}$/", $code)
+    || preg_match("/^GIR0[A-Z]{2}$/", $code)) {
+
+    preg_match('/^[a-zA-Z]*[0-9 ]+/', $postalcode, $matches);
+    $postalcode = substr_replace(str_replace(' ', '', $matches[0]), '', -1);
+    return TRUE;
+  }
+  // Return TRUE anyway. The code in http://drupal.org/files/issues/location.proximity.handler.patch
+  // does not protest if the postal code does not adhere to above regexps.
+  // @@@ Someone with knowledge about CA zipcodes please check whether we should return FALSE.
+  //     The database ({zipcode} cache) is better off when 'wrongly formatted' postal codes get denied.
+  return TRUE;
+}
diff -rup location-3.1/supported/location.de.inc location/supported/location.de.inc
--- location-3.1/supported/location.de.inc	2010-06-03 04:27:53.000000000 +0200
+++ location/supported/location.de.inc	2009-12-26 20:55:44.000000000 +0100
@@ -256,3 +256,11 @@ function location_get_postalcode_data_de
     return NULL;
   }
 }
+
+function location_standardize_postalcode_de(&$postalcode) {
+  // @@@ omg, like, srly. The "$dash_index === FALSE" SO does not make sense.
+  //     Someone please check the code in location_get_postalcode_data_de()
+  //     and insert something _proper_ here, please?
+  //     (Was someone trying to strip a trailing 'D-', or something?)
+  return TRUE;
+}
diff -rup location-3.1/supported/location.nl.inc location/supported/location.nl.inc
--- location-3.1/supported/location.nl.inc	2010-06-03 04:27:53.000000000 +0200
+++ location/supported/location.nl.inc	2010-03-20 16:47:59.000000000 +0100
@@ -20,6 +20,13 @@ function location_province_list_nl() {
   );
 }
 
+function location_timezone_data_nl($location) {
+  return array(
+    'timezone' => 1,
+    'dst' => 1
+  );
+}
+
 function location_map_link_nl_providers() {
   return array(
     'google' => array(
@@ -50,3 +57,19 @@ function location_map_link_nl_google($lo
     return NULL;
   }
 }
+
+function location_standardize_postalcode_nl(&$postalcode) {
+  if (!is_string($postalcode)) {
+    return FALSE;
+  }
+  $postalcode = strtoupper(str_replace(' ', '', $postalcode));
+  // 4-digit as well as 4+2 letters are accepted. (The latter yields better geocoding)
+  switch (strlen($postalcode)) {
+    case 4:
+      return is_numeric($postalcode);
+    case 6:
+      return is_numeric(substr($postalcode,0,4)) && preg_match('/^[A-Z]{2}/', substr($postalcode,-2));
+    default:
+      return FALSE;
+  }
+}
diff -rup location-3.1/supported/location.uk.inc location/supported/location.uk.inc
--- location-3.1/supported/location.uk.inc	2010-06-03 04:27:53.000000000 +0200
+++ location/supported/location.uk.inc	2009-12-26 21:19:36.000000000 +0100
@@ -238,6 +238,13 @@ function location_province_list_uk() {
     'WRX' => "Wrexham");
 }
 
+function location_timezone_data_uk($location) {
+  return array(
+    'timezone' => 0,
+    'dst' => 1
+  );
+}
+
 function location_map_link_uk_providers() {
   return array(
     'google' => array(
@@ -268,3 +275,24 @@ function location_map_link_uk_google($lo
     return NULL;
   }
 }
+
+function location_standardize_postalcode_uk(&$postalcode) {
+  if (!is_string($postalcode)) {
+    return FALSE;
+  }
+
+  $code = strtoupper(str_replace(' ','',$postalcode));
+  if (preg_match("/^[A-Z]{1,2}[0-9]{2,3}[A-Z]{2}$/",$code)
+    || preg_match("/^[A-Z]{1,2}[0-9]{1}[A-Z]{1}[0-9]{1}[A-Z]{2}$/",$code)
+    || preg_match("/^GIR0[A-Z]{2}$/",$postalcode)) {
+
+    preg_match('/^[a-zA-Z]*[0-9 ]+/', $postalcode, $matches);
+    $postalcode = substr_replace(str_replace(' ', '', $matches[0]), '', -1);
+    return TRUE;
+  }
+  // Return TRUE anyway. The code in http://drupal.org/files/issues/location.proximity.handler.patch
+  // does not protest if the postal code does not adhere to above regexps.
+  // @@@ Someone with knowledge about UK zipcodes please check whether we should return FALSE.
+  //     The database ({zipcode} cache) is better off when 'wrongly formatted' postal codes get denied.
+  return TRUE;
+}
diff -rup location-3.1/supported/location.us.inc location/supported/location.us.inc
--- location-3.1/supported/location.us.inc	2010-06-03 04:27:53.000000000 +0200
+++ location/supported/location.us.inc	2009-12-26 21:17:23.000000000 +0100
@@ -605,3 +605,16 @@ function location_province_list_numeric_
                '059' => 'Virgin Islands'
               );
 }
+
+function location_standardize_postalcode_us(&$postalcode) {
+  if (!is_string($postalcode)) {
+    return FALSE;
+  }
+
+  // If we're dealing with a 9-digit US zipcode, strip hyphen and the last 4 digits
+  $dash_index = strpos($postalcode, '-');
+  if ($dash_index !== FALSE) {
+    $postalcode = substr($postalcode, 0, $dash_index);
+  }
+  return is_numeric($postalcode);
+}
