#!/usr/bin/python

# tz_parse_time.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       July 7, 2003            bar
#       October 27, 2006        bar     parse another format
#       July 21, 2007           bar     nist ascii format
#       July 22, 2007           bar     .gpx format
#       July 24, 2007           bar     now that nist is back on line, let's subtract rather than add the msADV value
#       July 27, 2007           bar     exif and gps/exif timezone parsing
#                                       parse_time_zone
#       November 18, 2007       bar     turn on doxygen
#       November 20, 2007       bar     comments
#       November 27, 2007       bar     add fractional seconds to gpx and exif times (the latter 'cause they must have the same fields for the cheap code to work)
#       November 27, 2007       bar     insert boilerplate copyright
#       December 1, 2007        bar     time_diff_str()
#       December 3, 2007        bar     comment
#       December 9, 2007        bar     comments and allow 1900 years in the month-name forms 1 and 2
#                                       allow fractional seconds in parse_time_zone
#       May 17, 2008            bar     email adr
#       November 18, 2008       bar     hmmss_str()
#       May 21, 2011            bar     another time parse type MM dd, yyyy hh:mm:ss
#       --eodstamps--
##      \file
#
#
#       Do some ASCII time parsing.
#
#


import  re
import  time




##      Days-in-months table
nyda    = [ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ]

def make_utime(mo, d, y, h, mi, s) :
    """
        Given month, day, year, hour, minute, second,
        return the Unix (1/1/1970 based) time as a float.

        mo is 1..12 (or 0 if d has the day-of-year)
        d  is 1..31 or day-of-year 1..365)
        y  is 1938..2037 or 0..99
        h  is 0..23
        mi is 0..59
        s  is 0..59 or 0.0 .. 59.9999999....

        This routine accepts odd-ball values for parameters.
    """

    if  not 0 < mo <= len(nyda) :                               # we have a day-of-year and no month?
        mo  = 1

    if  y      >=  100 :
        y      -= 1970                                          # way-off years will get weird 'cause 100 year and 400 year and 1000 year logic isn't here, really !!!!
    else :
        y      %= 100                                           # yes, bad data to this routine gives bad results, not exceptions
        if  y  >= 38 :
            y  -= 70
        else :
            y  += 30
        pass


    t = float(s) + (
        (mi * 60) +
        (h * 3600) +
        ((d - 1) * 86400) +
        (nyda[mo - 1] * 86400) +
        ((365 * 86400) * y) +
        (86400 * ((y + 1) / 4))
        )

    if  ((y % 4) == 2) and (mo >= 3) :
        t  += 86400                                             #  add Feb 29th of this year if we're past it

    return(t)


def mktime(tm) :
    """ Return the GMT (absulute) time for the given mdyhms). Not to be confused with time.mktime() which returns localtime and must have time.timezone subtracted from it. """

    return(make_utime(tm[1], tm[2], tm[0], tm[3], tm[4], tm[5]))



def hmmss_str(t) :
    """ Return an hour:minute:second string for the given time. """

    t   = int(round(t or 0.0))
    return("%u:%02u:%02u" % ( t / 3600, (t % 3600) / 60, t % 60 ))




def time_diff_str(gmt, ot) :
    """ Return a string telling how far off a GMT time is to the "correct" 'gmt' time. """

    s   = ""

    d   = gmt - ot

    a       = " slow"
    if  d < 0 :
        a   = " fast"
        d   = -d

    d   = int(d)

    dd  = d / (24 * 3600)
    d  %=     (24 * 3600)
    if  dd :
        ss                  = ""
        if  dd > 1 :    ss  = "s"
        s  += " %u day%s" % ( dd, ss )

    dd  = d / 3600
    d  %=     3600
    if  dd :
        ss                  = ""
        if  dd > 1 :    ss  = "s"
        s  += " %u hour%s" % ( dd, ss )

    dd  = d / 60
    d  %=     60
    if  dd :
        ss                  = ""
        if  dd > 1 :    ss  = "s"
        s  += " %u minute%s" % ( dd, ss )

    dd  = int(d)
    if  dd :
        ss                  = ""
        if  dd > 1 :    ss  = "s"
        s  += " %u second%s" % ( dd, ss )

    s      += a

    return(s.strip())







##      regx to detect a time zone.
tz_re   = re.compile(r"(?:Z|GMT|([+-]?\d+)(?:\:(\d+)(?:\:(\d+(?:\.\d+)?))?)?)")


def parse_time_zone(s) :
    """
        Given a string, see if we can find the time zone in it.

        Return None if we cannot.

        Return the time zone offset from GMT in seconds if we can.
    """

    g   = tz_re.search(s)
    if  not g :
        return(None)

    neg =   False
    t   =   0
    if  g.lastindex > 0 :
        t   =   float(g.group(1)) * 3600
        if  t < 0 :
            t   = -t
            neg = True
        pass
    if  g.lastindex > 1 :
        t  += (float(g.group(2)) * 60)
    if  g.lastindex > 2 :
        t  += float(g.group(3))
    if  neg :
        t   = -t

    return(t)



##              Months in 3-char English form
month_str   =   "janfebmaraprmayjunjulaugsepoctnovdec"

##              regx to sense a month in 3-char or full name English
mon_re_str  =   r"(Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)(?:,|\s+)\s*"

##                                   (DOW)        DD          Month          Year              HH     MM     SS                             e.g. "Tue 23 Jan 2007 03:56:45" "Tuesday 23 January 2007 03:56:45"
form_1_re   =   re.compile(r"\s*(?:\b[a-z]+,?\s+)?(\d+)\s+" + mon_re_str + r"((?:19|20)\d\d)\s+(\d\d):(\d\d):(\d\d)\s+GMT\b",               re.IGNORECASE + re.DOTALL)

##                                   (DOW)                    Month          DD      HH     MM     SS       Year                            e.g. "Tue Jan 23 2007 03:56:45" "Tuesday January 23 2007 03:56:45"
form_2_re   =   re.compile(r"\s*(?:\b[a-z]+,?\s+)?"         + mon_re_str + r"(\d+)\s+(\d\d):(\d\d):(\d\d)\s+((?:19|20)\d\d)\b",             re.IGNORECASE + re.DOTALL)

##                                   (DOW)                    Month          DD        Year              HH      MM          SS             e.g. "Jan 23 2007 03:56:45" "Tuesday January 23, 2007 03:56"
form_3_re   =   re.compile(r"\s*(?:\b[a-z]+,?\s+)?"         + mon_re_str + r"(\d+),?\s+((?:19|20)\d\d)\s+(\d?\d):(\d?\d)(?:\:(\d?\d))?\b",  re.IGNORECASE + re.DOTALL)



##              regx to extract time/date from a nist time server response                                          e.g. "54444 07-12-10 07:00:12 00 0 0  32.5 UTC(NIST) *"
nist_re     =   re.compile(r"(\d+) (\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d) (\d+) (\d+) (\d+) (\d+\.\d+) UTC\(NIST\) \*")

##              regx to extract time/date from a .gpx file (and ISO 8601 - http://www.w3.org/TR/NOTE-datetime)      e.g. "2007-12-08T17:36:03Z" "2007-12-08T17:36:03-08:00" "2007-12-08T17:36:03.25-08:00"
gpx_re      =   re.compile(r"(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d(?:\.\d+)?)(?:Z|([+-]\d\d):(\d\d))")

##              regx to extract time/date from EXIF format                                                          e.g. "2007:12:08 18:19:11"
exif_re     =   re.compile(r"(\d\d\d\d):(\d\d):(\d\d)\s+(\d\d):(\d\d):(\d\d(?:\.\d+)?)(?:Z|([+-]\d\d):(\d\d))?")



def parse_time(s) :
    """
        Parse time in the form:         DOW DD Mon(th) YYYY HH:MM:SS GMT
        Or in this mailbox file form:   DOW Mon(th) DD HH:MM:SS YYYY
        The year must be in 20xx.
    """

    tzone   = 0         # time.timezone

    #
    #   Note: Win boxes don't have time.strptime() and it doesn't seem to work, anyway, on Gina/Python22, at any rate.
    #
    g       = form_1_re.search(s)
    if  g   :
        dc  = g.group(2)[0:3].lower()
        mon = int(month_str.find(dc) / 3) + 1

        try :
            t = mktime( ( int(g.group(3)), mon, int(g.group(1)), int(g.group(4)), int(g.group(5)), int(g.group(6)), -1, -1, 0 ) ) - tzone
            return(t)

        except ValueError :
            pass
        except OverflowError :
            pass
        pass

    g       = form_2_re.search(s)
    if  g   :
        dc  = g.group(1)[0:3].lower()
        mon = int(month_str.find(dc) / 3) + 1

        try :
            t = mktime( ( int(g.group(6)), mon, int(g.group(2)), int(g.group(3)), int(g.group(4)), int(((g.lastindex >= 5) and g.group(5)) or 0), -1, -1, 0 ) ) - tzone
            return(t)

        except ValueError :
            pass
        except OverflowError :
            pass
        pass


    g       = form_3_re.search(s)
    if  g   :
        dc  = g.group(1)[0:3].lower()
        mon = int(month_str.find(dc) / 3) + 1

        try :
            t = mktime( ( int(g.group(3)), mon, int(g.group(2)), int(g.group(4)), int(g.group(5)), int(((g.lastindex >= 6) and g.group(6)) or 0), -1, -1, 0 ) ) - tzone
            return(t)

        except ValueError :
            pass
        except OverflowError :
            pass
        pass


    g       = nist_re.search(s)
    if  g   :
        try :
            t   = mktime( ( int(g.group(2)), int(g.group(3)), int(g.group(4)), int(g.group(5)), int(g.group(6)), int(g.group(7)), -1, -1, 0 ) ) - tzone
            t  -= (float(g.group(11)) / 1000.0)

            return(t)

        except ValueError :
            pass
        except OverflowError :
            pass
        pass


    g       = gpx_re.search(s)
    if  not g :
        g   = exif_re.search(s)
    if  g   :
        try :
            t   = mktime( ( int(g.group(1)), int(g.group(2)), int(g.group(3)), int(g.group(4)), int(g.group(5)), float(g.group(6)), -1, -1, 0 ) ) - tzone

            if  g.lastindex == 8 :
                h   =   int(g.group(7)) * 3600
                m   =   int(g.group(8)) * 60
                if  h < 0 :
                    m   = -m
                t  -=   (h + m)

            return(t)

        except ValueError :
            pass
        except OverflowError :
            pass
        pass


    return(None)


#
#
#
if __name__ == '__main__' :
    import  sys

    sys.argv.pop(0)

    if  len(sys.argv) == 0 :
        sys.argv.append("From - Fri Oct 27 16:57:16 2006")
        sys.argv.append("Fri 27 ocT 2006 16:57:16 GMT")
        sys.argv.append("From - Fri Oct 27 16:57:16 1996")
        sys.argv.append("Fri 27 ocT 1996 16:57:16 GMT")
        sys.argv.append("    27 ocT,1996 16:57:16 GMT")
        sys.argv.append("2006-10-27T16:57:16Z")
        sys.argv.append("2006-10-27T15:50:16-01:07")
        sys.argv.append("2006:10:27 16:57:16")
        sys.argv.append("2006:10:27 16:57:16Z")
        sys.argv.append("2006:10:27 15:50:16-01:07")
        sys.argv.append("2006:10:27 17:59:16+01:02")
        sys.argv.append("October  27, 2006 17:59")
        sys.argv.append("October  27  2006 17:59:00")
        sys.argv.append("October, 27  2006 17:59:01")

        #
        #   test fractions of seconds
        #
        sys.argv.append("2006:10:27 16:57:16.25Z")
        sys.argv.append("2006:10:27 17:59:16.5+01:02")
        sys.argv.append("2006-10-27T16:57:16.25Z")
        sys.argv.append("2006-10-27T15:50:16.5-01:07")

        sys.argv.append("54303 06-10-27 16:57:16 50 0 0 500.0 UTC(NIST) *")

        sys.argv.append("1970-01-01T00:00:00.0000Z")
        sys.argv.append("1970-12-31T12:13:14.1516Z")
        sys.argv.append("1969-12-31T23:59:59.99999Z")
        sys.argv.append("1938-01-01T00:00:00.0000Z")
        sys.argv.append("2037-12-31T23:59:59.99Z")
        sys.argv.append("1993-02-01T00:01:02.03Z")
        sys.argv.append("1993-03-01T00:01:02.03Z")
        sys.argv.append("1994-02-01T00:01:02.03Z")
        sys.argv.append("1994-03-01T00:01:02.03Z")
        sys.argv.append("1995-02-01T00:01:02.03Z")
        sys.argv.append("1995-03-01T00:01:02.03Z")
        sys.argv.append("1996-02-01T00:01:02.03Z")
        sys.argv.append("1996-03-01T00:01:02.03Z")
        sys.argv.append("1997-02-01T00:01:02.03Z")
        sys.argv.append("1997-03-01T00:01:02.03Z")
        sys.argv.append("1999-02-01T00:01:02.03Z")
        sys.argv.append("1999-03-01T00:01:02.03Z")
        sys.argv.append("2000-02-01T00:01:02.03Z")
        sys.argv.append("2000-03-01T00:01:02.03Z")
        sys.argv.append("2001-02-01T00:01:02.03Z")
        sys.argv.append("2001-03-01T00:01:02.03Z")
        sys.argv.append("2000-01-01T00:01:02.03Z")
        sys.argv.append("2000-12-01T23:22:21.20Z")
        sys.argv.append("2038-01-02T03:04:05.06Z")
        sys.argv.append("2038-01-19T03:14:07Z")
        sys.argv.append("2038-01-19T03:14:08Z")
        sys.argv.append("2038-12-11T10:09:08.07Z")
        sys.argv.append("2039-12-11T10:09:08.07Z")

        print "All times should be 1161968236.0 - except NIST and fractional second times, which should be .5 less - and time tests after NIST."

    for a in xrange(len(sys.argv)) :
        p = sys.argv[a]

        print "%50s" % ( p ),

        t = parse_time(p)

        if  t == None :
            print "----"
        else :
            try :
                ts  = time.asctime(time.gmtime(t))
            except ValueError :
                ts  = ""
            print "%18.6f %s" % ( t, ts )

        pass
    pass



##      Public things.
__ALL__ = [
            'parse_time',
          ]


#
#
#
# eof

