| 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 )
|