Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix reading timezone from earning dates #2010

Open
wants to merge 7 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Change Log
===========

0.2.50
------
Fixes:
- price repair #2111 #2139
- download() appearance 2109
- isin() error #2099
- growth_estimates #2127
Also new docs #2132

0.2.49
------
Fix prices-clean rarely discarding good data #2122
Expand Down
2 changes: 1 addition & 1 deletion meta.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% set name = "yfinance" %}
{% set version = "0.2.49" %}
{% set version = "0.2.50" %}

package:
name: "{{ name|lower }}"
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ pytz>=2022.5
frozendict>=2.3.4
beautifulsoup4>=4.11.1
html5lib>=1.1
peewee>=3.16.2
peewee>=3.16.2
21 changes: 14 additions & 7 deletions tests/test_ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,14 @@
python -m unittest tests.ticker.TestTicker

"""
import pandas as pd

from tests.context import yfinance as yf
from tests.context import session_gbl
from yfinance.exceptions import YFPricesMissingError, YFInvalidPeriodError, YFNotImplementedError, YFTickerMissingError, YFTzMissingError, YFDataException


import unittest
import requests_cache
from typing import Union, Any, get_args, _GenericAlias
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse

import pandas as pd
import requests_cache

ticker_attributes = (
("major_holders", pd.DataFrame),
("institutional_holders", pd.DataFrame),
Expand Down Expand Up @@ -305,6 +301,7 @@ def test_earnings_dates(self):
data = self.ticker.earnings_dates
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")
self.assertEqual(data.index.tz.zone, "America/New_York")

def test_earnings_dates_with_limit(self):
# use ticker with lots of historic earnings
Expand All @@ -314,6 +311,7 @@ def test_earnings_dates_with_limit(self):
self.assertIsInstance(data, pd.DataFrame, "data has wrong type")
self.assertFalse(data.empty, "data is empty")
self.assertEqual(len(data), limit, "Wrong number or rows")
self.assertEqual(data.index[0].tz.zone, "America/New_York")

data_cached = ticker.get_earnings_dates(limit=limit)
self.assertIs(data, data_cached, "data not cached")
Expand All @@ -339,6 +337,15 @@ def test_earnings_dates_with_limit(self):
# data_cached = self.ticker.earnings_trend
# self.assertIs(data, data_cached, "data not cached")

def test_ticker_has_tz(self):
test_data = {"AMZN": "America/New_York", "LHA.DE": "Europe/Berlin", "6758.T": "Asia/Tokyo"}
for symbol, tz in test_data.items():
with self.subTest(f"{symbol}-{tz}"):
ticker = yf.Ticker(symbol)
data = ticker.get_earnings_dates(limit=1)
self.assertIsNotNone(data.index.tz)
self.assertEqual(data.index.tz.zone, tz)


class TestTickerHolders(unittest.TestCase):
session = None
Expand Down
62 changes: 54 additions & 8 deletions yfinance/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,25 @@

from __future__ import print_function

from io import StringIO
import json as _json
import warnings
from io import StringIO
from typing import Optional, Union
from urllib.parse import quote as urlencode

import pandas as pd
import requests

from . import utils, cache
from .const import _BASE_URL_, _ROOT_URL_
from .data import YfData
from .exceptions import YFEarningsDateMissing
from .scrapers.analysis import Analysis
from .scrapers.fundamentals import Fundamentals
from .scrapers.funds import FundsData
from .scrapers.history import PriceHistory
from .scrapers.holders import Holders
from .scrapers.quote import Quote, FastInfo
from .scrapers.history import PriceHistory
from .scrapers.funds import FundsData

from .const import _BASE_URL_, _ROOT_URL_


class TickerBase:
Expand Down Expand Up @@ -575,6 +574,15 @@ def get_earnings_dates(self, limit=12, proxy=None) -> Optional[pd.DataFrame]:

logger = utils.get_yf_logger()

ticker_tz = ""

def get_ticker_tz():
nonlocal ticker_tz
if ticker_tz == "":
self._quote.proxy = proxy or self.proxy
ticker_tz = self._get_ticker_tz(proxy=proxy, timeout=30)
return ticker_tz

page_size = min(limit, 100) # YF caps at 100, don't go higher
page_offset = 0
dates = None
Expand Down Expand Up @@ -639,10 +647,48 @@ def get_earnings_dates(self, limit=12, proxy=None) -> Optional[pd.DataFrame]:
# - combine and parse
dates[cn] = dates[cn] + ' ' + tzinfo["AM/PM"]
dates[cn] = pd.to_datetime(dates[cn], format="%b %d, %Y, %I %p")
# - instead of attempting decoding of ambiguous timezone abbreviation, just use 'info':

# Try to remap all ambiguous timezone values:
tzinfo['TZ'] = tzinfo['TZ'].str.replace('BST', 'Europe/London')
tzinfo['TZ'] = tzinfo['TZ'].str.replace('GMT', 'Europe/London')
if '.' not in self.ticker:
tzinfo['TZ'] = tzinfo['TZ'].str.replace('EST', 'America/New_York')
elif self.ticker.endswith(".AX"):
tzinfo['TZ'] = tzinfo['TZ'].str.replace('EST', 'Australia/Sydney')
tzinfo['TZ'] = tzinfo['TZ'].str.replace('MST', 'America/Denver')
tzinfo['TZ'] = tzinfo['TZ'].str.replace('PST', 'America/Los_Angeles')
if'.' not in self.ticker:
tzinfo['TZ'] = tzinfo['TZ'].str.replace('CST', 'America/Chicago')
else:
# Revisit if Cuba get a stock exchange
tzinfo['TZ'] = tzinfo['TZ'].str.replace('CST', 'Asia/Shanghai')
if self.ticker.endswith('.TA'):
tzinfo['TZ'] = tzinfo['TZ'].str.replace('IST', 'Asia/Jerusalem')
elif self.ticker.endswith('.IR'):
tzinfo['TZ'] = tzinfo['TZ'].str.replace('IST', 'Europe/Dublin')
elif self.ticker.endswith('.NS'):
tzinfo['TZ'] = tzinfo['TZ'].str.replace('IST', 'Asia/Kolkata')

# But in case still ambiguity that pytz cannot parse, have a backup:
self._quote.proxy = proxy or self.proxy
tz = self._get_ticker_tz(proxy=proxy, timeout=30)
dates[cn] = dates[cn].dt.tz_localize(tz)
tz_backup = self._get_ticker_tz(proxy=proxy, timeout=30)

if len(tzinfo['TZ'].unique())==1:
try:
dates[cn] = dates[cn].dt.tz_localize(tzinfo['TZ'].iloc[0])
except Exception:
dates[cn] = dates[cn].dt.tz_localize(tz_backup)
else:
dates2 = []
for i in range(len(dates)):
dt = dates[cn].iloc[i]
tz = tzinfo['TZ'].iloc[i]
try:
dt = dt.tz_localize(tz)
except Exception:
dt = dt.tz_localize(tz_backup)
dates2.append(dt)
dates[cn] = pd.to_datetime(dates2, utc=True).tz_convert(dates2[0].tzinfo)

dates = dates.set_index("Earnings Date")

Expand Down
2 changes: 1 addition & 1 deletion yfinance/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version = "0.2.49"
version = "0.2.50"
Loading