Skip to content

Commit 50c2baf

Browse files
committed
MAke relative date calculation more robust
1 parent a0e656c commit 50c2baf

File tree

1 file changed

+149
-118
lines changed

1 file changed

+149
-118
lines changed

faker/providers/date_time/__init__.py

+149-118
Original file line numberDiff line numberDiff line change
@@ -26,35 +26,22 @@ def datetime_to_timestamp(dt: Union[dtdate, datetime]) -> int:
2626
return timegm(dt.timetuple())
2727

2828

29+
def convert_timestamp_to_datetime(timestamp: Union[int, float], tzinfo: TzInfo) -> datetime:
30+
import datetime as dt
31+
32+
if timestamp >= 0:
33+
return dt.datetime.fromtimestamp(timestamp, tzinfo)
34+
else:
35+
return dt.datetime(1970, 1, 1, tzinfo=tzinfo) + dt.timedelta(seconds=int(timestamp))
36+
37+
2938
def timestamp_to_datetime(timestamp: Union[int, float], tzinfo: Optional[TzInfo]) -> datetime:
3039
if tzinfo is None:
3140
pick = convert_timestamp_to_datetime(timestamp, tzlocal())
3241
return pick.astimezone(tzutc()).replace(tzinfo=None)
3342
return convert_timestamp_to_datetime(timestamp, tzinfo)
3443

3544

36-
def get_now_date_time(
37-
start_date: Optional[DateParseType], end_date: Optional[DateParseType], tzinfo: Optional[TzInfo]
38-
) -> datetime:
39-
if isinstance(start_date, datetime) and not isinstance(end_date, datetime):
40-
now = start_date
41-
elif isinstance(end_date, datetime) and not isinstance(start_date, datetime):
42-
now = end_date
43-
else:
44-
now = datetime.now(tzinfo)
45-
return now
46-
47-
48-
def get_now_date(start_date: Optional[DateParseType], end_date: Optional[DateParseType]) -> dtdate:
49-
if isinstance(start_date, dtdate) and not isinstance(end_date, dtdate):
50-
now = start_date
51-
elif isinstance(end_date, dtdate) and not isinstance(start_date, dtdate):
52-
now = end_date
53-
else:
54-
now = dtdate.today()
55-
return now
56-
57-
5845
def change_year(current_date: dtdate, year_diff: int) -> dtdate:
5946
"""
6047
Unless the current_date is February 29th, it is fine to just subtract years.
@@ -1839,6 +1826,132 @@ def _rand_seconds(self, start_datetime: int, end_datetime: int) -> float:
18391826

18401827
regex = re.compile(timedelta_pattern)
18411828

1829+
@classmethod
1830+
def _is_absolute(cls, obj: Optional[DateParseType]) -> bool:
1831+
if obj is None:
1832+
return False
1833+
if isinstance(obj, (datetime, dtdate, int)):
1834+
return True
1835+
elif isinstance(obj, timedelta):
1836+
return False
1837+
elif isinstance(obj, str):
1838+
if obj in ("today", "now"):
1839+
return False
1840+
return cls.regex.fullmatch(obj) is None
1841+
return False
1842+
1843+
@classmethod
1844+
def _get_reference_date_time(
1845+
cls, start_date: Optional[DateParseType], end_date: Optional[DateParseType], tzinfo: Optional[TzInfo]
1846+
) -> datetime:
1847+
"""
1848+
Return Which datetime is absolute, or now if both are relative.
1849+
If both are absolute, return the most recent one.
1850+
If both are None, return now.
1851+
"""
1852+
min_ = datetime_to_timestamp(datetime.min)
1853+
now = datetime.now(tzinfo)
1854+
if start_date is None and end_date is None:
1855+
return now
1856+
1857+
start_int = cls._parse_date_time(start_date, now) if start_date is not None else min_
1858+
end_int = cls._parse_date_time(end_date, now) if end_date is not None else min_
1859+
if not cls._is_absolute(start_date) and not cls._is_absolute(end_date):
1860+
return now
1861+
if cls._is_absolute(start_date) and cls._is_absolute(end_date):
1862+
reference = max([start_int, end_int])
1863+
elif cls._is_absolute(start_date) and not cls._is_absolute(end_date):
1864+
reference = start_int
1865+
elif cls._is_absolute(end_date) and not cls._is_absolute(start_date):
1866+
reference = end_int
1867+
return timestamp_to_datetime(reference, tzinfo)
1868+
1869+
@classmethod
1870+
def _get_reference_date(cls, start_date: Optional[DateParseType], end_date: Optional[DateParseType]) -> dtdate:
1871+
reference = cls._get_reference_date_time(start_date, end_date, None)
1872+
return reference.date()
1873+
1874+
@classmethod
1875+
def _parse_start_datetime(cls, now: datetime, value: Optional[DateParseType]) -> int:
1876+
if value is None:
1877+
return 0
1878+
1879+
return cls._parse_date_time(value, now)
1880+
1881+
@classmethod
1882+
def _parse_end_datetime(cls, now: datetime, value: Optional[DateParseType]) -> int:
1883+
if value is None:
1884+
return datetime_to_timestamp(now)
1885+
1886+
return cls._parse_date_time(value, now)
1887+
1888+
@classmethod
1889+
def _parse_date_string(cls, value: str) -> Dict[str, float]:
1890+
parts = cls.regex.match(value)
1891+
if not parts:
1892+
raise ParseError(f"Can't parse date string `{value}`")
1893+
parts = parts.groupdict()
1894+
time_params: Dict[str, float] = {}
1895+
for name_, param_ in parts.items():
1896+
if param_:
1897+
time_params[name_] = int(param_)
1898+
1899+
if "years" in time_params:
1900+
if "days" not in time_params:
1901+
time_params["days"] = 0
1902+
time_params["days"] += 365.24 * time_params.pop("years")
1903+
if "months" in time_params:
1904+
if "days" not in time_params:
1905+
time_params["days"] = 0
1906+
time_params["days"] += 30.42 * time_params.pop("months")
1907+
1908+
if not time_params:
1909+
raise ParseError(f"Can't parse date string `{value}`")
1910+
return time_params
1911+
1912+
@classmethod
1913+
def _parse_timedelta(cls, value: Union[timedelta, str, float]) -> Union[float, int]:
1914+
if isinstance(value, timedelta):
1915+
return value.total_seconds()
1916+
if isinstance(value, str):
1917+
time_params = cls._parse_date_string(value)
1918+
return timedelta(**time_params).total_seconds() # type: ignore
1919+
if isinstance(value, (int, float)):
1920+
return value
1921+
raise ParseError(f"Invalid format for timedelta {value!r}")
1922+
1923+
@classmethod
1924+
def _parse_date_time(cls, value: DateParseType, now: datetime, tzinfo: Optional[TzInfo] = None) -> int:
1925+
if isinstance(value, (datetime, dtdate)):
1926+
return datetime_to_timestamp(value)
1927+
if isinstance(value, timedelta):
1928+
return datetime_to_timestamp(now + value)
1929+
if isinstance(value, str):
1930+
if value == "now":
1931+
return datetime_to_timestamp(datetime.now(tzinfo))
1932+
time_params = cls._parse_date_string(value)
1933+
return datetime_to_timestamp(now + timedelta(**time_params)) # type: ignore
1934+
if isinstance(value, int):
1935+
return value
1936+
raise ParseError(f"Invalid format for date {value!r}")
1937+
1938+
@classmethod
1939+
def _parse_date(cls, value: DateParseType, today: dtdate) -> dtdate:
1940+
if isinstance(value, datetime):
1941+
return value.date()
1942+
elif isinstance(value, dtdate):
1943+
return value
1944+
if isinstance(value, timedelta):
1945+
return today + value
1946+
if isinstance(value, str):
1947+
if value in ("today", "now"):
1948+
return today
1949+
time_params = cls._parse_date_string(value)
1950+
return today + timedelta(**time_params) # type: ignore
1951+
if isinstance(value, int):
1952+
return today + timedelta(value)
1953+
raise ParseError(f"Invalid format for date {value!r}")
1954+
18421955
def unix_time(
18431956
self,
18441957
end_datetime: Optional[DateParseType] = None,
@@ -1852,7 +1965,7 @@ def unix_time(
18521965
18531966
:example: 1061306726.6
18541967
"""
1855-
now = get_now_date_time(start_datetime, end_datetime, tzinfo=None)
1968+
now = self._get_reference_date_time(start_datetime, end_datetime, tzinfo=None)
18561969
start_datetime = self._parse_start_datetime(now, start_datetime)
18571970
end_datetime = self._parse_end_datetime(now, end_datetime)
18581971
return float(self._rand_seconds(start_datetime, end_datetime))
@@ -1905,7 +2018,7 @@ def date_time_ad(
19052018
# simply change that class method to use this magic number as a
19062019
# default value when None is provided.
19072020

1908-
now = get_now_date_time(start_datetime, end_datetime, tzinfo)
2021+
now = self._get_reference_date_time(start_datetime, end_datetime, tzinfo)
19092022
start_time = -62135596800 if start_datetime is None else self._parse_start_datetime(now, start_datetime)
19102023
end_datetime = self._parse_end_datetime(now, end_datetime)
19112024

@@ -1971,87 +2084,6 @@ def time_object(self, end_datetime: Optional[DateParseType] = None) -> dttime:
19712084
"""
19722085
return self.date_time(end_datetime=end_datetime).time()
19732086

1974-
@classmethod
1975-
def _parse_start_datetime(cls, now: datetime, value: Optional[DateParseType]) -> int:
1976-
if value is None:
1977-
return 0
1978-
1979-
return cls._parse_date_time(value, now)
1980-
1981-
@classmethod
1982-
def _parse_end_datetime(cls, now: datetime, value: Optional[DateParseType]) -> int:
1983-
if value is None:
1984-
return datetime_to_timestamp(now)
1985-
1986-
return cls._parse_date_time(value, now)
1987-
1988-
@classmethod
1989-
def _parse_date_string(cls, value: str) -> Dict[str, float]:
1990-
parts = cls.regex.match(value)
1991-
if not parts:
1992-
raise ParseError(f"Can't parse date string `{value}`")
1993-
parts = parts.groupdict()
1994-
time_params: Dict[str, float] = {}
1995-
for name_, param_ in parts.items():
1996-
if param_:
1997-
time_params[name_] = int(param_)
1998-
1999-
if "years" in time_params:
2000-
if "days" not in time_params:
2001-
time_params["days"] = 0
2002-
time_params["days"] += 365.24 * time_params.pop("years")
2003-
if "months" in time_params:
2004-
if "days" not in time_params:
2005-
time_params["days"] = 0
2006-
time_params["days"] += 30.42 * time_params.pop("months")
2007-
2008-
if not time_params:
2009-
raise ParseError(f"Can't parse date string `{value}`")
2010-
return time_params
2011-
2012-
@classmethod
2013-
def _parse_timedelta(cls, value: Union[timedelta, str, float]) -> Union[float, int]:
2014-
if isinstance(value, timedelta):
2015-
return value.total_seconds()
2016-
if isinstance(value, str):
2017-
time_params = cls._parse_date_string(value)
2018-
return timedelta(**time_params).total_seconds() # type: ignore
2019-
if isinstance(value, (int, float)):
2020-
return value
2021-
raise ParseError(f"Invalid format for timedelta {value!r}")
2022-
2023-
@classmethod
2024-
def _parse_date_time(cls, value: DateParseType, now: datetime, tzinfo: Optional[TzInfo] = None) -> int:
2025-
if isinstance(value, (datetime, dtdate)):
2026-
return datetime_to_timestamp(value)
2027-
if isinstance(value, timedelta):
2028-
return datetime_to_timestamp(now + value)
2029-
if isinstance(value, str):
2030-
if value == "now":
2031-
return datetime_to_timestamp(datetime.now(tzinfo))
2032-
time_params = cls._parse_date_string(value)
2033-
return datetime_to_timestamp(now + timedelta(**time_params)) # type: ignore
2034-
if isinstance(value, int):
2035-
return value
2036-
raise ParseError(f"Invalid format for date {value!r}")
2037-
2038-
@classmethod
2039-
def _parse_date(cls, value: DateParseType, today: dtdate) -> dtdate:
2040-
if isinstance(value, datetime):
2041-
return value.date()
2042-
elif isinstance(value, dtdate):
2043-
return value
2044-
if isinstance(value, timedelta):
2045-
return today + value
2046-
if isinstance(value, str):
2047-
if value in ("today", "now"):
2048-
return today
2049-
time_params = cls._parse_date_string(value)
2050-
return today + timedelta(**time_params) # type: ignore
2051-
if isinstance(value, int):
2052-
return today + timedelta(value)
2053-
raise ParseError(f"Invalid format for date {value!r}")
2054-
20552087
def date_time_between(
20562088
self,
20572089
start_date: DateParseType = "-30y",
@@ -2068,7 +2100,10 @@ def date_time_between(
20682100
:example: datetime('1999-02-02 11:42:52')
20692101
:return: datetime
20702102
"""
2071-
now = get_now_date_time(start_date, end_date, tzinfo)
2103+
if end_date is None:
2104+
end_date = "now"
2105+
2106+
now = self._get_reference_date_time(start_date, end_date, tzinfo)
20722107
start_date = self._parse_date_time(start_date, now, tzinfo=tzinfo)
20732108
end_date = self._parse_date_time(end_date, now, tzinfo=tzinfo)
20742109
if end_date - start_date <= 1:
@@ -2090,7 +2125,10 @@ def date_between(self, start_date: DateParseType = "-30y", end_date: DateParseTy
20902125
:example: Date('1999-02-02')
20912126
:return: Date
20922127
"""
2093-
today = get_now_date(start_date, end_date)
2128+
2129+
if end_date is None:
2130+
end_date = "now"
2131+
today = self._get_reference_date(start_date, end_date)
20942132
start_date = self._parse_date(start_date, today)
20952133
end_date = self._parse_date(end_date, today)
20962134
return self.date_between_dates(date_start=start_date, date_end=end_date)
@@ -2164,7 +2202,7 @@ def date_time_between_dates(
21642202
:example: datetime('1999-02-02 11:42:52')
21652203
:return: datetime
21662204
"""
2167-
today = get_now_date(datetime_start, datetime_end)
2205+
today = self._get_reference_date(datetime_start, datetime_end)
21682206
now = datetime.combine(today, datetime.min.time(), tzinfo)
21692207
datetime_start_ = (
21702208
datetime_to_timestamp(datetime.now(tzinfo))
@@ -2425,7 +2463,9 @@ def time_series(
24252463
``distrib`` is a callable that accepts ``<datetime>`` and returns ``<value>``
24262464
24272465
"""
2428-
now = get_now_date_time(start_date, end_date, tzinfo)
2466+
if end_date is None:
2467+
end_date = "now"
2468+
now = self._get_reference_date_time(start_date, end_date, tzinfo)
24292469
start_date_ = self._parse_date_time(start_date, now, tzinfo=tzinfo)
24302470
end_date_ = self._parse_date_time(end_date, now, tzinfo=tzinfo)
24312471

@@ -2530,12 +2570,3 @@ def date_of_birth(
25302570
dob = self.date_time_ad(tzinfo=tzinfo, start_datetime=start_date, end_datetime=end_date).date()
25312571

25322572
return dob if dob != start_date else dob + timedelta(days=1)
2533-
2534-
2535-
def convert_timestamp_to_datetime(timestamp: Union[int, float], tzinfo: TzInfo) -> datetime:
2536-
import datetime as dt
2537-
2538-
if timestamp >= 0:
2539-
return dt.datetime.fromtimestamp(timestamp, tzinfo)
2540-
else:
2541-
return dt.datetime(1970, 1, 1, tzinfo=tzinfo) + dt.timedelta(seconds=int(timestamp))

0 commit comments

Comments
 (0)