src/pyams_utils/date.py
changeset 289 c8e21d7dd685
child 292 b338586588ad
equal deleted inserted replaced
-1:000000000000 289:c8e21d7dd685
       
     1 #
       
     2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 
       
    13 __docformat__ = 'restructuredtext'
       
    14 
       
    15 
       
    16 # import standard packages
       
    17 from datetime import datetime
       
    18 
       
    19 # import interfaces
       
    20 from pyams_utils.interfaces.tales import ITALESExtension
       
    21 from zope.dublincore.interfaces import IZopeDublinCore
       
    22 
       
    23 # import packages
       
    24 from pyams_utils.adapter import adapter_config, ContextRequestViewAdapter
       
    25 from pyams_utils.request import check_request
       
    26 from pyams_utils.timezone import gmtime, tztime
       
    27 from zope.datetime import parseDatetimetz
       
    28 from zope.interface import Interface
       
    29 
       
    30 from pyams_utils import _
       
    31 
       
    32 
       
    33 def unidate(value):
       
    34     """Get specified date converted to unicode ISO format
       
    35     
       
    36     Dates are always assumed to be stored in GMT timezone
       
    37     
       
    38     :param date value: input date to convert to unicode
       
    39     :return: unicode; input date converted to unicode
       
    40 
       
    41     >>> from datetime import datetime
       
    42     >>> from pyams_utils.date import unidate
       
    43     >>> value = datetime(2016, 11, 15, 10, 13, 12)
       
    44     >>> unidate(value)
       
    45     '2016-11-15T10:13:12+00:00'
       
    46     """
       
    47     if value is not None:
       
    48         value = gmtime(value)
       
    49         return value.isoformat('T')
       
    50     return None
       
    51 
       
    52 
       
    53 def parse_date(value):
       
    54     """Get date specified in unicode ISO format to Python datetime object
       
    55     
       
    56     Dates are always assumed to be stored in GMT timezone
       
    57     
       
    58     :param str value: unicode date to be parsed
       
    59     :return: datetime; the specified value, converted to datetime
       
    60 
       
    61     >>> from pyams_utils.date import parse_date
       
    62     >>> parse_date('2016-11-15T10:13:12+00:00')
       
    63     datetime.datetime(2016, 11, 15, 10, 13, 12, tzinfo=<StaticTzInfo 'GMT'>)
       
    64     """
       
    65     if value is not None:
       
    66         return gmtime(parseDatetimetz(value))
       
    67     return None
       
    68 
       
    69 
       
    70 def date_to_datetime(value):
       
    71     """Get datetime value converted from a date or datetime object
       
    72     
       
    73     :param date/datetime value: a date or datetime value to convert
       
    74     :return: datetime; input value converted to datetime
       
    75 
       
    76     >>> from datetime import date, datetime
       
    77     >>> from pyams_utils.date import date_to_datetime
       
    78     >>> value = date(2016, 11, 15)
       
    79     >>> date_to_datetime(value)
       
    80     datetime.datetime(2016, 11, 15, 0, 0)
       
    81     >>> value = datetime(2016, 11, 15, 10, 13, 12)
       
    82     >>> value
       
    83     datetime.datetime(2016, 11, 15, 10, 13, 12)
       
    84     >>> date_to_datetime(value) is value
       
    85     True
       
    86     """
       
    87     if not value:
       
    88         return None
       
    89     if type(value) is datetime:
       
    90         return value
       
    91     return datetime(value.year, value.month, value.day)
       
    92 
       
    93 
       
    94 SH_DATE_FORMAT = _("%d/%m/%Y")
       
    95 SH_DATETIME_FORMAT = _("%d/%m/%Y - %H:%M")
       
    96 
       
    97 EXT_DATE_FORMAT = _("on %d/%m/%Y")
       
    98 EXT_DATETIME_FORMAT = _("on %d/%m/%Y at %H:%M")
       
    99 
       
   100 
       
   101 def format_date(value, format=EXT_DATE_FORMAT, request=None):
       
   102     """Format given date with the given format
       
   103 
       
   104     :param datetime value: the value to format
       
   105     :param str format: a format string to use by `strftime` function
       
   106     :param request: the request from which to extract localization info for translation
       
   107     :return: str; input datetime converted to given format
       
   108 
       
   109     >>> from datetime import datetime
       
   110     >>> from pyams_utils.date import format_date, SH_DATE_FORMAT
       
   111     >>> value = datetime(2016, 11, 15, 10, 13, 12)
       
   112     >>> format_date(value)
       
   113     'on 15/11/2016'
       
   114     >>> format_date(value, SH_DATE_FORMAT)
       
   115     '15/11/2016'
       
   116     """
       
   117     if not value:
       
   118         return '--'
       
   119     if request is None:
       
   120         request = check_request()
       
   121     localizer = request.localizer
       
   122     return datetime.strftime(tztime(value), localizer.translate(format))
       
   123 
       
   124 
       
   125 def format_datetime(value, format=EXT_DATETIME_FORMAT, request=None):
       
   126     """Format given datetime with the given format including time
       
   127 
       
   128     :param datetime value: the value to format
       
   129     :param str format: a format string to use by `strftime` function
       
   130     :param request: request; the request from which to extract localization info for translation
       
   131     :return: str; input datetime converted to given format
       
   132 
       
   133     >>> from datetime import datetime
       
   134     >>> from pyams_utils.date import format_datetime, SH_DATETIME_FORMAT
       
   135     >>> value = datetime(2016, 11, 15, 10, 13, 12)
       
   136     >>> format_datetime(value)
       
   137     'on 15/11/2016 at 10:13'
       
   138     >>> format_datetime(value, SH_DATETIME_FORMAT)
       
   139     '15/11/2016 - 10:13'
       
   140     """
       
   141     return format_date(value, format, request)
       
   142 
       
   143 
       
   144 def get_age(value, request=None):
       
   145     """Get 'human' age of a given datetime (including timezone) compared to current datetime (in UTC)
       
   146 
       
   147     :param datetime value: input datetime to be compared with current datetime
       
   148     :return: str; the delta value, converted to months, weeks, days, hours or minutes
       
   149     """
       
   150     if request is None:
       
   151         request = check_request()
       
   152     translate = request.localizer.translate
       
   153     now = gmtime(datetime.utcnow())
       
   154     delta = now - gmtime(value)
       
   155     if delta.days > 60:
       
   156         return translate(_("%d months ago")) % int(round(delta.days * 1.0 / 30))
       
   157     elif delta.days > 10:
       
   158         return translate(_("%d weeks ago")) % int(round(delta.days * 1.0 / 7))
       
   159     elif delta.days > 2:
       
   160         return translate(_("%d days ago")) % delta.days
       
   161     elif delta.days == 2:
       
   162         return translate(_("the day before yesterday"))
       
   163     elif delta.days == 1:
       
   164         return translate(_("yesterday"))
       
   165     else:
       
   166         hours = int(round(delta.seconds * 1.0 / 3600))
       
   167         if hours > 1:
       
   168             return translate(_("%d hours ago")) % hours
       
   169         elif delta.seconds > 300:
       
   170             return translate(_("%d minutes ago")) % int(round(delta.seconds * 1.0 / 60))
       
   171         else:
       
   172             return translate(_("less than 5 minutes ago"))
       
   173 
       
   174 
       
   175 def get_duration(v1, v2=None, request=None):
       
   176     """Get 'human' delta as string between two dates
       
   177 
       
   178     :param datetime v1: start date
       
   179     :param datetime v2: end date, or current date (in UTC) if None
       
   180     :param request: the request from which to extract localization infos
       
   181     :return: str; approximate delta between the two input dates
       
   182 
       
   183     >>> from datetime import datetime
       
   184     >>> from pyams_utils.date import get_duration
       
   185     >>> from pyramid.testing import DummyRequest
       
   186     >>> request = DummyRequest()
       
   187     >>> date1 = datetime(2015, 1, 1)
       
   188     >>> date2 = datetime(2014, 3, 1)
       
   189     >>> get_duration(date1, date2, request)
       
   190     '10 months'
       
   191 
       
   192     Dates order is not important:
       
   193 
       
   194     >>> get_duration(date2, date1, request)
       
   195     '10 months'
       
   196     >>> date2 = datetime(2014, 11, 10)
       
   197     >>> get_duration(date1, date2, request)
       
   198     '7 weeks'
       
   199     >>> date2 = datetime(2014, 12, 26)
       
   200     >>> get_duration(date1, date2, request)
       
   201     '6 days'
       
   202 
       
   203     For durations lower than 2 days, duration also display hours:
       
   204 
       
   205     >>> date1 = datetime(2015, 1, 1)
       
   206     >>> date2 = datetime(2015, 1, 2, 15, 10, 0)
       
   207     >>> get_duration(date1, date2, request)
       
   208     '1 day and 15 hours'
       
   209     >>> date2 = datetime(2015, 1, 2)
       
   210     >>> get_duration(date1, date2, request)
       
   211     '24 hours'
       
   212     >>> date2 = datetime(2015, 1, 1, 13, 12)
       
   213     >>> get_duration(date1, date2, request)
       
   214     '13 hours'
       
   215     >>> date2 = datetime(2015, 1, 1, 1, 15)
       
   216     >>> get_duration(date1, date2, request)
       
   217     '75 minutes'
       
   218     >>> date2 = datetime(2015, 1, 1, 0, 0, 15)
       
   219     >>> get_duration(date1, date2, request)
       
   220     '15 seconds'
       
   221     """
       
   222     if v2 is None:
       
   223         v2 = datetime.utcnow()
       
   224     assert isinstance(v1, datetime) and isinstance(v2, datetime)
       
   225     if request is None:
       
   226         request = check_request()
       
   227     translate = request.localizer.translate
       
   228     v1, v2 = min(v1, v2), max(v1, v2)
       
   229     delta = v2 - v1
       
   230     if delta.days > 60:
       
   231         return translate(_("%d months")) % int(round(delta.days * 1.0 / 30))
       
   232     elif delta.days > 10:
       
   233         return translate(_("%d weeks")) % int(round(delta.days * 1.0 / 7))
       
   234     elif delta.days >= 2:
       
   235         return translate(_("%d days")) % delta.days
       
   236     else:
       
   237         hours = int(round(delta.seconds * 1.0 / 3600))
       
   238         if delta.days == 1:
       
   239             if hours == 0:
       
   240                 return translate(_("24 hours"))
       
   241             else:
       
   242                 return translate(_("%d day and %d hours")) % (delta.days, hours)
       
   243         else:
       
   244             if hours > 2:
       
   245                 return translate(_("%d hours")) % hours
       
   246             else:
       
   247                 minutes = int(round(delta.seconds * 1.0 / 60))
       
   248                 if minutes > 2:
       
   249                     return translate(_("%d minutes")) % minutes
       
   250                 else:
       
   251                     return translate(_("%d seconds")) % delta.seconds
       
   252 
       
   253 
       
   254 @adapter_config(name='timestamp', context=(Interface, Interface, Interface), provides=ITALESExtension)
       
   255 class TimestampTalesAdapter(ContextRequestViewAdapter):
       
   256     """extension:timestamp(context) TALES adapter
       
   257 
       
   258     A PyAMS TALES extension to get timestamp based on last context modification date.
       
   259     """
       
   260 
       
   261     def render(self, context=None, formatting=None):
       
   262         if context is None:
       
   263             context = self.request.context
       
   264         if formatting == 'iso':
       
   265             format_func = datetime.isoformat
       
   266         else:
       
   267             format_func = datetime.timestamp
       
   268         dc = IZopeDublinCore(context, None)
       
   269         if dc is None:
       
   270             return format_func(datetime.utcnow())
       
   271         else:
       
   272             return format_func(dc.modified)