weatherdb 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. docker/Dockerfile +30 -0
  2. docker/docker-compose.yaml +58 -0
  3. docker/docker-compose_test.yaml +24 -0
  4. docker/start-docker-test.sh +6 -0
  5. docs/requirements.txt +10 -0
  6. docs/source/Changelog.md +2 -0
  7. docs/source/License.rst +7 -0
  8. docs/source/Methode.md +161 -0
  9. docs/source/_static/custom.css +8 -0
  10. docs/source/_static/favicon.ico +0 -0
  11. docs/source/_static/logo.png +0 -0
  12. docs/source/api/api.rst +15 -0
  13. docs/source/api/cli.rst +8 -0
  14. docs/source/api/weatherDB.broker.rst +10 -0
  15. docs/source/api/weatherDB.config.rst +7 -0
  16. docs/source/api/weatherDB.db.rst +23 -0
  17. docs/source/api/weatherDB.rst +22 -0
  18. docs/source/api/weatherDB.station.rst +56 -0
  19. docs/source/api/weatherDB.stations.rst +46 -0
  20. docs/source/api/weatherDB.utils.rst +22 -0
  21. docs/source/conf.py +137 -0
  22. docs/source/index.rst +33 -0
  23. docs/source/setup/Configuration.md +127 -0
  24. docs/source/setup/Hosting.md +9 -0
  25. docs/source/setup/Install.md +49 -0
  26. docs/source/setup/Quickstart.md +183 -0
  27. docs/source/setup/setup.rst +12 -0
  28. weatherdb/__init__.py +24 -0
  29. weatherdb/_version.py +1 -0
  30. weatherdb/alembic/README.md +8 -0
  31. weatherdb/alembic/alembic.ini +80 -0
  32. weatherdb/alembic/config.py +9 -0
  33. weatherdb/alembic/env.py +100 -0
  34. weatherdb/alembic/script.py.mako +26 -0
  35. weatherdb/alembic/versions/V1.0.0_initial_database_creation.py +898 -0
  36. weatherdb/alembic/versions/V1.0.2_more_charachters_for_settings+term_station_ma_raster.py +88 -0
  37. weatherdb/alembic/versions/V1.0.5_fix-ma-raster-values.py +152 -0
  38. weatherdb/alembic/versions/V1.0.6_update-views.py +22 -0
  39. weatherdb/broker.py +667 -0
  40. weatherdb/cli.py +214 -0
  41. weatherdb/config/ConfigParser.py +663 -0
  42. weatherdb/config/__init__.py +5 -0
  43. weatherdb/config/config_default.ini +162 -0
  44. weatherdb/db/__init__.py +3 -0
  45. weatherdb/db/connections.py +374 -0
  46. weatherdb/db/fixtures/RichterParameters.json +34 -0
  47. weatherdb/db/models.py +402 -0
  48. weatherdb/db/queries/get_quotient.py +155 -0
  49. weatherdb/db/views.py +165 -0
  50. weatherdb/station/GroupStation.py +710 -0
  51. weatherdb/station/StationBases.py +3108 -0
  52. weatherdb/station/StationET.py +111 -0
  53. weatherdb/station/StationP.py +807 -0
  54. weatherdb/station/StationPD.py +98 -0
  55. weatherdb/station/StationT.py +164 -0
  56. weatherdb/station/__init__.py +13 -0
  57. weatherdb/station/constants.py +21 -0
  58. weatherdb/stations/GroupStations.py +519 -0
  59. weatherdb/stations/StationsBase.py +1021 -0
  60. weatherdb/stations/StationsBaseTET.py +30 -0
  61. weatherdb/stations/StationsET.py +17 -0
  62. weatherdb/stations/StationsP.py +128 -0
  63. weatherdb/stations/StationsPD.py +24 -0
  64. weatherdb/stations/StationsT.py +21 -0
  65. weatherdb/stations/__init__.py +11 -0
  66. weatherdb/utils/TimestampPeriod.py +369 -0
  67. weatherdb/utils/__init__.py +3 -0
  68. weatherdb/utils/dwd.py +350 -0
  69. weatherdb/utils/geometry.py +69 -0
  70. weatherdb/utils/get_data.py +285 -0
  71. weatherdb/utils/logging.py +126 -0
  72. weatherdb-1.1.0.dist-info/LICENSE +674 -0
  73. weatherdb-1.1.0.dist-info/METADATA +765 -0
  74. weatherdb-1.1.0.dist-info/RECORD +77 -0
  75. weatherdb-1.1.0.dist-info/WHEEL +5 -0
  76. weatherdb-1.1.0.dist-info/entry_points.txt +2 -0
  77. weatherdb-1.1.0.dist-info/top_level.txt +3 -0
@@ -0,0 +1,30 @@
1
+ # libraries
2
+ import logging
3
+
4
+ from ..db.connections import db_engine
5
+ from .StationsBase import StationsBase
6
+ from .StationsP import StationsP
7
+
8
+ # set settings
9
+ # ############
10
+ __all__ = ["StationsBaseTET"]
11
+ log = logging.getLogger(__name__)
12
+
13
+ # class definition
14
+ ##################
15
+ class StationsBaseTET(StationsBase):
16
+ @db_engine.deco_update_privilege
17
+ def fillup(self, only_real=False, stids="all", **kwargs):
18
+ # create virtual stations if necessary
19
+ if not only_real:
20
+ meta = self.get_meta(
21
+ infos=["Station_id"], only_real=False)
22
+ meta_p = StationsP().get_meta(
23
+ infos=["Station_id"], only_real=False)
24
+ stids_missing = set(meta_p.index.values) - set(meta.index.values)
25
+ if stids != "all":
26
+ stids_missing = set(stids).intersection(stids_missing)
27
+ for stid in stids_missing:
28
+ self._StationClass(stid) # this creates the virtual station
29
+
30
+ super().fillup(only_real=only_real, stids=stids, **kwargs)
@@ -0,0 +1,17 @@
1
+ # libraries
2
+ import logging
3
+
4
+ from ..station import StationET
5
+ from .StationsBaseTET import StationsBaseTET
6
+
7
+ # set settings
8
+ # ############
9
+ __all__ = ["StationsET"]
10
+ log = logging.getLogger(__name__)
11
+
12
+ # class definition
13
+ ##################
14
+ class StationsET(StationsBaseTET):
15
+ """A class to work with and download potential Evapotranspiration (VPGB) data for several stations."""
16
+ _StationClass = StationET
17
+ _timeout_raw_imp = 120
@@ -0,0 +1,128 @@
1
+ # libraries
2
+ import logging
3
+
4
+ from ..db.connections import db_engine
5
+ from ..station import StationP
6
+ from .StationsBase import StationsBase
7
+
8
+ # set settings
9
+ # ############
10
+ __all__ = ["StationsP"]
11
+ log = logging.getLogger(__name__)
12
+
13
+ # class definition
14
+ ##################
15
+ class StationsP(StationsBase):
16
+ """A class to work with and download 10 minutes precipitation data for several stations."""
17
+ _StationClass = StationP
18
+ _timeout_raw_imp = 360
19
+
20
+ @db_engine.deco_update_privilege
21
+ def update_richter_class(self, stids="all", do_mp=True, **kwargs):
22
+ """Update the Richter exposition class.
23
+
24
+ Get the value from the raster, compare with the richter categories and save to the database.
25
+
26
+ Parameters
27
+ ----------
28
+ stids: string or list of int, optional
29
+ The Stations for which to compute.
30
+ Can either be "all", for all possible stations
31
+ or a list with the Station IDs.
32
+ The default is "all".
33
+ **kwargs : dict, optional
34
+ The keyword arguments to be handed to the station.StationP.update_richter_class and get_stations method.
35
+
36
+ Raises
37
+ ------
38
+ ValueError
39
+ If the given stids (Station_IDs) are not all valid.
40
+ """
41
+ self._run_method(
42
+ stations=self.get_stations(only_real=True, stids=stids, **kwargs),
43
+ method="update_richter_class",
44
+ name="update richter class for {para}".format(para=self._para.upper()),
45
+ kwds=kwargs,
46
+ do_mp=do_mp)
47
+
48
+ @db_engine.deco_update_privilege
49
+ def richter_correct(self, stids="all", **kwargs):
50
+ """Richter correct the filled data.
51
+
52
+ Parameters
53
+ ----------
54
+ stids: string or list of int, optional
55
+ The Stations for which to compute.
56
+ Can either be "all", for all possible stations
57
+ or a list with the Station IDs.
58
+ The default is "all".
59
+ **kwargs : dict, optional
60
+ The additional keyword arguments for the _run_method and get_stations method
61
+
62
+ Raises
63
+ ------
64
+ ValueError
65
+ If the given stids (Station_IDs) are not all valid.
66
+ """
67
+ self._run_method(
68
+ stations=self.get_stations(only_real=False, stids=stids, **kwargs),
69
+ method="richter_correct",
70
+ name="richter correction on {para}".format(para=self._para.upper()),
71
+ do_mp=False, **kwargs)
72
+
73
+ @db_engine.deco_update_privilege
74
+ def last_imp_corr(self, stids="all", do_mp=False, **kwargs):
75
+ """Richter correct the filled data for the last imported period.
76
+
77
+ Parameters
78
+ ----------
79
+ stids: string or list of int, optional
80
+ The Stations for which to compute.
81
+ Can either be "all", for all possible stations
82
+ or a list with the Station IDs.
83
+ The default is "all".
84
+ do_mp : bool, optional
85
+ Should the method be done in multiprocessing mode?
86
+ If False the methods will be called in threading mode.
87
+ Multiprocessing needs more memory and a bit more initiating time. Therefor it is only usefull for methods with a lot of computation effort in the python code.
88
+ If the most computation of a method is done in the postgresql database, then threading is enough to speed the process up.
89
+ The default is False.
90
+ **kwargs : dict, optional
91
+ The additional keyword arguments for the _run_method and get_stations method
92
+
93
+ Raises
94
+ ------
95
+ ValueError
96
+ If the given stids (Station_IDs) are not all valid.
97
+ """
98
+ stations = self.get_stations(only_real=True, stids=stids, **kwargs)
99
+ period = stations[0].get_last_imp_period(all=True)
100
+ log.info("The {para_long} Stations fillup of the last import is started for the period {min_tstp} - {max_tstp}".format(
101
+ para_long=self._para_long,
102
+ **period.get_sql_format_dict(format="%Y%m%d %H:%M")))
103
+ self._run_method(
104
+ stations=stations,
105
+ method="last_imp_corr",
106
+ kwds={"_last_imp_period": period},
107
+ name="richter correction on {para}".format(para=self._para.upper()),
108
+ do_mp=do_mp, **kwargs)
109
+
110
+ @db_engine.deco_update_privilege
111
+ def update(self, only_new=True, **kwargs):
112
+ """Make a complete update of the stations.
113
+
114
+ Does the update_raw, quality check, fillup and richter correction of the stations.
115
+
116
+ Parameters
117
+ ----------
118
+ only_new : bool, optional
119
+ Should a only new values be computed?
120
+ If False: The stations are updated for the whole possible period.
121
+ If True, the stations are only updated for new values.
122
+ The default is True.
123
+ """
124
+ super().update(only_new=only_new, **kwargs)
125
+ if only_new:
126
+ self.last_imp_richter_correct(**kwargs)
127
+ else:
128
+ self.richter_correct(**kwargs)
@@ -0,0 +1,24 @@
1
+ # libraries
2
+ import logging
3
+
4
+ from ..station import StationPD
5
+ from .StationsP import StationsP
6
+ from .StationsBase import StationsBase
7
+
8
+ # set settings
9
+ # ############
10
+ __all__ = ["StationsPD"]
11
+ log = logging.getLogger(__name__)
12
+
13
+ # class definition
14
+ ##################
15
+
16
+ class StationsPD(StationsBase):
17
+ """A class to work with and download daily precipitation data for several stations.
18
+
19
+ Those stations data are only downloaded to do some quality checks on the 10 minutes data.
20
+ Therefor there is no special quality check and richter correction done on this data.
21
+ If you want daily precipitation data, better use the 10 minutes station class (StationP) and aggregate to daily values.
22
+ """
23
+ _StationClass = StationPD
24
+ _timeout_raw_imp = 120
@@ -0,0 +1,21 @@
1
+ # libraries
2
+ import logging
3
+
4
+ from ..station import StationT
5
+ from .StationsBaseTET import StationsBaseTET
6
+
7
+ # set settings
8
+ # ############
9
+ __all__ = ["StationsT"]
10
+ log = logging.getLogger(__name__)
11
+
12
+ # class definition
13
+ ##################
14
+
15
+ class StationsT(StationsBaseTET):
16
+ """A class to work with and download temperature data for several stations."""
17
+ _StationClass = StationT
18
+ _timeout_raw_imp = 120
19
+
20
+ def get_quotient(self, **kwargs):
21
+ raise NotImplementedError("The quotient is not yet implemented for temperature.")
@@ -0,0 +1,11 @@
1
+ """
2
+ This module has grouping classes for all the stations of one parameter. E.G. StationsP (or StationsP) groups all the Precipitation Stations available.
3
+ Those classes can get used to do actions on all the stations.
4
+ """
5
+ from .StationsP import StationsP
6
+ from .StationsPD import StationsPD
7
+ from .StationsT import StationsT
8
+ from .StationsET import StationsET
9
+ from .GroupStations import GroupStations
10
+
11
+ __all__ = ["StationsP", "StationsPD", "StationsT", "StationsET", "GroupStations"]
@@ -0,0 +1,369 @@
1
+ import re
2
+ from pandas import Timestamp, NaT, Timedelta
3
+ import datetime
4
+
5
+ class TimestampPeriod(object):
6
+ """A class to save a Timespan with a minimal and maximal Timestamp.
7
+ """
8
+ _COMPARE = {
9
+ "inner": {
10
+ 0: max,
11
+ 1: min},
12
+ "outer": {
13
+ 0: min,
14
+ 1: max}}
15
+ _REGEX_HAS_TIME = re.compile(
16
+ r"((^\d{6}[ \-\.]+)"+ # 991231
17
+ r"|(^\d{8}[ \-\.]*)|" + # 19991231
18
+ r"(^\d{1,4}[ \-\.]\d{1,2}[ \-\.]\d{1,4}[ \-\.]+))" + # 1999-12-31
19
+ r"+(\d+)") # has additional numbers -> time
20
+
21
+ def __init__(self, start, end, tzinfo="UTC"):
22
+ """Initiate a TimestampPeriod.
23
+
24
+ Parameters
25
+ ----------
26
+ start : pd.Timestamp or similar
27
+ The start of the Period.
28
+ end : pd.Timestamp or similar
29
+ The end of the Period.
30
+ tzinfo : str or datetime.timezone object or None, optional
31
+ The timezone to set to the timestamps.
32
+ If the timestamps already have a timezone they will get converted.
33
+ If None, then the timezone is not changed or set.
34
+ The default is "UTC".
35
+ """
36
+ # check if input is a date or a timestamp
37
+ if ((isinstance(start, datetime.date) and isinstance(end, datetime.date)) or
38
+ (isinstance(start, str) and not self._REGEX_HAS_TIME.match(start) and
39
+ isinstance(end, str) and not self._REGEX_HAS_TIME.match(end))):
40
+ self.is_date = True
41
+ else:
42
+ self.is_date = False
43
+
44
+ # convert to correct timestamp format
45
+ period = list([start, end])
46
+ for i, tstp in enumerate(period):
47
+ if not isinstance(tstp, Timestamp):
48
+ period[i] = Timestamp(tstp)
49
+
50
+ # check timezone
51
+ self.tzinfo = tzinfo
52
+ if tzinfo is not None:
53
+ if period[i].tzinfo is None:
54
+ period[i] = period[i].tz_localize(tzinfo)
55
+ else:
56
+ period[i] = period[i].tz_convert(tzinfo)
57
+
58
+ self.start = period[0]
59
+ self.end = period[1]
60
+
61
+ @staticmethod
62
+ def _check_period(period):
63
+ if not isinstance(period, TimestampPeriod):
64
+ period = TimestampPeriod(*period)
65
+ return period
66
+
67
+ def union(self, other, how="inner"):
68
+ """Unite 2 TimestampPeriods to one.
69
+
70
+ Compares the Periods and computes a new one.
71
+
72
+ Parameters
73
+ ----------
74
+ other : TimestampPeriod
75
+ The other TimestampPeriod with whome to compare.
76
+ how : str, optional
77
+ How to compare the 2 TimestampPeriods.
78
+ Can be "inner" or "outer".
79
+ "inner": the maximal Timespan for both is computed.
80
+ "outer": The minimal Timespan for both is computed.
81
+ The default is "inner".
82
+
83
+ Returns
84
+ -------
85
+ TimestampPeriod
86
+ A new TimespanPeriod object uniting both TimestampPeriods.
87
+ """
88
+ other = self._check_period(other)
89
+
90
+ # check for daily period in elements
91
+ tdsother = [Timedelta(0), Timedelta(0)]
92
+ tdsself = [Timedelta(0), Timedelta(0)]
93
+ if self.is_date and not other.is_date and not other.is_empty():
94
+ tdsself[1] = Timedelta(
95
+ hours=other.end.hour,
96
+ minutes=other.end.minute,
97
+ seconds=other.end.second)
98
+ elif not self.is_date and other.is_date and not self.is_empty():
99
+ tdsother[1] = Timedelta(
100
+ hours=self.end.hour,
101
+ minutes=self.end.minute,
102
+ seconds=self.end.second)
103
+
104
+ # check if empty and inner
105
+ if how=="inner" and (self.is_empty() or other.is_empty()):
106
+ return TimestampPeriod(None, None)
107
+
108
+ # get the united period
109
+ period = [None, None]
110
+ for i in range(2):
111
+ comp_list = [val + td
112
+ for val, td in zip([self[i], other[i]],
113
+ [tdsself[i], tdsother[i]])
114
+ if isinstance(val, Timestamp)]
115
+ if len(comp_list) > 0:
116
+ period[i] = self._COMPARE[how][i](comp_list)
117
+
118
+ # check if end < start
119
+ if period[0]>=period[1]:
120
+ period = (None, None)
121
+
122
+ # return the period
123
+ if self.is_date and other.is_date and all(period):
124
+ # if both were data periods, then the result should also be a date period
125
+ return TimestampPeriod(
126
+ *[val.date() for val in period
127
+ if isinstance(val, Timestamp)])
128
+ else:
129
+ return TimestampPeriod(*period)
130
+
131
+ def get_period(self):
132
+ return (self.start, self.end)
133
+
134
+ def __getitem__(self, key):
135
+ if key == 0 or key == "start":
136
+ return self.start
137
+ elif key == 1 or key == "end":
138
+ return self.end
139
+
140
+ def __setitem__(self, key, value):
141
+ if value != Timestamp:
142
+ value = Timestamp(value)
143
+
144
+ if key == 0 or key == "start":
145
+ self.start = value
146
+ elif key == 1 or key == "end":
147
+ self.end = value
148
+
149
+ def __iter__(self):
150
+ return self.get_period().__iter__()
151
+
152
+ def __str__(self):
153
+ msg = "TimestampPeriod: {0} - {1}"
154
+ if self.is_date:
155
+ return msg.format(*self.strftime(format="%Y-%m-%d"))
156
+ else:
157
+ return msg.format(*self.strftime(format="%Y-%m-%d %H:%M:%S"))
158
+
159
+ def __repr__(self):
160
+ return self.__str__()
161
+
162
+ def __eq__(self, other):
163
+ other = self._check_period(other)
164
+
165
+ if self.start == other.start and self.end == other.end:
166
+ return True
167
+ else:
168
+ return False
169
+
170
+ def __ne__(self, other):
171
+ return not self.__eq__(other)
172
+
173
+ def __lt__(self, other):
174
+ return self.inside(other)
175
+
176
+ def __le__(self, other):
177
+ return self.inside(other)
178
+
179
+ def __gt__(self, other):
180
+ return self.contains(other)
181
+
182
+ def __ge__(self, other):
183
+ return self.contains(other)
184
+
185
+ def has_NaT(self):
186
+ """Has the TimestampPeriod at least one NaT.
187
+
188
+ This means that the start or end is not given.
189
+ Normally this should never happen, because it makes no sense.
190
+
191
+ Returns
192
+ -------
193
+ bool
194
+ True if the TimestampPeriod has at least on NaT.
195
+ False if the TimestampPeriod has at least a start or a end.
196
+ """
197
+ return any([tstp is NaT for tstp in self])
198
+
199
+ def has_only_NaT(self):
200
+ """Has the TimestampPeriod only NaT, meaning is empty.
201
+
202
+ This means that the start and end is not given.
203
+
204
+ Returns
205
+ -------
206
+ bool
207
+ True if the TimestampPeriod is empty.
208
+ False if the TimestampPeriod has a start and an end.
209
+ """
210
+ return all([tstp is NaT for tstp in self])
211
+
212
+ def is_empty(self):
213
+ """Is the TimestampPeriod empty.
214
+
215
+ This means that the start and end is not given.
216
+
217
+ Returns
218
+ -------
219
+ bool
220
+ True if the TimestampPeriod is empty.
221
+ False if the TimestampPeriod has a start and an end.
222
+ """
223
+ return self.has_only_NaT()
224
+
225
+ def strftime(self, format="%Y-%m-%d %H:%M:%S"):
226
+ """Convert the TimestampPeriod to a list of strings.
227
+
228
+ Formates the Timestamp as a string.
229
+
230
+ Parameters
231
+ ----------
232
+ format : str, optional
233
+ The Timestamp-format to use.
234
+ The Default is "%Y-%m-%d %H:%M:%S"
235
+
236
+ Returns
237
+ -------
238
+ list of 2 strings
239
+ A list of the start and end of the TimestampPeriod as formated string.
240
+ """
241
+ out = [tstp.strftime(format) if tstp is not NaT else None
242
+ for tstp in self.get_period()]
243
+ return out
244
+
245
+ def inside(self, other):
246
+ """Is the TimestampPeriod inside another TimestampPeriod?
247
+
248
+ Parameters
249
+ ----------
250
+ other : Timestampperiod or tuple of 2 Timestamp or Timestamp strings
251
+ The other Timestamp to test against.
252
+ Test if this TimestampPeriod is inside the other.
253
+
254
+ Returns
255
+ -------
256
+ bool
257
+ True if this TimestampPeriod is inside the other.
258
+ Meaning that the start is higher or equal than the others starts
259
+ and the end is smaller than the others end.
260
+ """
261
+ other = self._check_period(other)
262
+ if self.start >= other.start and self.end <= other.end:
263
+ return True
264
+ else:
265
+ return False
266
+
267
+ def contains(self, other):
268
+ """Does this TimestampPeriod contain another TimestampPeriod?
269
+
270
+ Parameters
271
+ ----------
272
+ other : Timestampperiod or tuple of 2 Timestamp or Timestamp strings
273
+ The other Timestamp to test against.
274
+ Test if this TimestampPeriod contains the other.
275
+
276
+ Returns
277
+ -------
278
+ bool
279
+ True if this TimestampPeriod contains the other.
280
+ Meaning that the start is smaller or equal than the others starts
281
+ and the end is higher than the others end.
282
+ """
283
+ other = self._check_period(other)
284
+ return other.inside(self)
285
+
286
+ def get_sql_format_dict(self, format="'%Y%m%d %H:%M'"):
287
+ """Get the dictionary to use in sql queries.
288
+
289
+ Parameters
290
+ ----------
291
+ format : str, optional
292
+ The Timestamp-format to use.
293
+ The Default is "'%Y%m%d %H:%M'"
294
+
295
+ Returns
296
+ -------
297
+ dict
298
+ a dictionary with 2 keys (min_tstp, max_tstp) and the corresponding Timestamp as formated string.
299
+ """
300
+ period_str = self.strftime(format=format)
301
+ period_str = [str(el).replace("None", "NULL") for el in period_str]
302
+ return dict(min_tstp=period_str[0], max_tstp=period_str[1])
303
+
304
+ def get_interval(self):
305
+ """Get the interval of the TimestampPeriod.
306
+
307
+ Returns
308
+ -------
309
+ pd.Timedelta
310
+ The interval of this TimestampPeriod.
311
+ E.G. Timedelta(2 days 12:30:12)
312
+ """
313
+ return self.end - self.start
314
+
315
+ def get_middle(self):
316
+ """Get the middle Timestamp of the TimestampPeriod.
317
+
318
+ Returns
319
+ -------
320
+ Timestamp
321
+ The middle Timestamp of this TimestampPeriod.
322
+ """
323
+ middle = self.start + self.get_interval() / 2
324
+ if self.is_date:
325
+ middle = Timestamp(middle.date())
326
+ if self.tzinfo is not None:
327
+ if middle.tzinfo is None:
328
+ middle = middle.tz_localize(self.tzinfo)
329
+ return middle
330
+
331
+ def copy(self):
332
+ """Copy this TimestampPeriod.
333
+
334
+ Returns
335
+ -------
336
+ TimestampPeriod
337
+ a new TimestampPeriod object that is equal to this one.
338
+ """
339
+ new = TimestampPeriod(self.start, self.end)
340
+ new.is_date = self.is_date
341
+ return new
342
+
343
+ def expand_to_timestamp(self):
344
+ if self.is_date:
345
+ return TimestampPeriod(
346
+ start=self.start,
347
+ end=self.end + Timedelta(
348
+ hours=23, minutes=59, seconds=59, milliseconds=999))
349
+ else:
350
+ return self
351
+
352
+ def set_tz(self, tzinfo):
353
+ """Set the TimestampPeriod to a new timezone.
354
+
355
+ Parameters
356
+ ----------
357
+ tzinfo : str or datetime.timezone object
358
+ The timezone to set the TimestampPeriod to.
359
+
360
+ Returns
361
+ -------
362
+ TimestampPeriod
363
+ This TimestampPeriod.
364
+ """
365
+ if not self.is_date:
366
+ self.start.replace(tzinfo=tzinfo)
367
+ self.end.replace(tzinfo=tzinfo)
368
+ return self
369
+
@@ -0,0 +1,3 @@
1
+ from .TimestampPeriod import TimestampPeriod
2
+ from . import dwd
3
+ from . import geometry