New formatter function 'date_arithmetic' that simplifies computing new dates from an existing one

This commit is contained in:
Charles Haley 2021-06-03 15:31:55 +01:00
parent 06c7d20872
commit e03405df1b
2 changed files with 61 additions and 1 deletions

View File

@ -389,6 +389,16 @@ In `GPM` the functions described in `Single Function Mode` all require an additi
* ``connected_device_uuid(storage_location_key)`` -- if a device is connected then return the device uuid (unique id), otherwise return the empty string. Each storage location on a device has a different uuid. The ``storage_location_key`` location names are ``'main'``, ``'carda'`` and ``'cardb'``. This function works only in the GUI.
* ``current_library_name()`` -- return the last name on the path to the current calibre library.
* ``current_library_path()`` -- return the full path to the current calibre library.
* ``date_arithmetic(date, calc_spec, fmt)`` -- Calculate a new date from ``date`` using ``calc_spec``. Return the new date formatted according to optional ``fmt``: if not supplied then the result will be in ISO format. The calc_spec is a string formed by concatenating pairs of ``vW`` (``valueWhat``) where ``v`` is a possibly-negative number and W is one of the following letters:
* ``s``: add ``v`` seconds to ``date``
* ``m``: add ``v`` minutes to ``date``
* ``h``: add ``v`` hours to ``date``
* ``d``: add ``v`` days to ``date``
* ``w``: add ``v`` weeks to ``date``
* ``y``: add ``v`` years to ``date``, where a year is 365 days.
Example: ``'1s3d-1m'`` will add 1 second, add 3 days, and subtract 1 month from ``date``.
* ``days_between(date1, date2)`` -- return the number of days between ``date1`` and ``date2``. The number is positive if ``date1`` is greater than ``date2``, otherwise negative. If either ``date1`` or ``date2`` are not dates, the function returns the empty string.
* ``divide(x, y)`` -- returns ``x / y``. Throws an exception if either ``x`` or ``y`` are not numbers. This function can usually be replaced by the ``/`` operator.
* ``eval(string)`` -- evaluates the string as a program, passing the local variables. This permits using the template processor to construct complex results from local variables. In :ref:`Template Program Mode <template_mode>`, because the `{` and `}` characters are interpreted before the template is evaluated you must use `[[` for the `{` character and `]]` for the ``}`` character. They are converted automatically. Note also that prefixes and suffixes (the `|prefix|suffix` syntax) cannot be used in the argument to this function when using :ref:`Template Program Mode <template_mode>`.

View File

@ -13,7 +13,7 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import inspect, re, traceback, numbers
from datetime import datetime
from datetime import datetime, timedelta
from math import trunc, floor, ceil, modf
from calibre import human_readable, prints
@ -1612,6 +1612,55 @@ class BuiltinDaysBetween(BuiltinFormatterFunction):
return '%.1f'%(i.days + (i.seconds/(24.0*60.0*60.0)))
class BuiltinDateArithmetic(BuiltinFormatterFunction):
name = 'date_arithmetic'
arg_count = -1
category = 'Date functions'
__doc__ = doc = _('date_arithmetic(date, calc_spec, fmt) -- '
"Calculate a new date from 'date' using 'calc_spec'. Return the "
"new date formatted according to optional 'fmt': if not supplied "
"then the result will be in iso format. The calc_spec is a string "
"formed by concatenating pairs of 'vW' (valueWhat) where 'v' is a "
"possibly-negative number and W is one of the following letters: "
"s: add 'v' seconds to 'date' "
"m: add 'v' minutes to 'date' "
"h: add 'v' hours to 'date' "
"d: add 'v' days to 'date' "
"w: add 'v' weeks to 'date' "
"y: add 'v' years to 'date', where a year is 365 days. "
"Example: '1s3d-1m' will add 1 second, add 3 days, and subtract 1 "
"month from 'date'.")
calc_ops = {
's': lambda v: timedelta(seconds=v),
'm': lambda v: timedelta(minutes=v),
'h': lambda v: timedelta(hours=v),
'd': lambda v: timedelta(days=v),
'w': lambda v: timedelta(weeks=v),
'y': lambda v: timedelta(days=v * 365),
}
def evaluate(self, formatter, kwargs, mi, locals, date, calc_spec, fmt=None):
try:
d = parse_date(date)
if d == UNDEFINED_DATE:
return ''
while calc_spec:
mo = re.match('([-+\d]+)([smhdwy])', calc_spec)
if mo is None:
raise ValueError(
_("{0}: invalid calculation specifier '{1}'").format(
'date_arithmetic', calc_spec))
d += self.calc_ops[mo[2]](int(mo[1]))
calc_spec = calc_spec[len(mo[0]):]
return format_date(d, fmt if fmt else 'iso')
except ValueError as e:
raise e
except Exception as e:
traceback.print_exc()
raise ValueError(_("{0}: error: {1}").format('date_arithmetic', str(e)))
class BuiltinLanguageStrings(BuiltinFormatterFunction):
name = 'language_strings'
arg_count = 2
@ -2044,6 +2093,7 @@ _formatter_builtins = [
BuiltinCapitalize(), BuiltinCharacter(), BuiltinCheckYesNo(), BuiltinCeiling(),
BuiltinCmp(), BuiltinConnectedDeviceName(), BuiltinConnectedDeviceUUID(), BuiltinContains(),
BuiltinCount(), BuiltinCurrentLibraryName(), BuiltinCurrentLibraryPath(),
BuiltinDateArithmetic(),
BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(), BuiltinFirstNonEmpty(),
BuiltinField(), BuiltinFieldExists(),
BuiltinFinishFormatting(), BuiltinFirstMatchingCmp(), BuiltinFloor(),