OwlCyberSecurity - MANAGER
Edit File: dateops.py
## # Copyright (c) 2006-2017 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ## """ Date/time Utilities """ __all__ = [ "normalizeForIndex", "floatoffset", "compareDateTime", "differenceDateTime", "timeRangesOverlap", "normalizePeriodList", "clipPeriod" ] from pycalendar.datetime import DateTime from pycalendar.timezone import Timezone from pycalendar.period import Period from datetime import date, datetime import dateutil.tz import calendar def normalizeForIndex(dt): """ Normalize a L{DateTime} object for use in the Index. Convert to date-time in UTC. @param dt: a L{DateTime} object to normalize @return: the normalized DateTime """ if not isinstance(dt, DateTime): raise TypeError("%r is not a DateTime instance" % (dt,)) dt = dt.duplicate() if dt.isDateOnly(): dt.setDateOnly(False) dt.setHHMMSS(0, 0, 0) dt.setTimezoneID(None) # Keep it floating return dt elif dt.floating(): return dt else: dt.adjustToUTC() return dt def normalizeToUTC(dt): """ Normalize a L{DateTime} object to UTC. """ if not isinstance(dt, DateTime): raise TypeError("%r is not a DateTime instance" % (dt,)) dt = dt.duplicate() if dt.isDateOnly(): dt.setDateOnly(False) dt.setHHMMSS(0, 0, 0) dt.setTimezoneUTC(True) return dt elif dt.floating(): dt.setTimezoneUTC(True) return dt else: dt.adjustToUTC() return dt def normalizeForExpand(dt): """ Normalize a L{DateTime} object for use with the CalDAV expand option. Convert to date-time in UTC, leave date only and floating alone. @param dt: a L{DateTime} object to normalize @return: the normalized DateTime """ if not isinstance(dt, DateTime): raise TypeError("%r is not a DateTime instance" % (dt,)) dt = dt.duplicate() if dt.isDateOnly() or dt.floating(): return dt else: dt.adjustToUTC() return dt def floatoffset(dt, pytz): """ Apply the timezone offset to the supplied time, then force tz to utc. This gives the local date-time as if the local tz were UTC. It can be used in floating time comparisons with UTC date-times. @param dt: a L{DateTime} object to normalize @param pytz: a L{Timezone} object to apply offset from @return: the normalized DateTime """ if pytz is None: pytz = Timezone.UTCTimezone dt = dt.duplicate() dt.adjustTimezone(pytz) dt.setTimezoneUTC(True) return dt def adjustFloatingToTimezone(dtadjust, dtcopyfrom, pytz=None): dtadjust = dtadjust.duplicate() dtadjust.setTimezone(pytz if pytz else dtcopyfrom.getTimezone()) return dtadjust def compareDateTime(dt1, dt2, defaulttz=None): if dt1.floating() and not dt2.floating(): dt1 = adjustFloatingToTimezone(dt1, dt2, defaulttz) elif dt2.floating() and not dt1.floating(): dt2 = adjustFloatingToTimezone(dt2, dt1, defaulttz) return dt1.compareDateTime(dt2) def differenceDateTime(start, end, defaulttz=None): if start.floating() and not end.floating(): start = adjustFloatingToTimezone(start, end, defaulttz) elif end.floating() and not start.floating(): end = adjustFloatingToTimezone(end, start, defaulttz) return end - start def timeRangesOverlap(start1, end1, start2, end2, defaulttz=None): # Can't compare date-time and date only, so normalize # to date only if they are mixed. if (start1 is not None) and not start1.isDateOnly() and (start2 is not None) and start2.isDateOnly(): start1.setDateOnly(True) if (start2 is not None) and not start2.isDateOnly() and (start1 is not None) and start1.isDateOnly(): start2.setDateOnly(True) if (end1 is not None) and not end1.isDateOnly() and (end2 is not None) and end2.isDateOnly(): end1.setDateOnly(True) if (end2 is not None) and not end2.isDateOnly() and (end1 is not None) and end1.isDateOnly(): end2.setDateOnly(True) # Note that start times are inclusive and end times are not. if start1 is not None and start2 is not None: if end1 is not None and end2 is not None: return compareDateTime(start1, end2, defaulttz) < 0 and compareDateTime(end1, start2, defaulttz) > 0 elif end1 is None: return compareDateTime(start1, start2, defaulttz) >= 0 and compareDateTime(start1, end2, defaulttz) < 0 elif end2 is None: return compareDateTime(start2, end1, defaulttz) < 0 else: return False elif start1 is not None: return compareDateTime(start1, end2, defaulttz) < 0 elif start2 is not None: return compareDateTime(end1, end2, defaulttz) < 0 and compareDateTime(end1, start2, defaulttz) > 0 else: return False def normalizePeriodList(periods): """ Normalize the list of periods by merging overlapping or consecutive ranges and sorting the list by each periods start. @param list: a list of tuples of L{Period}. The list is changed in place. """ # First sort the list def sortPeriods(p1, p2): """ Compare two periods. Sort by their start and then end times. A period is a L{Period}. @param p1: first period @param p2: second period @return: 1 if p1>p2, 0 if p1==p2, -1 if p1<p2 """ assert isinstance(p1, Period), "Period is not a Period: %r" % (p1,) assert isinstance(p2, Period), "Period is not a Period: %r" % (p2,) if p1.getStart() == p2.getStart(): cmp1 = p1.getEnd() cmp2 = p2.getEnd() else: cmp1 = p1.getStart() cmp2 = p2.getStart() return compareDateTime(cmp1, cmp2) for period in periods: period.adjustToUTC() periods.sort(cmp=sortPeriods) # Now merge overlaps and consecutive periods index = None p = None pe = None for i in xrange(len(periods)): if p is None: index = i p = periods[i] pe = p.getEnd() continue ie = periods[i].getEnd() if (pe >= periods[i].getStart()): if ie > pe: periods[index] = Period(periods[index].getStart(), ie) pe = ie periods[i] = None else: index = i p = periods[i] pe = p.getEnd() periods[:] = [x for x in periods if x] def clipPeriod(period, clipPeriod): """ Clip the start/end period so that it lies entirely within the clip period. @param period: the (start, end) tuple for the period to be clipped. @param clipPeriod: the (start, end) tuple for the period to clip to. @return: the (start, end) tuple for the clipped period, or None if the period is outside the clip period """ start = period.getStart() end = period.getEnd() clipStart = clipPeriod.getStart() clipEnd = clipPeriod.getEnd() if start < clipStart: start = clipStart if end > clipEnd: end = clipEnd if start >= end: return None else: # Try to preserve use of duration in period result = Period(start, end) result.setUseDuration(period.getUseDuration()) return result def pyCalendarToSQLTimestamp(pydt): if pydt.isDateOnly(): return date(year=pydt.getYear(), month=pydt.getMonth(), day=pydt.getDay()) else: return datetime( year=pydt.getYear(), month=pydt.getMonth(), day=pydt.getDay(), hour=pydt.getHours(), minute=pydt.getMinutes(), second=pydt.getSeconds(), tzinfo=None ) def parseSQLTimestampToPyCalendar(ts, withTimezone=None): """ Parse an SQL formated timestamp into a DateTime @param ts: the SQL timestamp @type ts: C{str} @return: L{DateTime} result """ if isinstance(ts, datetime): return DateTime( year=ts.year, month=ts.month, day=ts.day, hours=ts.hour, minutes=ts.minute, seconds=ts.second, tzid=withTimezone, ) else: # Format is "%Y-%m-%d %H:%M:%S" return DateTime( year=int(ts[0:4]), month=int(ts[5:7]), day=int(ts[8:10]), hours=int(ts[11:13]), minutes=int(ts[14:16]), seconds=int(ts[17:19]), tzid=withTimezone, ) def tupleFromDateTime(dt): """ Convert a L{DateTime} into a L{tuple} of L{int}s that performs better when pickled. @param dt: the date time to convert @type dt: L{DateTime} @return: L{tuple} result """ # Expand the date and time components into a tuple return ( dt.mYear, dt.mMonth, dt.mDay, dt.mHours, dt.mMinutes, dt.mSeconds ) def tupleToDateTime(tp, withTimezone=None): """ Turn an L{tuple} produced by L{tupleFromDateTime} into an L{DateTime} and use the provided time zone. @param tp: the tuple to convert @type tp: L{tuple} @param withTimezone: time zone to use or L{None} @type withTimezone: L{Timezone} @return: L{DateTime} result """ # Construct date time from tuple components return DateTime( year=tp[0], month=tp[1], day=tp[2], hours=tp[3], minutes=tp[4], seconds=tp[5], tzid=withTimezone, ) def parseSQLDateToPyCalendar(ts): """ Parse an SQL formated date into a DateTime @param ts: the SQL date @type ts: C{str} @return: L{DateTime} result """ if isinstance(ts, date): return DateTime( year=ts.year, month=ts.month, day=ts.day, ) else: # Format is "%Y-%m-%d", though Oracle may add zero time which we ignore return DateTime( year=int(ts[0:4]), month=int(ts[5:7]), day=int(ts[8:10]) ) def datetimeMktime(dt): assert isinstance(dt, date) if dt.tzinfo is None: dt.replace(tzinfo=dateutil.tz.tzutc()) return calendar.timegm(dt.utctimetuple())