Repository: ranaroussi/yfinance

Issue:
Fixed error: if zone.upper() == 'UTC': AttributeError: 'float' object has no attribute 'upper'
### Discussed in https://github.com/ranaroussi/yfinance/discussions/1134

<div type='discussions-op-text'>

<sup>Originally posted by **Slepetys** October 31, 2022</sup>
Hi yfinancers,

I would like to report an error and how I fixed it.

In the long cascade of error messages, I found the most informative as:
`if zone.upper() == 'UTC': AttributeError: 'float' object has no attribute 'upper'`

which is caused when a ticker symbol is passed to yfinance, but it does not exist. In most cases, yfinance handles such kind of situations well, but occasionally it returns an error like the message above.

After digging, I found that the exception handler in the **base.py** module, _history_ function was not dealing well when the timezone is not defined (either Nan or None) in the prior call.

In version 0.1.84 I found such an error, I fixed just adding an additional exceptional handling step specific to such kind of condition:

First line: 153
```
            try:
                tz = self._get_ticker_tz(debug_mode, proxy, timeout)

                if tz != tz or tz is None:
                    return utils.empty_df()

            except KeyError as e:
            ...
```

It is possible to emulate the error through:
```
# Emulate yFinance error
from datetime import datetime as datetime

import yfinance as yf
print ('yfinance version = '+yf.__version__)
_start = datetime (2022,10,1)
_end   = datetime (2022,10,30)

# Ticker Symbol that exists - NO ERROR:
ts_real = 'AAPL'
_OHLC = yf.download (ts_real,start=_start,end=_end,interval='1d')
print (_OHLC) #No error

# Ticker Symbol NOT exists - NO ERROR!
ts_real = 'AACQ'
_OHLC = yf.download (ts_real,start=_start,end=_end,interval='1d')
```
 And the error follows:
```
Exception in thread Thread-6:
Traceback (most recent call last):
  File "C:\Users\Roberto\anaconda3\lib\threading.py", line 932, in _bootstrap_inner
    self.run()
  File "C:\Users\Roberto\anaconda3\lib\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\Roberto\anaconda3\lib\site-packages\multitasking\__init__.py", line 102, in _run_via_pool
    return callee(*args, **kwargs)
  File "C:\Users\Roberto\anaconda3\lib\site-packages\yfinance\multi.py", line 199, in _download_one_threaded
    data = _download_one(ticker, start, end, auto_adjust, back_adjust,
  File "C:\Users\Roberto\anaconda3\lib\site-packages\yfinance\multi.py", line 213, in _download_one
    return Ticker(ticker).history(period=period, interval=interval,
  File "C:\Users\Roberto\anaconda3\lib\site-packages\yfinance\base.py", line 171, in history
    end = utils._parse_user_dt(end, tz)
  File "C:\Users\Roberto\anaconda3\lib\site-packages\yfinance\utils.py", line 157, in _parse_user_dt
    dt = _tz.timezone(exchange_tz).localize(dt)
  File "C:\Users\Roberto\anaconda3\lib\site-packages\pytz\__init__.py", line 170, in timezone
    if zone.upper() == 'UTC':
AttributeError: 'float' object has no attribute 'upper'
```

I hope this helps future versions.

All the best
Roberto Slepetys





</div>


Discussion:

Resolving patch:
diff --git a/yfinance/base.py b/yfinance/base.py
--- a/yfinance/base.py
+++ b/yfinance/base.py
@@ -150,17 +150,14 @@ def history(self, period="1mo", interval="1d",
 
         if start or period is None or period.lower() == "max":
             # Check can get TZ. Fail => probably delisted
-            try:
-                tz = self._get_ticker_tz(debug_mode, proxy, timeout)
-            except KeyError as e:
-                if "exchangeTimezoneName" in str(e):
-                    shared._DFS[self.ticker] = utils.empty_df()
-                    shared._ERRORS[self.ticker] = err_msg
-                    if "many" not in kwargs and debug_mode:
-                        print('- %s: %s' % (self.ticker, err_msg))
-                    return utils.empty_df()
-                else:
-                    raise
+            tz = self._get_ticker_tz(debug_mode, proxy, timeout)
+            if tz is None:
+                # Every valid ticker has a timezone. Missing = problem
+                shared._DFS[self.ticker] = utils.empty_df()
+                shared._ERRORS[self.ticker] = err_msg
+                if "many" not in kwargs and debug_mode:
+                    print('- %s: %s' % (self.ticker, err_msg))
+                return utils.empty_df()
 
             if end is None:
                 end = int(_time.time())
@@ -336,6 +333,19 @@ def _get_ticker_tz(self, debug_mode, proxy, timeout):
             return self._tz
 
         tkr_tz = utils.cache_lookup_tkr_tz(self.ticker)
+
+        if tkr_tz is not None:
+            invalid_value = isinstance(tkr_tz, str)
+            if not invalid_value:
+                try:
+                    _tz.timezone(tz)
+                except:
+                    invalid_value = True
+            if invalid_value:
+                # Clear from cache and force re-fetch
+                utils.cache_store_tkr_tz(self.ticker, None)
+                tkr_tz = None
+
         if tkr_tz is None:
             tkr_tz = self._fetch_ticker_tz(debug_mode, proxy, timeout)
 
diff --git a/yfinance/utils.py b/yfinance/utils.py
--- a/yfinance/utils.py
+++ b/yfinance/utils.py
@@ -336,32 +336,35 @@ def cache_lookup_tkr_tz(tkr):
         return None
 
     mutex.acquire()
-    df = _pd.read_csv(fp)
+    df = _pd.read_csv(fp, index_col="Ticker")
     mutex.release()
-    f = df["Ticker"] == tkr
-    if sum(f) == 0:
+    if tkr in df.index:
+        return df.loc[tkr,"Tz"]
+    else:
         return None
-
-    return df["Tz"][f].iloc[0]
 def cache_store_tkr_tz(tkr,tz):
-    df = _pd.DataFrame({"Ticker":[tkr], "Tz":[tz]})
 
     dp = get_cache_dirpath()
+    fp = _os.path.join(dp, "tkr-tz.csv")
     mutex.acquire()
     if not _os.path.isdir(dp):
         _os.makedirs(dp)
-    fp = _os.path.join(dp, "tkr-tz.csv")
-    if not _os.path.isfile(fp):
-        df.to_csv(fp, index=False)
-        mutex.release()
-        return
-
-    df_all = _pd.read_csv(fp)
-    f = df_all["Ticker"]==tkr
-    if sum(f) > 0:
-        mutex.release()
-        raise Exception("Tkr {} tz already in cache".format(tkr))
+    if (not _os.path.isfile(fp)) and (tz is not None):
+        df = _pd.DataFrame({"Tz":[tz]}, index=[tkr])
+        df.index.name = "Ticker"
+        df.to_csv(fp)
 
-    _pd.concat([df_all,df]).to_csv(fp, index=False)
+    else:
+        df = _pd.read_csv(fp, index_col="Ticker")
+        if tz is None:
+            # Delete if in cache:
+            if tkr in df.index:
+                df.drop(tkr).to_csv(fp)
+        else:
+            if tkr in df.index:
+                raise Exception("Tkr {} tz already in cache".format(tkr))
+            df.loc[tkr,"Tz"] = tz
+            df.to_csv(fp)
+    
     mutex.release()
 


Test patch: