def range_check_date(cls, YYYY=None, MM=None, DD=None, Www=None, D=None, DDD=None, rangedict=None): if rangedict is None: rangedict = cls.DATE_RANGE_DICT if 'YYYY' in rangedict: YYYY = rangedict['YYYY'].rangefunc(YYYY, rangedict['YYYY']) if 'MM' in rangedict: MM = rangedict['MM'].rangefunc(MM, rangedict['MM']) if 'DD' in rangedict: DD = rangedict['DD'].rangefunc(DD, rangedict['DD']) if 'Www' in rangedict: Www = rangedict['Www'].rangefunc(Www, rangedict['Www']) if 'D' in rangedict: D = rangedict['D'].rangefunc(D, rangedict['D']) if 'DDD' in rangedict: DDD = rangedict['DDD'].rangefunc(DDD, rangedict['DDD']) if DD is not None: #Check calendar if DD > calendar.monthrange(YYYY, MM)[1]: raise DayOutOfBoundsError('{0} is out of range for {1}-{2}'.format(DD, YYYY, MM)) if DDD is not None: if calendar.isleap(YYYY) is False and DDD == 366: raise DayOutOfBoundsError('{0} is only valid for leap year.'.format(DDD)) return (YYYY, MM, DD, Www, D, DDD)
def _parse_week_day(datestr): #datestr is of the format YYYY-Www-D, YYYYWwwD # #W is the week number prefix, ww is the week number, between 1 and 53 #0 is not a valid week number, which differs from the Python implementation # #D is the weekday number, between 1 and 7, which differs from the Python #implementation which is between 0 and 6 isoyear = int(datestr[0:4]) gregorianyearstart = _iso_year_start(isoyear) #Week number will be the two characters after the W windex = datestr.find('W') isoweeknumber = int(datestr[windex + 1:windex + 3]) if isoweeknumber == 0 or isoweeknumber > 53: raise WeekOutOfBoundsError('Week number must be between 1..53.') if datestr.find('-') != -1 and len(datestr) == 10: #YYYY-Www-D isoday = int(datestr[9:10]) elif len(datestr) == 8: #YYYYWwwD isoday = int(datestr[7:8]) else: raise ISOFormatError( '"{0}" is not a valid ISO 8601 week date.'.format(datestr)) if isoday == 0 or isoday > 7: raise DayOutOfBoundsError('Weekday number must be between 1..7.') return gregorianyearstart + datetime.timedelta(weeks=isoweeknumber - 1, days=isoday - 1)
def build_date(cls, YYYY=None, MM=None, DD=None, Www=None, D=None, DDD=None): if YYYY is not None: # Truncated dates, like '19', refer to 1900-1999 inclusive, # we simply parse to 1900 if len(YYYY) < 4: # Shift 0s in from the left to form complete year YYYY = YYYY.ljust(4, "0") year = cls.cast(YYYY, int, thrownmessage="Invalid year string.") if MM is not None: month = cls.cast(MM, int, thrownmessage="Invalid month string.") else: month = 1 if DD is not None: day = cls.cast(DD, int, thrownmessage="Invalid day string.") else: day = 1 if Www is not None: weeknumber = cls.cast(Www, int, thrownmessage="Invalid week string.") if weeknumber == 0 or weeknumber > 53: raise WeekOutOfBoundsError("Week number must be between " "1..53.") else: weeknumber = None if DDD is not None: dayofyear = cls.cast(DDD, int, thrownmessage="Invalid day string.") else: dayofyear = None if D is not None: dayofweek = cls.cast(D, int, thrownmessage="Invalid day string.") if dayofweek == 0 or dayofweek > 7: raise DayOutOfBoundsError("Weekday number must be between " "1..7.") else: dayofweek = None # 0000 (1 BC) is not representable as a Python date so a ValueError is # raised if year == 0: raise YearOutOfBoundsError("Year must be between 1..9999.") if dayofyear is not None: return PythonTimeBuilder._build_ordinal_date(year, dayofyear) if weeknumber is not None: return PythonTimeBuilder._build_week_date( year, weeknumber, isoday=dayofweek ) return datetime.date(year, month, day)
def range_check_date(cls, YYYY=None, MM=None, DD=None, Www=None, D=None, DDD=None, rangedict=None): if rangedict is None: rangedict = cls.DATE_RANGE_DICT if "YYYY" in rangedict: YYYY = rangedict["YYYY"].rangefunc(YYYY, rangedict["YYYY"]) if "MM" in rangedict: MM = rangedict["MM"].rangefunc(MM, rangedict["MM"]) if "DD" in rangedict: DD = rangedict["DD"].rangefunc(DD, rangedict["DD"]) if "Www" in rangedict: Www = rangedict["Www"].rangefunc(Www, rangedict["Www"]) if "D" in rangedict: D = rangedict["D"].rangefunc(D, rangedict["D"]) if "DDD" in rangedict: DDD = rangedict["DDD"].rangefunc(DDD, rangedict["DDD"]) if DD is not None: # Check calendar if DD > calendar.monthrange(YYYY, MM)[1]: raise DayOutOfBoundsError( "{0} is out of range for {1}-{2}".format(DD, YYYY, MM)) if DDD is not None: if calendar.isleap(YYYY) is False and DDD == 366: raise DayOutOfBoundsError( "{0} is only valid for leap year.".format(DDD)) return (YYYY, MM, DD, Www, D, DDD)
def _build_ordinal_date(isoyear, isoday): # Day of year to a date # https://stackoverflow.com/questions/2427555/python-question-year-and-day-of-year-to-date builtdate = datetime.date(isoyear, 1, 1) + datetime.timedelta(days=isoday - 1) # Enforce ordinal day limitation # https://bitbucket.org/nielsenb/aniso8601/issues/14/parsing-ordinal-dates-should-only-allow if isoday == 0 or builtdate.year != isoyear: raise DayOutOfBoundsError("Day of year must be from 1..365, " "1..366 for leap year.") return builtdate
def _parse_ordinal_date(datestr): #datestr is of the format YYYY-DDD or YYYYDDD #DDD can be from 1 - 36[5,6], this matches Python's definition isoyear = int(datestr[0:4]) if datestr.find('-') != -1: #YYYY-DDD isoday = int(datestr[(datestr.find('-') + 1):]) else: #YYYYDDD isoday = int(datestr[4:]) parseddate = datetime.date(isoyear, 1, 1) + datetime.timedelta(days=isoday - 1) #Enforce ordinal day limitation #https://bitbucket.org/nielsenb/aniso8601/issues/14/parsing-ordinal-dates-should-only-allow if isoday == 0 or parseddate.year != isoyear: raise DayOutOfBoundsError( 'Day of year must be from 1..365, 1..366 for leap year.') return parseddate
def range_check_duration(cls, PnY=None, PnM=None, PnW=None, PnD=None, TnH=None, TnM=None, TnS=None, rangedict=None): years = 0 months = 0 days = 0 weeks = 0 hours = 0 minutes = 0 seconds = 0 microseconds = 0 PnY, PnM, PnW, PnD, TnH, TnM, TnS = BaseTimeBuilder.range_check_duration( PnY, PnM, PnW, PnD, TnH, TnM, TnS, rangedict=cls.DURATION_RANGE_DICT) if PnY is not None: if type(PnY) is FractionalComponent: years = PnY.principal microseconds = PnY.microsecondremainder else: years = PnY if years * DAYS_PER_YEAR > TIMEDELTA_MAX_DAYS: raise YearOutOfBoundsError( 'Duration exceeds maximum timedelta size.') if PnM is not None: if type(PnM) is FractionalComponent: months = PnM.principal microseconds = PnM.microsecondremainder else: months = PnM if months * DAYS_PER_MONTH > TIMEDELTA_MAX_DAYS: raise MonthOutOfBoundsError( 'Duration exceeds maximum timedelta size.') if PnW is not None: if type(PnW) is FractionalComponent: weeks = PnW.principal microseconds = PnW.microsecondremainder else: weeks = PnW if weeks * DAYS_PER_WEEK > TIMEDELTA_MAX_DAYS: raise WeekOutOfBoundsError( 'Duration exceeds maximum timedelta size.') if PnD is not None: if type(PnD) is FractionalComponent: days = PnD.principal microseconds = PnD.microsecondremainder else: days = PnD if days > TIMEDELTA_MAX_DAYS: raise DayOutOfBoundsError( 'Duration exceeds maximum timedelta size.') if TnH is not None: if type(TnH) is FractionalComponent: hours = TnH.principal microseconds = TnH.microsecondremainder else: hours = TnH if hours // HOURS_PER_DAY > TIMEDELTA_MAX_DAYS: raise HoursOutOfBoundsError( 'Duration exceeds maximum timedelta size.') if TnM is not None: if type(TnM) is FractionalComponent: minutes = TnM.principal microseconds = TnM.microsecondremainder else: minutes = TnM if minutes // MINUTES_PER_DAY > TIMEDELTA_MAX_DAYS: raise MinutesOutOfBoundsError( 'Duration exceeds maximum timedelta size.') if TnS is not None: if type(TnS) is FractionalComponent: seconds = TnS.principal microseconds = TnS.microsecondremainder else: seconds = TnS if seconds // SECONDS_PER_DAY > TIMEDELTA_MAX_DAYS: raise SecondsOutOfBoundsError( 'Duration exceeds maximum timedelta size.') years, months, weeks, days, hours, minutes, seconds, microseconds = PythonTimeBuilder._distribute_microseconds( microseconds, (years, months, weeks, days, hours, minutes, seconds), (MICROSECONDS_PER_YEAR, MICROSECONDS_PER_MONTH, MICROSECONDS_PER_WEEK, MICROSECONDS_PER_DAY, MICROSECONDS_PER_HOUR, MICROSECONDS_PER_MINUTE, MICROSECONDS_PER_SECOND)) #Note that weeks can be handled without conversion to days totaldays = years * DAYS_PER_YEAR + months * DAYS_PER_MONTH + days #Check against timedelta limits if totaldays + weeks * DAYS_PER_WEEK + hours // HOURS_PER_DAY + minutes // MINUTES_PER_DAY + seconds // SECONDS_PER_DAY > TIMEDELTA_MAX_DAYS: raise DayOutOfBoundsError( 'Duration exceeds maximum timedelta size.') return (None, None, weeks, totaldays, hours, minutes, FractionalComponent(seconds, microseconds))