prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Spider att is broken
  During the global build at 2021-06-02-14-42-40, spider **att** failed with **0 features** and **5433 errors**.
  
  Here's [the log](https://data.alltheplaces.xyz/runs/2021-06-02-14-42-40/logs/att.txt) and [the output](https://data.alltheplaces.xyz/runs/2021-06-02-14-42-40/output/att.geojson) ([on a map](https://data.alltheplaces.xyz/map.html?show=https://data.alltheplaces.xyz/runs/2021-06-02-14-42-40/output/att.geojson))
  
  </issue>
  <code>
  [start of locations/spiders/att.py]
  1 import scrapy
  2 import json
  3 import re
  4 from locations.items import GeojsonPointItem
  5 from locations.hours import OpeningHours
  6 
  7 
  8 DAY_MAPPING = {
  9     "MONDAY": "Mo",
  10     "TUESDAY": "Tu",
  11     "WEDNESDAY": "We",
  12     "THURSDAY": "Th",
  13     "FRIDAY": "Fr",
  14     "SATURDAY": "Sa",
  15     "SUNDAY": "Su"
  16 }
  17 
  18 
  19 class ATTScraper(scrapy.Spider):
  20     name = "att"
  21     item_attributes = { 'brand': "AT&T", 'brand_wikidata': "Q35476" }
  22     allowed_domains = ['www.att.com']
  23     start_urls = (
  24         'https://www.att.com/stores/us',
  25     )
  26     download_delay = 0.2
  27 
  28     def parse_hours(self, store_hours):
  29         opening_hours = OpeningHours()
  30         store_data = json.loads(store_hours)
  31 
  32         for store_day in store_data:
  33             if len(store_day["intervals"]) < 1:
  34                 continue
  35             day = DAY_MAPPING[store_day["day"]]
  36             open_time = str(store_day["intervals"][0]["start"])
  37             if open_time == '0':
  38                 open_time = '0000'
  39             close_time = str(store_day["intervals"][0]["end"])
  40             if close_time == '0':
  41                 close_time = '2359'
  42             opening_hours.add_range(day=day,
  43                                     open_time=open_time,
  44                                     close_time=close_time,
  45                                     time_format='%H%M'
  46                                     )
  47 
  48         return opening_hours.as_opening_hours()
  49 
  50     def parse(self, response):
  51         urls = response.xpath('//a[@class="Directory-listLink"]/@href').extract()
  52         is_store_list = response.xpath('//a[@class="Teaser-titleLink"]/@href').extract()
  53 
  54         if not urls and is_store_list:
  55             urls = response.xpath('//a[@class="Teaser-titleLink"]/@href').extract()
  56         for url in urls:
  57             if url.count('/') >= 2:
  58                 yield scrapy.Request(response.urljoin(url), callback=self.parse_store)
  59             else:
  60                 yield scrapy.Request(response.urljoin(url))
  61 
  62     def parse_store(self, response):
  63         ref = re.search(r'.+/(.+?)/?(?:\.html|$)', response.url).group(1)
  64 
  65         properties = {
  66             'ref': ref,
  67             'name': response.xpath('normalize-space(//span[@class="LocationName-brand"]/text())').extract_first(),
  68             'addr_full': response.xpath('normalize-space(//meta[@itemprop="streetAddress"]/@content)').extract_first(),
  69             'city': response.xpath('normalize-space(//meta[@itemprop="addressLocality"]/@content)').extract_first(),
  70             'state': response.xpath('normalize-space(//abbr[@itemprop="addressRegion"]/text())').extract_first(),
  71             'postcode': response.xpath('normalize-space(//span[@itemprop="postalCode"]/text())').extract_first(),
  72             'country': response.xpath('normalize-space(//abbr[@itemprop="addressCountry"]/text())').extract_first(),
  73             'phone': response.xpath('normalize-space(//span[@itemprop="telephone"]//text())').extract_first(),
  74             'website': response.url,
  75             'lat': response.xpath('normalize-space(//meta[@itemprop="latitude"]/@content)').extract_first(),
  76             'lon': response.xpath('normalize-space(//meta[@itemprop="longitude"]/@content)').extract_first(),
  77         }
  78 
  79         hours = response.xpath('//span[@class="c-location-hours-today js-location-hours"]/@data-days').extract_first()
  80         properties['opening_hours'] = self.parse_hours(hours)
  81 
  82         yield GeojsonPointItem(**properties)
  83
  [end of locations/spiders/att.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/att.py b/locations/spiders/att.py
  --- a/locations/spiders/att.py
  +++ b/locations/spiders/att.py
  @@ -76,7 +76,7 @@
               'lon': response.xpath('normalize-space(//meta[@itemprop="longitude"]/@content)').extract_first(),
           }
   
  -        hours = response.xpath('//span[@class="c-location-hours-today js-location-hours"]/@data-days').extract_first()
  +        hours = response.xpath('//span[@class="c-hours-today js-hours-today"]/@data-days').extract_first()
           properties['opening_hours'] = self.parse_hours(hours)
   
           yield GeojsonPointItem(**properties)
  
verification_info: {"golden_diff": "diff --git a/locations/spiders/att.py b/locations/spiders/att.py\n--- a/locations/spiders/att.py\n+++ b/locations/spiders/att.py\n@@ -76,7 +76,7 @@\n             'lon': response.xpath('normalize-space(//meta[@itemprop=\"longitude\"]/@content)').extract_first(),\n         }\n \n-        hours = response.xpath('//span[@class=\"c-location-hours-today js-location-hours\"]/@data-days').extract_first()\n+        hours = response.xpath('//span[@class=\"c-hours-today js-hours-today\"]/@data-days').extract_first()\n         properties['opening_hours'] = self.parse_hours(hours)\n \n         yield GeojsonPointItem(**properties)\n", "issue": "Spider att is broken\nDuring the global build at 2021-06-02-14-42-40, spider **att** failed with **0 features** and **5433 errors**.\n\nHere's [the log](https://data.alltheplaces.xyz/runs/2021-06-02-14-42-40/logs/att.txt) and [the output](https://data.alltheplaces.xyz/runs/2021-06-02-14-42-40/output/att.geojson) ([on a map](https://data.alltheplaces.xyz/map.html?show=https://data.alltheplaces.xyz/runs/2021-06-02-14-42-40/output/att.geojson))\n", "before_files": [{"content": "import scrapy\nimport json\nimport re\nfrom locations.items import GeojsonPointItem\nfrom locations.hours import OpeningHours\n\n\nDAY_MAPPING = {\n    \"MONDAY\": \"Mo\",\n    \"TUESDAY\": \"Tu\",\n    \"WEDNESDAY\": \"We\",\n    \"THURSDAY\": \"Th\",\n    \"FRIDAY\": \"Fr\",\n    \"SATURDAY\": \"Sa\",\n    \"SUNDAY\": \"Su\"\n}\n\n\nclass ATTScraper(scrapy.Spider):\n    name = \"att\"\n    item_attributes = { 'brand': \"AT&T\", 'brand_wikidata': \"Q35476\" }\n    allowed_domains = ['www.att.com']\n    start_urls = (\n        'https://www.att.com/stores/us',\n    )\n    download_delay = 0.2\n\n    def parse_hours(self, store_hours):\n        opening_hours = OpeningHours()\n        store_data = json.loads(store_hours)\n\n        for store_day in store_data:\n            if len(store_day[\"intervals\"]) < 1:\n                continue\n            day = DAY_MAPPING[store_day[\"day\"]]\n            open_time = str(store_day[\"intervals\"][0][\"start\"])\n            if open_time == '0':\n                open_time = '0000'\n            close_time = str(store_day[\"intervals\"][0][\"end\"])\n            if close_time == '0':\n                close_time = '2359'\n            opening_hours.add_range(day=day,\n                                    open_time=open_time,\n                                    close_time=close_time,\n                                    time_format='%H%M'\n                                    )\n\n        return opening_hours.as_opening_hours()\n\n    def parse(self, response):\n        urls = response.xpath('//a[@class=\"Directory-listLink\"]/@href').extract()\n        is_store_list = response.xpath('//a[@class=\"Teaser-titleLink\"]/@href').extract()\n\n        if not urls and is_store_list:\n            urls = response.xpath('//a[@class=\"Teaser-titleLink\"]/@href').extract()\n        for url in urls:\n            if url.count('/') >= 2:\n                yield scrapy.Request(response.urljoin(url), callback=self.parse_store)\n            else:\n                yield scrapy.Request(response.urljoin(url))\n\n    def parse_store(self, response):\n        ref = re.search(r'.+/(.+?)/?(?:\\.html|$)', response.url).group(1)\n\n        properties = {\n            'ref': ref,\n            'name': response.xpath('normalize-space(//span[@class=\"LocationName-brand\"]/text())').extract_first(),\n            'addr_full': response.xpath('normalize-space(//meta[@itemprop=\"streetAddress\"]/@content)').extract_first(),\n            'city': response.xpath('normalize-space(//meta[@itemprop=\"addressLocality\"]/@content)').extract_first(),\n            'state': response.xpath('normalize-space(//abbr[@itemprop=\"addressRegion\"]/text())').extract_first(),\n            'postcode': response.xpath('normalize-space(//span[@itemprop=\"postalCode\"]/text())').extract_first(),\n            'country': response.xpath('normalize-space(//abbr[@itemprop=\"addressCountry\"]/text())').extract_first(),\n            'phone': response.xpath('normalize-space(//span[@itemprop=\"telephone\"]//text())').extract_first(),\n            'website': response.url,\n            'lat': response.xpath('normalize-space(//meta[@itemprop=\"latitude\"]/@content)').extract_first(),\n            'lon': response.xpath('normalize-space(//meta[@itemprop=\"longitude\"]/@content)').extract_first(),\n        }\n\n        hours = response.xpath('//span[@class=\"c-location-hours-today js-location-hours\"]/@data-days').extract_first()\n        properties['opening_hours'] = self.parse_hours(hours)\n\n        yield GeojsonPointItem(**properties)\n", "path": "locations/spiders/att.py"}]}