Time Maps
=========


.. _relative-time-map:

Relative Time Map
-----------------

The `RelativeTimeMap` maps strings of relative times to seconds. It tries to be as liberal as possible so there has to be a fair amount of certainty that the string is in fact a time and not something similar but different. '5 yolks' will match '5' years, for instance, and if that is not the desired behavior then something else has to do a check first to filter out bad strings.

It uses `dateutil <http://labix.org/python-dateutil>`_ to calculate everything but the seconds because dateutil will handle the ambiguous values like years (which have leap-years) and months which have 28, 29, 30, or 31 days.

.. _relative-time-map-model:

The UML Model
-------------

.. uml::

   RelativeTimeMap --|> BaseClass
   RelativeTimeMap : re.RegexObject year_expression
   RelativeTimeMap : re.RegexObject month_expression
   RelativeTimeMap : re.RegexObject week_expression
   RelativeTimeMap : re.RegexObject day_expression
   RelativeTimeMap : re.RegexObject hour_expression
   RelativeTimeMap : re.RegexObject minute_expression
   RelativeTimeMap : re.RegexObject second_expression      
   
.. currentmodule:: ape.infrastructure.timemap
.. autosummary::
   :toctree: api

   RelativeTimeMap



.. _relative-time-map-groups:

Relative Time Map Groups
------------------------



.. _ape-relative-time:

The RelativeTime
----------------

This is an attempt to extend the `timedelta` with weeks, hours, and minutes. The original intention was to also allow months and years, requiring the use of the `dateutil` package, but at this point I can't see an immediate use for it, so I'll stop at weeks, since it doesn't need special cases the way months and years do.

.. '

.. uml::

   RelativeTime -|> BaseClass
   RelativeTime o-- RelativeTimeMap
   RelativeTime : __init__(source)
   RelativeTime : int days
   RelativeTime : int seconds
   RelativeTime : int microseconds
   RelativeTime : datetime.timedelta
   RelativeTime : float total_seconds

By and large the intention is to use this like a time-delta object but with extra fields, so the operators will be overloaded too.

.. autosummary::
   :toctree: api

   RelativeTime
   RelativeTime.time_map
   RelativeTime.days
   RelativeTime.seconds
   RelativeTime.microseconds
   RelativeTime.reset
   RelativeTime.total_seconds
   source_required

   
I've run into a circular call problem -- my intention was to defer the calculations of the properties until someone called them, but since the larger properties cascade into the smaller ones (e.g. 1.5 minutes would add 30 to seconds to get rid of the fraction) I have to have a single point where the calculations are done so that all of them are done. To do this without requiring anyone to do an explicit method call I'm doing the calculations on the setting of the source. Since I am averse to forcing calculations on construction of an object, the `source` parameter has been changed to optional and setting it to None should reset the fields.

To make it more obvious that the fields have not been populated they will have the default of None. This way if the source has not been set and a calculation is attempted using the fields it will raise an error.

Reading the timedelta information more closely, it appears that it actually accepts anything I want to give it (up to weeks), it just converts them to days, seconds, and microseconds to store them. So the cascading that I was trying to do (separating out the fraction and propagating it to another unit) is actually unnecessary unless there's a need to preserve the separate units. To make it easier I won't.

Well... now that I think about it, I've changed things so much that it has brought me back around to where using the relativedelta might make sense. Unfortunately I still need the hack, but this should allow it to accept months and years.

.. '

.. csv-table:: relativetimedelta Attributes
   :header: Constructor, Attribute

   years,
   months,
   weeks,
   days, days
   hours,
   minutes
   seconds, seconds
   microseconds, microseconds

The timedelta only stores three attributes -- days, seconds, and microseconds, athough it takes the other attributes on construction of the object and then converts them to the three permanent attributes.

.. warning:: ``timedelta`` takes floats (like 3.2) but the extra attributes for relativedelta (months and years) have to be integers.

ApeError Translators
--------------------

Since the operations are expected to raise ApeErrors whenever possible, these decorators will translate standard exceptions to ApeErrors.



The Timedelta
-------------

.. currentmodule:: datetime
.. autosummary::
   :toctree: api

   timedelta
   timedelta.days
   timedelta.seconds
   timedelta.microseconds
   timedelta.total_seconds

Since the timedelta supports operations, the RelativeTime has overloaded the following operations so that it behaves like a timedelta object.   

.. csv-table:: Timedelta Operations
   :header: Operation, Description

   t1 + t2, Sums two timedeltas
   t1 - t2, Subtracts one timedelta from another
   t1 * int, Mulitplies a timedelta by an integer
   t1 // int, Calculates the floor of a timedelta, throws away the remainder
   +t1, Makes the timedelta positive
   -t1, Negates the timedelta
   abs(t1), Depends on number of days
   str(t), string 
   repr(t), representation string

Equality and inequality have also been implemented but there are two warnings:

    * If you have too many decimal places for parameters fed to a timedelta they might not equate with the RelativeTime, even if given the same parameters

    * This only works if the RelativeTime is on the left-hand-side (RelativeTime == timedelta)

.. .. _relativetime-class-diagram:
.. 
.. RelativeTime Class Diagram
.. --------------------------
.. 
.. This is an auto-generated diagram of the :ref:`RelativeTime Class <relative-time>`.
.. 
.. <<name='relativetime_class_diagram', echo=False, wrap=False, results='sphinx'>>=
.. if IN_PWEAVE:
..     this_file = os.path.join(os.getcwd(), 'timemap.py')
..     class_diagram_file = class_diagram(class_name="RelativeTime",
..                                        filter='OTHER',
..                                        module=this_file)
..     print ".. image:: {0}".format(class_diagram_file)
.. 
.. 

.. _ape-absolute-time:

AbsoluteTime
------------

This is a class to get ``datetime`` objects based on a string input. It really is just a pass-through to ``dateutil.parser.parse`` but holds persistent values so they don't have to be passed in for every function call by the user.

.. '

.. currentmodule:: dateutil.parser
.. autosummary::
   :toctree: api

   parse

.. currentmodule:: datetime
.. autosummary::
   :toctree: api

   datetime

.. uml::

   AbsoluteTime -|> BaseClass
   AbsoluteTime o-- dateutil.parse
   AbsoluteTime : datetime default
   AbsoluteTime : boolean ignoretz
   AbsoluteTime : (function or dict) tzinfos
   AbsoluteTime : boolean dayfirst
   AbsoluteTime : boolean yearfirst
   AbsoluteTime : boolean fuzzy
   AbsoluteTime : parserinfo parserinfo
   AbsoluteTime : datetime __call__(string)

.. currentmodule:: ape.interface.timemap
.. autosummary::
   :toctree: api

   AbsoluteTime
   AbsoluteTime.__call__



.. .. _absolutetime-class-diagram:
.. 
.. AbsoluteTime Class Diagram
.. --------------------------
.. 
.. This is an auto-generated diagram of the :ref:`AbsoluteTime Class <ape-absolute-time>`.
.. 
.. <<name='absolutetime_class_diagram', echo=False, wrap=False, results='sphinx'>>=
.. if IN_PWEAVE:
..     this_file = os.path.join(os.getcwd(), 'timemap.py')
..     class_diagram_file = class_diagram(class_name="AbsoluteTime",
..                                        filter='OTHER',
..                                        module=this_file)
..     print ".. image:: {0}".format(class_diagram_file)
.. 
.. 

The AbsoluteTime Attributes
---------------------------

These are the same as the ``parse`` function's arguments. I think in most cases the defaults are all that you'll need, but I'll at least document how the `dayfirst` and `yearfirst` arguments affect the format precedence. If the timestamp is unambiguous, they won't matter, but for the cases where the fields *are* ambiguous (e.g. '11-11-23') the two parameters decide what to assume about the timestamp.

.. csv-table:: Format Precedence
   :header: ``dayfirst``, ``yearfirst``, Order of Precedence (Left to Right)
   :delim: ;

   False; False; MM-DD-YY, DD-MM-YY, YY-MM-DD (default)
   False; True; YY-MM-DD, MM-DD-YY, DD-MM-YY
   True; False; DD-MM-YY, MM-DD-YY, YY-MM-DD
   True; True; YY-MM-DD, DD-MM-YY, MM-DD-yy

Time Validator
--------------

Since I'm switching from the ConfigurationMap to ConfigObj, I need a validator that knows about time conversions.
..'

::

    time_validator = Validator({'relative_time':RelativeTime,
                           'absolute_time':AbsoluteTime()})
    



.. .. _timemap-module-diagram:
..    
.. Module Diagram
.. --------------
.. 
.. This is a diagram of dependencies for this module.
.. 
.. <<name='module_diagram', echo=False, wrap=False, results='sphinx'>>=
.. if IN_PWEAVE:
..     module_diagram_file = module_diagram(module=this_file, project='timemap')
..     print ".. image:: {0}".format(module_diagram_file)
..