src/pyams_utils/date.py
changeset 410 0b9e3e67f43d
parent 408 cf2304af0fab
child 441 b3b41d340a04
equal deleted inserted replaced
404:3f249488b3ff 410:0b9e3e67f43d
    14 
    14 
    15 This module provides several functions concerning conversion, parsing and formatting of
    15 This module provides several functions concerning conversion, parsing and formatting of
    16 dates and datetimes.
    16 dates and datetimes.
    17 """
    17 """
    18 
    18 
    19 __docformat__ = 'restructuredtext'
       
    20 
       
    21 from datetime import datetime
    19 from datetime import datetime
    22 
    20 
    23 from zope.datetime import parseDatetimetz
    21 from zope.datetime import parseDatetimetz
    24 from zope.dublincore.interfaces import IZopeDublinCore
    22 from zope.dublincore.interfaces import IZopeDublinCore
    25 from zope.interface import Interface
    23 from zope.interface import Interface
    27 from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
    25 from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
    28 from pyams_utils.interfaces.tales import ITALESExtension
    26 from pyams_utils.interfaces.tales import ITALESExtension
    29 from pyams_utils.request import check_request
    27 from pyams_utils.request import check_request
    30 from pyams_utils.timezone import gmtime, tztime
    28 from pyams_utils.timezone import gmtime, tztime
    31 
    29 
       
    30 
       
    31 __docformat__ = 'restructuredtext'
       
    32 
    32 from pyams_utils import _
    33 from pyams_utils import _
    33 
    34 
    34 
    35 
    35 def unidate(value):
    36 def unidate(value):
    36     """Get specified date converted to unicode ISO format
    37     """Get specified date converted to unicode ISO format
    37     
    38 
    38     Dates are always assumed to be stored in GMT timezone
    39     Dates are always assumed to be stored in GMT timezone
    39     
    40 
    40     :param date value: input date to convert to unicode
    41     :param date value: input date to convert to unicode
    41     :return: unicode; input date converted to unicode
    42     :return: unicode; input date converted to unicode
    42 
    43 
    43     >>> from datetime import datetime
    44     >>> from datetime import datetime
    44     >>> from pyams_utils.date import unidate
    45     >>> from pyams_utils.date import unidate
    52     return None
    53     return None
    53 
    54 
    54 
    55 
    55 def parse_date(value):
    56 def parse_date(value):
    56     """Get date specified in unicode ISO format to Python datetime object
    57     """Get date specified in unicode ISO format to Python datetime object
    57     
    58 
    58     Dates are always assumed to be stored in GMT timezone
    59     Dates are always assumed to be stored in GMT timezone
    59     
    60 
    60     :param str value: unicode date to be parsed
    61     :param str value: unicode date to be parsed
    61     :return: datetime; the specified value, converted to datetime
    62     :return: datetime; the specified value, converted to datetime
    62 
    63 
    63     >>> from pyams_utils.date import parse_date
    64     >>> from pyams_utils.date import parse_date
    64     >>> parse_date('2016-11-15T10:13:12+00:00')
    65     >>> parse_date('2016-11-15T10:13:12+00:00')
    69     return None
    70     return None
    70 
    71 
    71 
    72 
    72 def date_to_datetime(value):
    73 def date_to_datetime(value):
    73     """Get datetime value converted from a date or datetime object
    74     """Get datetime value converted from a date or datetime object
    74     
    75 
    75     :param date/datetime value: a date or datetime value to convert
    76     :param date/datetime value: a date or datetime value to convert
    76     :return: datetime; input value converted to datetime
    77     :return: datetime; input value converted to datetime
    77 
    78 
    78     >>> from datetime import date, datetime
    79     >>> from datetime import date, datetime
    79     >>> from pyams_utils.date import date_to_datetime
    80     >>> from pyams_utils.date import date_to_datetime
    86     >>> date_to_datetime(value) is value
    87     >>> date_to_datetime(value) is value
    87     True
    88     True
    88     """
    89     """
    89     if not value:
    90     if not value:
    90         return None
    91         return None
    91     if type(value) is datetime:
    92     if isinstance(value, datetime):
    92         return value
    93         return value
    93     return datetime(value.year, value.month, value.day)
    94     return datetime(value.year, value.month, value.day)
    94 
    95 
    95 
    96 
    96 SH_DATE_FORMAT = _("%d/%m/%Y")
    97 SH_DATE_FORMAT = _("%d/%m/%Y")
    98 
    99 
    99 EXT_DATE_FORMAT = _("on %d/%m/%Y")
   100 EXT_DATE_FORMAT = _("on %d/%m/%Y")
   100 EXT_DATETIME_FORMAT = _("on %d/%m/%Y at %H:%M")
   101 EXT_DATETIME_FORMAT = _("on %d/%m/%Y at %H:%M")
   101 
   102 
   102 
   103 
   103 def format_date(value, format=EXT_DATE_FORMAT, request=None):
   104 def format_date(value, format_string=EXT_DATE_FORMAT, request=None):
   104     """Format given date with the given format
   105     """Format given date with the given format
   105 
   106 
   106     :param datetime value: the value to format
   107     :param datetime value: the value to format
   107     :param str format: a format string to use by `strftime` function
   108     :param str format_string: a format string to use by `strftime` function
   108     :param request: the request from which to extract localization info for translation
   109     :param request: the request from which to extract localization info for translation
   109     :return: str; input datetime converted to given format
   110     :return: str; input datetime converted to given format
   110 
   111 
   111     >>> from datetime import datetime
   112     >>> from datetime import datetime
   112     >>> from pyams_utils.date import format_date, SH_DATE_FORMAT
   113     >>> from pyams_utils.date import format_date, SH_DATE_FORMAT
   119     if not value:
   120     if not value:
   120         return '--'
   121         return '--'
   121     if request is None:
   122     if request is None:
   122         request = check_request()
   123         request = check_request()
   123     localizer = request.localizer
   124     localizer = request.localizer
   124     return datetime.strftime(tztime(value), localizer.translate(format))
   125     return datetime.strftime(tztime(value), localizer.translate(format_string))
   125 
   126 
   126 
   127 
   127 def format_datetime(value, format=EXT_DATETIME_FORMAT, request=None):
   128 def format_datetime(value, format_string=EXT_DATETIME_FORMAT, request=None):
   128     """Format given datetime with the given format including time
   129     """Format given datetime with the given format including time
   129 
   130 
   130     :param datetime value: the value to format
   131     :param datetime value: the value to format
   131     :param str format: a format string to use by `strftime` function
   132     :param str format_string: a format string to use by `strftime` function
   132     :param request: request; the request from which to extract localization info for translation
   133     :param request: request; the request from which to extract localization info for translation
   133     :return: str; input datetime converted to given format
   134     :return: str; input datetime converted to given format
   134 
   135 
   135     >>> from datetime import datetime
   136     >>> from datetime import datetime
   136     >>> from pyams_utils.date import format_datetime, SH_DATETIME_FORMAT
   137     >>> from pyams_utils.date import format_datetime, SH_DATETIME_FORMAT
   138     >>> format_datetime(value)
   139     >>> format_datetime(value)
   139     'on 15/11/2016 at 10:13'
   140     'on 15/11/2016 at 10:13'
   140     >>> format_datetime(value, SH_DATETIME_FORMAT)
   141     >>> format_datetime(value, SH_DATETIME_FORMAT)
   141     '15/11/2016 - 10:13'
   142     '15/11/2016 - 10:13'
   142     """
   143     """
   143     return format_date(value, format, request)
   144     return format_date(value, format_string, request)
   144 
   145 
   145 
   146 
   146 def get_age(value, request=None):
   147 def get_age(value, request=None):
   147     """Get 'human' age of a given datetime (including timezone) compared to current datetime (in UTC)
   148     """Get 'human' age of a given datetime (including timezone) compared to current datetime
       
   149     (in UTC)
   148 
   150 
   149     :param datetime value: input datetime to be compared with current datetime
   151     :param datetime value: input datetime to be compared with current datetime
   150     :return: str; the delta value, converted to months, weeks, days, hours or minutes
   152     :return: str; the delta value, converted to months, weeks, days, hours or minutes
   151     """
   153     """
   152     if request is None:
   154     if request is None:
   153         request = check_request()
   155         request = check_request()
   154     translate = request.localizer.translate
   156     translate = request.localizer.translate
   155     now = gmtime(datetime.utcnow())
   157     now = gmtime(datetime.utcnow())
   156     delta = now - gmtime(value)
   158     delta = now - gmtime(value)
   157     if delta.days > 60:
   159     if delta.days > 60:
   158         return translate(_("%d months ago")) % int(round(delta.days * 1.0 / 30))
   160         result = translate(_("%d months ago")) % int(round(delta.days * 1.0 / 30))
   159     elif delta.days > 10:
   161     elif delta.days > 10:
   160         return translate(_("%d weeks ago")) % int(round(delta.days * 1.0 / 7))
   162         result = translate(_("%d weeks ago")) % int(round(delta.days * 1.0 / 7))
   161     elif delta.days > 2:
   163     elif delta.days > 2:
   162         return translate(_("%d days ago")) % delta.days
   164         result = translate(_("%d days ago")) % delta.days
   163     elif delta.days == 2:
   165     elif delta.days == 2:
   164         return translate(_("the day before yesterday"))
   166         result = translate(_("the day before yesterday"))
   165     elif delta.days == 1:
   167     elif delta.days == 1:
   166         return translate(_("yesterday"))
   168         result = translate(_("yesterday"))
   167     else:
   169     else:  # less than one day
   168         hours = int(round(delta.seconds * 1.0 / 3600))
   170         hours = int(round(delta.seconds * 1.0 / 3600))
   169         if hours > 1:
   171         if hours > 1:
   170             return translate(_("%d hours ago")) % hours
   172             result = translate(_("%d hours ago")) % hours
   171         elif delta.seconds > 300:
   173         elif delta.seconds > 300:
   172             return translate(_("%d minutes ago")) % int(round(delta.seconds * 1.0 / 60))
   174             result = translate(_("%d minutes ago")) % int(round(delta.seconds * 1.0 / 60))
   173         else:
   175         else:
   174             return translate(_("less than 5 minutes ago"))
   176             result = translate(_("less than 5 minutes ago"))
   175 
   177     return result
   176 
   178 
   177 def get_duration(v1, v2=None, request=None):
   179 
       
   180 def get_duration(first, last=None, request=None):  # pylint: disable=too-many-branches
   178     """Get 'human' delta as string between two dates
   181     """Get 'human' delta as string between two dates
   179 
   182 
   180     :param datetime v1: start date
   183     :param datetime first: start date
   181     :param datetime v2: end date, or current date (in UTC) if None
   184     :param datetime last: end date, or current date (in UTC) if None
   182     :param request: the request from which to extract localization infos
   185     :param request: the request from which to extract localization infos
   183     :return: str; approximate delta between the two input dates
   186     :return: str; approximate delta between the two input dates
   184 
   187 
   185     >>> from datetime import datetime
   188     >>> from datetime import datetime
   186     >>> from pyams_utils.date import get_duration
   189     >>> from pyams_utils.date import get_duration
   219     '75 minutes'
   222     '75 minutes'
   220     >>> date2 = datetime(2015, 1, 1, 0, 0, 15)
   223     >>> date2 = datetime(2015, 1, 1, 0, 0, 15)
   221     >>> get_duration(date1, date2, request)
   224     >>> get_duration(date1, date2, request)
   222     '15 seconds'
   225     '15 seconds'
   223     """
   226     """
   224     if v2 is None:
   227     if last is None:
   225         v2 = datetime.utcnow()
   228         last = datetime.utcnow()
   226     assert isinstance(v1, datetime) and isinstance(v2, datetime)
   229     assert isinstance(first, datetime) and isinstance(last, datetime)
   227     if request is None:
   230     if request is None:
   228         request = check_request()
   231         request = check_request()
   229     translate = request.localizer.translate
   232     translate = request.localizer.translate
   230     v1, v2 = min(v1, v2), max(v1, v2)
   233     first, last = min(first, last), max(first, last)
   231     delta = v2 - v1
   234     delta = last - first
   232     if delta.days > 60:
   235     if delta.days > 60:
   233         return translate(_("%d months")) % int(round(delta.days * 1.0 / 30))
   236         result = translate(_("%d months")) % int(round(delta.days * 1.0 / 30))
   234     elif delta.days > 10:
   237     elif delta.days > 10:
   235         return translate(_("%d weeks")) % int(round(delta.days * 1.0 / 7))
   238         result = translate(_("%d weeks")) % int(round(delta.days * 1.0 / 7))
   236     elif delta.days >= 2:
   239     elif delta.days >= 2:
   237         return translate(_("%d days")) % delta.days
   240         result = translate(_("%d days")) % delta.days
   238     else:
   241     else:
   239         hours = int(round(delta.seconds * 1.0 / 3600))
   242         hours = int(round(delta.seconds * 1.0 / 3600))
   240         if delta.days == 1:
   243         if delta.days == 1:
   241             if hours == 0:
   244             if hours == 0:
   242                 return translate(_("24 hours"))
   245                 result = translate(_("24 hours"))
   243             else:
   246             else:
   244                 return translate(_("%d day and %d hours")) % (delta.days, hours)
   247                 result = translate(_("%d day and %d hours")) % (delta.days, hours)
   245         else:
   248         else:
   246             if hours > 2:
   249             if hours > 2:
   247                 return translate(_("%d hours")) % hours
   250                 result = translate(_("%d hours")) % hours
   248             else:
   251             else:
   249                 minutes = int(round(delta.seconds * 1.0 / 60))
   252                 minutes = int(round(delta.seconds * 1.0 / 60))
   250                 if minutes > 2:
   253                 if minutes > 2:
   251                     return translate(_("%d minutes")) % minutes
   254                     result = translate(_("%d minutes")) % minutes
   252                 else:
   255                 else:
   253                     return translate(_("%d seconds")) % delta.seconds
   256                     result = translate(_("%d seconds")) % delta.seconds
   254 
   257     return result
   255 
   258 
   256 @adapter_config(name='timestamp', context=(Interface, Interface, Interface), provides=ITALESExtension)
   259 
       
   260 @adapter_config(name='timestamp', context=(Interface, Interface, Interface),
       
   261                 provides=ITALESExtension)
   257 class TimestampTalesAdapter(ContextRequestViewAdapter):
   262 class TimestampTalesAdapter(ContextRequestViewAdapter):
   258     """extension:timestamp(context) TALES adapter
   263     """extension:timestamp(context) TALES adapter
   259 
   264 
   260     A PyAMS TALES extension to get timestamp based on last context modification date.
   265     A PyAMS TALES extension to get timestamp based on last context modification date.
   261     """
   266     """
   262 
   267 
   263     def render(self, context=None, formatting=None):
   268     def render(self, context=None, formatting=None):
       
   269         """Render TALES extension"""
   264         if context is None:
   270         if context is None:
   265             context = self.request.context
   271             context = self.request.context
   266         if formatting == 'iso':
   272         if formatting == 'iso':
   267             format_func = datetime.isoformat
   273             format_func = datetime.isoformat
   268         else:
   274         else:
   269             format_func = datetime.timestamp
   275             format_func = datetime.timestamp
   270         dc = IZopeDublinCore(context, None)
   276         zdc = IZopeDublinCore(context, None)
   271         if dc is None:
   277         if zdc is None:
   272             return format_func(tztime(datetime.utcnow()))
   278             return format_func(tztime(zdc.modified))
   273         else:
   279         return format_func(tztime(datetime.utcnow()))
   274             return format_func(tztime(dc.modified))