Ticket #2651: ampext.py

File ampext.py, 4.0 KB (added by indigo, 15 years ago)

implementation of nice things

Line 
1import decimal, datetime, re
2from twisted.protocols import amp
3
4
5class _FixedOffsetTZInfo(datetime.tzinfo):
6    '''"Timezones? What timezones?" -- Python standard library'''
7
8    def __init__(self, sign, hours, minutes):
9        self.name = '%s%02i:%02i' % (sign, hours, minutes)
10        if sign == '-':
11            hours = -hours
12            minutes = -minutes
13        elif sign != '+':
14            raise ValueError, 'invalid sign for timezone %r' % (sign,)
15        self.offset = datetime.timedelta(hours=hours, minutes=minutes)
16
17    def utcoffset(self, dt):
18        return self.offset
19
20    def dst(self, dt):
21        return datetime.timedelta(0)
22
23    def tzname(self, dt):
24        return self.name
25
26
27UTCtzinfo = _FixedOffsetTZInfo('+', 0, 0)
28
29
30class Decimal(amp.Argument):
31    '''Encodes decimal.Decimal.
32   
33    Encoding on the wire is done with str() and Decimal(), which works great
34    for python, but may not be the greatest considering interoperability with
35    other decimal types. Someone should look at Decimal.__str__ and see if it
36    is specified sufficiently well to be implemented elsewhere.
37    '''
38    fromString = decimal.Decimal
39    def toString(self, inObject):
40        return str(decimal.Decimal(inObject))
41
42
43class DateTime(amp.Argument):
44    '''Encodes datetime.DateTime.
45   
46    Wire format: '%04i-%02i-%02iT%02i:%02i:%02i.%06i%s%02i:%02i'. Fields in
47    order are: year, month, day, hour, minute, second, microsecond, timezone
48    direction (+ or -), timezone hour, timezone minute. Encoded string is
49    always exactly 32 characters long. This format is compatible with ISO 8601,
50    but that does not mean all ISO 8601 dates can be accepted.
51
52    Also, note that the datetime module's notion of a "timezone" can be
53    complex, but the wire format includes only a fixed offset, so the
54    conversion is not lossless. A lossless transmission of a DateTime instance
55    is not feasible since the receiving end would require a python interpreter.
56    '''
57    def fromString(self, s):
58        if len(s) != 32:
59            raise ValueError, 'invalid date format %r' % (s,)
60
61        # strptime doesn't have any way to parse the microseconds or timezone
62
63        date, timeAndZone = s.split('T', 1)
64        time, zoneSign, zone = re.split(r'(\+|-)', timeAndZone, 2)
65        year, month, day = map(int, date.split('-'))
66        hour, minute, secondAndMicrosecond = time.split(':', 2)
67        hour = int(hour)
68        minute = int(minute)
69        second, microsecond = map(int, secondAndMicrosecond.split('.', 1))
70        zoneHour, zoneMinute = map(int, zone.split(':', 2))
71
72        return datetime.datetime(year, month, day, hour, minute, second, microsecond, _FixedOffsetTZInfo(zoneSign, zoneHour, zoneMinute))
73
74    def toString(self, i):
75        offset = i.utcoffset()
76        if offset is None:
77            raise ValueError, 'datetimes going through AMP must have an associated timezone. You may find ampext.UTCtzinfo useful.'
78
79        minutesOffset = (offset.days * 86400 + offset.seconds) // 60
80
81        if minutesOffset > 0:
82            sign = '+'
83        else:
84            sign = '-'
85
86        # strftime has no way to format the microseconds, or put a ':' in the
87        # timezone. Suprise!
88
89        return '%04i-%02i-%02iT%02i:%02i:%02i.%06i%s%02i:%02i' % (
90            i.year,
91            i.month,
92            i.day,
93            i.hour,
94            i.minute,
95            i.second,
96            i.microsecond,
97            sign,
98            abs(minutesOffset) // 60,
99            abs(minutesOffset) % 60)
100
101
102def test():
103    timezones = [
104        _FixedOffsetTZInfo('+', 0, 0),
105        _FixedOffsetTZInfo('+', 2, 0),
106        _FixedOffsetTZInfo('-', 5, 0),
107        _FixedOffsetTZInfo('+', 3, 19),
108        _FixedOffsetTZInfo('-', 6, 59),
109    ]
110    # callables taking a tz parameter and returning a datetime in that tz
111    datetimes = [
112        datetime.datetime.now,
113    ]
114
115    argument = DateTime()
116
117    print 'checking DateTime'
118    for tz in timezones:
119        for makeDt in datetimes:
120            dt = makeDt(tz)
121            assert argument.fromString(argument.toString(dt)) == dt
122
123    print 'all tests passed'