Better name for the date parse unit test class
[imap-fix-internaldate] / src / mail_date_parser.py
1 '''
2 mail_date_parser.py - The module contains the MailDateParser 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
18 import datetime, time
19 import re
20 import logging
21
22 #reg expressions
23 RECEIVED_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]))*')
26 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])'
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])')
29 CONTROL_SYMBOLS = re.compile(r'[\n\r\t]')
30
31 class MailDateParser:
32     """This class extracts dates from imap server responses and compares them.
33     This class contains only static methods."""
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."""
46         fetchresult = CONTROL_SYMBOLS.sub('', fetchresult)
47         received_dates = RECEIVED_DATE.findall(fetchresult)
48         if(len(received_dates)==0):
49             return ""
50         else: received_date = received_dates[0]
51         logging.debug("Retrieved date %s from header %s.", received_date, fetchresult)
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):
93         """Compares datetime objects for deviation given certain tolerance.
94         Returns 1 if there is a significant difference."""
95         logging.debug("Comparing dates %s <> %s.", date1, date2)
96         timedelta = abs(date1 - date2)
97         if(timedelta.total_seconds() > tolerance):
98             return True
99         else:
100             return False