""" Date/Time string parsing module.

    XXX This module still has prototype status.

    (c) 1998, Copyright Marc-Andre Lemburg; All Rights Reserved.
    See the documentation for further information on copyrights,
    or contact the author (mal@lemburg.com).
"""
import types,re,string
import DateTime,ISO,ARPA,Timezone

# Enable to produce debugging output
__debug__ = 0

# REs for matching date and time parts in a string; These REs
# parse a superset of ARPA, ISO, American and European style dates.
# Timezones are supported via the Timezone submodule.

_year = '(?P<year>-?\d+\d\d)'
_year_epoch = '(?:' + _year + '(?P<epoch> *[ABCDE\.]+)?)'

_month = '(?P<month>\d?\d)'
_fullmonth = '(?P<month>\d\d)'
_litmonth = ('(?P<litmonth>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'
	     '[a-z,\.;]*')
litmonthtable = ARPA.litmonthtable

_day = '(?P<day>\d?\d)'
_usday = '(?P<day>\d?\d)(?:st|nd|rd|th|[,\.;])?'
_fullday = '(?P<day>\d\d)'
_litday = '(?P<litday>Mon|Tue|Wed|Thu|Fri|Sat|Sun)[a-z]*'
litdaytable = ARPA.litdaytable

_hour = '(?P<hour>\d?\d)'
_minute = '(?P<minute>\d\d)'
_second = '(?P<second>\d\d(?:\.\d+)?)'

_days = '(?P<days>\d*\d(?:\.\d+)?)'
_hours = '(?P<hours>\d*\d(?:\.\d+)?)'
_minutes = '(?P<minutes>\d*\d(?:\.\d+)?)'
_seconds = '(?P<seconds>\d*\d(?:\.\d+)?)'

_sign = '(?:(?P<sign>[-+]) *)'
_week = 'W(?P<week>\d?\d)'
_zone = Timezone.zone

_weekdate = _year + '-?(?:' + _week + '-?' + _day + '?)?'
_isodate = _year + '-?' + _fullmonth + '-?' + _fullday
_eurodate = _day + '\.' + _month + '\.' + _year_epoch
_usdate = _month + '/' + _day + '/' + _year_epoch
_litdate = ('(?:'+ _litday + ',? )? *' + 
	    _usday + ' *' + 
	    '[- ] *(?:' + _litmonth + '|'+ _month +') *[- ]' +
	    _year_epoch + '?')
_altlitdate = ('(?:'+ _litday + ',? )? *' + 
	       _litmonth + '[ ,.a-z]+' + 
	       _usday + '[ a-z]+' +
	       _year_epoch + '?')

_time = _hour + ':?' + _minute + ':?' + _second + '? *' + _zone + '?'

_isodelta = (_sign + '?(?:' + 
	     _days + ':' + _hours + ':' + _minutes + ':' + _seconds + 
	     '|' +
	     _hours + ':' + _minutes + ':' + _seconds + 
	     '|' +
	     _hours + ':' + _minutes + ')')
_litdelta = (_sign + '?' +
	     '(?:' + _days + ' *d[a-z]*[,; ]*)?' + 
	     '(?:' + _hours + ' *h[a-z]*[,; ]*)?' + 
	     '(?:' + _minutes + ' *m[a-z]*[,; ]*)?' +
	     '(?:' + _seconds + ' *s[a-z]*[,; ]*)?')

_timeRE = re.compile(_time,re.I)
_isodateRE = re.compile(_isodate,re.I)
_eurodateRE = re.compile(_eurodate,re.I)
_usdateRE = re.compile(_usdate,re.I)
_litdateRE = re.compile(_litdate,re.I)
_altlitdateRE = re.compile(_altlitdate,re.I)
_isodeltaRE = re.compile(_isodelta)
_litdeltaRE = re.compile(_litdelta)

def _parse_date(text,

		atoi=string.atoi,atof=string.atof,
		add_century=DateTime.add_century,
		now=DateTime.now):
		
    match = _eurodateRE.search(text)
    if match:
	day,month,year,epoch = match.groups()
	if len(year) == 2:
	    # Y2K problem:
	    year = add_century(atoi(year))
	else:
	    year = atoi(year)
	if epoch and 'B' in epoch:
	    year = -year + 1
	month = atoi(month)
	day = atoi(day)
	if __debug__: print 'euro'
    else:
	match = _usdateRE.search(text)
	if match:
	    month,day,year,epoch = match.groups()
	    if len(year) == 2:
		# Y2K problem:
		year = add_century(atoi(year))
	    else:
		year = atoi(year)
	    if epoch and 'B' in epoch:
		year = -year + 1
	    month = atoi(month)
	    day = atoi(day)
	    if __debug__: print 'us'
	else:
	    match = _isodateRE.search(text)
	    if match:
		year,month,day = match.groups()
		if len(year) == 2:
		    # Y2K problem:
		    year = add_century(atoi(year))
		else:
		    year = atoi(year)
		# default to January, 1
		if not month:
		    month = 1
		else:
		    month = atoi(month)
		if not day:
		    day = 1
		else:
		    day = atoi(day)
		if __debug__: print 'iso'
	    else:
		match = _litdateRE.search(text)
		if match:
		    litday,day,litmonth,month,year,epoch = match.groups()
		else:
		    match = _altlitdateRE.search(text)
		    if match: 
			litday,litmonth,day,year,epoch = match.groups()
			month = '<missing>'
			if __debug__: print 'alternative literal'
		if match:
		    if __debug__: print match.groups()
		    if not year:
			year = now().year
		    else:
			if len(year) == 2:
			    # Y2K problem:
			    year = add_century(atoi(year))
			else:
			    year = atoi(year)
		    if epoch and 'B' in epoch:
			year = -year + 1
		    if litmonth:
			try:
			    month = litmonthtable[litmonth]
			except KeyError:
			    raise ValueError,\
				  'wrong month name: "%s"' % litmonth
		    else:
			month = atoi(month)
		    day = atoi(day)
		    if __debug__: print 'literal'
		else:
		    # No date part: default to today
		    date = now()
		    if __debug__: print 'default'
		    year = date.year
		    month = date.month
		    day = date.day

    if match:
	# Remove date from text
	left,right = match.span()
	if __debug__: print 'parsed date:',repr(text[left:right])
	text = text[:left] + text[right:]

    return text,day,month,year

def _parse_time(text,

		atoi=string.atoi,atof=string.atof):
		
    # Time: default to midnight, localtime
    hour,minute,second,offset = 0,0,0.0,0
    match = _timeRE.search(text)
    if match:
	hour,minute,second,zone = match.groups()
	if zone:
	    # Convert to UTC offset
	    offset = Timezone.utc_offset(zone)
	hour = atoi(hour)
	minute = atoi(minute)
	if not second:
	    second = 0.0
	else:
	    second = atof(second)

    if match:
	# Remove time from text
	left,right = match.span()
	if __debug__: print 'parsed time:',repr(text[left:right])
	text = text[:left] + text[right:]

    return text,hour,minute,second,offset

###

def DateTimeFromString(text,

		       DateTime=DateTime.DateTime):

    """ DateTimeFromString(text)
    
        Returns a DateTime instance reflecting the date and time given
        in text. In case a timezone is given, the returned instance
        will point to the corresponding UTC time value. Otherwise, the
        value is set as given in the string.

	Inserts default values for missing parts. Default is today for
	the date part and 0:00:00 for the time part.

    """
    # Date
    text,day,month,year = _parse_date(text)

    if __debug__:
	print 'without date:',repr(text)

    # Time
    text,hour,minute,second,offset = _parse_time(text)

    if __debug__:
	print 'without time:',repr(text)
	print year,month,day,hour,minute,second,offset

    return DateTime(year,month,day,hour,minute,second) - offset

def DateFromString(text,

		   DateTime=DateTime.DateTime):

    """ DateFromString(text)
    
        Returns a DateTime instance reflecting the date given in
        text. A possibly included time part is ignored.

	Inserts default values for missing parts. Default is today for
	the date part and 0:00:00 for the time part.

    """
    # Date
    text,day,month,year = _parse_date(text)

    if __debug__:
	print 'without date:',repr(text)
	print year,month,day,hour,minute,second,offset

    return DateTime(year,month,day)

def DateTimeDeltaFromString(text,

			    atoi=string.atoi,atof=string.atof,
			    DateTimeDelta=DateTime.DateTimeDelta):

    """ DateTimeDeltaFromString(text)
    
        Returns a DateTimeDelta instance reflecting the delta given in
        text. Defaults to 0:00:00:00.00 for parts that are not
        included in the textual representation or cannot be parsed.

    """
    # Date
    match = _isodeltaRE.search(text)
    if match:
	groups = match.groups()
	if __debug__: print groups
	sign = groups[0]
	days = groups[1]
	if days:
	    days = atof(days)
	else:
	    days = 0.0
	hours = atof(groups[2] or groups[5] or groups[8])
	minutes = atof(groups[3] or groups[6] or groups[9])
	seconds = groups[4] or groups[7]
	if seconds:
	    seconds = atof(seconds)
	else:
	    seconds = 0.0
	if sign != '-':
	    return DateTimeDelta(days,hours,minutes,seconds)
	else:
	    return -DateTimeDelta(days,hours,minutes,seconds)

    else:
	match = _litdeltaRE.search(text)
	if match:
	    sign,days,hours,minutes,seconds = match.groups()
	    if days:
		days = atof(days)
	    else:
		days = 0.0
	    if hours:
		hours = atof(hours)
	    else:
		hours = 0.0
	    if minutes:
		minutes = atof(minutes)
	    else:
		minutes = 0.0
	    if seconds:
		seconds = atof(seconds)
	    else:
		seconds = 0.0
	    if sign != '-':
		return DateTimeDelta(days,hours,minutes,seconds)
	    else:
		return -DateTimeDelta(days,hours,minutes,seconds)

	else:
	    # Not matched:
	    return DateTimeDelta(0.0)

# Aliases
TimeFromString = DateTimeDeltaFromString
TimeDeltaFromString = DateTimeDeltaFromString
