Next / Previous / Contents / TCC Help System / NM Tech homepage

9.8. parseTime(): Convert an external time string

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:

  1. 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.

  2. 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.

  3. 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.

  4. If there is anything left at this point, it is an error.

sidereal.py
# - - -   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.

sidereal.py
    #-- 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.

sidereal.py
    #-- 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.

sidereal.py
    #-- 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.

sidereal.py
    #-- 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”.

sidereal.py
    #-- 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.

sidereal.py
    #-- 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 )