-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdateconvert.py
277 lines (242 loc) · 9.14 KB
/
dateconvert.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
'''
Datierung.
An exercise in Python. Converts Latin dates found in medieval chronicles
and documents into conventional format.
Copyright (C) 2012 Ilia Kurenkov
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
For questions feel free to email me:
ilia kurenkov at gmail
Please keep in mind, that the only currently supported input date format is:
DD refdate Month Year
where refdate stands for nones/ides/calendes and all the numbers are roman
numerals.
For more information about the program in general, please refer to the
README.txt
This particular file contains two classes: Month and DateProcessor that between
them provide the functionality to convert some date from roman to conventional
format and also tell what day of the week that date corresponded to.
'''
import re # used to scanning strings
''' to start with some constants: names of days of the week... '''
DAYS = ['Sunday', 'Saturday', 'Friday', 'Thursday', 'Wednesday', 'Tuesday',
'Monday'
]
''' dictionary of brackets used to find day of week based on sunday letter '''
BRACKETS = {(1,10):1, (2,3,11):2, (4,7): 3, (8,0):4, (9,12):5, (5,0):6, (6,0):7}
''' list of sunday letters '''
LETTERS = ['D','C','B','A','G','F','E']
''' dictionary of centuries as keys and index changes as values. for more, see
narrow_to_century() method
'''
CENTURIES = {0:0, 100:-1, 200:-2, 300:-3, 400:-4, 500:-5, 600:-6,
700:0, 800:-1, 900:-2, 1000:-3, 1100:-4, 1200:-5, 1300:-6,
1400:0, 1500:-1, 1600:-2, 1700:-3, 1800:-4, 1900:-5, 2000:-6
}
''' class for months '''
class Month:
def __init__(self, regex, month_num, d=31):
self.name = re.compile(regex)
self.num = month_num
self.lastDay = d
self.Calends = 1
self.Nones = 5
self.Ides = 13
self.check_lateness(month_num)
def check_lateness(self, n):
# check if month has later nones and ides dates
if n in [3,5,7,10]:
self.Nones, self.Ides = 7, 15
def find_date(self, n, flag=None):
# return date of month
# takes arabic number as argument along with flags described below
if flag == 'c':
return self.Calends - n+1
elif flag == 'n':
return self.Nones - n+1
elif flag == 'i':
return self.Ides - n+1
elif flag == 'e':
return self.lastDay - n
else:
print 'Invalid flag, please use c, n, e or i'
return None
''' create instances of months '''
jan = Month('[Jj]an(uar)?y?', 1)
feb = Month('[Ff]eb(ruar)?y?', 2, d=28)
mar = Month('[Mm]ar(ch)?', 3)
apr = Month('[Aa]pr(il)?', 4, d=30)
may = Month('[Mm]ay', 5)
jun = Month('[Jj]une?', 6, d=30)
jul = Month('[Jj]uly?', 7)
aug = Month('[Aa]ug(ust)?', 8)
sept = Month('[Ss]ept(ember)?', 9, d=30)
okt = Month('[Oo][ck]t(ober)?', 10)
nov = Month('[Nn]ov(ember)?', 11, d=30)
dec = Month('[Dd]ec(ember)?', 12)
''' create regex pattern objects for calendes, nones and ides '''
calendes = re.compile('[KkCc]alende?s')
nones = re.compile('[Nn]ones')
ides = re.compile('[Ii]des')
''' put months into a list that we can loop over later '''
MONTHS = [jan, feb, mar, apr, may, jun, jul, aug, sept, okt, nov, dec]
''' Define class for processing dates. I found this necessary so as not to
initiate and pass around certain variables everytime certain methods are run
'''
class DateProcessor:
def __init__(self, in_date):
''' class constructor '''
self.one = re.compile('[iI]')
self.four = re.compile('([iI][Vv])')
self.five = re.compile('[vV]')
self.nine = re.compile('[iI][xX]')
self.ten = re.compile('[Xx]')
self.fifty = re.compile('[lL]')
self.hundred = re.compile('[Cc]')
self.five_hundred = re.compile('[dD]')
self.thousand = re.compile('[mM]')
in_date = in_date.split()
self.day = self.convert_to_arab(in_date[0])
self.cni = in_date[1]
self.month = in_date[2]
self.year = self.convert_to_arab(in_date[3])
def convert_to_arab(self, roman):
arab = 0
''' start by finding occurences of 4 and 9 '''
if self.four.search(roman):
arab += 4
roman = self.four.sub('', roman) # deleted to avoid counting it again
if self.nine.search(roman):
arab += 9
roman = self.nine.sub('', roman)
''' Then we count all the occurences of the other characters, since
they do not overlap.
'''
ones = len(self.one.findall(roman))
fives = len(self.five.findall(roman))*5
tens = len(self.ten.findall(roman))*10
fifties = len(self.fifty.findall(roman))*50
hundreds = len(self.hundred.findall(roman))*100
five_hundreds = len(self.five_hundred.findall(roman))*500
thousands = len(self.thousand.findall(roman))*1000
''' We then sum up all the counts and return that sum '''
arab = arab + ones + fives + fifties + tens + hundreds + five_hundreds + thousands
return arab
def calculate_date(self):
''' Argumentless method modifies self.day and self.month to reflect the
conventional day and month information.
'''
for m in MONTHS: #loop over month objects to see which one matches
if m.name.search(self.month):
outputMonth = m
'''Having found a prelim month, we try to identify the reference date.
'''
if nones.search(self.cni):
self.day = outputMonth.find_date(self.day,'n')
elif ides.search(self.cni):
self.day = outputMonth.find_date(self.day,'i')
elif calendes.search(self.cni):
self.day = outputMonth.find_date(self.day,'c')
'''If value of day negative, go back on month and count again. '''
if self.day < 0:
outputMonth = MONTHS[MONTHS.index(outputMonth)-1]
self.day = outputMonth.find_date(abs(self.day),'e')
''' once all is set, finalize the value of self.month. '''
self.month = outputMonth.num
def narrow_to_century(self):
'''Method to tie a year value with a century and use that along with
the year number striped of hundreds/thousands. These values are then
used by the find_sunday_letter() method to calculate the appropriate
sunday letter(s) for this year. '''
tempCent = {} #temporary dict to store centuries that match 1st filter
for cent in CENTURIES:
if self.year-cent >= 0:
tempCent[cent] = CENTURIES[cent]
for c in tempCent:
if self.year-c == min(self.year-cent for cent in tempCent):
return (self.year-c, tempCent[c])
def find_sunday_letter(self, years, start=0, first=True, leap=False):
if not first:
''' if this is not first run '''
indx = start + years
leap_year = leap
else:
''' if first run '''
indx = start + years + years/4 + 1
if not years % 4:
''' If a leap year, counterintuitively phrased to satisfy
python's syntax, since we want 'years % 4' to equal 0, and this
is how Python checks for this.
'''
leap_year=True
else:
leap_year = False
try:
if leap_year:
return LETTERS[indx-1] + LETTERS[indx]
else:
return LETTERS[indx]
except IndexError:
''' If our index value is out of range, we recursively keep
decreasing it by 7 (length of our week)
'''
return self.find_sunday_letter(indx-7, first=False, leap=leap_year)
def find_weekday(self):
''' Method to determine what day of the week our date was. Since this
is a somewhat side-line feature, the method actually returns the
weekday instead of modifying instance attributes, as the
calculate_date() one does.
'''
date = self.day
month = self.month
cent_bracket = self.narrow_to_century()
letter = self.find_sunday_letter(cent_bracket[0], start=cent_bracket[1])
if len(letter) == 2: #if we have a leap year
if month <= 2: #if the month is january or february
letter = letter[0] #use first sunday letter
else:
letter = letter[1] # use second sunday letter
''' Identify bracket number. '''
for b in BRACKETS:
if month in b:
bracket_num = BRACKETS[b]
''' Now find first member of bracket. '''
if not bracket_num % 2: #if bracket number is even
firstMember = (bracket_num + 8)/2
else:
firstMember = (bracket_num + 1)/2
''' Find how many columns we need to move from first bracket date. '''
steps = date - firstMember
if steps >= 0:
steps = steps % 7
else:
steps = 7 + steps
L = (LETTERS[4:]+LETTERS[:4])[::-1] #we reshuffle the letter list
weekDay = DAYS[L.index(letter) - steps]
return weekDay
def main():
''' Main method to be run if file invoked from command line.
Currently takes no arguments.
'''
''' When creating input, please keep in mind that it must be in the format:
DD refdate Month Year
with refdate referring to ides/nones/calendes, all nums must be roman nums.
'''
#input_date = raw_input('Please enter a roman date in the following format:\
#\nDD ides/calends/nonnes MM YEAR\n')
#input_date = 'iv calendes march MMXII' # just an example date
input_date = 'iv calendes march MM' # just an example date
DP = DateProcessor(input_date)
DP.calculate_date()
weekday = DP.find_weekday()
print DP.day, DP.month, DP.year, weekday
if __name__ == '__main__':
main()