Module pyfdate
[hide private]
[frames] | no frames]

Source Code for Module pyfdate

   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"   # m=nameOfMonth  d=dayOfMonth y=year 
  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  # make constants for month names and weekday names 
 140  # this may not work for all languages. (see below) 
 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  # If a file called pyfdate_local exists, import it. 
 152  # This enables optional customization for different languages: 
 153  # German, French, Spanish, British English, etc. 
 154  #----------------------------------------------------------------------- 
 155  try: from pyfdate_local import * 
 156  except ImportError: pass 
 157   
 158  #--------------------------------------------------------------------- 
 159  #   set up some constants 
 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 # JAN 
 174          ,  2:28 # FEB  # does not apply to leap years 
 175          ,  3:31 # MAR 
 176          ,  4:30 # APR 
 177          ,  5:31 # MAY 
 178          ,  6:30 # JUN 
 179          ,  7:31 # JUL 
 180          ,  8:31 # AUG 
 181          ,  9:30 # SEP 
 182          , 10:31 # OCT 
 183          , 11:30 # NOV 
 184          , 12:31 # DEC 
 185          } 
 186   
 187   
 188  #################################################################################### 
 189  # 
 190  #            class Period 
 191  # 
 192  #################################################################################### 
 193   
194 -class Period:
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 # CONSTRUCTOR 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 # first argument is a timedelta. Ignore the other args. 224 timedelta = arg1 225 self.period_seconds = timedelta.seconds + (timedelta.days * SECONDS_IN_DAY) 226 return 227 228 if isinstance(arg1, Period): 229 # first argument is a Period. Ignore the other args. 230 self.period_seconds = copy.deepcopy(arg1.period_seconds) 231 return 232 233 # else, arguments represent days, hours, minutes, seconds in a period. 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 # __abs__ 244 #-----------------------------------------------------------------------------------
245 - def __abs__(self):
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 # __add__ 255 #-----------------------------------------------------------------------------------
256 - def __add__(self, arg):
257 """ 258 Add one period to another. 259 @return: a new Period object 260 @rtype: Period 261 """ 262 if isinstance(arg, Period): pass # no problem 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 # comparison method 271 #-----------------------------------------------------------------------------------
272 - def __eq__(self,arg):
273 """ 274 Compare two Period objects for equality. 275 @rtype: boolean 276 """ 277 if isinstance(arg, Period): pass # no problem 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 # comparison method 287 #-----------------------------------------------------------------------------------
288 - def __ge__(self,arg):
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 # comparison method 300 #-----------------------------------------------------------------------------------
301 - def __gt__(self,arg):
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 # no problem 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 # comparison method 315 #-----------------------------------------------------------------------------------
316 - def __le__(self,arg):
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 # comparison method 327 #-----------------------------------------------------------------------------------
328 - def __lt__(self,arg):
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 # comparison method 339 #-----------------------------------------------------------------------------------
340 - def __neq__(self,arg):
341 """ 342 Compare two Period objects for inequality. 343 @rtype: boolean 344 """ 345 return not self.__eq__(arg)
346 347 #----------------------------------------------------------------------------------- 348 # __str__ 349 #-----------------------------------------------------------------------------------
350 - def __str__(self):
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 # __sub__ 363 #-----------------------------------------------------------------------------------
364 - def __sub__(self, arg):
365 """ 366 Subtract one period from another. 367 @return: a new Period object 368 @rtype: Period 369 """ 370 if isinstance(arg, Period): pass # no problem 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 # subtract 380 #-----------------------------------------------------------------------------------
381 - def subtract(self, **kwargs):
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 # minus() method is alias for subtract() method 397 minus = subtract 398 399 400 #----------------------------------------------------------------------------------- 401 # add 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() # p is a new Period object 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 # plus() method is alias for add() method 433 plus = add 434 435 #----------------------------------------------------------------------------------- 436 # clone 437 #-----------------------------------------------------------------------------------
438 - def clone(self):
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 # get_days 448 #-----------------------------------------------------------------------------------
449 - def get_days(self):
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 # get_hours 462 #-----------------------------------------------------------------------------------
463 - def get_hours(self):
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 # get_minutes 476 #-----------------------------------------------------------------------------------
477 - def get_minutes(self):
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 # get_seconds 490 #-----------------------------------------------------------------------------------
491 - def get_seconds(self):
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 # get_p 504 #-----------------------------------------------------------------------------------
505 - def get_p(self, **