prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Spider target_au is broken
  During the global build at 2021-05-26-14-42-23, spider **target_au** failed with **0 features** and **16 errors**.
  
  Here's [the log](https://data.alltheplaces.xyz/runs/2021-05-26-14-42-23/logs/target_au.log) and [the output](https://data.alltheplaces.xyz/runs/2021-05-26-14-42-23/output/target_au.geojson) ([on a map](https://data.alltheplaces.xyz/map.html?show=https://data.alltheplaces.xyz/runs/2021-05-26-14-42-23/output/target_au.geojson))
  
  </issue>
  <code>
  [start of locations/spiders/target_au.py]
  1 import scrapy
  2 
  3 from locations.hours import OpeningHours
  4 from locations.items import GeojsonPointItem
  5 
  6 
  7 class TargetAUSpider(scrapy.Spider):
  8     name = "target_au"
  9     item_attributes = { 'brand': "Target", 'brand_wikidata': "Q7685854" }
  10     allowed_domains = ["target.com.au"]
  11     states = ["nsw","vic","qld","nt", "act", "sa", "tas", "wa"]
  12     headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0",
  13                 "Referer": "https://www.target.com.au/store-finder"}
  14 
  15     custom_settings = {'DOWNLOAD_DELAY' : 0.5,}
  16 
  17     def start_requests(self):
  18         url = "https://www.target.com.au/store-finder/state/{}"
  19         for state in self.states:
  20             yield scrapy.Request(url.format(state),headers=self.headers, callback=self.parse)
  21 
  22 
  23     def parse(self, response):
  24         store_links = response.xpath('//a[@class="table-tap-canonical"]/@href').getall()
  25         for link in store_links:
  26             yield scrapy.Request(response.urljoin(link), callback=self.parse_store, headers=self.headers)
  27 
  28     def _parse_hour_str(self, hour_string):
  29         time_, am_pm = tuple(hour_string.split(" "))
  30         hour, min = tuple(time_.split(":"))
  31         hour = int(hour)
  32         if am_pm == "PM":
  33             hour += 12
  34         return f"{hour}:{min}"
  35 
  36     def parse_hours(self, hours_node):
  37         opening_hours = OpeningHours()
  38         days = hours_node.xpath(".//dt/text()").getall()
  39         hours = hours_node.xpath(".//dd/text()").getall()
  40         for idx, day in enumerate(days):
  41             store_hours = hours[idx]
  42             if "–" not in store_hours or ":" not in store_hours:
  43                 continue
  44             parts = store_hours.strip().split(" – ")
  45             open_time = self._parse_hour_str(parts[0])
  46             close_time = self._parse_hour_str(parts[1])
  47             opening_hours.add_range(day[0:2], open_time, close_time)
  48         
  49         return opening_hours.as_opening_hours()
  50 
  51 
  52 
  53     def parse_store(self, response):
  54         store_name = response.xpath("//h4/text()").get().replace("Target – ","")
  55         address_header = response.xpath("//span[@itemprop='streetAddress']/strong/text()").get()
  56         address = " ".join(response.xpath("//span[@itemprop='streetAddress']/text()").getall()).strip()
  57         if address_header:
  58             address = address_header + " " + address
  59         locality = response.xpath("//span[@itemprop='addressLocality']/text()").get()
  60         region = response.xpath("//span[@itemprop='addressRegion']/text()").get()
  61         post_code = response.xpath("//span[@itemprop='postalCode']/text()").get()
  62         phone_number = response.xpath("//span[@itemprop='telephone']/text()").get()
  63         hours_section = response.xpath("(//dl)[1]")[0]
  64         opening_hours = self.parse_hours(hours_section)
  65         lat = response.xpath("//div[@data-embedded-json='store-content-data']//@data-lat").get()
  66         lon = response.xpath("//div[@data-embedded-json='store-content-data']//@data-lng").get()
  67 
  68         yield GeojsonPointItem(lat=lat,
  69                                 lon=lon,
  70                                 name=store_name,
  71                                 addr_full=address,
  72                                 city=locality,
  73                                 state=region,
  74                                 postcode=post_code,
  75                                 country="AU",
  76                                 phone=phone_number,
  77                                 website=response.url,
  78                                 opening_hours=opening_hours,
  79                                 ref=response.url.split("/")[-1])  
  80
  [end of locations/spiders/target_au.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/locations/spiders/target_au.py b/locations/spiders/target_au.py
  --- a/locations/spiders/target_au.py
  +++ b/locations/spiders/target_au.py
  @@ -26,6 +26,8 @@
               yield scrapy.Request(response.urljoin(link), callback=self.parse_store, headers=self.headers)
   
       def _parse_hour_str(self, hour_string):
  +        if hour_string == "Midnight":
  +            return self._parse_hour_str("12:00 AM")
           time_, am_pm = tuple(hour_string.split(" "))
           hour, min = tuple(time_.split(":"))
           hour = int(hour)
  
verification_info: {"golden_diff": "diff --git a/locations/spiders/target_au.py b/locations/spiders/target_au.py\n--- a/locations/spiders/target_au.py\n+++ b/locations/spiders/target_au.py\n@@ -26,6 +26,8 @@\n             yield scrapy.Request(response.urljoin(link), callback=self.parse_store, headers=self.headers)\n \n     def _parse_hour_str(self, hour_string):\n+        if hour_string == \"Midnight\":\n+            return self._parse_hour_str(\"12:00 AM\")\n         time_, am_pm = tuple(hour_string.split(\" \"))\n         hour, min = tuple(time_.split(\":\"))\n         hour = int(hour)\n", "issue": "Spider target_au is broken\nDuring the global build at 2021-05-26-14-42-23, spider **target_au** failed with **0 features** and **16 errors**.\n\nHere's [the log](https://data.alltheplaces.xyz/runs/2021-05-26-14-42-23/logs/target_au.log) and [the output](https://data.alltheplaces.xyz/runs/2021-05-26-14-42-23/output/target_au.geojson) ([on a map](https://data.alltheplaces.xyz/map.html?show=https://data.alltheplaces.xyz/runs/2021-05-26-14-42-23/output/target_au.geojson))\n", "before_files": [{"content": "import scrapy\n\nfrom locations.hours import OpeningHours\nfrom locations.items import GeojsonPointItem\n\n\nclass TargetAUSpider(scrapy.Spider):\n    name = \"target_au\"\n    item_attributes = { 'brand': \"Target\", 'brand_wikidata': \"Q7685854\" }\n    allowed_domains = [\"target.com.au\"]\n    states = [\"nsw\",\"vic\",\"qld\",\"nt\", \"act\", \"sa\", \"tas\", \"wa\"]\n    headers = {\"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0\",\n                \"Referer\": \"https://www.target.com.au/store-finder\"}\n\n    custom_settings = {'DOWNLOAD_DELAY' : 0.5,}\n\n    def start_requests(self):\n        url = \"https://www.target.com.au/store-finder/state/{}\"\n        for state in self.states:\n            yield scrapy.Request(url.format(state),headers=self.headers, callback=self.parse)\n\n\n    def parse(self, response):\n        store_links = response.xpath('//a[@class=\"table-tap-canonical\"]/@href').getall()\n        for link in store_links:\n            yield scrapy.Request(response.urljoin(link), callback=self.parse_store, headers=self.headers)\n\n    def _parse_hour_str(self, hour_string):\n        time_, am_pm = tuple(hour_string.split(\" \"))\n        hour, min = tuple(time_.split(\":\"))\n        hour = int(hour)\n        if am_pm == \"PM\":\n            hour += 12\n        return f\"{hour}:{min}\"\n\n    def parse_hours(self, hours_node):\n        opening_hours = OpeningHours()\n        days = hours_node.xpath(\".//dt/text()\").getall()\n        hours = hours_node.xpath(\".//dd/text()\").getall()\n        for idx, day in enumerate(days):\n            store_hours = hours[idx]\n            if \"\u2013\" not in store_hours or \":\" not in store_hours:\n                continue\n            parts = store_hours.strip().split(\" \u2013 \")\n            open_time = self._parse_hour_str(parts[0])\n            close_time = self._parse_hour_str(parts[1])\n            opening_hours.add_range(day[0:2], open_time, close_time)\n        \n        return opening_hours.as_opening_hours()\n\n\n\n    def parse_store(self, response):\n        store_name = response.xpath(\"//h4/text()\").get().replace(\"Target \u2013 \",\"\")\n        address_header = response.xpath(\"//span[@itemprop='streetAddress']/strong/text()\").get()\n        address = \" \".join(response.xpath(\"//span[@itemprop='streetAddress']/text()\").getall()).strip()\n        if address_header:\n            address = address_header + \" \" + address\n        locality = response.xpath(\"//span[@itemprop='addressLocality']/text()\").get()\n        region = response.xpath(\"//span[@itemprop='addressRegion']/text()\").get()\n        post_code = response.xpath(\"//span[@itemprop='postalCode']/text()\").get()\n        phone_number = response.xpath(\"//span[@itemprop='telephone']/text()\").get()\n        hours_section = response.xpath(\"(//dl)[1]\")[0]\n        opening_hours = self.parse_hours(hours_section)\n        lat = response.xpath(\"//div[@data-embedded-json='store-content-data']//@data-lat\").get()\n        lon = response.xpath(\"//div[@data-embedded-json='store-content-data']//@data-lng\").get()\n\n        yield GeojsonPointItem(lat=lat,\n                                lon=lon,\n                                name=store_name,\n                                addr_full=address,\n                                city=locality,\n                                state=region,\n                                postcode=post_code,\n                                country=\"AU\",\n                                phone=phone_number,\n                                website=response.url,\n                                opening_hours=opening_hours,\n                                ref=response.url.split(\"/\")[-1])  \n", "path": "locations/spiders/target_au.py"}]}