1import os
2import pickle
3import zlib
4from datetime import datetime, timedelta, timezone, tzinfo
5from pathlib import Path
6
7import regex as re
8
9from .timezones import timezone_info_list
10
11
12class StaticTzInfo(tzinfo):
13 def __init__(self, name, offset):
14 self.__offset = offset
15 self.__name = name
16
17 def tzname(self, dt):
18 return self.__name
19
20 def utcoffset(self, dt):
21 return self.__offset
22
23 def dst(self, dt):
24 return timedelta(0)
25
26 def __repr__(self):
27 return "<%s '%s'>" % (self.__class__.__name__, self.__name)
28
29 def localize(self, dt, is_dst=False):
30 if dt.tzinfo is not None:
31 raise ValueError("Not naive datetime (tzinfo is already set)")
32 return dt.replace(tzinfo=self)
33
34 def __getinitargs__(self):
35 return self.__name, self.__offset
36
37
38def pop_tz_offset_from_string(date_string, as_offset=True):
39 if _search_regex_ignorecase.search(date_string):
40 for name, info in _tz_offsets:
41 timezone_re = info["regex"]
42 timezone_match = timezone_re.search(date_string)
43 if timezone_match:
44 start, stop = timezone_match.span()
45 date_string = date_string[: start + 1] + date_string[stop:]
46 return (
47 date_string,
48 StaticTzInfo(name, info["offset"]) if as_offset else name,
49 )
50 return date_string, None
51
52
53def word_is_tz(word):
54 return bool(_search_regex.match(word))
55
56
57def convert_to_local_tz(datetime_obj, datetime_tz_offset):
58 return datetime_obj - datetime_tz_offset + local_tz_offset
59
60
61def build_tz_offsets(search_regex_parts):
62 def get_offset(tz_obj, regex, repl="", replw=""):
63 return (
64 tz_obj[0],
65 {
66 "regex": re.compile(
67 re.sub(repl, replw, regex % tz_obj[0]), re.IGNORECASE
68 ),
69 "offset": timedelta(seconds=tz_obj[1]),
70 },
71 )
72
73 for tz_info in timezone_info_list:
74 for regex in tz_info["regex_patterns"]:
75 for tz_obj in tz_info["timezones"]:
76 search_regex_parts.append(tz_obj[0])
77 yield get_offset(tz_obj, regex)
78
79 # alternate patterns
80 for replace, replacewith in tz_info.get("replace", []):
81 search_regex_parts.append(re.sub(replace, replacewith, tz_obj[0]))
82 yield get_offset(tz_obj, regex, repl=replace, replw=replacewith)
83
84
85def get_local_tz_offset():
86 offset = datetime.now() - datetime.now(tz=timezone.utc).replace(tzinfo=None)
87 offset = timedelta(days=offset.days, seconds=round(offset.seconds, -1))
88 return offset
89
90
91local_tz_offset = get_local_tz_offset()
92
93_tz_offsets = None
94_search_regex = None
95_search_regex_ignorecase = None
96
97
98def _load_offsets(cache_path, current_hash):
99 global _tz_offsets, _search_regex, _search_regex_ignorecase
100
101 try:
102 with open(cache_path, mode="rb") as file:
103 (
104 serialized_hash,
105 _tz_offsets,
106 _search_regex,
107 _search_regex_ignorecase,
108 ) = pickle.load(file)
109 if current_hash is None or current_hash == serialized_hash:
110 return
111 except (FileNotFoundError, ValueError, TypeError):
112 pass
113
114 _search_regex_parts = []
115 _tz_offsets = list(build_tz_offsets(_search_regex_parts))
116 _search_regex = re.compile("|".join(_search_regex_parts))
117 _search_regex_ignorecase = re.compile("|".join(_search_regex_parts), re.IGNORECASE)
118
119 with open(cache_path, mode="wb") as file:
120 pickle.dump(
121 (current_hash, _tz_offsets, _search_regex, _search_regex_ignorecase),
122 file,
123 protocol=5,
124 )
125
126
127CACHE_PATH = Path(__file__).parent.joinpath("data", "dateparser_tz_cache.pkl")
128
129if "BUILD_TZ_CACHE" in os.environ:
130 current_hash = zlib.crc32(str(timezone_info_list).encode("utf-8"))
131else:
132 current_hash = None
133
134_load_offsets(CACHE_PATH, current_hash)