Skip to content

Instantly share code, notes, and snippets.

@atr000
Forked from zacharyvoase/daterange.py
Created July 20, 2009 22:10
Show Gist options
  • Select an option

  • Save atr000/150952 to your computer and use it in GitHub Desktop.

Select an option

Save atr000/150952 to your computer and use it in GitHub Desktop.

Revisions

  1. zacharyvoase revised this gist Jun 21, 2009. 1 changed file with 69 additions and 38 deletions.
    107 changes: 69 additions & 38 deletions daterange.py
    Original file line number Diff line number Diff line change
    @@ -37,7 +37,7 @@
    ...
    StopIteration
    >>> g3 = daterange(start, step=datetime.timedelta(days=2))
    >>> g3 = daterange(start, step='2 days')
    >>> g3.next()
    datetime.date(2009, 6, 21)
    >>> g3.next()
    @@ -47,8 +47,7 @@
    >>> g3.next()
    datetime.date(2009, 6, 27)
    >>> g4 = daterange(start, to=datetime.date(2009, 6, 25),
    ... step=datetime.timedelta(days=2))
    >>> g4 = daterange(start, to=datetime.date(2009, 6, 25), step='2 days')
    >>> g4.next()
    datetime.date(2009, 6, 21)
    >>> g4.next()
    @@ -67,6 +66,7 @@


    def daterange(date, to=None, step=datetime.timedelta(days=1)):

    """
    Similar to the built-in ``xrange()``, only for datetime objects.
    @@ -78,27 +78,38 @@ def daterange(date, to=None, step=datetime.timedelta(days=1)):
    If the ``step`` keyword is provided, this will be used as the step size
    instead of the default of 1 day. It should be either an instance of
    ``datetime.timedelta``, an integer, or a string representing an integer.
    If it is either of the latter two, it will be interpreted as a number of
    days.
    ``datetime.timedelta``, an integer, a string representing an integer, or
    a string representing a ``delta()`` value (consult the documentation for
    ``delta()`` for more information). If it is an integer (or string thereof)
    then it will be interpreted as a number of days. If it is not a simple
    integer string, then it will be passed to ``delta()`` to get an instance
    of ``datetime.timedelta()``.
    Note that, due to the similar interfaces of both objects, this function
    will accept both ``datetime.datetime`` and ``datetime.date`` objects.
    will accept both ``datetime.datetime`` and ``datetime.date`` objects. If
    a date is given, then the values yielded will be dates themselves. A
    caveat is in order here: if you provide a date, the step should have at
    least a ‘days’ component; otherwise the same date will be yielded forever.
    """

    if to is None:
    condition = lambda d: True
    else:
    condition = lambda d: (d <= to)

    if (isinstance(step, (int, long)) or re.match(r'^([\d]+)$', str(step))):
    if isinstance(step, (int, long)):
    # By default, integers are interpreted in days. For more granular
    # steps, use a `datetime.timedelta()` instance.
    step = datetime.timedelta(days=int(step))
    step = datetime.timedelta(days=step)
    elif isinstance(step, basestring):
    try:
    step = delta(step)
    except ValueError:
    pass
    # If the string
    if re.match(r'^(\d+)$', str(step)):
    step = datetime.timedelta(days=int(step))
    else:
    try:
    step = delta(step)
    except ValueError:
    pass

    if not isinstance(step, datetime.timedelta):
    raise TypeError('Invalid step value: %r' % (step,))
    @@ -109,9 +120,10 @@ def daterange(date, to=None, step=datetime.timedelta(days=1)):
    date += step


    def delta(delta_string):
    class delta(object):

    """
    Build instances of ``datetime.timedelta`` using shorter spec strings.
    Build instances of ``datetime.timedelta`` using short, friendly strings.
    ``delta()`` allows you to build instances of ``datetime.timedelta`` in
    fewer characters and with more readability by using short strings instead
    @@ -139,31 +151,50 @@ def delta(delta_string):
    'microseconds')
    If an illegal specifier is present, the parser will raise a ValueError.
    This utility is provided as a class, but acts as a function (using the
    ``__new__`` method). This is so that the names and aliases for units are
    stored on the class object itself: as ``UNIT_NAMES``, which is a mapping
    of names to aliases, and ``UNIT_ALIASES``, the converse.
    """
    specifiers = (specifier.strip() for specifier in delta_string.split(','))
    kwargs = {}

    for specifier in specifiers:
    try:
    number, unit = re.match(r'^(\d+)\s*(\w+)$', specifier).groups()
    except AttributeError:
    raise ValueError('Invalid delta specifier: %r' % (specifier,))
    number, unit = int(number), unit.lower()

    UNIT_NAMES = {
    ## unit_name: unit_aliases
    'days': 'd day'.split(),
    'hours': 'h hr hrs hour'.split(),
    'minutes': 'm min mins minute'.split(),
    'seconds': 's sec secs second'.split(),
    'microseconds': 'ms microsec microsecs microsecond'.split(),
    }

    # Turn `UNIT_NAMES` inside-out, so that unit aliases point to canonical
    # unit names.
    UNIT_ALIASES = {}

    for cname, aliases in UNIT_NAMES.items():
    for alias in aliases:
    UNIT_ALIASES[alias] = cname
    # Make the canonical unit name point to itself.
    UNIT_ALIASES[cname] = cname

    def __new__(cls, string):
    specifiers = (specifier.strip() for specifier in string.split(','))
    kwargs = {}

    for specifier in specifiers:
    match = re.match(r'^(\d+)\s*(\w+)$', specifier)
    if not match:
    raise ValueError('Invalid delta specifier: %r' % (specifier,))

    number, unit_alias = match.groups()
    number, unit_alias = int(number), unit_alias.lower()

    unit_cname = cls.UNIT_ALIASES.get(unit_alias)
    if not unit_cname:
    raise ValueError('Invalid unit: %r' % (unit_alias,))
    kwargs[unit_cname] = kwargs.get(unit_cname, 0) + number

    # Throughout this we use `kwargs.get(unit, 0) + num` so that multiple
    # appearances of the same unit will be summed together.
    if unit in 'd day days'.split():
    kwargs['days'] = kwargs.get('days', 0) + number
    elif unit in 'h hr hrs hour hours'.split():
    kwargs['hours'] = kwargs.get('hours', 0) + number
    elif unit in 'm min mins minute minutes'.split():
    kwargs['minutes'] = kwargs.get('minutes', 0) + number
    elif unit in 's sec secs second seconds'.split():
    kwargs['seconds'] = kwargs.get('seconds', 0) + number
    elif unit in 'ms microsec microsecs microsecond microseconds'.split():
    kwargs['microseconds'] = kwargs.get('microseconds', 0) + number

    return datetime.timedelta(**kwargs)
    return datetime.timedelta(**kwargs)


    if __name__ == '__main__':
  2. zacharyvoase revised this gist Jun 21, 2009. 1 changed file with 64 additions and 2 deletions.
    66 changes: 64 additions & 2 deletions daterange.py
    Original file line number Diff line number Diff line change
    @@ -84,7 +84,7 @@ def daterange(date, to=None, step=datetime.timedelta(days=1)):
    Note that, due to the similar interfaces of both objects, this function
    will accept both ``datetime.datetime`` and ``datetime.date`` objects.
    """
    """
    if to is None:
    condition = lambda d: True
    else:
    @@ -94,16 +94,78 @@ def daterange(date, to=None, step=datetime.timedelta(days=1)):
    # By default, integers are interpreted in days. For more granular
    # steps, use a `datetime.timedelta()` instance.
    step = datetime.timedelta(days=int(step))
    elif isinstance(step, basestring):
    try:
    step = delta(step)
    except ValueError:
    pass

    if not isinstance(step, datetime.timedelta):
    raise TypeError('Invalid step value: %r' % (step_size,))
    raise TypeError('Invalid step value: %r' % (step,))

    # The main generation loop.
    while condition(date):
    yield date
    date += step


    def delta(delta_string):
    """
    Build instances of ``datetime.timedelta`` using shorter spec strings.
    ``delta()`` allows you to build instances of ``datetime.timedelta`` in
    fewer characters and with more readability by using short strings instead
    of a long sequence of keyword arguments.
    A typical (but very precise) spec string looks like this:
    '1 day, 4 hours, 5 minutes, 3 seconds, 120 microseconds'
    ``datetime.timedelta`` doesn’t allow deltas containing months or years,
    because of the differences between different months, leap years, etc., so
    this function doesn’t support them either.
    The parser is very simple; it takes a series of comma-separated values,
    each of which represents a number of units of time (such as one day,
    four hours, five minutes, et cetera). These ‘specifiers’ consist of a
    number and a unit of time, optionally separated by whitespace. The units
    of time accepted are (case-insensitive):
    * Days ('d', 'day', 'days')
    * Hours ('h', 'hr', 'hrs', 'hour', 'hours')
    * Minutes ('m', 'min', 'mins', 'minute', 'minutes')
    * Seconds ('s', 'sec', 'secs', 'second', 'seconds')
    * Microseconds ('ms', 'microsec', 'microsecs' 'microsecond',
    'microseconds')
    If an illegal specifier is present, the parser will raise a ValueError.
    """
    specifiers = (specifier.strip() for specifier in delta_string.split(','))
    kwargs = {}

    for specifier in specifiers:
    try:
    number, unit = re.match(r'^(\d+)\s*(\w+)$', specifier).groups()
    except AttributeError:
    raise ValueError('Invalid delta specifier: %r' % (specifier,))
    number, unit = int(number), unit.lower()

    # Throughout this we use `kwargs.get(unit, 0) + num` so that multiple
    # appearances of the same unit will be summed together.
    if unit in 'd day days'.split():
    kwargs['days'] = kwargs.get('days', 0) + number
    elif unit in 'h hr hrs hour hours'.split():
    kwargs['hours'] = kwargs.get('hours', 0) + number
    elif unit in 'm min mins minute minutes'.split():
    kwargs['minutes'] = kwargs.get('minutes', 0) + number
    elif unit in 's sec secs second seconds'.split():
    kwargs['seconds'] = kwargs.get('seconds', 0) + number
    elif unit in 'ms microsec microsecs microsecond microseconds'.split():
    kwargs['microseconds'] = kwargs.get('microseconds', 0) + number

    return datetime.timedelta(**kwargs)


    if __name__ == '__main__':
    import doctest
    doctest.testmod()
  3. zacharyvoase revised this gist Jun 21, 2009. 1 changed file with 91 additions and 32 deletions.
    123 changes: 91 additions & 32 deletions daterange.py
    Original file line number Diff line number Diff line change
    @@ -1,50 +1,109 @@
    # -*- coding: utf-8 -*-

    """
    Example Usage
    =============
    >>> import datetime
    >>> start = datetime.date(2009, 6, 21)
    >>> g1 = daterange(start)
    >>> g1.next()
    datetime.date(2009, 6, 21)
    >>> g1.next()
    datetime.date(2009, 6, 22)
    >>> g1.next()
    datetime.date(2009, 6, 23)
    >>> g1.next()
    datetime.date(2009, 6, 24)
    >>> g1.next()
    datetime.date(2009, 6, 25)
    >>> g1.next()
    datetime.date(2009, 6, 26)
    >>> g2 = daterange(start, to=datetime.date(2009, 6, 25))
    >>> g2.next()
    datetime.date(2009, 6, 21)
    >>> g2.next()
    datetime.date(2009, 6, 22)
    >>> g2.next()
    datetime.date(2009, 6, 23)
    >>> g2.next()
    datetime.date(2009, 6, 24)
    >>> g2.next()
    datetime.date(2009, 6, 25)
    >>> g2.next()
    Traceback (most recent call last):
    ...
    StopIteration
    >>> g3 = daterange(start, step=datetime.timedelta(days=2))
    >>> g3.next()
    datetime.date(2009, 6, 21)
    >>> g3.next()
    datetime.date(2009, 6, 23)
    >>> g3.next()
    datetime.date(2009, 6, 25)
    >>> g3.next()
    datetime.date(2009, 6, 27)
    >>> g4 = daterange(start, to=datetime.date(2009, 6, 25),
    ... step=datetime.timedelta(days=2))
    >>> g4.next()
    datetime.date(2009, 6, 21)
    >>> g4.next()
    datetime.date(2009, 6, 23)
    >>> g4.next()
    datetime.date(2009, 6, 25)
    >>> g4.next()
    Traceback (most recent call last):
    ...
    StopIteration
    """

    import datetime
    import re


    def daterange(*args):
    def daterange(date, to=None, step=datetime.timedelta(days=1)):
    """
    Similar to the built-in ``xrange()``, only for dates.
    The function can be called with between one and three arguments. If called
    with one, it will yield all dates *from* the one provided, ad infinitum.
    This is markedly different from the built-in ``xrange()``.
    Similar to the built-in ``xrange()``, only for datetime objects.
    If called with two dates, it will yield all dates from the first, up to
    and including the last.
    If called with just a ``datetime`` object, it will keep yielding values
    forever, starting with that date/time and counting in steps of 1 day.
    If called with three arguments, the third should be an instance of
    ``datetime.timedelta`` or an integer specifying the step size in days. It
    will yield all dates which lie between the first and second arguments
    inclusive, with the given step.
    """
    # Set up a default step value.
    step = datetime.timedelta(days=1)
    If the ``to_date`` keyword is provided, it will count up to and including
    that date/time (again, in steps of 1 day by default).
    if len(args) == 0:
    raise TypeError('daterange() expects at least 1 argument')
    If the ``step`` keyword is provided, this will be used as the step size
    instead of the default of 1 day. It should be either an instance of
    ``datetime.timedelta``, an integer, or a string representing an integer.
    If it is either of the latter two, it will be interpreted as a number of
    days.
    if len(args) >= 1:
    date = args[0]
    Note that, due to the similar interfaces of both objects, this function
    will accept both ``datetime.datetime`` and ``datetime.date`` objects.
    """
    if to is None:
    condition = lambda d: True

    if len(args) >= 2:
    up_to_date = args[1]
    condition = lambda d: (d <= up_to_date)
    else:
    condition = lambda d: (d <= to)

    if len(args) >= 3:
    step_size = args[2]
    if (isinstance(step_size, (int, long)) or
    re.match(r'([\d]+)', str(step_size))):
    step = datetime.timedelta(days=int(step_size))
    elif isinstance(step_size, datetime.timedelta):
    step = step_size
    else:
    raise TypeError('Invalid step value: %r' % (step_size,))
    if (isinstance(step, (int, long)) or re.match(r'^([\d]+)$', str(step))):
    # By default, integers are interpreted in days. For more granular
    # steps, use a `datetime.timedelta()` instance.
    step = datetime.timedelta(days=int(step))

    if not isinstance(step, datetime.timedelta):
    raise TypeError('Invalid step value: %r' % (step_size,))

    # The main generation loop.
    while condition(date):
    yield date
    date += step


    if __name__ == '__main__':
    import doctest
    doctest.testmod()
  4. zacharyvoase renamed this gist Jun 21, 2009. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  5. zacharyvoase created this gist Jun 21, 2009.
    50 changes: 50 additions & 0 deletions gistfile1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,50 @@
    # -*- coding: utf-8 -*-

    import datetime


    def daterange(*args):
    """
    Similar to the built-in ``xrange()``, only for dates.

    The function can be called with between one and three arguments. If called
    with one, it will yield all dates *from* the one provided, ad infinitum.
    This is markedly different from the built-in ``xrange()``.

    If called with two dates, it will yield all dates from the first, up to
    and including the last.

    If called with three arguments, the third should be an instance of
    ``datetime.timedelta`` or an integer specifying the step size in days. It
    will yield all dates which lie between the first and second arguments
    inclusive, with the given step.
    """
    # Set up a default step value.
    step = datetime.timedelta(days=1)

    if len(args) == 0:
    raise TypeError('daterange() expects at least 1 argument')

    if len(args) >= 1:
    date = args[0]
    condition = lambda d: True

    if len(args) >= 2:
    up_to_date = args[1]
    condition = lambda d: (d <= up_to_date)

    if len(args) >= 3:
    step_size = args[2]
    if (isinstance(step_size, (int, long)) or
    re.match(r'([\d]+)', str(step_size))):
    step = datetime.timedelta(days=int(step_size))
    elif isinstance(step_size, datetime.timedelta):
    step = step_size
    else:
    raise TypeError('Invalid step value: %r' % (step_size,))

    # The main generation loop.
    while condition(date):
    yield date
    date += step