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