This function validates and converts an external time of
day s, with optional time zone modifier,
as described in http://www.nmt.edu/tcc/help/lang/python/examples/sidereal/parseTime.html.
Rather than try to do all the parsing and breaking out of pieces with one big regular expression, we'll use a mixed strategy that goes roughly like this:
The string s must start with one or
more digits, optionally followed by a decimal point
and more digits. Remove this and save it as a float in decHour.
If what is left starts with “:”, remove that, followed by another float
number, and save as as decMinute.
Similarly, if what follows that is another “:”, remove
the second colon and save the number that must follow
in decSecond.
If there is still something left, it must be a time
zone modifier. Call Section 9.9, “parseZone(): Process a time zone
suffix” to
validate and convert that to an instance of a class
that inherits from datetime.tzinfo.
If there is anything left at this point, it is an error.
# - - - p a r s e T i m e
def parseTime ( s ):
"""Validate and convert a time and optional zone.
[ s is a string ->
if s is a valid time with optional zone suffix ->
return that time as a datetime.time
else -> raise SyntaxError ]
"""
The first order of business is to remove the time from the
front of the string. We use Section 9.16, “parseFloat(): Parse a floating-point
number” to
match each floating-point number.
#-- 1 -
# [ if s starts with FLOAT_PAT ->
# decHour := matching part of s as a float
# minuteTail := part s past the match
# else -> raise SyntaxError ]
decHour, minuteTail = parseFloat ( s, "Hour number" )
If minuteTail starts with a colon, remove
the minutes.
#-- 2 --
# [ if minuteTail starts with ":" followed by FLOAT_PAT ->
# decMinute := part matching FLOAT_PAT as a float
# secondTail := part of minuteTail after the match
# else if minuteTail starts with ":" not followed by
# FLOAT_PAT ->
# raise SyntaxError
# else ->
# decMinute := 0.0
# secondTail := minuteTail ]
if minuteTail.startswith(':'):
m = FLOAT_PAT.match ( minuteTail[1:] )
if m is None:
raise SyntaxError, ( "Expecting minutes: '%s'" %
minuteTail )
else:
decMinute = float(m.group())
secondTail = minuteTail[m.end()+1:]
else:
decMinute = 0.0
secondTail = minuteTail
If secondTail starts with a colon, remove
the seconds.
#-- 3 --
# [ if secondTail starts with ":" followed by FLOAT_PAT ->
# decSecond := part matching FLOAT_PAT as a float
# zoneTail := part of secondTail after the match
# else if secondTail starts with ":" not followed by
# FLOAT_PAT ->
# raise SyntaxError
# else ->
# decSecond := 0.0
# zoneTail := secondTail ]
if secondTail.startswith(':'):
m = FLOAT_PAT.match ( secondTail[1:] )
if m is None:
raise SyntaxError, ( "Expecting seconds: '%s'" %
secondTail )
else:
decSecond = float(m.group())
zoneTail = secondTail[m.end()+1:]
else:
decSecond = 0.0
zoneTail = secondTail
If anything is left, it had better be a zone.
#-- 4 --
# [ if zoneTail is empty ->
# tz := None
# else if zoneTail is a valid zone suffix ->
# tz := that zone information as an instance of a class
# that inherits from datetime.tzinfo
# else -> raise SyntaxError ]
if len(zoneTail) == 0:
tz = None
else:
tz = parseZone ( zoneTail )
All that remains is to assemble the parts. At this point
we have three numbers for hours, minutes, and seconds,
any of which may not be integral. In order to conform
to the way the datetime module likes to
see times, we'll convert that to decimal hours, then back
to mixed units, using Section 11, “dmsUnits: Mixed-units converter”.
#-- 5 --
# [ hours := decHour + decMinute/60.0 + decSecond/3600.0 ]
hours = dmsUnits.mixToSingle ( (decHour, decMinute, decSecond) )
The datetime.time() constructor wants integer
hours, minutes, seconds, and microseconds. The dmsUnits.singleToMix() method returns integer hours,
integer minutes, and floating seconds; we'll do the remaining
two conversions explicitly.
#-- 6 --
# [ return a datetime.time representing hours ]
hh, mm, seconds = dmsUnits.singleToMix ( hours )
wholeSeconds, fracSeconds = divmod ( seconds, 1.0 )
ss = int(wholeSeconds)
usec = int ( fracSeconds * 1e6 )
return datetime.time ( hh, mm, ss, usec, tz )