""" This module provides a set of constructors and routines to convert
    between DateTime[Delta] instances and ARPA representations of date
    and time. The format is specified by RFC822 + RFC1123.

    (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 DateTime

import re,string

# Grammar: RFC822 + RFC1123 + depreciated RFC850
_litday = '(?P<litday>Mon|Tue|Wed|Thu|Fri|Sat|Sun)[a-z]*'
_litmonth = '(?P<litmonth>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'\
            '[a-z]*'
_date = ('(?:(?P<day>\d?\d)(?: +' + _litmonth + 
	 ' +|-(?P<month>\d?\d)-)(?P<year>(?:\d\d)?\d\d))')
_zone = ('(?P<zone>UT|UTC|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|'
	 '[A-Z]|[+-]\d\d\d\d)')
_time = ('(?:(?P<hour>\d\d):(?P<minute>\d\d)'
	 '(?::(?P<second>\d\d))?(?: +'+_zone+')?)')
#       Timezone information is made optional because some mail apps
#       forget to add it (most of these seem to be spamming engines, btw).
#       It defaults to UTC.
_datetime = '(?:'+ _litday + ', )? *' + _date + ' +' + _time # + '$'
#       We are not strict on the extra characters: some applications
#       add extra information to the date header field. Additional spaces
#       between the fields and extra characters in the literal day
#       and month fields are also silently ignored.

_arpadatetime = re.compile(_datetime)

# Translation tables
_daytable = {'Mon':0, 'Tue':1, 'Wed':2, 'Thu':3, 'Fri':4, 'Sat':5, 'Sun':6 }
_monthtable = {'Jan':1, 'Feb':2, 'Mar':3, 'Apr':4, 'May':5, 'Jun':6,
	      'Jul':7, 'Aug':8, 'Sep':9, 'Oct':10, 'Nov':11, 'Dec':12 }
_days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
_months = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
	         'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]


_zonetable = {# Standards
              'UT':0, 'UTC':0, 'GMT':0,
	      # US time zones (normal + dst)
              'AST':-4, 'ADT':-3,
              'EST':-5, 'EDT':-4,
              'CST':-6, 'CDT':-5,
              'MST':-7, 'MDT':-6,
              'PST':-8, 'PDT':-7,
	      # US military time zones
	      'Z': 0,
	      'A': 1,
	      'B': 2,
	      'C': 3,
	      'D': 4,
	      'E': 5,
	      'F': 6,
	      'G': 7,
	      'H': 8,
	      'I': 9,
	      'K': 10,
	      'L': 11,
	      'M': 12,
	      'N':-1,
	      'O':-2,
	      'P':-3,
	      'Q':-4,
	      'R':-5,
	      'S':-6,
	      'T':-7,
	      'U':-8,
	      'V':-9,
	      'W':-10,
	      'X':-11,
	      'Y':-12
              }    
_zoneoffset = re.compile('(?P<sign>[+-])(?P<value>\d\d\d\d)')

def _utc_offset(zone,

	       atoi=string.atoi):

    """ Return the UTC time zone offset as DateTimeDelta instance.
    """ 
    if zone is None:
	return 0
    if _zonetable.has_key(zone):
	return _zonetable[zone]*DateTime.oneHour
    offset = _zoneoffset.match(zone)
    if not offset:
	raise ValueError,'wrong format or unkown time zone -- use +-HHMM'
    sign,value = offset.groups()
    value = atoi(value[:2])*60 + atoi(value[3:])
    if sign == '-':
	value = -value
    return value*DateTime.oneMinute

def ParseDateTime(arpastring,

		  strip=string.strip,atoi=string.atoi,atof=string.atof):

    """ParseDateTime(arpastring)

    Returns a DateTime instance reflecting the given ARPA date assuming
    it is local time (timezones are silently ignored)."""

    s = strip(arpastring)
    date = _arpadatetime.match(s)
    if not date:
	raise ValueError,'wrong format or unkown time zone'
    litday,day,litmonth,month,year,hour,minute,second,zone = date.groups()
    if len(year) == 2:
	year = 1900 + atoi(year)
    else:
	year = atoi(year)
    if litmonth:
	try:
	    month = _monthtable[litmonth]
	except KeyError:
	    raise ValueError,'wrong month format'
    else:
	month = atoi(month)
    day = atoi(day)
    hour = atoi(hour)
    minute = atoi(minute)
    if second is None:
	second = 0.0
    else:
	second = atof(second)
    # litday and timezone are ignored
    return DateTime.DateTime(year,month,day,hour,minute,second)

def ParseDateTimeGMT(arpastring,

		     strip=string.strip,atoi=string.atoi,atof=string.atof):

    """ParseDateTimeGMT(arpastring)

    Returns a DateTime instance reflecting the given ARPA date converting
    it to UTC (timezones are honored)."""

    s = strip(arpastring)
    date = _arpadatetime.match(s)
    if not date:
	raise ValueError,'wrong format or unkown time zone'
    litday,day,litmonth,month,year,hour,minute,second,zone = date.groups()
    if len(year) == 2:
	year = 1900 + atoi(year)
    else:
	year = atoi(year)
    if litmonth:
	try:
	    month = _monthtable[litmonth]
	except KeyError:
	    raise ValueError,'wrong month format'
    else:
	month = atoi(month)
    day = atoi(day)
    hour = atoi(hour)
    minute = atoi(minute)
    if second is None:
	second = 0.0
    else:
	second = atof(second)
    offset = _utc_offset(zone)
    # litday is ignored
    return DateTime.DateTime(year,month,day,hour,minute,second) - offset

# Alias
ParseDateTimeUTC = ParseDateTimeGMT

def str(datetime,tz=None,
        # Locals:
        tz_offset=DateTime.tz_offset):

    """str(datetime,tz=DateTime.tz_offset(datetime))

    Returns the datetime instance as ARPA date string. tz can be given
    as DateTimeDelta instance providing the time zone difference from
    datetime's zone to UTC. It defaults to
    DateTime.tz_offset(datetime) which assumes local time. """

    if tz is None:
        tz = tz_offset(datetime)
    return '%s, %02i %s %04i %02i:%02i:%02i %+03i%02i' % (
	_days[datetime.day_of_week], datetime.day, 
	_months[datetime.month], datetime.year,
	datetime.hour, datetime.minute, datetime.second,
	tz.hour,tz.minute)

def strGMT(datetime):

    """ strGMT(datetime)

    Returns the datetime instance as ARPA date string assuming it
    is given in UTC. """

    return '%s, %02i %s %04i %02i:%02i:%02i GMT' % (
	_days[datetime.day_of_week], datetime.day, 
	_months[datetime.month], datetime.year,
	datetime.hour, datetime.minute, datetime.second)

# Alias
strUTC = strGMT

def _test():
    import sys, os, rfc822
    file = os.path.join(os.environ['HOME'], 'nsmail/Inbox')
    f = open(file, 'r')
    while 1:
	m = rfc822.Message(f)
	if not m:
	    break
	print 'From:', m.getaddr('from')
	print 'To:', m.getaddrlist('to')
	print 'Subject:', m.getheader('subject')
	raw = m.getheader('date')
	try:
	    date = ParseDateTimeUTC(raw)
	    print 'Date:',strUTC(date)
	except ValueError,why:
	    print 'PROBLEMS:',repr(raw),'-->',why
            raw_input('...hit return to continue')
	print
	# Netscape mail file
	while 1:
	    line = f.readline()
	    if line[:6] == 'From -':
		break

if __name__ == '__main__':
    _test()
