Style improvements and cache from settings validation added
[imap-fix-internaldate] / src / mail_date_parser.py
CommitLineData
c9da760a 1'''
8a9d4c89 2mail_date_parser.py - The module contains the MailDateParser class.
c9da760a
PD
3
4Copyright (c) 2012 Intra2net AG
5Author: Plamen Dimitrov
6
7This program is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or
10(at your option) any later version.
11
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
c9da760a
PD
16'''
17
7a1d4c35 18import datetime, time
c9da760a 19import re
8fe4e3ff 20import logging
c9da760a
PD
21
22#reg expressions
23RECEIVED_DATE = re.compile(r'(0?[1-9]|[1-2][0-9]|3[01])\s+([A-Z][a-z][a-z])\s+'
24 r'(19[0-9]{2}|[2-9][0-9]{3}|[0-9]{2})\s+(2[0-3]|[0-1][0-9]):([0-5][0-9])(?::(60|[0-5][0-9]))?\s*'
25 r'(?:([-\+])([0-9]{2})([0-5][0-9]))*')
26INTERNAL_DATE = re.compile(r'(?P<day>[ 0123][0-9])-(?P<mon>[A-Z][a-z][a-z])-(?P<year>[0-9][0-9][0-9][0-9])'
27 r' (?P<hour>[0-9][0-9]):(?P<min>[0-9][0-9]):(?P<sec>[0-9][0-9])'
28 r' (?P<zonen>[-+])(?P<zoneh>[0-9][0-9])(?P<zonem>[0-9][0-9])')
29CONTROL_SYMBOLS = re.compile(r'[\n\r\t]')
30
8a9d4c89 31class MailDateParser:
7a1d4c35
PD
32 """This class extracts dates from imap server responses and compares them.
33 This class contains only static methods."""
c9da760a
PD
34
35 def __init__(self):
36 return
37
38 @classmethod
39 def extract_internal_date(cls, fetchresult):
40 """Extracts the internal date from INTERNALDATE, returns datetime."""
41 return datetime.datetime.fromtimestamp(time.mktime(fetchresult))
42
43 @classmethod
44 def extract_received_date(cls, fetchresult):
45 """Extracts the first date from RECEIVED, returns datetime."""
8a9d4c89 46 fetchresult = CONTROL_SYMBOLS.sub('', fetchresult)
c9da760a
PD
47 received_dates = RECEIVED_DATE.findall(fetchresult)
48 if(len(received_dates)==0):
49 return ""
50 else: received_date = received_dates[0]
8fe4e3ff 51 logging.debug("Retrieved date %s from header %s.", received_date, fetchresult)
c9da760a
PD
52 month = datetime.datetime.strptime(received_date[1],'%b').month
53
54 if(received_date[3]!=""):
55 hours = int(received_date[3])
56 else: hours = 0
57 if(received_date[4]!=""):
58 minutes = int(received_date[4])
59 else: minutes = 0
60 if(received_date[5]!=""):
61 seconds = int(received_date[5])
62 else: seconds = 0
63
64 if(received_date[6]!=""):
65 zonen = received_date[6]
66 else: zonen = b'+'
67 if(received_date[7]!=""):
68 zoneh = int(received_date[7])
69 else: zoneh = 0
70 if(received_date[8]!=""):
71 zonem = int(received_date[8])
72 else: zonem = 0
73 # subtract time zone to get unified time
74 zone = (zoneh * 60 + zonem) * 60
75 if(zonen == b'-'):
76 zone = -zone
77
78 time_tuple = (int(received_date[2]), month, int(received_date[0]), hours, minutes, seconds, -1, -1, -1)
79 #'mktime' assumes arg in local timezone, so add timezone/altzone
80 utc = time.mktime(time_tuple)
81 #adjust to DST
82 if(time.daylight and time.localtime(utc)[-1]):
83 zone = zone + time.altzone
84 else:
85 zone = zone + time.timezone
86
87 received_time_tuple = time.localtime(utc - zone)
88 converted_received_date = datetime.datetime.fromtimestamp(time.mktime(received_time_tuple))
89 return converted_received_date
90
91 @classmethod
92 def compare_dates(cls, date1, date2, tolerance=1800):
db3f09a6
PD
93 """Compares datetime objects for deviation given certain tolerance.
94 Returns 1 if there is a significant difference."""
8fe4e3ff 95 logging.debug("Comparing dates %s <> %s.", date1, date2)
c9da760a
PD
96 timedelta = abs(date1 - date2)
97 if(timedelta.total_seconds()>tolerance):
8301e589 98 return True
c9da760a 99 else:
8301e589 100 return False