Source code for rattail.time

# -*- coding: utf-8; -*-
################################################################################
#
#  Rattail -- Retail Software Framework
#  Copyright © 2010-2023 Lance Edgar
#
#  This file is part of Rattail.
#
#  Rattail is free software: you can redistribute it and/or modify it under the
#  terms of the GNU General Public License as published by the Free Software
#  Foundation, either version 3 of the License, or (at your option) any later
#  version.
#
#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
#  details.
#
#  You should have received a copy of the GNU General Public License along with
#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Time Utilities
"""

import calendar
import datetime
import warnings

import pytz


[docs] def localtime(config, time=None, key='default', from_utc=False, tzinfo=True): """ Return a datetime which has been localized to a particular timezone. The :func:`timezone()` function will be used to obtain the timezone to which the time value will be localized. :param config: Reference to a config object. :param time: Optional :class:`python:datetime.datetime` instance to be localized. If not provided, the current time ("now") is assumed. :param tzinfo: Boolean indicating whether the result should contain ``tzinfo`` or not, i.e. whether it should be time zone "aware" (``True``) or "naive" (``False``). :param key: Config key to be used in determining to which timezone the time should be localized. """ zone = timezone(config, key) if time is None: time = datetime.datetime.utcnow() time = pytz.utc.localize(time) time = zone.normalize(time.astimezone(zone)) elif time.tzinfo: time = zone.normalize(time.astimezone(zone)) elif from_utc: time = pytz.utc.localize(time) time = zone.normalize(time.astimezone(zone)) else: time = zone.localize(time) if not tzinfo: time = time.replace(tzinfo=None) return time
[docs] def timezone(config, key='default'): """ Return a timezone object based on the definition found in config. :param config: Reference to a config object. :param key: Config key used to determine which timezone should be returned. :returns: A ``pytz.tzinfo`` instance, created using the Olson time zone name found in the config file. An example of the configuration syntax which is assumed by this function: .. code-block:: ini [rattail] # retrieve with: timezone(config) timezone.default = America/Los_Angeles # retrieve with: timezone(config, 'headoffice') timezone.headoffice = America/Chicago # retrieve with: timezone(config, 'satellite') timezone.satellite = America/New_York See `Wikipedia`_ for the full list of Olson time zone names. .. _`Wikipedia`: http://en.wikipedia.org/wiki/List_of_tz_database_time_zones """ # Don't *require* the correct config option just yet, so we can fall back # to 'local' for default if necessary. zone = config.get('rattail', 'timezone.{}'.format(key), usedb=False) if zone is None and key == 'default': zone = config.get('rattail', 'timezone.local', usedb=False) if zone is None: # Okay, now let's require the correct one. zone = config.require('rattail', 'timezone.{0}'.format(key)) return pytz.timezone(zone)
[docs] def make_utc(time=None, tzinfo=False): """ Convert a timezone-aware time back to a naive UTC equivalent. If no time is specified, the current time is assumed. """ if time is None: time = datetime.datetime.utcnow() if time.tzinfo: utctime = pytz.utc.normalize(time.astimezone(pytz.utc)) else: utctime = pytz.utc.localize(time) if tzinfo: return utctime return utctime.replace(tzinfo=None)
[docs] def get_sunday(date): """ Return a ``datetime.date`` instance corresponding to Sunday of the given week, according to the ``date`` parameter. """ weekday = date.weekday() if weekday == 6: # Sunday return date return date - datetime.timedelta(days=weekday + 1)
[docs] def get_monday(date): """ Return a ``datetime.date`` instance corresponding to Monday of the given week, according to the ``date`` parameter. Note that this assumes the week *begins* on Monday, so if a Sunday is passed then the previous Monday will be returned. """ weekday = date.weekday() return date - datetime.timedelta(days=weekday)
[docs] def next_month(date): """ Returns a date object for the first day of "next" month, where the "current" month is determined by ``date`` param. """ current = last_of_month(date) return current + datetime.timedelta(days=1)
[docs] def previous_month(date, months=1): """ Returns the first day of the month which is a given number of ``months`` previous to the "current" month, as determined by ``date``. """ current = first_of_month(date) while months >= current.month: months -= current.month current = current.replace(year=current.year - 1, month=12) return current.replace(month=current.month - months)
[docs] def months_ago(date, months): """ Returns the "equivalent" day from a previous month. This refers to the day number within the month, so e.g. if ``date`` is 2021-02-13 and ``months`` is 3 then it should return 2020-11-13. Note that this is not always strictly possible, for instance if ``date`` is 2021-03-31 and ``months`` is 1 then it "should" return 2021-02-31 which is of course not valid. So in this case it will find the "greatest" day number which is valid, and return that, e.g. 2021-02-28. """ month = previous_month(date, months) day = date.day while True: try: return month.replace(day=day) except ValueError: day -= 1
[docs] def first_of_month(date): """ Returns a date representing the first day of whichever month ``date`` falls in. """ return date.replace(day=1)
[docs] def last_of_month(date): """ Returns a date representing the last day of whichever month ``date`` falls in. """ last_day = calendar.monthrange(date.year, date.month)[1] return date.replace(day=last_day)
[docs] def first_of_year(date): """ Returns a date representing the first day of whichever year ``date`` falls in. """ return date.replace(month=1, day=1)
[docs] def date_range(start, end, step=1): """ Generator which yields all dates between ``start`` and ``end``, *inclusive*. """ date = start while date <= end: yield date date += datetime.timedelta(days=step)