1"""Canonical ISO date format YYYY-MM-DDTHH:mm:SS+HH:mm
2
3This parser is _extremely_ strict, and the dates that match it,
4though really easy to work with for the computer, are not particularly
5readable.  See the iso_date_loose module for a slightly relaxed
6definition which allows the "T" character to be replaced by a
7" " character, and allows a space before the timezone offset, as well
8as allowing the integer values to use non-0-padded integers.
9
10
11    ISO_date -- YYYY-MM-DD format, with a month and date optional
12    ISO_time -- HH:mm:SS format, with minutes and seconds optional
13    ISO_date_time -- YYYY-MM-DD HH:mm:SS+HH:mm format,
14        with time optional and TimeZone offset optional
15
16Interpreter:
17    MxInterpreter
18        Interprets the parse tree as mx.DateTime values
19        ISO_date and ISO_time
20            returns DateTime objects
21        Time only
22            returns RelativeDateTime object which, when
23            added to a DateTime gives you the given time
24            within that day
25"""
26try:
27    from mx import DateTime
28    haveMX = 1
29except ImportError:
30    haveMX = 0
31from simpleparse.parser import Parser
32from simpleparse import common, objectgenerator
33from simpleparse.common import chartypes, numbers
34from simpleparse.dispatchprocessor import *
35
36c = {}
37
38declaration ="""
39year      := digit,digit,digit,digit
40month     := digit,digit
41day       := digit,digit
42
43hour      := digit,digit
44minute    := digit,digit
45second    := digit,digit
46offset_sign := [-+]
47offset    := offset_sign, hour, time_separator?, minute
48
49<date_separator> := '-'
50<time_separator> := ':'
51
52ISO_date   := year, (date_separator, month, (date_separator, day)?)?
53ISO_time   := hour, (time_separator, minute, (time_separator, second)?)?
54ISO_date_time  := ISO_date, ([T], ISO_time)?, offset?
55"""
56
57
58
59
60_p = Parser( declaration )
61for name in ["ISO_time","ISO_date", "ISO_date_time"]:
62    c[ name ] = objectgenerator.LibraryElement(
63        generator = _p._generator,
64        production = name,
65    )
66common.share( c )
67
68if haveMX:
69    class MxInterpreter(DispatchProcessor):
70        """Interpret a parsed ISO_date_time_loose in GMT/UTC time or localtime
71        """
72        def __init__(
73            self,
74            inputLocal = 1,
75            returnLocal = 1,
76        ):
77            self.inputLocal = inputLocal
78            self.returnLocal = returnLocal
79        dateName = 'ISO_date'
80        timeName = 'ISO_time'
81        def ISO_date_time( self, info, buffer):
82            """Interpret the loose ISO date + time format"""
83            (tag, left, right, sublist) = info
84            set = singleMap( sublist, self, buffer )
85            base, time, offset = (
86                set.get(self.dateName),
87                set.get(self.timeName) or DateTime.RelativeDateTime(hour=0,minute=0,second=0),
88                set.get( "offset" ),
89            )
90            base = base + time
91            offset = set.get( "offset" )
92            if offset is not None:
93                # an explicit timezone was entered, convert to gmt and return as appropriate...
94                gmt = base - offset
95                if self.returnLocal:
96                    return gmt.localtime()
97                else:
98                    return gmt
99            # was in the default input locale (either gmt or local)
100            if self.inputLocal and self.returnLocal:
101                return base
102            elif not self.inputLocal and not self.returnLocal:
103                return base
104            elif self.inputLocal and not self.returnLocal:
105                # return gmt from local...
106                return base.gmtime()
107            else:
108                return base.localtime()
109        def ISO_date( self, info, buffer):
110            """Interpret the ISO date format"""
111            (tag, left, right, sublist) = info
112            set = {}
113            for item in sublist:
114                set[ item[0] ] = dispatch( self, item, buffer)
115            return DateTime.DateTime(
116                set.get("year") or now().year,
117                set.get("month") or 1,
118                set.get("day") or 1,
119            )
120        def ISO_time( self, info, buffer):
121            """Interpret the ISO time format"""
122            (tag, left, right, sublist) = info
123            set = {}
124            for item in sublist:
125                set[ item[0] ] = dispatch( self, item, buffer)
126            return DateTime.RelativeDateTime(
127                hour = set.get("hour") or 0,
128                minute = set.get("minute") or 0,
129                second = set.get("second") or 0,
130            )
131
132        integer = numbers.IntInterpreter()
133        second =  offset_minute = offset_hour = year = month = day = hour =minute =integer
134
135        def offset( self, info, buffer):
136            """Calculate the time zone offset as a date-time delta"""
137            (tag, left, right, sublist) = info
138            set = singleMap( sublist, self, buffer )
139            direction = set.get('offset_sign',1)
140            hour = set.get( "hour", 0)
141            minute = set.get( "minute", 0)
142            delta = DateTime.DateTimeDelta( 0, hour*direction, minute*direction)
143            return delta
144
145        def offset_sign( self , info, buffer):
146            """Interpret the offset sign as a multiplier"""
147            (tag, left, right, sublist) = info
148            v = buffer [left: right]
149            if v in ' +':
150                return 1
151            else:
152                return -1
153
154