Package gen :: Package lib :: Module date
[frames] | no frames]

Source Code for Module gen.lib.date

   1  # 
   2  # Gramps - a GTK+/GNOME based genealogy program 
   3  # 
   4  # Copyright (C) 2000-2007  Donald N. Allingham 
   5  # 
   6  # This program is free software; you can redistribute it and/or modify 
   7  # it under the terms of the GNU General Public License as published by 
   8  # the Free Software Foundation; either version 2 of the License, or 
   9  # (at your option) any later version. 
  10  # 
  11  # This program is distributed in the hope that it will be useful,  
  12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  14  # GNU General Public License for more details. 
  15  # 
  16  # You should have received a copy of the GNU General Public License 
  17  # along with this program; if not, write to the Free Software 
  18  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  19  # 
  20   
  21  # $Id: date.py 10242 2008-03-09 19:51:36Z acraphae $ 
  22   
  23  """Support for dates.""" 
  24   
  25  #------------------------------------------------------------------------ 
  26  # 
  27  # Python modules 
  28  # 
  29  #------------------------------------------------------------------------ 
  30  from gettext import gettext as _ 
  31   
  32  #------------------------------------------------------------------------ 
  33  # 
  34  # Set up logging 
  35  # 
  36  #------------------------------------------------------------------------ 
  37  import logging 
  38  log = logging.getLogger(".Date") 
  39   
  40  #------------------------------------------------------------------------- 
  41  # 
  42  # Gnome/GTK modules 
  43  # 
  44  #------------------------------------------------------------------------- 
  45   
  46   
  47  #------------------------------------------------------------------------ 
  48  # 
  49  # Gramps modules 
  50  # 
  51  #------------------------------------------------------------------------ 
  52  from gen.lib.calendar import (gregorian_sdn, julian_sdn, hebrew_sdn,  
  53                                french_sdn, persian_sdn, islamic_sdn,  
  54                                gregorian_ymd, julian_ymd, hebrew_ymd,  
  55                                french_ymd, persian_ymd, islamic_ymd) 
  56  import Config 
  57   
  58  #------------------------------------------------------------------------- 
  59  # 
  60  # DateError exception 
  61  # 
  62  #------------------------------------------------------------------------- 
63 -class DateError(Exception):
64 """Error used to report Date errors."""
65 - def __init__(self, value=""):
66 Exception.__init__(self) 67 self.value = value
68
69 - def __str__(self):
70 return self.value
71 72 #------------------------------------------------------------------------- 73 # 74 # Date class 75 # 76 #-------------------------------------------------------------------------
77 -class Date:
78 """ 79 The core date handling class for GRAMPs. 80 81 Supports partial dates, compound dates and alternate calendars. 82 """ 83 MOD_NONE = 0 84 MOD_BEFORE = 1 85 MOD_AFTER = 2 86 MOD_ABOUT = 3 87 MOD_RANGE = 4 88 MOD_SPAN = 5 89 MOD_TEXTONLY = 6 90 91 QUAL_NONE = 0 92 QUAL_ESTIMATED = 1 93 QUAL_CALCULATED = 2 94 95 CAL_GREGORIAN = 0 96 CAL_JULIAN = 1 97 CAL_HEBREW = 2 98 CAL_FRENCH = 3 99 CAL_PERSIAN = 4 100 CAL_ISLAMIC = 5 101 102 EMPTY = (0, 0, 0, False) 103 104 _POS_DAY = 0 105 _POS_MON = 1 106 _POS_YR = 2 107 _POS_SL = 3 108 _POS_RDAY = 4 109 _POS_RMON = 5 110 _POS_RYR = 6 111 _POS_RSL = 7 112 113 _calendar_convert = [ 114 gregorian_sdn, 115 julian_sdn, 116 hebrew_sdn, 117 french_sdn, 118 persian_sdn, 119 islamic_sdn, 120 ] 121 122 _calendar_change = [ 123 gregorian_ymd, 124 julian_ymd, 125 hebrew_ymd, 126 french_ymd, 127 persian_ymd, 128 islamic_ymd, 129 ] 130 131 calendar_names = ["Gregorian", 132 "Julian", 133 "Hebrew", 134 "French Republican", 135 "Persian", 136 "Islamic"] 137 138 139 ui_calendar_names = [_("Gregorian"), 140 _("Julian"), 141 _("Hebrew"), 142 _("French Republican"), 143 _("Persian"), 144 _("Islamic")] 145
146 - def __init__(self, *source, **kwargs):
147 """ 148 Create a new Date instance. 149 """ 150 calendar = kwargs.get("calendar", None) 151 modifier = kwargs.get("modifier", None) 152 quality = kwargs.get("quality", None) 153 #### setup None, Date, or numbers 154 if len(source) == 0: 155 source = None 156 elif len(source) == 1: 157 if type(source[0]) == int: 158 source = (source[0], 0, 0) 159 else: 160 source = source[0] 161 elif len(source) == 2: 162 source = (source[0], source[1], 0) 163 elif len(source) == 3: 164 pass # source is ok 165 else: 166 raise AttributeError, "invalid args to Date: %s" % source 167 #### ok, process either date or tuple 168 if type(source) == tuple: 169 if calendar == None: 170 self.calendar = Date.CAL_GREGORIAN 171 else: 172 self.calendar = self.lookup_calendar(calendar) 173 if modifier == None: 174 self.modifier = Date.MOD_NONE 175 else: 176 self.modifier = self.lookup_modifier(modifier) 177 if quality == None: 178 self.quality = Date.QUAL_NONE 179 else: 180 self.quality = self.lookup_quality(quality) 181 self.dateval = Date.EMPTY 182 self.text = u"" 183 self.sortval = 0 184 self.set_yr_mon_day(*source) 185 elif type(source) == str: 186 if (calendar != None or 187 modifier != None or 188 quality != None): 189 raise AttributeError("can't set calendar, modifier, or " 190 "quality with string date") 191 import DateHandler 192 source = DateHandler.parser.parse(source) 193 self.calendar = source.calendar 194 self.modifier = source.modifier 195 self.quality = source.quality 196 self.dateval = source.dateval 197 self.text = source.text 198 self.sortval = source.sortval 199 elif source: 200 self.calendar = source.calendar 201 self.modifier = source.modifier 202 self.quality = source.quality 203 self.dateval = source.dateval 204 self.text = source.text 205 self.sortval = source.sortval 206 else: 207 self.calendar = Date.CAL_GREGORIAN 208 self.modifier = Date.MOD_NONE 209 self.quality = Date.QUAL_NONE 210 self.dateval = Date.EMPTY 211 self.text = u"" 212 self.sortval = 0
213
214 - def serialize(self, no_text_date=False):
215 """ 216 Convert to a series of tuples for data storage. 217 """ 218 if no_text_date: 219 text = u'' 220 else: 221 text = self.text 222 223 return (self.calendar, self.modifier, self.quality, 224 self.dateval, text, self.sortval)
225
226 - def unserialize(self, data):
227 """ 228 Load from the format created by serialize. 229 """ 230 (self.calendar, self.modifier, self.quality, 231 self.dateval, self.text, self.sortval) = data 232 return self
233
234 - def copy(self, source):
235 """ 236 Copy all the attributes of the given Date instance to the present 237 instance, without creating a new object. 238 """ 239 self.calendar = source.calendar 240 self.modifier = source.modifier 241 self.quality = source.quality 242 self.dateval = source.dateval 243 self.text = source.text 244 self.sortval = source.sortval
245
246 - def __cmp__(self, other):
247 """ 248 Compare two dates. 249 250 Comparison function. Allows the usage of equality tests. 251 This allows you do run statements like 'date1 <= date2' 252 """ 253 if isinstance(other, Date): 254 return cmp(self.sortval, other.sortval) 255 else: 256 return -1
257
258 - def __add__(self, other):
259 """ 260 Date arithmetic: Date() + years, or Date() + (years, [months, [days]]). 261 """ 262 if type(other) == int: 263 return self.copy_offset_ymd(other) 264 elif type(other) in [tuple, list]: 265 return self.copy_offset_ymd(*other) 266 else: 267 raise AttributeError, "unknown date add type: %s " % type(other)
268
269 - def __radd__(self, other):
270 """ 271 Add a number + Date() or (years, months, days) + Date(). 272 """ 273 return self + other
274
275 - def __sub__(self, other):
276 """ 277 Date arithmetic: Date() - years, Date - (y,m,d), or Date() - Date(). 278 """ 279 if type(other) == int: # Date - value -> Date 280 return self.copy_offset_ymd(-other) 281 elif type(other) in [tuple, list]: # Date - (y, m, d) -> Date 282 return self.copy_offset_ymd(*map(lambda x: -x, other)) 283 elif type(other) == type(self): # Date1 - Date2 -> tuple 284 # We should make sure that Date2 + tuple -> Date1 and 285 # Date1 - tuple -> Date2 286 d1 = map(lambda i: i or 1, self.get_ymd()) 287 d2 = map(lambda i: i or 1, other.get_ymd()) 288 date1 = self 289 date2 = other 290 if d1 < d2: 291 d1, d2 = d2, d1 292 date1, date2 = date2, date1 293 # d1 - d2 (1998, 12, 32) - (1982, 12, 15) 294 if self.calendar != other.calendar: 295 diff = date1.sortval - date2.sortval 296 return (diff/365, (diff % 365)/30, (diff % 365) % 30) 297 # days: 298 if d2[2] > d1[2]: 299 # months: 300 if d2[1] > d1[1]: 301 d1[0] -= 1 302 d1[1] += 12 303 d1[1] -= 1 304 d1[2] += 31 305 # months: 306 if d2[1] > d1[1]: 307 d1[0] -= 1 # from years 308 d1[1] += 12 # to months 309 days = d1[2] - d2[2] 310 months = d1[1] - d2[1] 311 years = d1[0] - d2[0] 312 if days > 31: 313 months += days / 31 314 days = days % 31 315 if months > 12: 316 years += months / 12 317 months = months % 12 318 # estimate: (years, months, days) 319 # Check transitivity: 320 eDate = date1 - (years, months, days) 321 if eDate < date2: # too small 322 diff = 0 323 while eDate < date2 and diff < 60: 324 diff += 1 325 eDate = eDate + (0, 0, diff) 326 if diff == 60: 327 return None 328 return (years, months, days - diff) 329 elif eDate > date2: 330 diff = 0 331 while eDate > date2 and diff > -60: 332 diff -= 1 333 eDate = eDate - (0, 0, abs(diff)) 334 if diff == -60: 335 return None 336 return (years, months, days + diff) 337 else: 338 return (years, months, days) 339 else: 340 raise AttributeError, "unknown date sub type: %s " % type(other)
341 342 # Can't use this (as is) as this breaks comparing dates to None 343 #def __eq__(self, other): 344 # return self.sortval == other.sortval 345
346 - def __lt__(self, other):
347 return self.sortval < other.sortval
348
349 - def __gt__(self, other):
350 return self.sortval > other.sortval
351
352 - def is_equal(self, other):
353 """ 354 Return 1 if the given Date instance is the same as the present 355 instance IN ALL REGARDS. 356 357 Needed, because the __cmp__ only looks at the sorting value, and 358 ignores the modifiers/comments. 359 """ 360 if self.modifier == other.modifier \ 361 and self.modifier == Date.MOD_TEXTONLY: 362 value = self.text == other.text 363 else: 364 value = (self.calendar == other.calendar and 365 self.modifier == other.modifier and 366 self.quality == other.quality and 367 self.dateval == other.dateval) 368 return value
369
370 - def get_start_stop_range(self):
371 """ 372 Return the minimal start_date, and a maximal stop_date corresponding 373 to this date, given in Gregorian calendar. 374 375 Useful in doing range overlap comparisons between different dates. 376 377 Note that we stay in (YR,MON,DAY) 378 """ 379 380 def yr_mon_day(dateval): 381 """ 382 Local function to swap order for easy comparisons, and correct 383 year of slash date. 384 385 Slash date is given as year1/year2, where year1 is Julian year, 386 and year2=year1+1 the Gregorian year. 387 388 Slash date is already taken care of. 389 """ 390 return (dateval[Date._POS_YR], dateval[Date._POS_MON], 391 dateval[Date._POS_DAY])
392 def date_offset(dateval, offset): 393 """ 394 Local function to do date arithmetic: add the offset, return 395 (year,month,day) in the Gregorian calendar. 396 """ 397 new_date = Date() 398 new_date.set_yr_mon_day(dateval[0], dateval[1], dateval[2]) 399 return new_date.offset(offset)
400 401 datecopy = Date(self) 402 #we do all calculation in Gregorian calendar 403 datecopy.convert_calendar(Date.CAL_GREGORIAN) 404 start = yr_mon_day(datecopy.get_start_date()) 405 stop = yr_mon_day(datecopy.get_stop_date()) 406 407 if stop == (0, 0, 0): 408 stop = start 409 410 stopmax = list(stop) 411 if stopmax[0] == 0: # then use start_year, if one 412 stopmax[0] = start[Date._POS_YR] 413 if stopmax[1] == 0: 414 stopmax[1] = 12 415 if stopmax[2] == 0: 416 stopmax[2] = 31 417 startmin = list(start) 418 if startmin[1] == 0: 419 startmin[1] = 1 420 if startmin[2] == 0: 421 startmin[2] = 1 422 # if BEFORE, AFTER, or ABOUT/EST, adjust: 423 if self.modifier == Date.MOD_BEFORE: 424 stopmax = date_offset(startmin, -1) 425 fdiff = Config.get(Config.DATE_BEFORE_RANGE) 426 startmin = (stopmax[0] - fdiff, stopmax[1], stopmax[2]) 427 elif self.modifier == Date.MOD_AFTER: 428 startmin = date_offset(stopmax, 1) 429 fdiff = Config.get(Config.DATE_AFTER_RANGE) 430 stopmax = (startmin[0] + fdiff, startmin[1], startmin[2]) 431 elif (self.modifier == Date.MOD_ABOUT or 432 self.quality == Date.QUAL_ESTIMATED): 433 fdiff = Config.get(Config.DATE_ABOUT_RANGE) 434 startmin = (startmin[0] - fdiff, startmin[1], startmin[2]) 435 stopmax = (stopmax[0] + fdiff, stopmax[1], stopmax[2]) 436 # return tuples not lists, for comparisons 437 return (tuple(startmin), tuple(stopmax)) 438
439 - def match(self, other_date, comparison="="):
440 """ 441 Compare two dates using sophisticated techniques looking for any match 442 between two possible dates, date spans and qualities. 443 444 The other comparisons for Date (is_equal() and __cmp() don't actually 445 look for anything other than a straight match, or a simple comparison 446 of the sortval. 447 448 comparison =,== : 449 Returns True if any part of other_date matches any part of self 450 comparison < : 451 Returns True if any part of other_date < any part of self 452 comparison << : 453 Returns True if all parts of other_date < all parts of self 454 comparison > : 455 Returns True if any part of other_date > any part of self 456 comparison >> : 457 Returns True if all parts of other_date > all parts of self 458 """ 459 if (other_date.modifier == Date.MOD_TEXTONLY or 460 self.modifier == Date.MOD_TEXTONLY): 461 if comparison in ["=", "=="]: 462 return (self.text.upper().find(other_date.text.upper()) != -1) 463 else: 464 return False 465 466 # Obtain minimal start and maximal stop in Gregorian calendar 467 other_start, other_stop = other_date.get_start_stop_range() 468 self_start, self_stop = self.get_start_stop_range() 469 470 if comparison in ["=", "=="]: 471 # If some overlap then match is True, otherwise False. 472 return ((self_start <= other_start <= self_stop) or 473 (self_start <= other_stop <= self_stop) or 474 (other_start <= self_start <= other_stop) or 475 (other_start <= self_stop <= other_stop)) 476 elif comparison == "<": 477 # If any < any 478 return self_start < other_stop 479 elif comparison == "<<": 480 # If all < all 481 return self_stop < other_start 482 elif comparison == ">": 483 # If any > any 484 return self_stop > other_start 485 elif comparison == ">>": 486 # If all > all 487 return self_start > other_stop 488 else: 489 raise AttributeError, ("invalid match comparison operator: '%s'" % 490 comparison)
491
492 - def __str__(self):
493 """ 494 Produce a string representation of the Date object. 495 496 If the date is not valid, the text representation is displayed. If 497 the date is a range or a span, a string in the form of 498 'YYYY-MM-DD - YYYY-MM-DD' is returned. Otherwise, a string in 499 the form of 'YYYY-MM-DD' is returned. 500 """ 501 if self.quality == Date.QUAL_ESTIMATED: 502 qual = "est " 503 elif self.quality == Date.QUAL_CALCULATED: 504 qual = "calc " 505 else: 506 qual = "" 507 508 if self.modifier == Date.MOD_BEFORE: 509 pref = "bef " 510 elif self.modifier == Date.MOD_AFTER: 511 pref = "aft " 512 elif self.modifier == Date.MOD_ABOUT: 513 pref = "abt " 514 else: 515 pref = "" 516 517 if self.calendar != Date.CAL_GREGORIAN: 518 cal = " (%s)" % Date.calendar_names[self.calendar] 519 else: 520 cal = "" 521 522 if self.modifier == Date.MOD_TEXTONLY: 523 val = self.text 524 elif self.modifier == Date.MOD_RANGE or self.modifier == Date.MOD_SPAN: 525 val = "%04d-%02d-%02d - %04d-%02d-%02d" % ( 526 self.dateval[Date._POS_YR], self.dateval[Date._POS_MON], 527 self.dateval[Date._POS_DAY], self.dateval[Date._POS_RYR], 528 self.dateval[Date._POS_RMON], self.dateval[Date._POS_RDAY]) 529 else: 530 val = "%04d-%02d-%02d" % ( 531 self.dateval[Date._POS_YR], self.dateval[Date._POS_MON], 532 self.dateval[Date._POS_DAY]) 533 return "%s%s%s%s" % (qual, pref, val, cal)
534
535 - def get_sort_value(self):
536 """ 537 Return the sort value of Date object. 538 539 If the value is a text string, 0 is returned. Otherwise, the 540 calculated sort date is returned. The sort date is rebuilt on every 541 assignment. 542 543 The sort value is an integer representing the value. The sortval is 544 the integer number of days that have elapsed since Monday, January 1, 545 4713 BC in the proleptic Julian calendar. 546 See http://en.wikipedia.org/wiki/Julian_day 547 """ 548 return self.sortval
549
550 - def get_modifier(self):
551 """ 552 Return an integer indicating the calendar selected. 553 554 The valid values are:: 555 556 MOD_NONE = no modifier (default) 557 MOD_BEFORE = before 558 MOD_AFTER = after 559 MOD_ABOUT = about 560 MOD_RANGE = date range 561 MOD_SPAN = date span 562 MOD_TEXTONLY = text only 563 """ 564 return self.modifier
565
566 - def set_modifier(self, val):
567 """ 568 Set the modifier for the date. 569 """ 570 if val not in (Date.MOD_NONE, Date.MOD_BEFORE, Date.MOD_AFTER, 571 Date.MOD_ABOUT, Date.MOD_RANGE, Date.MOD_SPAN, 572 Date.MOD_TEXTONLY): 573 raise DateError("Invalid modifier") 574 self.modifier = val
575
576 - def get_quality(self):
577 """ 578 Return an integer indicating the calendar selected. 579 580 The valid values are:: 581 582 QUAL_NONE = normal (default) 583 QUAL_ESTIMATED = estimated 584 QUAL_CALCULATED = calculated 585 """ 586 return self.quality
587
588 - def set_quality(self, val):
589 """ 590 Set the quality selected for the date. 591 """ 592 if val not in (Date.QUAL_NONE, Date.QUAL_ESTIMATED, 593 Date.QUAL_CALCULATED): 594 raise DateError("Invalid quality") 595 self.quality = val
596
597 - def get_calendar(self):
598 """ 599 Return an integer indicating the calendar selected. 600 601 The valid values are:: 602 603 CAL_GREGORIAN - Gregorian calendar 604 CAL_JULIAN - Julian calendar 605 CAL_HEBREW - Hebrew (Jewish) calendar 606 CAL_FRENCH - French Republican calendar 607 CAL_PERSIAN - Persian calendar 608 CAL_ISLAMIC - Islamic calendar 609 """ 610 return self.calendar
611
612 - def set_calendar(self, val):
613 """ 614 Set the calendar selected for the date. 615 """ 616 if val not in (Date.CAL_GREGORIAN, Date.CAL_JULIAN, Date.CAL_HEBREW, 617 Date.CAL_FRENCH, Date.CAL_PERSIAN, Date.CAL_ISLAMIC): 618 raise DateError("Invalid calendar") 619 self.calendar = val
620
621 - def get_start_date(self):
622 """ 623 Return a tuple representing the start date. 624 625 If the date is a compound date (range or a span), it is the first part 626 of the compound date. If the date is a text string, a tuple of 627 (0, 0, 0, False) is returned. Otherwise, a date of (DD, MM, YY, slash) 628 is returned. If slash is True, then the date is in the form of 1530/1. 629 """ 630 if self.modifier == Date.MOD_TEXTONLY: 631 val = Date.EMPTY 632 else: 633 val = self.dateval[0:4] 634 return val
635
636 - def get_stop_date(self):
637 """ 638 Return a tuple representing the second half of a compound date. 639 640 If the date is not a compound date, (including text strings) a tuple 641 of (0, 0, 0, False) is returned. Otherwise, a date of (DD, MM, YY, slash) 642 is returned. If slash is True, then the date is in the form of 1530/1. 643 """ 644 if self.modifier == Date.MOD_RANGE or self.modifier == Date.MOD_SPAN: 645 val = self.dateval[4:8] 646 else: 647 val = Date.EMPTY 648 return val
649
650 - def _get_low_item(self, index):
651 """ 652 Return the item specified. 653 """ 654 if self.modifier == Date.MOD_TEXTONLY: 655 val = 0 656 else: 657 val = self.dateval[index] 658 return val
659
660 - def _get_low_item_valid(self, index):
661 """ 662 Determine if the item specified is valid. 663 """ 664 if self.modifier == Date.MOD_TEXTONLY: 665 val = False 666 else: 667 val = self.dateval[index] != 0 668 return val
669
670 - def _get_high_item(self, index):
671 """ 672 Return the item specified. 673 """ 674 if self.modifier == Date.MOD_SPAN or self.modifier == Date.MOD_RANGE: 675 val = self.dateval[index] 676 else: 677 val = 0 678 return val
679
680 - def get_year(self):
681 """ 682 Return the year associated with the date. 683 684 If the year is not defined, a zero is returned. If the date is a 685 compound date, the lower date year is returned. 686 """ 687 return self._get_low_item(Date._POS_YR)
688
689 - def set_yr_mon_day(self, year, month, day):
690 """ 691 Set the year, month, and day values. 692 """ 693 dv = list(self.dateval) 694 dv[Date._POS_YR] = year 695 dv[Date._POS_MON] = month 696 dv[Date._POS_DAY] = day 697 self.dateval = tuple(dv) 698 self._calc_sort_value()
699
700 - def set_yr_mon_day_offset(self, year=0, month=0, day=0):
701 """ 702 Set the year, month, and day values by offset. 703 """ 704 dv = list(self.dateval) 705 if dv[Date._POS_YR]: 706 dv[Date._POS_YR] += year 707 elif year: 708 dv[Date._POS_YR] = year 709 if dv[Date._POS_MON]: 710 dv[Date._POS_MON] += month 711 elif month: 712 if month < 0: 713 dv[Date._POS_MON] = 1 + month 714 else: 715 dv[Date._POS_MON] = month 716 # Fix if month out of bounds: 717 if month != 0: # only check if changed 718 if dv[Date._POS_MON] == 0: # subtraction 719 dv[Date._POS_MON] = 12 720 dv[Date._POS_YR] -= 1 721 elif dv[Date._POS_MON] < 0: # subtraction 722 dv[Date._POS_YR] -= int((-dv[Date._POS_MON]) / 12) + 1 723 dv[Date._POS_MON] = (dv[Date._POS_MON] % 12) 724 elif dv[Date._POS_MON] > 12 or dv[Date._POS_MON] < 1: 725 dv[Date._POS_YR] += int(dv[Date._POS_MON] / 12) 726 dv[Date._POS_MON] = dv[Date._POS_MON] % 12 727 self.dateval = tuple(dv) 728 self._calc_sort_value() 729 if day != 0 or dv[Date._POS_DAY] > 28: 730 self.set_yr_mon_day(*self.offset(day))
731
732 - def copy_offset_ymd(self, year=0, month=0, day=0):
733 """ 734 Return a Date copy based on year, month, and day offset. 735 """ 736 orig_cal = self.calendar 737 if self.calendar != 0: 738 new_date = self.to_calendar("gregorian") 739 else: 740 new_date = self 741 retval = Date(new_date) 742 retval.set_yr_mon_day_offset(year, month, day) 743 if orig_cal == 0: 744 return retval 745 else: 746 retval.convert_calendar(orig_cal) 747 return retval
748
749 - def copy_ymd(self, year=0, month=0, day=0):
750 """ 751 Return a Date copy with year, month, and day set. 752 """ 753 retval = Date(self) 754 retval.set_yr_mon_day(year, month, day) 755 return retval
756
757 - def set_year(self, year):
758 """ 759 Set the year value. 760 """ 761 self.dateval = self.dateval[0:2] + (year, ) + self.dateval[3:] 762 self._calc_sort_value()
763
764 - def get_year_valid(self):
765 """ 766 Return true if the year is valid. 767 """ 768 return self._get_low_item_valid(Date._POS_YR)
769
770 - def get_month(self):
771 """ 772 Return the month associated with the date. 773 774 If the month is not defined, a zero is returned. If the date is a 775 compound date, the lower date month is returned. 776 """ 777 return self._get_low_item(Date._POS_MON)
778
779 - def get_month_valid(self):
780 """ 781 Return true if the month is valid 782 """ 783 return self._get_low_item_valid(Date._POS_MON)
784
785 - def get_day(self):
786 """ 787 Return the day of the month associated with the date. 788 789 If the day is not defined, a zero is returned. If the date is a 790 compound date, the lower date day is returned. 791 """ 792 return self._get_low_item(Date._POS_DAY)
793
794 - def get_day_valid(self):
795 """ 796 Return true if the day is valid. 797 """ 798 return self._get_low_item_valid(Date._POS_DAY)
799
800 - def get_valid(self):
801 """ 802 Return true if any part of the date is valid. 803 """ 804 return self.modifier != Date.MOD_TEXTONLY
805
806 - def get_stop_year(self):
807 """ 808 Return the day of the year associated with the second part of a 809 compound date. 810 811 If the year is not defined, a zero is returned. 812 """ 813 return self._get_high_item(Date._POS_RYR)
814
815 - def get_stop_month(self):
816 """ 817 Return the month of the month associated with the second part of a 818 compound date. 819 820 If the month is not defined, a zero is returned. 821 """ 822 return self._get_high_item(Date._POS_RMON)
823
824 - def get_stop_day(self):
825 """ 826 Return the day of the month associated with the second part of a 827 compound date. 828 829 If the day is not defined, a zero is returned. 830 """ 831 return self._get_high_item(Date._POS_RDAY)
832
833 - def get_high_year(self):
834 """ 835 Return the high year estimate. 836 837 For compound dates with non-zero stop year, the stop year is returned. 838 Otherwise, the start year is returned. 839 """ 840 if self.is_compound(): 841 ret = self.get_stop_year() 842 if ret: 843 return ret 844 else: 845 return self.get_year()
846
847 - def get_text(self):
848 """ 849 Return the text value associated with an invalid date. 850 """ 851 return self.text
852
853 - def set(self, quality, modifier, calendar, value, text=None):
854 """ 855 Set the date to the specified value. 856 857 Parameters are:: 858 859 quality - The date quality for the date (see get_quality 860 for more information) 861 modified - The date modifier for the date (see get_modifier 862 for more information) 863 calendar - The calendar associated with the date (see 864 get_calendar for more information). 865 value - A tuple representing the date information. For a 866 non-compound date, the format is (DD, MM, YY, slash) 867 and for a compound date the tuple stores data as 868 (DD, MM, YY, slash1, DD, MM, YY, slash2) 869 text - A text string holding either the verbatim user input 870 or a comment relating to the date. 871 872 The sort value is recalculated. 873 """ 874 875 if modifier in (Date.MOD_NONE, Date.MOD_BEFORE, 876 Date.MOD_AFTER, Date.MOD_ABOUT) and len(value) < 4: 877 raise DateError("Invalid value. Should be: (DD, MM, YY, slash)") 878 if modifier in (Date.MOD_RANGE, Date.MOD_SPAN) and len(value) < 8: 879 raise DateError("Invalid value. Should be: (DD, MM, " 880 "YY, slash1, DD, MM, YY, slash2)") 881 if modifier not in (Date.MOD_NONE, Date.MOD_BEFORE, Date.MOD_AFTER, 882 Date.MOD_ABOUT, Date.MOD_RANGE, Date.MOD_SPAN, 883 Date.MOD_TEXTONLY): 884 raise DateError("Invalid modifier") 885 if quality not in (Date.QUAL_NONE, Date.QUAL_ESTIMATED, 886 Date.QUAL_CALCULATED): 887 raise DateError("Invalid quality") 888 if calendar not in (Date.CAL_GREGORIAN, Date.CAL_JULIAN, 889 Date.CAL_HEBREW, Date.CAL_FRENCH, 890 Date.CAL_PERSIAN, Date.CAL_ISLAMIC): 891 raise DateError("Invalid calendar") 892 893 self.quality = quality 894 self.modifier = modifier 895 self.calendar = calendar 896 self.dateval = value 897 year = max(value[Date._POS_YR], 1) 898 month = max(value[Date._POS_MON], 1) 899 day = max(value[Date._POS_DAY], 1) 900 if year == 0 and month == 0 and day == 0: 901 self.sortval = 0 902 else: 903 func = Date._calendar_convert[calendar] 904 self.sortval = func(year, month, day) 905 if text: 906 self.text = text
907
908 - def _calc_sort_value(self):
909 """ 910 Calculate the numerical sort value associated with the date. 911 """ 912 year = max(self.dateval[Date._POS_YR], 1) 913 month = max(self.dateval[Date._POS_MON], 1) 914 day = max(self.dateval[Date._POS_DAY], 1) 915 if year == 0 and month == 0 and day == 0: 916 self.sortval = 0 917 else: 918 func = Date._calendar_convert[self.calendar] 919 self.sortval = func(year, month, day)
920
921 - def convert_calendar(self, calendar):
922 """ 923 Convert the date from the current calendar to the specified calendar. 924 """ 925 if calendar == self.calendar: 926 return 927 (year, month, day) = Date._calendar_change[calendar](self.sortval) 928 if self.is_compound(): 929 ryear = max(self.dateval[Date._POS_RYR], 1) 930 rmonth = max(self.dateval[Date._POS_RMON], 1) 931 rday = max(self.dateval[Date._POS_RDAY], 1) 932 sdn = Date._calendar_convert[self.calendar](ryear, rmonth, rday) 933 (nyear, nmonth, nday) = Date._calendar_change[calendar](sdn) 934 self.dateval = (day, month, year, self.dateval[Date._POS_SL], 935 nday, nmonth, nyear, self.dateval[Date._POS_RSL]) 936 else: 937 self.dateval = (day, month, year, self.dateval[Date._POS_SL]) 938 self.calendar = calendar
939
940 - def set_as_text(self, text):
941 """ 942 Set the day to a text string, and assign the sort value to zero. 943 """ 944 self.modifier = Date.MOD_TEXTONLY 945 self.text = text 946 self.sortval = 0
947
948 - def set_text_value(self, text):
949 """ 950 Set the text string to a given text. 951 """ 952 self.text = text
953
954 - def is_empty(self):
955 """ 956 Return True if the date contains no information (empty text). 957 """ 958 return (self.modifier == Date.MOD_TEXTONLY and not self.text) or \ 959 (self.get_start_date()==Date.EMPTY 960 and self.get_stop_date()==Date.EMPTY)
961
962 - def is_compound(self):
963 """ 964 Return True if the date is a date range or a date span. 965 """ 966 return self.modifier == Date.MOD_RANGE \ 967 or self.modifier == Date.MOD_SPAN
968
969 - def is_regular(self):
970 """ 971 Return True if the date is a regular date. 972 973 The regular date is a single exact date, i.e. not text-only, not 974 a range or a span, not estimated/calculated, not about/before/after 975 date, and having year, month, and day all non-zero. 976 """ 977 return self.modifier == Date.MOD_NONE \ 978 and self.quality == Date.QUAL_NONE \ 979 and self.get_year_valid() and self.get_month_valid() \ 980 and self.get_day_valid()
981
982 - def get_ymd(self):
983 """ 984 Return (year, month, day). 985 """ 986 return (self.get_year(), self.get_month(), self.get_day())
987
988 - def offset(self, value):
989 """ 990 Return (year, month, day) of this date +- value. 991 """ 992 return Date._calendar_change[Date.CAL_GREGORIAN](self.sortval + value)
993
994 - def offset_date(self, value):
995 """ 996 Return (year, month, day) of this date +- value. 997 """ 998 return Date(Date._calendar_change[Date.CAL_GREGORIAN](self.sortval + value))
999
1000 - def lookup_calendar(self, calendar):
1001 """ 1002 Lookup calendar name in the list of known calendars, even if translated. 1003 """ 1004 calendar_lower = [n.lower() for n in Date.calendar_names] 1005 ui_lower = [n.lower() for n in Date.ui_calendar_names] 1006 if calendar.lower() in calendar_lower: 1007 return calendar_lower.index(calendar.lower()) 1008 elif calendar.lower() in ui_lower: 1009 return ui_lower.index(calendar.lower()) 1010 else: 1011 raise AttributeError("invalid calendar: '%s'" % calendar)
1012
1013 - def lookup_quality(self, quality):
1014 """ 1015 Lookup date quality keyword, even if translated. 1016 """ 1017 qualities = ["none", "estimated", "calculated"] 1018 ui_qualities = [_("none"), _("estimated"), _("calculated")] 1019 if quality.lower() in qualities: 1020 return qualities.index(quality.lower()) 1021 elif quality.lower() in ui_qualities: 1022 return ui_qualities.index(quality.lower()) 1023 else: 1024 raise AttributeError("invalid quality: '%s'" % quality)
1025
1026 - def lookup_modifier(self, modifier):
1027 """ 1028 Lookup date modifier keyword, even if translated. 1029 """ 1030 mods = ["none", "before", "after", "about", 1031 "range", "span", "textonly"] 1032 ui_mods = [_("none"), _("before"), _("after"), _("about"), 1033 _("range"), _("span"), _("textonly")] 1034 if modifier.lower() in mods: 1035 return mods.index(modifier.lower()) 1036 elif modifier.lower() in ui_mods: 1037 return ui_mods.index(modifier.lower()) 1038 else: 1039 raise AttributeError("invalid modifier: '%s'" % modifier)
1040
1041 - def to_calendar(self, calendar_name):
1042 """ 1043 Return a new Date object in the calendar calendar_name. 1044 1045 >>> Date("Jan 1 1591").to_calendar("julian") 1046 1590-12-22 (Julian) 1047 """ 1048 cal = self.lookup_calendar(calendar_name) 1049 retval = Date(self) 1050 retval.convert_calendar(cal) 1051 return retval
1052
1053 - def get_slash(self):
1054 """ 1055 Return true if the date is a slash-date. 1056 """ 1057 return self._get_low_item_valid(Date._POS_SL)
1058