1 """
2 @version: 0.7
3 @date: 2008-08-28
4 @status: beta
5 @author: Stephen Ferg
6 @contact: http://www.ferg.org/contact_info
7 @license: Creative Commons Attribution License 2.0 http://creativecommons.org/licenses/by/2.0/
8 @see: http://www.ferg.org/pyfdate
9 @note:
10
11 ABOUT PYFDATE
12
13 The purpose of pyfdate is to provide a variety of user-friendly features
14 for working with datetimes, doing date arithmetic, and formatting dates,
15 times, and periods of time.
16
17 Pyfdate doesn't attempt to be all things to all people: rather, its goal
18 is to make it easy to do 95% if the things that people want to do with
19 dates and times.
20
21 Pyfdate provides two classes:
22
23 ------------------------------------------------------------------------
24
25 Time:
26 - A specific point in time, identified by
27 year, month, day, hour, minute, second.
28 (Python's datetime module calls this a "datetime".)
29
30 Period:
31 - An amount of time, measured in days, hours, minutes, and seconds.
32
33 ------------------------------------------------------------------------
34
35 In order to keep pyfdate simple, there are a number of things that it does not attempt to do.
36 - Pyfdate doesn't know anything about timezones.
37 - Pyfdate doesn't know anything about daylight savings time.
38 - Pyfdate doesn't provide any facilities for parsing strings into date/times.
39 (It does, however, provide a useful numsplit() function which performs a kind of parsing.)
40 - The smallest unit of time that Pyfdate can handle is a second.
41 It cannot be used to work with hundredths or thousandths of seconds.
42
43 INTERNATIONAL LANGUAGE SUPPORT
44
45 By default, pyfdate's language for displaying dates and times
46 (e.g. weekday names and month names) is American English.
47
48 If pyfdate can successfully import pyfdate_local,
49 the language for displaying dates and times
50 is taken from the pyfdate_local.py file.
51
52 Localization files are available for a number of languages.
53 For example: pyfdate_local_francais.py for French.
54
55 To use the file for language foobar,
56 simply copy pyfdate_local_foobar.py to pyfdate_local.py
57
58 And of course it is possible to customize pyfdate_local.py for any particular
59 language of your choice. If you create (or correct) a localization file
60 for pyfdate, please share it with others by submitting it for inclusion
61 in the pyfdate distribution.
62
63
64 BACKGROUND
65
66 Many ideas for pyfdate functionality grew out of Steve Ferg's experiences with
67 an earlier (non-Python) program called "fdate", which provided datetime
68 arithmetic functionality for MS-DOS batch files. The name "pyfdate" is derived
69 from "Python" and "fdate".
70 """
71 from pprint import pprint
72 import time, copy, sys, os, types
73
74 try:
75 import datetime
76 except ImportError, e:
77 raise AssertionError("Error importing datetime.\n"
78 +"Note that pyfdate requires Python version 2.3+.\n"
79 +"You are now running Python version " + sys.version)
80 from pprint import pprint
81
82 """
83 Language-specific interface for pyfdate, for language: American English
84 """
85 LANG = "American English"
86 TimeExpressedIn24Hours = False
87 CivilTimeSeparator = ":"
88 CivilDateFormat = "m d, y"
89
90 HOUR = "hour"
91 HOURS = "hours"
92 MINUTE = "minute"
93 MINUTES = "minutes"
94 SECOND = "second"
95 SECONDS = "seconds"
96
97 DAY = "day"
98 DAYS = "days"
99 HOUR = "hour"
100 HOURS = "hours"
101 MINUTE = "minute"
102 MINUTES = "minutes"
103 SECOND = "second"
104 SECONDS = "seconds"
105
106 WEEK = "week"
107 WEEKS = "weeks"
108 MONTH = "month"
109 MONTHS = "months"
110 YEAR = "year"
111 YEARS = "years"
112
113 MONTH_NAMES = \
114 { 1:"January"
115 , 2:"February"
116 , 3:"March"
117 , 4:"April"
118 , 5:"May"
119 , 6:"June"
120 , 7:"July"
121 , 8:"August"
122 , 9:"September"
123 ,10:"October"
124 ,11:"November"
125 ,12:"December"
126 }
127
128 WEEKDAY_NAMES = \
129 {1:"Monday"
130 ,2:"Tuesday"
131 ,3:"Wednesday"
132 ,4:"Thursday"
133 ,5:"Friday"
134 ,6:"Saturday"
135 ,7:"Sunday"
136 }
137
138
139
140
141
142 for __monthNumber, __monthName in MONTH_NAMES.items():
143 __monthName = __monthName.upper().replace("-","_")
144 exec(__monthName + " = " + str(__monthNumber))
145
146 for __weekdayNumber, __weekdayName in WEEKDAY_NAMES.items():
147 __weekdayName = __weekdayName.upper().replace("-","_")
148 exec(__weekdayName + " = " + str(__weekdayNumber))
149
150
151
152
153
154
155 try: from pyfdate_local import *
156 except ImportError: pass
157
158
159
160
161 NEXT = "NEXT"
162 NEAREST = "NEAREST"
163 PREVIOUS = "PREVIOUS"
164
165 SECONDS_IN_MINUTE = 60
166 MINUTES_IN_HOUR = 60
167 HOURS_IN_DAY = 24
168 SECONDS_IN_HOUR = SECONDS_IN_MINUTE * MINUTES_IN_HOUR
169 SECONDS_IN_DAY = SECONDS_IN_HOUR * HOURS_IN_DAY
170 MINUTES_IN_DAY = MINUTES_IN_HOUR * HOURS_IN_DAY
171
172 NORMAL_DAYS_IN_MONTH = \
173 { 1:31
174 , 2:28
175 , 3:31
176 , 4:30
177 , 5:31
178 , 6:30
179 , 7:31
180 , 8:31
181 , 9:30
182 , 10:31
183 , 11:30
184 , 12:31
185 }
186
187
188
189
190
191
192
193
195 """
196 A pyfdate.Period is an amount of time.
197
198 pyfdate.Period performs a function similar to the standard library datetime.timedelta.
199 pyfdate.Period, however, is implemented differently than the datetime.timedelta class.
200 pyfdate.Period stores only a single value: self.period_seconds.
201 This may be a positive or negative value.
202
203 pyfdate.Period objects (like datetime.timedelta objects) are used to do date
204 arithmetic. But since pyfdate.Time provides more sophisticated date
205 arithmetic features than datetime.datetime, pyfdate. Periods are probably
206 more widely used for their display capabilities than for their date
207 arithmetic capabilities.
208 """
209
210
211
212 - def __init__(self, arg1=0,hours=0,minutes=0,seconds=0):
213 """
214 Thhe constructor of a Period object.
215
216 Constructor expects arguments of:
217 - a series of positional arguments: [days [, hours[,minutes[,seconds]]]], or
218 - a datetime.timedelta object, or
219 - a pyfdate.Period object
220 @rtype: Period
221 """
222 if isinstance(arg1, datetime.timedelta):
223
224 timedelta = arg1
225 self.period_seconds = timedelta.seconds + (timedelta.days * SECONDS_IN_DAY)
226 return
227
228 if isinstance(arg1, Period):
229
230 self.period_seconds = copy.deepcopy(arg1.period_seconds)
231 return
232
233
234 days = arg1
235 self.period_seconds = (
236 (days * SECONDS_IN_DAY )
237 + (hours * SECONDS_IN_HOUR )
238 + (minutes * SECONDS_IN_MINUTE)
239 + seconds )
240
241
242
243
244
246 """
247 Returns a clone of myself, with my absolute value.
248 @return: a clone of myself, with my absolute value
249 @rtype: Period
250 """
251 return Period(0,0,0,abs(self.period_seconds))
252
253
254
255
257 """
258 Add one period to another.
259 @return: a new Period object
260 @rtype: Period
261 """
262 if isinstance(arg, Period): pass
263 else:
264 raise AssertionError("Cannot add a "
265 + arg.__class__.__name__ + " object to a Period object.")
266
267 return Period(0,0,0, self.period_seconds + arg.period_seconds)
268
269
270
271
273 """
274 Compare two Period objects for equality.
275 @rtype: boolean
276 """
277 if isinstance(arg, Period): pass
278 else:
279 raise AssertionError("Cannot compare Period object with "
280 + arg.__class__.__name__ + " object.")
281
282 if self.period_seconds == arg.period_seconds: return True
283 return False
284
285
286
287
289 """
290 Compares two Period objects to determine if one is greater than,
291 or equal to, the other.
292 @rtype: boolean
293 """
294 if self.__gt__(arg): return True
295 if self.__eq__(arg): return True
296 return False
297
298
299
300
302 """
303 Compares two Period objects to determine if one is greater than the other.
304 @rtype: boolean
305 """
306 if isinstance(arg, Period): pass
307 else:
308 raise AssertionError("Cannot compare Period object with "
309 + arg.__class__.__name__ + " object.")
310 if self.period_seconds > arg.period_seconds: return True
311 return False
312
313
314
315
317 """
318 Compares two Period objects to determine if one is less than,
319 or equal to, the other.
320 @rtype: boolean
321 """
322 if self.__gt__(arg): return False
323 return True
324
325
326
327
329 """
330 Compares two Period objects to determine if one is less than the other.
331 @rtype: boolean
332 """
333 if self.__gt__(arg): return False
334 if self.__eq__(arg): return False
335 return True
336
337
338
339
341 """
342 Compare two Period objects for inequality.
343 @rtype: boolean
344 """
345 return not self.__eq__(arg)
346
347
348
349
351 """
352 Returns a string representation of a Period.
353 @rtype: string
354 @return: a string representation of a Period.
355 e.g.::
356 "4 days 3 hours 20 minutes 45 seconds"
357 """
358 return self.get_p()
359
360
361
362
363
365 """
366 Subtract one period from another.
367 @return: a new Period object
368 @rtype: Period
369 """
370 if isinstance(arg, Period): pass
371 else:
372 raise AssertionError("Cannot subtract a "
373 + arg.__class__.__name__ + " object from a Period object.")
374
375 return Period(0,0,0, self.period_seconds - arg.period_seconds)
376
377
378
379
380
382 """
383 A general method for subtracting time from a Period object.
384 @rtype: Period
385
386 @note: Example:
387 ::
388 p1 = Period()
389 p2 = p1.plus(weeks=2,days=3,hours=4,minutes=99, seconds=1)
390 """
391 for key,value in kwargs.items():
392 kwargs[key] = value * -1
393
394 return self.add(**kwargs)
395
396
397 minus = subtract
398
399
400
401
402
403 - def add(self, **kwargs):
404 """
405 A general method for adding time to a Period object. To
406 subtract time, add time in negative increments or use the subtract() method.
407 @rtype: Period
408 @note:
409 Example::
410 p1 = Period()
411 p2 = p1.plus(weeks=2,days=3,hours=4,minutes=99, seconds=1)
412 """
413 argNum = 0
414 p = self.clone()
415 for key, value in kwargs.items():
416 argNum += 1
417 if False: pass
418 elif key in ("day" ,"days" , DAY , DAYS ): p = Period(0,0,0,p.period_seconds + (value * SECONDS_IN_DAY ))
419 elif key in ("hour" ,"hours" , HOUR , HOURS ): p = Period(0,0,0,p.period_seconds + (value * SECONDS_IN_HOUR ))
420 elif key in ("minute" ,"minutes", MINUTE, MINUTES): p = Period(0,0,0,p.period_seconds + (value * SECONDS_IN_MINUTE))
421 elif key in ("second" ,"seconds", SECOND, SECONDS): p = Period(0,0,0,p.period_seconds + value )
422 elif key in ("week" ,"weeks" , WEEK , WEEKS ): p = Period(0,0,0,p.period_seconds + (7*(value * SECONDS_IN_DAY)))
423 else :
424 raise AssertionError(
425 self.__class__.__name__ +".plus()"
426 + " received invalid keyword argument: " + str(key)
427 + " in argument " + str(argNum) + ".\n"
428 + kwargsToString(**kwargs)
429 )
430 return p
431
432
433 plus = add
434
435
436
437
439 """
440 Return a clone of myself.
441 @return: a clone of myself
442 @rtype: Period
443 """
444 return Period(0,0,0,self.period_seconds)
445
446
447
448
450 """
451 Return the days portion of the period, as an int.
452 @rtype: int
453 @return: days, e.g.::
454 3
455 """
456 days, hours, minutes, seconds = self.get_tuple()
457 return days
458 days = property(get_days)
459
460
461
462
464 """
465 Return the hours portion of the Period, as an int.
466 @rtype: int
467 @return: hours, e.g.::
468 15
469 """
470 days, hours, minutes, seconds = self.get_tuple()
471 return hours
472 hours = property(get_hours)
473
474
475
476
478 """
479 Return the minutes portion of the Period, as an int.
480 @rtype: int
481 @return: minutes, e.g.::
482 45
483 """
484 days, hours, minutes, seconds = self.get_tuple()
485 return minutes
486 minutes = property(get_minutes)
487
488
489
490
492 """
493 Return the seconds portion of the Period, as an int.
494 @rtype: int
495 @return: seconds, e.g.::
496 45
497 """
498 days, hours, minutes, seconds = self.get_tuple()
499 return seconds
500 seconds = property(get_seconds)
501
502
503
504