''' ==================================================================== Copyright (c) 2003-2009 Barry A Scott. All rights reserved. This software is licensed as described in the file LICENSE.txt, which you should have received as part of this distribution. ==================================================================== ''' _debug_parse_time = 0 class DateTimeSyntaxError( Exception ): def __init__( self, reason ): Exception.__init__( self ) self._reason = reason def reason( self ): return self._reason def __str__( self ): return self._reason class DateSyntaxError( DateTimeSyntaxError ): def __init__( self, reason ): DateTimeSyntaxError.__init__( self, reason ) class TimeSyntaxError( DateTimeSyntaxError ): def __init__( self, reason ): DateTimeSyntaxError.__init__( self, reason ) def parse_time( time_string ): """parse_time( time_string ) returns the UTC time represented by time_string british locale defaults used formats understood for a date are: dayname - today, yesterday, monday, tuesday... dd/mm/yy[yy] - numeric date dd-mmm-yy[yy] - month as jan,feb,etc... n units - n units of time ago where units are: seconds minutes hours days weeks months years formats understood for time are: HH:MM - absolute time in hours and minutes HH:MM:SS - absolute time in hours, minutes and seconds formats understood for data and time are: adate atime - absolute date followed by absolute time rdate atime - relative date followed by absolute time atime adate - absolute time followed by absolute date atime rdate - absolute time followed by relative date """ if _debug_parse_time: print( '* parse_time: time_string=',time_string ) date = DateConversions() have_date = 0 have_time = 0 table = string.maketrans("-/"," ") time_list = string.split( string.translate( time_string, table ) ) try: day_time = convert_time( time_list[0] ) time_list = time_list[1:] if _debug_parse_time: print( '* parse_time: Time_list[0] is a time' ) except TimeSyntaxError: try: day_time = convert_time( time_list[-1] ) time_list = time_list[:-1] if _debug_parse_time: print( '* parse_time: Time_list[-1] is a time' ) except TimeSyntaxError: day_time = 0 if len(time_list) == 0: # default to today at time result = date.midnight + day_time if _debug_parse_time: print( '* parse_time: 1 return',format_time(result) ) return result match_type = date.numeric_type matches = [] for word in time_list: day_matches = date.findMatchingDateName( word ) if len(day_matches) == 0: raise DateSyntaxError( "%s unknown date word" % word ) if date.isAmbiguous( day_matches ): raise DateSyntaxError( date.reportAmbiguity( word, day_matches ) ) this_type = date.typeOfMatch( day_matches ) if this_type != date.numeric_type: if match_type == date.numeric_type: match_type = this_type elif match_type != this_type: raise DateSyntaxError( "ambiguous mix of unit and month names" ) matches.append( day_matches[0] ) if _debug_parse_time: print( '* parse_time: matches=',matches ) if match_type == date.day_type: if len(matches) != 1: raise DateSyntaxError( "too many day words" ) day_matches = matches[0] result = date.convertDay( day_matches[2] ) + day_time if _debug_parse_time: print( '* parse_time: 2 return',format_time(result) ) return result if match_type == date.unit_type: # expect a set of pair of <num> <unit> if _debug_parse_time >= 2: print( 'matches',matches ) if (len(matches)&1) == 1: raise DateSyntaxError( 'must have an even number parameters when using time units' ) time_offset = 0 for index in range( 0, len(matches), 2 ): value_tuple = matches[index] unit_tuple = matches[index+1] if value_tuple[1] != date.numeric_type: raise DateSyntaxError( 'Expecting a number of units' ) if unit_tuple[1] != date.unit_type: raise DateSyntaxError( 'Expecting a unit name' ) value = value_tuple[2] unit = unit_tuple[2] time_offset = time_offset + value*unit result = date.now - time_offset if _debug_parse_time: print( '* parse_time: 3 return',format_time(result) ) return result if match_type == date.month_type: # absolute date if len(matches) < 1 or len(matches) > 3: raise DateSyntaxError( 'too many date parts' ) day = -1 month = -1 year = -1 num_month_types = 0 for entry in matches: if date.isMonth( entry ): num_month_types = num_month_types + 1 if num_month_types != 1: raise DateSyntaxError( 'too many months in the date string' ) if date.isMonth( matches[0] ): month = matches[0][2] day = matches[1][2] if len(matches) == 3: year = matches[2][2] else: day = matches[0][2] if matches[1][1] != date.month_type: raise DateSyntaxError( 'expecting month as first or second part of date' ) month = matches[1][2] if len(matches) == 3: year = matches[2][2] seconds = day_time%60 minutes = (day_time/60)%60 hours = day_time/60/60 result = date.absDate( day, month, year, hours, minutes, seconds ) if _debug_parse_time: print( '* parse_time: 4 return',format_time(result) ) return result if match_type == date.numeric_type and len(matches) == 3: # assume its in locale order - which is assumed to be D M Y day = matches[0][2] month = matches[1][2] year = matches[2][2] seconds = day_time%60 minutes = (day_time/60)%60 hours = day_time/60/60 result = date.absDate( day, month, year, hours, minutes, seconds ) if _debug_parse_time: print( '* parse_time: 4 return',format_time(result) ) return result raise DateSyntaxError( 'cannot understand date and time string ' + time_string ) def convert_time( time_str ): time_list = string.split( time_str, ':' ) if len(time_list) < 2: # not a time - no ":" raise TimeSyntaxError( "Not a time" ) if len(time_list) > 3: raise TimeSyntaxError( "Too many time parts" ) hour = time_list[0] minute = time_list[1] second = '0' if len(time_list) > 2: second = time_list[2] try: hour = string.atoi( hour ) minute = string.atoi( minute ) second = string.atoi( second ) except: return -1 if( hour < 0 or hour > 23 ): raise TimeSyntaxError( "hour value of %d invalid" % hour ) if( minute < 0 or minute > 59 ): raise TimeSyntaxError( "minutes value of %d invalid" % hour ) if( second < 0 or second > 59 ): raise TimeSyntaxError( "seconds value of %d invalid" % hour ) day_time = (hour*60 + minute)*60 + second return day_time class DateConversions: seconds_in_one_day = 24*60*60 day_type = 1 month_type = 2 unit_type = 3 numeric_type = 4 date_names = [ # day names ('today', day_type, 0), ('yesterday', day_type, -1), ('monday', day_type, 1), ('tuesday', day_type, 2), ('wednesday', day_type, 3), ('thursday', day_type, 4), ('friday', day_type, 5), ('saturday', day_type, 6), ('sunday', day_type, 7), # month names ('january', month_type, 1), ('feburary', month_type, 2), ('march', month_type, 3), ('april', month_type, 4), ('may', month_type, 5), ('june', month_type, 6), ('july', month_type, 7), ('august', month_type, 8), ('september', month_type, 9), ('october', month_type, 10), ('november', month_type, 11), ('december', month_type, 12), # unit names ('seconds', unit_type, 1), ('minutes', unit_type, 60), ('hours', unit_type, 60*60), ('days', unit_type, 24*60*60.), ('weeks', unit_type, 7*24*60*60), ('months', unit_type, 30*24*60*60), ('years', unit_type, 365*24*60*60) ] def __init__( self ): self.now = time.time() self.year, self.month, self.day, self.hour, self.minute, self.second, self.weekday, self.julian, self.dst = time.localtime( self.now ) self.midnight = time.mktime( (self.year, self.month, self.day, 0, 0, 0, self.weekday, self.julian, self.dst) ) def convertDay( self, day_offset ): if day_offset == 0: # today return self.midnight elif day_offset == -1: # yesterday return self.midnight - self.seconds_in_one_day else: # day of week offset = day_offset - self.weekday - 1 # make sure its in the past if offset >= 0: offset = offset - 7 return self.midnight + offset*self.seconds_in_one_day def absDate( self, day, month, year, hour=0, minute=0, second=0 ): future_check = 0 if year < 0: year = self.year future_check = 1 elif year < 70: year = year + 2000 elif year < 100: year = year + 1900 try: date = time.mktime( (year, month, day, hour, minute, second, self.weekday, self.julian, -1) ) except OverflowError: raise DateSyntaxError( 'cannot convert date and time year=%d/month=%d/day=%d %d:%d:%d' % (year, month, day, hour, minute, second) ) if date > self.now and future_check: year = year - 1 try: date = time.mktime( (year, month, day, hour, minute, second, self.weekday, self.julian, -1) ) except OverflowError: raise DateSyntaxError( 'cannot convert date and time %d/%d/%d %d:%d:%d' % (year, month, day, hour, minute, second) ) return date def findMatchingDateName( self, name ): try: value = string.atoi( name ) return [(name, self.numeric_type, value )] except: pass matches = [] name_len = len(name) for entry in self.date_names: entry_name = entry[0] if len(entry_name) >= name_len and entry_name[0:name_len] == name: matches.append( entry ) return matches def typeOfMatch( self, matches ): return matches[0][1] def isAmbiguous( self, matches ): return len(matches) > 1 def isDay( self, matches ): return matches[1] == self.day_type def isMonth( self, matches ): return matches[1] == self.month_type def isUnit( self, matches ): return matches[1] == self.unit_type def isNumeric( self, matches ): return matches[1] == self.numeric_type def reportAmbiguity( self, name, tuples ): names = map( lambda t: t[0], tuples ) return "%s is ambiguous, it matches: %s" % (name, string.join( names, ', ' ))