123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870 |
- """
- .. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
- """
- import datetime
- import re
- from typing import List, Optional, Union
- import dateutil.parser
- import dateutil.relativedelta as rdelta
- import typepy
- from .__version__ import __author__, __copyright__, __email__, __license__, __version__
- class DateTimeRange:
- """
- A class that represents a range of datetime.
- :param datetime.datetime/str start_datetime: |param_start_datetime|
- :param datetime.datetime/str end_datetime: |param_end_datetime|
- :Examples:
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- :Output:
- .. parsed-literal::
- 2015-03-22T10:00:00+0900 - 2015-03-22T10:10:00+0900
- .. py:attribute:: start_time_format
- :type: str
- :value: "%Y-%m-%dT%H:%M:%S%z"
- Conversion format string for :py:attr:`.start_datetime`.
- .. seealso:: :py:meth:`.get_start_time_str`
- .. py:attribute:: end_time_format
- :type: str
- :value: "%Y-%m-%dT%H:%M:%S%z"
- Conversion format string for :py:attr:`.end_datetime`.
- .. seealso:: :py:meth:`.get_end_time_str`
- """
- NOT_A_TIME_STR = "NaT"
- def __init__(
- self,
- start_datetime=None,
- end_datetime=None,
- start_time_format="%Y-%m-%dT%H:%M:%S%z",
- end_time_format="%Y-%m-%dT%H:%M:%S%z",
- ):
- self.set_time_range(start_datetime, end_datetime)
- self.start_time_format = start_time_format
- self.end_time_format = end_time_format
- self.is_output_elapse = False
- self.separator = " - "
- def __repr__(self):
- if self.is_output_elapse:
- suffix = f" ({self.end_datetime - self.start_datetime})"
- else:
- suffix = ""
- return self.separator.join((self.get_start_time_str(), self.get_end_time_str())) + suffix
- def __eq__(self, other):
- if not isinstance(other, DateTimeRange):
- return False
- return all(
- [self.start_datetime == other.start_datetime, self.end_datetime == other.end_datetime]
- )
- def __ne__(self, other):
- if not isinstance(other, DateTimeRange):
- return True
- return any(
- [self.start_datetime != other.start_datetime, self.end_datetime != other.end_datetime]
- )
- def __add__(self, other):
- return DateTimeRange(self.start_datetime + other, self.end_datetime + other)
- def __iadd__(self, other):
- self.set_start_datetime(self.start_datetime + other)
- self.set_end_datetime(self.end_datetime + other)
- return self
- def __sub__(self, other):
- return DateTimeRange(self.start_datetime - other, self.end_datetime - other)
- def __isub__(self, other):
- self.set_start_datetime(self.start_datetime - other)
- self.set_end_datetime(self.end_datetime - other)
- return self
- def __contains__(self, x):
- """
- :param x:
- |datetime|/``DateTimeRange`` instance to compare.
- Parse and convert to |datetime| if the value type is |str|.
- :type x: |datetime|/``DateTimeRange``/|str|
- :return: |True| if the ``x`` is within the time range
- :rtype: bool
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- print("2015-03-22T10:05:00+0900" in time_range)
- print("2015-03-22T10:15:00+0900" in time_range)
- time_range_smaller = DateTimeRange("2015-03-22T10:03:00+0900", "2015-03-22T10:07:00+0900")
- print(time_range_smaller in time_range)
- :Output:
- .. parsed-literal::
- True
- False
- True
- .. seealso::
- :py:meth:`.validate_time_inversion`
- """
- self.validate_time_inversion()
- if isinstance(x, DateTimeRange):
- return x.start_datetime >= self.start_datetime and x.end_datetime <= self.end_datetime
- try:
- value = dateutil.parser.parse(x)
- except (TypeError, AttributeError):
- value = x
- return self.start_datetime <= value <= self.end_datetime
- @property
- def start_datetime(self):
- """
- :return: Start time of the time range.
- :rtype: datetime.datetime
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- time_range.start_datetime
- :Output:
- .. parsed-literal::
- datetime.datetime(2015, 3, 22, 10, 0, tzinfo=tzoffset(None, 32400))
- """
- return self.__start_datetime
- @property
- def end_datetime(self):
- """
- :return: End time of the time range.
- :rtype: datetime.datetime
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- time_range.end_datetime
- :Output:
- .. parsed-literal::
- datetime.datetime(2015, 3, 22, 10, 10, tzinfo=tzoffset(None, 32400))
- """
- return self.__end_datetime
- @property
- def timedelta(self):
- """
- :return:
- (|attr_end_datetime| - |attr_start_datetime|) as |timedelta|
- :rtype: datetime.timedelta
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- time_range.timedelta
- :Output:
- .. parsed-literal::
- datetime.timedelta(0, 600)
- """
- return self.end_datetime - self.start_datetime
- def is_set(self):
- """
- :return:
- |True| if both |attr_start_datetime| and
- |attr_end_datetime| were not |None|.
- :rtype: bool
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange()
- print(time_range.is_set())
- time_range.set_time_range("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- print(time_range.is_set())
- :Output:
- .. parsed-literal::
- False
- True
- """
- return all([self.start_datetime is not None, self.end_datetime is not None])
- def validate_time_inversion(self):
- """
- Check time inversion of the time range.
- :raises ValueError:
- If |attr_start_datetime| is
- bigger than |attr_end_datetime|.
- :raises TypeError:
- Any one of |attr_start_datetime| and |attr_end_datetime|,
- or both is inappropriate datetime value.
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange("2015-03-22T10:10:00+0900", "2015-03-22T10:00:00+0900")
- try:
- time_range.validate_time_inversion()
- except ValueError:
- print("time inversion")
- :Output:
- .. parsed-literal::
- time inversion
- """
- if not self.is_set():
- # for python2/3 compatibility
- raise TypeError
- if self.start_datetime > self.end_datetime:
- raise ValueError(
- "time inversion found: {:s} > {:s}".format(
- str(self.start_datetime), str(self.end_datetime)
- )
- )
- def is_valid_timerange(self):
- """
- :return:
- |True| if the time range is
- not null and not time inversion.
- :rtype: bool
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange()
- print(time_range.is_valid_timerange())
- time_range.set_time_range("2015-03-22T10:20:00+0900", "2015-03-22T10:10:00+0900")
- print(time_range.is_valid_timerange())
- time_range.set_time_range("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- print(time_range.is_valid_timerange())
- :Output:
- .. parsed-literal::
- False
- False
- True
- .. seealso::
- :py:meth:`.is_set`
- :py:meth:`.validate_time_inversion`
- """
- try:
- self.validate_time_inversion()
- except (TypeError, ValueError):
- return False
- return self.is_set()
- def is_intersection(self, x):
- """
- :param DateTimeRange x: Value to compare
- :return: |True| if intersect with ``x``
- :rtype: bool
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- x = DateTimeRange("2015-03-22T10:05:00+0900", "2015-03-22T10:15:00+0900")
- time_range.is_intersection(x)
- :Output:
- .. parsed-literal::
- True
- """
- return self.intersection(x).is_set()
- def get_start_time_str(self):
- """
- :return:
- |attr_start_datetime| as |str| formatted with
- |attr_start_time_format|.
- Return |NaT| if the invalid value or the invalid format.
- :rtype: str
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- print(time_range.get_start_time_str())
- time_range.start_time_format = "%Y/%m/%d %H:%M:%S"
- print(time_range.get_start_time_str())
- :Output:
- .. parsed-literal::
- 2015-03-22T10:00:00+0900
- 2015/03/22 10:00:00
- """
- try:
- return self.start_datetime.strftime(self.start_time_format)
- except AttributeError:
- return self.NOT_A_TIME_STR
- def get_end_time_str(self):
- """
- :return:
- |attr_end_datetime| as a |str| formatted with
- |attr_end_time_format|.
- Return |NaT| if invalid datetime or format.
- :rtype: str
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- print(time_range.get_end_time_str())
- time_range.end_time_format = "%Y/%m/%d %H:%M:%S"
- print(time_range.get_end_time_str())
- :Output:
- .. parsed-literal::
- 2015-03-22T10:10:00+0900
- 2015/03/22 10:10:00
- """
- try:
- return self.end_datetime.strftime(self.end_time_format)
- except AttributeError:
- return self.NOT_A_TIME_STR
- def get_timedelta_second(self):
- """
- :return: (|attr_end_datetime| - |attr_start_datetime|) as seconds
- :rtype: float
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- time_range.get_timedelta_second()
- :Output:
- .. parsed-literal::
- 600.0
- """
- return self.timedelta.total_seconds()
- def set_start_datetime(self, value, timezone=None):
- """
- Set the start time of the time range.
- :param value: |param_start_datetime|
- :type value: |datetime|/|str|
- :raises ValueError: If the value is invalid as a |datetime| value.
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange()
- print(time_range)
- time_range.set_start_datetime("2015-03-22T10:00:00+0900")
- print(time_range)
- :Output:
- .. parsed-literal::
- NaT - NaT
- 2015-03-22T10:00:00+0900 - NaT
- """
- self.__start_datetime = self.__normalize_datetime_value(value, timezone)
- def set_end_datetime(self, value, timezone=None):
- """
- Set the end time of the time range.
- :param datetime.datetime/str value: |param_end_datetime|
- :raises ValueError: If the value is invalid as a |datetime| value.
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange()
- print(time_range)
- time_range.set_end_datetime("2015-03-22T10:10:00+0900")
- print(time_range)
- :Output:
- .. parsed-literal::
- NaT - NaT
- NaT - 2015-03-22T10:10:00+0900
- """
- self.__end_datetime = self.__normalize_datetime_value(value, timezone)
- def set_time_range(self, start, end):
- """
- :param datetime.datetime/str start: |param_start_datetime|
- :param datetime.datetime/str end: |param_end_datetime|
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange()
- print(time_range)
- time_range.set_time_range("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- print(time_range)
- :Output:
- .. parsed-literal::
- NaT - NaT
- 2015-03-22T10:00:00+0900 - 2015-03-22T10:10:00+0900
- """
- self.set_start_datetime(start)
- self.set_end_datetime(end)
- @staticmethod
- def __compare_relativedelta(lhs, rhs):
- if lhs.years < rhs.years:
- return -1
- if lhs.years > rhs.years:
- return 1
- if lhs.months < rhs.months:
- return -1
- if lhs.months > rhs.months:
- return 1
- if lhs.days < rhs.days:
- return -1
- if lhs.days > rhs.days:
- return 1
- if lhs.hours < rhs.hours:
- return -1
- if lhs.hours > rhs.hours:
- return 1
- if lhs.minutes < rhs.minutes:
- return -1
- if lhs.minutes > rhs.minutes:
- return 1
- if lhs.seconds < rhs.seconds:
- return -1
- if lhs.seconds > rhs.seconds:
- return 1
- if lhs.microseconds < rhs.microseconds:
- return -1
- if lhs.microseconds > rhs.microseconds:
- return 1
- return 0
- def __compare_timedelta(self, lhs, seconds):
- try:
- rhs = datetime.timedelta(seconds=seconds)
- if lhs < rhs:
- return -1
- if lhs > rhs:
- return 1
- return 0
- except TypeError:
- return self.__compare_relativedelta(
- lhs.normalized(), rdelta.relativedelta(seconds=seconds)
- )
- def range(self, step):
- """
- Return an iterator object.
- :param step: Step of iteration.
- :type step: |timedelta|/dateutil.relativedelta.relativedelta
- :return: iterator
- :rtype: iterator
- :Sample Code:
- .. code:: python
- import datetime
- from datetimerange import DateTimeRange
- time_range = DateTimeRange("2015-01-01T00:00:00+0900", "2015-01-04T00:00:00+0900")
- for value in time_range.range(datetime.timedelta(days=1)):
- print(value)
- :Output:
- .. parsed-literal::
- 2015-01-01 00:00:00+09:00
- 2015-01-02 00:00:00+09:00
- 2015-01-03 00:00:00+09:00
- 2015-01-04 00:00:00+09:00
- """
- if self.__compare_timedelta(step, 0) == 0:
- raise ValueError("step must be not zero")
- is_inversion = False
- try:
- self.validate_time_inversion()
- except ValueError:
- is_inversion = True
- if not is_inversion:
- if self.__compare_timedelta(step, seconds=0) < 0:
- raise ValueError(f"invalid step: expect greater than 0, actual={step}")
- else:
- if self.__compare_timedelta(step, seconds=0) > 0:
- raise ValueError(f"invalid step: expect less than 0, actual={step}")
- current_datetime = self.start_datetime
- while current_datetime <= self.end_datetime:
- yield current_datetime
- current_datetime = current_datetime + step
- def intersection(self, x):
- """
- Newly set a time range that overlaps
- the input and the current time range.
- :param DateTimeRange x:
- Value to compute intersection with the current time range.
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- dtr0 = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- dtr1 = DateTimeRange("2015-03-22T10:05:00+0900", "2015-03-22T10:15:00+0900")
- dtr0.intersection(dtr1)
- :Output:
- .. parsed-literal::
- 2015-03-22T10:05:00+0900 - 2015-03-22T10:10:00+0900
- """
- self.validate_time_inversion()
- x.validate_time_inversion()
- if any([x.start_datetime in self, self.start_datetime in x]):
- start_datetime = max(self.start_datetime, x.start_datetime)
- end_datetime = min(self.end_datetime, x.end_datetime)
- else:
- start_datetime = None
- end_datetime = None
- return DateTimeRange(
- start_datetime=start_datetime,
- end_datetime=end_datetime,
- start_time_format=self.start_time_format,
- end_time_format=self.end_time_format,
- )
- def subtract(self, x):
- """
- Remove a time range from this one and return the result.
- - The result will be ``[self.copy()]`` if the second range does not overlap the first
- - The result will be ``[]`` if the second range wholly encompasses the first range
- - The result will be ``[new_range]`` if the second range overlaps one end of the range
- - The result will be ``[new_range1, new_range2]`` if the second range is
- an internal sub range of the first
- :param DateTimeRange x:
- Range to remove from this one.
- :return: List(DateTimeRange)
- List of new ranges when the second range is removed from this one
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- dtr0 = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- dtr1 = DateTimeRange("2015-03-22T10:05:00+0900", "2015-03-22T10:15:00+0900")
- dtr0.subtract(dtr1)
- :Output:
- .. parsed-literal::
- [2015-03-22T10:00:00+0900 - 2015-03-22T10:05:00+0900]
- """
- overlap = self.intersection(x)
- # No intersection, return a copy of the original
- if not overlap.is_set() or overlap.get_timedelta_second() <= 0:
- return [
- DateTimeRange(
- start_datetime=self.start_datetime,
- end_datetime=self.end_datetime,
- start_time_format=self.start_time_format,
- end_time_format=self.end_time_format,
- )
- ]
- # Case 2, full overlap, subtraction results in empty set
- if (
- overlap.start_datetime == self.start_datetime
- and overlap.end_datetime == self.end_datetime
- ):
- return []
- # Case 3, overlap on start
- if overlap.start_datetime == self.start_datetime:
- return [
- DateTimeRange(
- start_datetime=overlap.end_datetime,
- end_datetime=self.end_datetime,
- start_time_format=self.start_time_format,
- end_time_format=self.end_time_format,
- )
- ]
- # Case 4, overlap on end
- if overlap.end_datetime == self.end_datetime:
- return [
- DateTimeRange(
- start_datetime=self.start_datetime,
- end_datetime=overlap.start_datetime,
- start_time_format=self.start_time_format,
- end_time_format=self.end_time_format,
- )
- ]
- # Case 5, underlap, two new ranges are needed.
- return [
- DateTimeRange(
- start_datetime=self.start_datetime,
- end_datetime=overlap.start_datetime,
- start_time_format=self.start_time_format,
- end_time_format=self.end_time_format,
- ),
- DateTimeRange(
- start_datetime=overlap.end_datetime,
- end_datetime=self.end_datetime,
- start_time_format=self.start_time_format,
- end_time_format=self.end_time_format,
- ),
- ]
- def encompass(self, x):
- """
- Newly set a time range that encompasses
- the input and the current time range.
- :param DateTimeRange x:
- Value to compute encompass with the current time range.
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- dtr0 = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- dtr1 = DateTimeRange("2015-03-22T10:05:00+0900", "2015-03-22T10:15:00+0900")
- dtr0.encompass(dtr1)
- :Output:
- .. parsed-literal::
- 2015-03-22T10:00:00+0900 - 2015-03-22T10:15:00+0900
- """
- self.validate_time_inversion()
- x.validate_time_inversion()
- return DateTimeRange(
- start_datetime=min(self.start_datetime, x.start_datetime),
- end_datetime=max(self.end_datetime, x.end_datetime),
- start_time_format=self.start_time_format,
- end_time_format=self.end_time_format,
- )
- def truncate(self, percentage):
- """
- Truncate ``percentage`` / 2 [%] of whole time from first and last time.
- :param float percentage: Percentage of truncate.
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- time_range = DateTimeRange(
- "2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- time_range.is_output_elapse = True
- print(time_range)
- time_range.truncate(10)
- print(time_range)
- :Output:
- .. parsed-literal::
- 2015-03-22T10:00:00+0900 - 2015-03-22T10:10:00+0900 (0:10:00)
- 2015-03-22T10:00:30+0900 - 2015-03-22T10:09:30+0900 (0:09:00)
- """
- self.validate_time_inversion()
- if percentage < 0:
- raise ValueError("discard_percent must be greater or equal to zero: " + str(percentage))
- if percentage == 0:
- return
- discard_time = self.timedelta // int(100) * int(percentage / 2)
- self.__start_datetime += discard_time
- self.__end_datetime -= discard_time
- def split(self, separator: Union[str, datetime.datetime]) -> List["DateTimeRange"]:
- """
- Split the DateTimerange in two DateTimerange at a specifit datetime.
- :param Union[str, datetime.datetime] separator:
- Date and time to split the DateTimeRange.
- This value will be included for both of the ranges after split.
- :Sample Code:
- .. code:: python
- from datetimerange import DateTimeRange
- dtr = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
- dtr.split("2015-03-22T10:05:00+0900")
- :Output:
- .. parsed-literal::
- [2015-03-22T10:00:00+0900 - 2015-03-22T10:05:00+0900,
- 2015-03-22T10:05:00+0900 - 2015-03-22T10:10:00+0900]
- """
- self.validate_time_inversion()
- separatingseparation = self.__normalize_datetime_value(separator, timezone=None)
- if (separatingseparation not in self) or (
- separatingseparation in (self.start_datetime, self.end_datetime)
- ):
- return [
- DateTimeRange(
- start_datetime=self.start_datetime,
- end_datetime=self.end_datetime,
- start_time_format=self.start_time_format,
- end_time_format=self.end_time_format,
- )
- ]
- return [
- DateTimeRange(
- start_datetime=self.start_datetime,
- end_datetime=separatingseparation,
- start_time_format=self.start_time_format,
- end_time_format=self.end_time_format,
- ),
- DateTimeRange(
- start_datetime=separatingseparation,
- end_datetime=self.end_datetime,
- start_time_format=self.start_time_format,
- end_time_format=self.end_time_format,
- ),
- ]
- def __normalize_datetime_value(self, value, timezone):
- if value is None:
- return None
- try:
- return typepy.type.DateTime(
- value, strict_level=typepy.StrictLevel.MIN, timezone=timezone
- ).convert()
- except typepy.TypeConversionError as e:
- raise ValueError(e)
- @classmethod
- def from_range_text(
- cls,
- range_text: str,
- separator: str = "-",
- start_time_format: Optional[str] = None,
- end_time_format: Optional[str] = None,
- ) -> "DateTimeRange":
- """Create a ``DateTimeRange`` instance from a datetime range text.
- :param str range_text:
- Input text that includes datetime range.
- e.g. ``2021-01-23T10:00:00+0400 - 2021-01-232T10:10:00+0400``
- :param str separator:
- Text that separating the ``range_text``.
- :return: DateTimeRange
- Created instance.
- """
- dattime_ranges = re.split(r"\s+{}\s+".format(re.escape(separator)), range_text.strip())
- if len(dattime_ranges) != 2:
- raise ValueError("range_text should include two datetime that separated by hyphen")
- start, end = dattime_ranges
- kwargs = {
- "start_datetime": start,
- "end_datetime": end,
- }
- if start_time_format:
- kwargs["start_time_format"] = start_time_format
- if end_time_format:
- kwargs["end_time_format"] = end_time_format
- return DateTimeRange(**kwargs)
|