root / shoop / tribune / engine / clocks.py

1
# -*- coding: utf-8 -*-
2
import datetime, re
3
4
SUP_KEYS = u'¹²³⁴⁵⁶⁷⁸⁹'
5
6
def Clock_Indice(indice):
7
    """
8
    Renvoi un indice d'horloge sous plusieurs formats
9
    """
10
    indice_sup = ''
11
    
12
    # Indice en string sur deux digits forcés
13
    indice_padded = str( indice ).zfill(2)
14
    
15
    # Indice du type ²
16
    if indice > 1:
17
        indice_sup = "".join( [SUP_KEYS[int(i)-1] for i in str(indice)] )
18
19
20
    return indice, indice_padded, indice_sup
21
22
class ClockstampManipulator:
23
    """
24
    Manipulateur d'horloge 
25
    """
26
    def __init__(self, clockstamp):
27
        self.clockstamp = clockstamp
28
        self.obj = False
29
        self.now = datetime.datetime.now()
30
        # Format "time" par défaut
31
        self.datestamp = None
32
        self.timestamp = clockstamp
33
        # Vérification de la validité de base
34
        self.is_valid = False
35
        try:
36
            int(self.clockstamp)
37
        except ValueError:
38
            pass
39
        else:
40
            # Un time fait au moins 4caractères, un datetime pas plus de 14
41
            if len(self.clockstamp)>=4 and len(self.clockstamp)<=16:
42
                self.is_valid = True
43
        # Un datetime fait toujours plus de 6caractères (pour le time complet) 
44
        # et jamais plus de 14
45
        self.is_datetime = False
46
        if len(self.clockstamp)>8 and len(self.clockstamp)<=16:
47
            self.is_datetime = True
48
        self.is_time = self.is_valid and not( self.is_datetime )
49
        # Détection d'une horloge avec "datetime" ou "time"
50
        if self.is_datetime:
51
            if len(self.clockstamp)>12:
52
                # Date avec l'année
53
                self.datestamp = clockstamp[0:8]
54
                self.timestamp = clockstamp[8:]
55
            else:
56
                # Date sans l'année
57
                self.datestamp = clockstamp[0:4]
58
                self.timestamp = clockstamp[4:]
59
            self.obj = self.get_clock_object(clocktype="datetime")
60
        else:
61
            self.obj = self.get_clock_object(clocktype="time")
62
        
63
    def get_clock_object(self, clocktype="datetime"):
64
        """
65
        Renvoi un objet datetime.time() de l'horloge si s'en est bien une.
66
        """
67
        if self.is_valid:
68
            # Heure de base, avec la microsecond à zéro
69
            clockDict = self.format_clock_time()
70
            if self.is_datetime:
71
                clockDict.update( self.format_clock_date() )
72
            # Rajoute une étape de fin
73
            clockDict_end = clockDict.copy()
74
            clockDict_end['microsecond'] = 999999
75
            # Init de l'objet datetime
76
            try:
77
                obj = getattr(datetime, clocktype)(**clockDict)
78
            except ValueError:
79
                self.is_valid = False
80
            else:
81
                return obj
82
        return None
83
    
84
    def format_clock_date(self):
85
        """
86
        Dictionnaire de la date de l'horloge. Le format date doit etre sous 
87
        la forme: YYYYMMDD
88
        
89
        YYYY : Année sous 4digits
90
        MM : Mois sous 2digits
91
        DD : Jour sous 2digits
92
        """
93
        clockDict = {
94
            'year': self.now.year,
95
            'month': self.now.month,
96
            'day': self.now.day,
97
        }
98
        clock = self.datestamp
99
        # Année optionnelle
100
        if len(clock) > 4:
101
            clockDict['year'] = int(clock[0:4])
102
            clock = clock[4:]
103
        # Mois
104
        if len(clock) > 0:
105
            clockDict['month'] = int(clock[0:2])
106
            clock = clock[2:]
107
            # Jour
108
            if len(clock) > 0:
109
                clockDict['day'] = int(clock[0:2])
110
        
111
        return clockDict
112
        
113
    def format_clock_time(self):
114
        """
115
        Dictionnaire du time de l'horloge. Le format time doit etre sous 
116
        la forme: HHTTSS
117
        
118
        HH : Heures sous 2digits
119
        TT : Minutes sous 2digits
120
        SS : Secondes sous 2digits
121
        
122
        Les secondes sont toujours optionnelles quelque soit le format.
123
        """
124
        clockDict = {
125
            'hour': 0,
126
            'minute': 0,
127
            'second': 0,
128
            'microsecond': 0,
129
        }
130
        clock = self.timestamp
131
        # Heure
132
        clockDict['hour'] = int(clock[0:2])
133
        clock = clock[2:]
134
        # Minutes
135
        if len(clock) > 0:
136
            clockDict['minute'] = int(clock[0:2])
137
            clock = clock[2:]
138
            # Secondes
139
            if len(clock) > 0:
140
                clockDict['second'] = int(clock[0:2])
141
                clock = clock[2:]
142
                # Microseconde de départ
143
                clockDict['microsecond'] = 0
144
        
145
        return clockDict
146
        
147
    def get_lookup(self):
148
        """
149
        Renvoi le bon lookup selon qu'on a un time ou un datetime.
150
        
151
        Le lookup renvoi toujours une requete de type "range" qui englobe 
152
        toute les microsecondes, étant donné qu'on ne les connaient jamais via 
153
        l'horloge.
154
        On les prends donc toute car l'éventualité que plusieurs posts soit 
155
        postés la meme seconde est assez maigre pour l'instant sauf avec des 
156
        bots.
157
        """
158
        # On doit vérifier qu'on a bien un datetime avant d'appeler ses méthodes
159
        if self.is_datetime:
160
            return self.get_datetime_lookup()
161
        # Dans le cas contraire on a obligatoirement un time
162
        else:
163
            return self.get_time_lookup()
164
        
165
    def get_time_lookup(self):
166
        """
167
        Renvoi un lookup de recherche pour les dernières horloges de la meme 
168
        heure.
169
        """
170
        if self.obj:
171
            return {
172
                'norloge__range': (self.obj, self.obj.replace(microsecond=999999)),
173
            }
174
        
175
        return {}
176
    
177
    def get_datetime_lookup(self):
178
        """
179
        Renvoi un lookup de recherche pour les dernières horloges qui ont la 
180
        meme date et heure que celle donnée.
181
        """
182
        if self.obj:
183
            return {
184
                'pub_date__range': (self.obj, self.obj.replace(microsecond=999999)),
185
            }
186
        
187
        return {}
188
    
189
class ClockFinder:
190
    """
191
    Manipulateur de recherche et remplacement d'horloges à partir d'une regex.
192
    Le format de l'horloge peut prendre plusieurs formes possibles :
193
    
194
    * HH:TT
195
    * HH:TT:SS
196
    * MM/DD#HH:TT
197
    * MM/DD#HH:TT:SS
198
    * YYYY/MM/DD#HH:TT
199
    * YYYY/MM/DD#HH:TT:SS
200
    
201
    Avec YYYY pour l'année, MM le mois, DD le jour, HH l'heure, TT les minutes, 
202
    SS les secondes. Tous sur deux digits sauf l'année avec quatres digits.
203
    """
204
    def __init__(self):
205
        # Mois/jour sur 2 digits chacun
206
        self.daymonth_pattern = r"(\d\d)/(\d\d)"
207
        # Année optionnelle sur 4 digits
208
        self.year_pattern = r"((\d\d\d\d)?/)"
209
        # Temps avec Heure, minute et seconde, tous sur 2 digits, seconde est optionnelle
210
        self.time_pattern = r"(\d?\d)?:?(\d\d):(\d\d)(?:\b)" 
211
        # Date complète
212
        self.date_pattern = r"(?:\b)("+self.year_pattern+r"?"+self.daymonth_pattern+"?#)?"
213
        # Regex de date compilée
214
        self.re_clock = re.compile(self.date_pattern+self.time_pattern)
215
        # Template de substitution
216
        self.template_sub = "<clock>%s</clock>"
217
    
218
    def replace_allclocks(self, text, replace_func=None, template_sub=None):
219
        """
220
        Enferme chaque horloge dans un span. Ne prends que les horloges sur trois
221
        segments et sans les ².
222
        
223
        @text le texte ou chercher des horloges
224
        @replace_func une fonction de substitution qui remplace celle déja 
225
        incluse.
226
        """
227
        if template_sub:
228
            self.template_sub = template_sub
229
        if not replace_func:
230
            replace_func = self._replace_clock
231
        
232
        return self.re_clock.sub(replace_func, text)
233
    
234
    def _replace_clock(self, matchedPart):
235
        """
236
        Fonction de substitution pour le remplacement d'horloges
237
        """
238
        tpl = self.template_sub
239
        clock = matchedPart.group(0)
240
        # Vieux hack dégueulasse pour retirer les espaces matchés par ma regex 
241
        # qui semble un peu défaillante
242
        if matchedPart.group(0).startswith(' '):
243
            clock = clock.lstrip()
244
            tpl = ' '+tpl
245
        if matchedPart.group(0).endswith(' '):
246
            clock = clock.rstrip()
247
            tpl = tpl+' '
248
        # Renvoi l'horloge formattée
249
        return tpl % clock
250
    
251
# Tests
252
if __name__ == "__main__":
253
    # Tests des timestamp
254
    # Liste de timestamp valides ou pas à tester
255
    clockstampsList = [
256
        "07311001", # horloge correcte juste avec le time complet
257
        "17112201", # horloge correcte juste avec le time complet
258
        "1200", # horloge correcte juste avec le time sans les secondes
259
        "2008032810431601", # horloge correcte, complète avec l'année
260
        "032810431601", # horloge correcte, complète sans l'année
261
        "456452008032810431601", # horloge trop longue
262
        "42", # horloge trop courte
263
        "foo", # pas une horloge
264
        "17602201", # horloge avec une heure invalide
265
        "2008032535431601", # horloge avec une heure invalide
266
        "2008043110431601", # horloge avec une date invalide dans le calendrier
267
        "2008043110431601", # horloge avec une date invalide dans le calendrier
268
    ]
269
    for clock in clockstampsList:
270
        print "~"*80
271
        print "%s) %s [%s]" % (clockstampsList.index(clock)+1, clock, len(clock))
272
        # Objet du timestamp
273
        clockObject = ClockstampManipulator(clock)
274
        # Retourne un lookup de recherche pour le timestamp
275
        obj = clockObject.get_lookup()
276
        # Rapport visuels des vérifs
277
        print "is_valid:%s, is_datetime:%s, is_time:%s" % (clockObject.is_valid, clockObject.is_datetime, clockObject.is_time)
278
        if obj:
279
            print obj
280
281
    #messageList = [
282
        #u"€",
283
        #u"20:00 ho ho flooppopopop plap ",
284
        #u"21:42:42 blabla [:alf]",
285
        #u"15:12:55 ouyiha 13:45",
286
        #u"1:42:42 [:huit] burp",
287
        #u"02/12#02:42:11 mwahahaha",
288
        #u"28/03#10:43:42 coin coin 1977/06/02#10:42 arf",
289
        #u"""20082803#10:43:42 Non mais <b class="shout">HO!</b> 20000528#22:25 Je dis stop""",
290
        #u"2008/28/03#10:43:42 plonk 2000/05/28#22:25 Lorem ipsum",
291
        #u"Une url http://192.168.0.101:8888/about/ ?",
292
        #u"test 1:00:11 13:59:07 22:34:48 22:34:48",
293
    #]
294
    #clockregObject = ClockFinder()
295
    #for post in messageList:
296
        #print "~"*80
297
        #print "%s) %s" % (messageList.index(post)+1, post)
298
        ## Rapport visuels des vérifs
299
        #print "   %s" % clockregObject.replace_allclocks( post )