Source code for pyams_utils.date

#
# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#

"""PyAMS_utils.date module

This module provides several functions concerning conversion, parsing and formatting of
dates and datetimes.
"""

from datetime import datetime

from zope.datetime import parseDatetimetz
from zope.dublincore.interfaces import IZopeDublinCore
from zope.interface import Interface

from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
from pyams_utils.interfaces.tales import ITALESExtension
from pyams_utils.request import check_request
from pyams_utils.timezone import gmtime, tztime


__docformat__ = 'restructuredtext'

from pyams_utils import _


[docs]def unidate(value): """Get specified date converted to unicode ISO format Dates are always assumed to be stored in GMT timezone :param date value: input date to convert to unicode :return: unicode; input date converted to unicode >>> from datetime import datetime >>> from pyams_utils.date import unidate >>> value = datetime(2016, 11, 15, 10, 13, 12) >>> unidate(value) '2016-11-15T10:13:12+00:00' """ if value is not None: value = gmtime(value) return value.isoformat('T') return None
[docs]def parse_date(value): """Get date specified in unicode ISO format to Python datetime object Dates are always assumed to be stored in GMT timezone :param str value: unicode date to be parsed :return: datetime; the specified value, converted to datetime >>> from pyams_utils.date import parse_date >>> parse_date('2016-11-15T10:13:12+00:00') datetime.datetime(2016, 11, 15, 10, 13, 12, tzinfo=<StaticTzInfo 'GMT'>) """ if value is not None: return gmtime(parseDatetimetz(value)) return None
[docs]def date_to_datetime(value): """Get datetime value converted from a date or datetime object :param date/datetime value: a date or datetime value to convert :return: datetime; input value converted to datetime >>> from datetime import date, datetime >>> from pyams_utils.date import date_to_datetime >>> value = date(2016, 11, 15) >>> date_to_datetime(value) datetime.datetime(2016, 11, 15, 0, 0) >>> value = datetime(2016, 11, 15, 10, 13, 12) >>> value datetime.datetime(2016, 11, 15, 10, 13, 12) >>> date_to_datetime(value) is value True """ if not value: return None if isinstance(value, datetime): return value return datetime(value.year, value.month, value.day)
SH_DATE_FORMAT = _("%d/%m/%Y") SH_DATETIME_FORMAT = _("%d/%m/%Y - %H:%M") EXT_DATE_FORMAT = _("on %d/%m/%Y") EXT_DATETIME_FORMAT = _("on %d/%m/%Y at %H:%M")
[docs]def format_date(value, format_string=EXT_DATE_FORMAT, request=None): """Format given date with the given format :param datetime value: the value to format :param str format_string: a format string to use by `strftime` function :param request: the request from which to extract localization info for translation :return: str; input datetime converted to given format >>> from datetime import datetime >>> from pyams_utils.date import format_date, SH_DATE_FORMAT >>> value = datetime(2016, 11, 15, 10, 13, 12) >>> format_date(value) 'on 15/11/2016' >>> format_date(value, SH_DATE_FORMAT) '15/11/2016' """ if not value: return '--' if request is None: request = check_request() localizer = request.localizer return datetime.strftime(tztime(value), localizer.translate(format_string))
[docs]def format_datetime(value, format_string=EXT_DATETIME_FORMAT, request=None): """Format given datetime with the given format including time :param datetime value: the value to format :param str format_string: a format string to use by `strftime` function :param request: request; the request from which to extract localization info for translation :return: str; input datetime converted to given format >>> from datetime import datetime >>> from pyams_utils.date import format_datetime, SH_DATETIME_FORMAT >>> value = datetime(2016, 11, 15, 10, 13, 12) >>> format_datetime(value) 'on 15/11/2016 at 10:13' >>> format_datetime(value, SH_DATETIME_FORMAT) '15/11/2016 - 10:13' """ return format_date(value, format_string, request)
[docs]def get_age(value, request=None): """Get 'human' age of a given datetime (including timezone) compared to current datetime (in UTC) :param datetime value: input datetime to be compared with current datetime :return: str; the delta value, converted to months, weeks, days, hours or minutes """ if request is None: request = check_request() translate = request.localizer.translate now = gmtime(datetime.utcnow()) delta = now - gmtime(value) if delta.days > 60: result = translate(_("%d months ago")) % int(round(delta.days * 1.0 / 30)) elif delta.days > 10: result = translate(_("%d weeks ago")) % int(round(delta.days * 1.0 / 7)) elif delta.days > 2: result = translate(_("%d days ago")) % delta.days elif delta.days == 2: result = translate(_("the day before yesterday")) elif delta.days == 1: result = translate(_("yesterday")) else: # less than one day hours = int(round(delta.seconds * 1.0 / 3600)) if hours > 1: result = translate(_("%d hours ago")) % hours elif delta.seconds > 300: result = translate(_("%d minutes ago")) % int(round(delta.seconds * 1.0 / 60)) else: result = translate(_("less than 5 minutes ago")) return result
[docs]def get_duration(first, last=None, request=None): # pylint: disable=too-many-branches """Get 'human' delta as string between two dates :param datetime first: start date :param datetime last: end date, or current date (in UTC) if None :param request: the request from which to extract localization infos :return: str; approximate delta between the two input dates >>> from datetime import datetime >>> from pyams_utils.date import get_duration >>> from pyramid.testing import DummyRequest >>> request = DummyRequest() >>> date1 = datetime(2015, 1, 1) >>> date2 = datetime(2014, 3, 1) >>> get_duration(date1, date2, request) '10 months' Dates order is not important: >>> get_duration(date2, date1, request) '10 months' >>> date2 = datetime(2014, 11, 10) >>> get_duration(date1, date2, request) '7 weeks' >>> date2 = datetime(2014, 12, 26) >>> get_duration(date1, date2, request) '6 days' For durations lower than 2 days, duration also display hours: >>> date1 = datetime(2015, 1, 1) >>> date2 = datetime(2015, 1, 2, 15, 10, 0) >>> get_duration(date1, date2, request) '1 day and 15 hours' >>> date2 = datetime(2015, 1, 2) >>> get_duration(date1, date2, request) '24 hours' >>> date2 = datetime(2015, 1, 1, 13, 12) >>> get_duration(date1, date2, request) '13 hours' >>> date2 = datetime(2015, 1, 1, 1, 15) >>> get_duration(date1, date2, request) '75 minutes' >>> date2 = datetime(2015, 1, 1, 0, 0, 15) >>> get_duration(date1, date2, request) '15 seconds' """ if last is None: last = datetime.utcnow() assert isinstance(first, datetime) and isinstance(last, datetime) if request is None: request = check_request() translate = request.localizer.translate first, last = min(first, last), max(first, last) delta = last - first if delta.days > 60: result = translate(_("%d months")) % int(round(delta.days * 1.0 / 30)) elif delta.days > 10: result = translate(_("%d weeks")) % int(round(delta.days * 1.0 / 7)) elif delta.days >= 2: result = translate(_("%d days")) % delta.days else: hours = int(round(delta.seconds * 1.0 / 3600)) if delta.days == 1: if hours == 0: result = translate(_("24 hours")) else: result = translate(_("%d day and %d hours")) % (delta.days, hours) else: if hours > 2: result = translate(_("%d hours")) % hours else: minutes = int(round(delta.seconds * 1.0 / 60)) if minutes > 2: result = translate(_("%d minutes")) % minutes else: result = translate(_("%d seconds")) % delta.seconds return result
[docs]@adapter_config(name='timestamp', context=(Interface, Interface, Interface), provides=ITALESExtension) class TimestampTalesAdapter(ContextRequestViewAdapter): """extension:timestamp(context) TALES adapter A PyAMS TALES extension to get timestamp based on last context modification date. """
[docs] def render(self, context=None, formatting=None): """Render TALES extension""" if context is None: context = self.request.context if formatting == 'iso': format_func = datetime.isoformat else: format_func = datetime.timestamp zdc = IZopeDublinCore(context, None) if zdc is None: return format_func(tztime(zdc.modified)) return format_func(tztime(datetime.utcnow()))