Initial submission of working tool
[imap-fix-internaldate] / date_interpreter.py
CommitLineData
c9da760a
PD
1'''
2date_interpreter.py - The module contains the MailIterator class.
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.
16
17Add '-t' argument when running the module for a test mode.
18For a detailed list of each message with a date conflict change
19the 'log_level' in the configuration file from '30' to '20'.
20'''
21
22import datetime
23import re
24import time
25
26#reg expressions
27RECEIVED_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]))*')
30INTERNAL_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])')
33CONTROL_SYMBOLS = re.compile(r'[\n\r\t]')
34
35class 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