# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
##
## Copyright (C) 2005-2013 Async Open Source
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU Lesser General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program 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 Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., or visit: http://www.gnu.org/.
##
##
## Author(s): Stoq Team <stoq-devel@async.com.br>
##
"""Utilities for working with dates"""
import collections
import datetime
from dateutil.relativedelta import relativedelta
from dateutil.rrule import rrule, DAILY, WEEKLY, MONTHLY, YEARLY
from stoqlib.lib.translation import stoqlib_gettext
_ = stoqlib_gettext
(INTERVALTYPE_DAY,
INTERVALTYPE_WEEK,
INTERVALTYPE_MONTH,
INTERVALTYPE_YEAR,
INTERVALTYPE_BIWEEK,
INTERVALTYPE_QUARTER,
INTERVALTYPE_YEAR) = range(7)
_Interval = collections.namedtuple(
'Interval', 'multiple singular plural adverb constant')
_interval_types = [
_Interval(1, _('day'), _('days'), _('Daily'), INTERVALTYPE_DAY),
_Interval(1, _('week'), _('weeks'), _('Weekly'), INTERVALTYPE_WEEK),
_Interval(2, _('week'), _('weeks'), _('Biweekly'), INTERVALTYPE_BIWEEK),
_Interval(1, _('month'), _('months'), _('Monthly'), INTERVALTYPE_MONTH),
_Interval(4, _('month'), _('months'), _('Quarterly'), INTERVALTYPE_QUARTER),
_Interval(1, _('year'), _('years'), _('Yearly'), INTERVALTYPE_YEAR),
]
[docs]def localnow():
"""Get the current date according to the local timezone.
This is relative to the clock on the computer where Stoq is run.
:rtype: datetime.datetime object
:returns: right now according to the current locale
"""
# FIXME: When we can use TIMEZONE WITH TIMESTAMP in PostgreSQL
# this should set the timezone.
return datetime.datetime.now()
[docs]def localtoday():
"""Get the beginning of the current date according to the local timezone.
This is relative to the clock on the computer where Stoq is run.
:rtype: datetime.datetime object
:returns: today according to the current locale
"""
return localnow().replace(hour=0,
minute=0,
second=0,
microsecond=0)
[docs]def localdate(year, month, day):
"""Get a date according to the local timezone.
This will return a date at midnight for the current locale.
This is relative to the clock on the computer where Stoq is run.
:param int year: the year in four digits
:param int month: the month (1-12)
:param int day: the day (1-31)
:rtype: datetime.datetime object
:returns: a date according to the current locale
"""
return localdatetime(year, month, day)
[docs]def localdatetime(year, month, day, hour=0, minute=0, second=0,
microsecond=0):
"""Get a datetime according to the local timezone.
This will return a date at midnight for the current locale.
This is relative to the clock on the computer where Stoq is run.
:param int year: the year in four digits
:param int month: the month (1-12)
:param int day: the day (1-31)
:param int hour: the hour (0-23)
:param int minute: the minute (0-59)
:param int second: the second (0-59)
:param int microsecond: the microsecond (1-99999)
:rtype: datetime.datetime object
:returns: a date according to the current locale
"""
# FIXME: When we can use TIMEZONE WITH TIMESTAMP in PostgreSQL
# this should set the timezone.
return datetime.datetime(year=year, day=day, month=month,
hour=hour, minute=minute, second=second,
microsecond=microsecond)
[docs]def get_interval_type_items(with_multiples=False,
plural=False,
adverb=False):
"""Get a list of items suitable for putting into a combo.
You can get three variants, singular, plural or adverb depending
on how you want to display it. You can skip multiples if you plan
to use them in conjuction with a spinbutton
:param with_multiples: if multiples such as biweekly and quarterly should
be included
:param plural: if the plural variant should be used
:param adverb: if the adverbial form should be used
:returns: a list of tuples (labels, interval_type)
"""
labels = []
for interval in _interval_types:
if not with_multiples and interval.multiple > 1:
continue
if adverb:
label = interval.adverb
elif plural:
label = interval.plural
else:
label = interval.singular
labels.append((label, interval.constant))
return labels
[docs]def get_month_names():
return [_('January'),
_('February'),
_('March'),
_('April'),
_('May'),
_('June'),
_('July'),
_('August'),
_('September'),
_('October'),
_('November'),
_('December')]
[docs]def get_short_month_names():
return [_('Jan'),
_('Feb'),
_('Mar'),
_('Apr'),
_('May'),
_('Jun'),
_('Jul'),
_('Aug'),
_('Sep'),
_('Oct'),
_('Nov'),
_('Dec')]
[docs]def get_day_names():
return [_('Sunday'),
_('Monday'),
_('Tuesday'),
_('Wednesday'),
_('Thursday'),
_('Friday'),
_('Saturday')]
[docs]def get_short_day_names():
return [_('Sun'),
_('Mon'),
_('Tue'),
_('Wed'),
_('Thu'),
_('Fri'),
_('Sat')]
[docs]def create_date_interval(interval_type,
start_date=None,
end_date=None,
interval=None,
count=None):
"""Generate a bunch of dates given a set of parameters.
There are two ways of using this, either as:
* interval_type, interval, count, start_date
or:
* interval_type, start_date, end_date
:param interval_type: one of the INTERVALTYPE_* above
:param intervals: interval between the dates
:param count: number of items to create
:param start_date: start :class:`date <datetime.date>`
:param end_date: end :class:`date <datetime.date>`
:returns: a :class:`datetime.rrule.rrule` object
"""
if ((not interval and
not count and
not start_date) and
(not start_date and
not end_date)):
raise TypeError("Needs interval/count/end date or start/end date")
if interval and (not start_date and not count):
raise TypeError("interval needs start_date/count")
if interval and end_date:
raise TypeError("Can't specify both interval and end date")
bymonthday = None
bysetpos = None
if interval_type == INTERVALTYPE_DAY:
freq = DAILY
_interval = 1
elif interval_type == INTERVALTYPE_WEEK:
freq = WEEKLY
_interval = 1
elif interval_type == INTERVALTYPE_BIWEEK:
freq = WEEKLY
_interval = 2
elif interval_type == INTERVALTYPE_MONTH:
# This really means: last day of month, it'll never
# cross month boundaries even if there are less than
# 31 days.
bysetpos = 1
bymonthday = (start_date.day, -1)
freq = MONTHLY
_interval = 1
elif interval_type == INTERVALTYPE_QUARTER:
freq = MONTHLY
_interval = 3
elif interval_type == INTERVALTYPE_YEAR:
freq = YEARLY
_interval = 1
else:
raise AssertionError(interval_type)
if not interval:
interval = _interval
return rrule(dtstart=start_date,
until=end_date,
freq=freq,
interval=interval,
count=count,
bymonthday=bymonthday,
bysetpos=bysetpos)
[docs]def interval_type_as_relativedelta(interval_type):
"""
Gets a interval_type as a relativedelta
:returns: a relativedelta
"""
if interval_type == INTERVALTYPE_DAY:
return relativedelta(days=1)
elif interval_type == INTERVALTYPE_WEEK:
return relativedelta(weeks=1)
elif interval_type == INTERVALTYPE_BIWEEK:
return relativedelta(weeks=2)
elif interval_type == INTERVALTYPE_MONTH:
return relativedelta(months=1)
elif interval_type == INTERVALTYPE_QUARTER:
return relativedelta(months=3)
elif interval_type == INTERVALTYPE_YEAR:
return relativedelta(years=1)
else:
raise AssertionError(interval_type)
[docs]def get_month_intervals_for_year(year):
"""Returns a list of tuples with first and last day of a month"""
months = iter(
rrule(MONTHLY,
count=24, # 2 per year, 12 months
bymonthday=(1, -1),
dtstart=datetime.datetime(year, 1, 1)))
while True:
try:
yield months.next(), months.next()
except StopIteration:
break
def _df(seconds, denominator, past, text_future, text_past):
if past:
return text_past % ((seconds + denominator / 2) / denominator, )
else:
return text_future % ((seconds + denominator / 2) / denominator, )
# pretty_date() is:
# __author__ = "S Anand (sanand@s-anand.net)"
# __copyright__ = "Copyright 2010, S Anand"
# __license__ = "WTFPL"
[docs]def pretty_date(time=False, asdays=False):
'''Returns a pretty formatted date.
Inputs:
time is a datetime object or an int timestamp
asdays is True if you only want to measure days, not seconds
'''
now = datetime.datetime.now()
if type(time) is int:
time = datetime.datetime.fromtimestamp(time)
elif not time:
time = now
if time > now:
past = False
diff = time - now
else:
past = True
diff = now - time
seconds = diff.seconds
days = diff.days
if days == 0 and not asdays:
if seconds < 10:
return _('now')
elif seconds < 60:
return _df(seconds, 1, past,
_('in %d seconds'),
_('%d seconds ago'))
elif seconds < 120:
return _('a minute ago') if past else _('in a minute')
elif seconds < 3600:
return _df(seconds, 60, past,
_('in %d minutes'),
_('%d minutes ago'))
elif seconds < 7200:
return _('an hour ago') if past else _('in an hour')
else:
return _df(seconds, 3600, past,
_('in %d hours'),
_('%d hours ago'))
else:
if days == 0:
return _('today')
elif days == 1:
return _('yesterday') if past else _('tomorrow')
elif days == 2:
return _('day before') if past else _('day after')
elif days < 7:
return _df(days, 1, past,
_('in %d days'),
_('%d days ago'))
elif days < 14:
return _('last week') if past else _('next week')
elif days < 31:
return _df(days, 7, past,
_('in %d weeks'),
_('%d weeks ago'))
elif days < 61:
return _('last month') if past else _('next month')
elif days < 365:
return _df(days, 30, past,
_('in %d months'),
_('%d months ago'))
elif days < 730:
return _('last year') if past else _('next year')
else:
return _df(days, 365, past,
_('in %d years'),
_('%d years ago'))