#!/usr/bin/python

  #
  # Copyright (c) 2004, Mike Taylor
  # All rights reserved.
  #
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions are met:
  #
  #   * Redistributions of source code must retain the above copyright notice,
  #     this list of conditions, and the following disclaimer.
  #   * Redistributions in binary form must reproduce the above copyright notice,
  #     this list of conditions, and the following disclaimer in the
  #     documentation and/or other materials provided with the distribution.
  #   * Neither the name of the author of this software nor the name of
  #     contributors to this software may be used to endorse or promote products
  #     derived from this software without specific prior written consent.
  #
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  # ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  # POSSIBILITY OF SUCH DAMAGE.
  #

__author__    = "Mike Taylor <bear@code-bear.com>"
__copyright__ = "Copyright (c) 2004 Mike Taylor"
__license__   = "BSD"
__revision__  = "$Id$"

import os, string, re, time

RE_SPECIAL  = r'(?P<special>^[in|last|next]+)\s+'
RE_UNITS    = r'\s+(?P<units>[hour|minute|second|day|week|month|year]+)'
RE_QUNITS   = r'(?P<qunits>[0-9]+[hmsdwmy])'
RE_MODIFIER = r'(?P<modifier>[from|before|after|ago|prior]+)\s+'

CRE_SPECIAL  = re.compile(RE_SPECIAL, re.IGNORECASE)
CRE_UNITS    = re.compile(RE_UNITS, re.IGNORECASE)
CRE_QUNITS   = re.compile(RE_QUNITS, re.IGNORECASE)
CRE_MODIFIER = re.compile(RE_MODIFIER, re.IGNORECASE)

  # Used to adjust the returned date before/after the source

_Modifiers = {'from':    1,
              'before': -1,
              'after':   1,
              'ago':     1,
              'prior':  -1}

_Minute =  60
_Hour   =  60 * _Minute
_Day    =  24 * _Hour
_Week   =   7 * _Day
_Month  =  30 * _Day
_Year   = 365 * _Day

  # This looks hokey - but it is a nice simple way to get
  # the proper unit value and it has the advantage that 
  # later I can morph it into something localized.
  # Any trailing s will be removed before lookup.
  
_Units = {'second': 1,
          'sec':    1,
          's':      1,
          'minute': _Minute,
          'min':    _Minute,
          'm':      _Minute,
          'hour':   _Hour,
          'hr':     _Hour,
          'h':      _Hour,
          'day':    _Day,
          'dy':     _Day,
          'd':      _Day,
          'week':   _Week,
          'wk':     _Week,
          'w':      _Week,
          'month':  _Month,
          'mth':    _Month,
          'm':      _Month,
          'year':   _Year,
          'yr':     _Year,
          'y':      _Year} 

def _buildTime(sourceTime, quantity, modifier, units):
  """Take quantity, modifier and units strings and convert them
     into values, calcuate the time and return the adjusted
     sourceTime
  """
  # print '[%s][%s][%s]' % (quantity, modifier, units)
  
  q = int(quantity)
  
  if _Modifiers.has_key(modifier):
    q = q * _Modifiers[modifier]

  if units[-1] == 's':
    units = units[:-1]
    
  if _Units.has_key(units):   
    u = _Units[units]
  else:
    u = 1
  
  # print 'sourceTime [%d]' % sourceTime
  # print 'quantity   [%d]' % q
  # print 'units      [%d]' % u
    
  return sourceTime + (q * u)
  
  
def parse(timeString, sourceTime=None):
  """Parse timeString and return the number of seconds from sourceTime
     that the timeString expression represents.

     This version of parse understands only the more basic of expressions
     in this form: 
     
       <quantity> <units> <modifier> <target>
       
     Example:
     
       5 minutes from now
       last week
       2 hours before noon
      
      Valid units     - hour, minute, second, month, week, day and year
                        (including their plural forms)
      Valid modifiers - from, before, after, ago, prior
  """
  
  if sourceTime == None:
    sourceTime = int(time.time())
  else:
    sourceTime = int(sourceTime)
  
  quantity = ''
  units    = ''
  modifier = ''
  target   = ''
  
  s = string.strip(string.lower(timeString))

  m = CRE_SPECIAL.search(s)
  
  if m <> None:
    target = 'now'
    
    if m.group('special') == 'last':
      modifier = 'before'
    else:
      modifier = 'from'

    s = s[m.end('special'):]

    m = CRE_UNITS.search(s)
    
    if m <> None:
      units    = m.group('units')
      quantity = s[:m.start('units')]
      s        = s[m.end('units'):]

  else:
    m = CRE_MODIFIER.search(s)
    
    if m <> None:
      modifier = m.group('modifier')
      target   = s[m.end('modifier'):]
      s        = s[:m.start('modifier'):]
  
    m = CRE_UNITS.search(s)
    
    if m <> None:
      units    = m.group('units')
      quantity = s[:m.start('units')]
      target   = s[m.end('units'):]
  
  return _buildTime(sourceTime, quantity, modifier, units)

def _test(text, value):
  print text

  v = parse(text)
  
  print '\t%s\t%d\t%d' % ((v == value), value, v)

  #
  # TODO
  # 
  #  - make month unit adjustment aware of the actual number of days in each 
  #    month between source and target
  #  - handle edge case where quantity and unit are merged: 5s for 5 sec
  #  - handle compound/nested quantites and modifiers
  #  - bring unit test over from prototype into this file
  #  - convert 'five' to 5 and also 'twenty five' to 25



if __name__ == '__main__':
  start = int(time.time())
  tests = { '5 minutes from now':  start + 5 * _Minute,
            '5 min from now':      start + 5 * _Minute,
            'in 5 minutes':        start + 5 * _Minute,
            '5 days from today':   start + 5 * _Day,
            '5 days before today': start - 5 * _Day,
            '5 minutes':           start + 5 * _Minute,
            '5 min':               start + 5 * _Minute,
            '30 seconds from now': start + 30,
            '30 sec from now':     start + 30,
            '30 seconds':          start + 30,
            '30 sec':              start + 30,
            '1 week':              start + _Week,
            '1 wk':                start + _Week,
            '1 week':              start + _Week,
            '5 days':              start + 5 * _Day,
            '1 year':              start + _Year,
            '2 years':             start + 2 * _Year}
            
  for test in tests.keys():
    _test(test, tests[test])

            



