weatherdb 1.1.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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