Source code for moments.timestamp

# ----------------------------------------------------------------------------
# moments
# Copyright (c) 2009-2010, Charles Brandt
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ----------------------------------------------------------------------------
"""
*2010.10.24 11:49:23
much of this functionality exists in the dateutil included with python
http://labix.org/python-dateutil
I didn't know about that module at the time
Its parse function is really nice.

*2010.03.16 10:56:36 todo
would be nice if format was more generalized
it is difficult when there are different versions of a string
depending on the degree of accuracy. (seconds, no seconds, etc)

*2009.07.17 07:36:52
considering that timestamps have different formats
might be nice to associate that format with the object instance
and make it settable/configurable.

depending on format
could have different output for date, time, accuracies, etc.

*2010.03.16 10:20:38
some time ago...
moved time to be the first keyword argument
that way Timestamp objects can be initialized from a standard python
datetime object, without specifying the time keyword

*2009.03.14 18:23:13 subclass datetime why_not
would be nice if this were a subclass of datetime
with special formatters built in
tried this last week with ill effects, because...

datetime objects in python are immutable
class attributes like year, month, and day are read-only

subclassing requires overriding __new__, not __init___
http://stackoverflow.com/questions/399022/why-cant-i-subclass-datetime-date
http://www.python.org/doc/current/reference/datamodel.html#object.__new__

due to multiple ways of initializing, we don't want to require that
year, month, day
be passed in, like datetime does

could add those arguments to the init function if that was needed
by those using Timestamp objects in place of datetime objects

"""

from datetime import datetime, timedelta, date
from datetime import time as dttime
from time import strptime
import time as pytime

import re

time_format = "%Y.%m.%d %H:%M"

def has_timestamp(line):
    (ts, remainder) = parse_line_for_time(line)
    if ts:
        return True
    else:
        return False

def get_timestamp(line):
    #original way:
    #removes the leading "*"
    #return line[1:17]
    
    ts, tags = parse_line_for_time(line)
    return ts

[docs]def parse_line_for_time(line): """ look at the line, determine if there is a timestamp return the timestamp, and the remainder if so """ #most specific second_regex = "[-\*](19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01]) \d\d:\d\d:\d\d" second_search = re.compile(second_regex) minute_regex = "[-\*](19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01]) \d\d:\d\d" #time_search = "\*\d\d\d\d.\d\d.\d\d \d\d:\d\d" minute_search = re.compile(minute_regex) #hopefully optionally match a leading '-' or '*' #day_regex = "[-\*]*(19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])" #*2009.03.03 14:38:58 #requiring the leading '*' now... #old logs should be converted manually if not updated at this point day_regex = "[-\*](19|20)\d\d[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])" day_search = re.compile(day_regex) s = second_search.match(line) m = minute_search.match(line) d = day_search.match(line) #print s.span() if s: ts = line[s.span()[0]:s.span()[1]] #s.expand() elif m: ts = line[m.span()[0]:m.span()[1]] #s.expand() #ts = m.expand() elif d: ts = line[d.span()[0]:d.span()[1]] #s.expand() #ts = d.expand() else: ts = None if ts: remainder = line[len(ts):] else: entry_regex = "\*" entry_search = re.compile(entry_regex) if entry_search.match(line): remainder = line.split('*')[1] else: remainder = '' return [ts, remainder]
[docs]class Timestamp(object): """ Timestamps have different ways of being formatted, and this object is a common place to store these. compact and cstamp are the same thing. """ def __init__(self, auto=None, tstamp=None, cstamp=None, compact=None, now=True, format=None, accuracy=None): #this is the internal datetime object: #it is available externally via self.datetime self.dt = None #might be handy to know what we determined #after parsing line #or remembering if it gets set elsewhere # #NOTE: if it gets passed in, it will get overwritten if #string conversion accurracy is different. self.accuracy = accuracy self.format = format if auto is not None: if isinstance(auto, datetime): self.dt = auto elif isinstance(auto, Timestamp): #this doesn't seem to work: #self = auto self.dt = auto.dt elif isinstance(auto, str) or isinstance(auto, unicode): #self.from_text(auto) self.parse(auto) else: #print "Unknown auto item: %s (type: %s)" % (auto, type(auto)) raise ValueError, "Unknown Timestamp start value: %s of type %s" % (auto, type(auto)) elif tstamp: self.from_text(tstamp) elif cstamp: self.from_compact(cstamp) elif compact: self.from_compact(compact) elif now: self.dt = datetime.now() def __getattr__(self, name): """ if the Timestamp class doesn't have the attribute, check if the associated datetime object does have the attribute. """ if name == 'datetime' or name == 'time': return self.__getattribute__('dt') elif name in dir(self.dt): return self.dt.__getattribute__(name) else: raise AttributeError def __setattr__(self, name, value): if name == 'datetime' or name == 'dt': object.__setattr__(self, 'dt', value) #get all of the typical assignments: else: object.__setattr__(self, name, value) def __str__(self): text_time = '' if self.dt.strftime("%S") != "00": text_time = self.dt.strftime("%Y.%m.%d %H:%M:%S") elif self.dt.strftime("%H:%M") != "00:00": text_time = self.dt.strftime("%Y.%m.%d %H:%M") else: text_time = self.dt.strftime("%Y.%m.%d") return text_time #aka auto_text
[docs] def parse(self, text_time): """ Attempt to automatically determine the time format in use similar to dateutil.parser parse but no timezones note: TypeError: can't compare offset-naive and offset-aware datetimes from dateutil.parser import parse self.dt = parse(text_time) """ if re.search('T', text_time) and re.search(':', text_time): if re.search('Z', text_time): self.from_gps(text_time) else: self.from_google_calendar(text_time) elif re.search('T', text_time): self.from_apple_compact(text_time) elif re.search("-", text_time) or re.search("/", text_time) or re.search("\.", text_time): self.from_text(text_time) else: self.from_compact(text_time)
[docs] def round(self, accuracy=None): """ return a new timestamp object with the desired accuracy """ c = self.compact(accuracy) return Timestamp(compact=c)
[docs] def text(self, accuracy=None): """ return a string representation of our internal datetime object with a format like: YYYYMMDDHHMMSS controlled by 'accuracy' """ #if it was not passed in, see if it has been set elsewhere if not accuracy and self.accuracy: accuracy = self.accuracy if accuracy == 'year': return self.dt.strftime("%Y") elif accuracy == 'month': return self.dt.strftime("%Y.%m") elif (accuracy == 'day') or ((self.dt.hour == 0) and (self.dt.minute == 0) and (self.dt.second == 0)): return self.dt.strftime("%Y.%m.%d") elif accuracy == 'hour': return self.dt.strftime("%Y.%m.%d %H") elif (accuracy == 'minute') or (self.dt.second == 0): return self.dt.strftime("%Y.%m.%d %H:%M") else: return self.dt.strftime("%Y.%m.%d %H:%M:%S") #was time_to_tstamp
[docs] def compact(self, accuracy=None): """ return a string representation of our internal datetime object with a format like: YYYYMMDDHHMMSS controlled by 'accuracy' """ #if it was not passed in, see if it has been set elsewhere if not accuracy and self.accuracy: accuracy = self.accuracy if accuracy == 'year': return self.dt.strftime("%Y") elif accuracy == 'month': return self.dt.strftime("%Y%m") elif (accuracy == 'day') or ((self.dt.hour == 0) and (self.dt.minute == 0) and (self.dt.second == 0)): return self.dt.strftime("%Y%m%d") elif accuracy == 'hour': return self.dt.strftime("%Y%m%d%H") elif (accuracy == 'minute') or (self.dt.second == 0): return self.dt.strftime("%Y%m%d%H%M") else: return self.dt.strftime("%Y%m%d%H%M%S")
[docs] def filename(self, suffix=".txt"): """ often need to generate a filename from a timestamp this makes it easy! """ return self.compact(accuracy='day') + suffix
[docs] def epoch(self): """ convert Timestamps to a 'seconds since epoc' format return the current timestamp object as the number of seconds since the epoch. aka POSIX timestamp aka utime (?) *2009.11.04 13:57:55 http://stackoverflow.com/questions/255035/converting-datetime-to-posix-time """ #import time, datetime return pytime.mktime(self.dt.timetuple())
[docs] def from_epoch(self, posix_time): """ accept a posix time and convert it to a local datetime (in current Timestamp) http://docs.python.org/library/datetime.html#datetime.datetime.fromtimestamp see also datetime.utcfromtimestamp() """ self.dt = datetime.fromtimestamp(posix_time) #was tstamp_to_time
[docs] def from_compact(self, text_time): """ take a string of the format: YYYYMMDDHHMMSS return a python datetime object """ if len(text_time) == 14: self.dt = datetime(*(strptime(text_time, "%Y%m%d%H%M%S")[0:6])) self.accuracy = 'second' elif len(text_time) == 12: self.dt = datetime(*(strptime(text_time, "%Y%m%d%H%M")[0:6])) self.accuracy = 'minute' elif len(text_time) == 10: self.dt = datetime(*(strptime(text_time, "%Y%m%d%H")[0:6])) self.accuracy = 'hour' elif len(text_time) == 8: self.dt = datetime(*(strptime(text_time, "%Y%m%d")[0:6])) self.accuracy = 'day' elif len(text_time) == 6: self.dt = datetime(*(strptime(text_time, "%Y%m")[0:6])) self.accuracy = 'month' elif len(text_time) == 4: self.dt = datetime(*(strptime(text_time, "%Y")[0:6])) self.accuracy = 'year' else: #some other format #self.dt = None raise AttributeError, "Unknown compact time format: %s" % text_time return self.dt #was log.text_to_time #in ruby: parse
[docs] def from_text(self, text_time): """ take a string of the format: YYYY.MM.DD HH:MM return a python datetime object """ #removed leading char if needed if re.match("[-\*]", text_time): text_time = text_time[1:] if re.search("-", text_time): format = "%Y-%m-%d %H:%M:%S" elif re.search("/", text_time): format = "%Y/%m/%d %H:%M:%S" else: format = "%Y.%m.%d %H:%M:%S" if len(text_time) > 19: #2007-05-03T15:56:05-04:00 # #but what about 2009-07-17 07:14:17.003537? #truncating for now #TODO: accept micro seconds text_time = text_time[:19] time = datetime(*(strptime(text_time, "%Y-%m-%dT%H:%M:%S")[0:6])) self.accuracy = 'second' elif len(text_time) == 19: # this only works with python 2.5 # (current default is 2.4.4 for zope) #return datetime.strptime(text_time, self.text_time_format) # e.g. "%Y.%m.%d %H:%M:%S" time = datetime(*(strptime(text_time, format)[0:6])) self.accuracy = 'second' elif len(text_time) == 16: # e.g. "%Y.%m.%d %H:%M" time = datetime(*(strptime(text_time, format[:14])[0:6])) self.accuracy = 'minute' elif len(text_time) == 10: # e.g. "%Y.%m.%d" time = datetime(*(strptime(text_time, format[:8])[0:6])) self.accuracy = 'day' else: #some other format #time = None raise ValueError, "Unknown time string format passed to Timestamp.from_text: %s (type: %s)" % (text_time, type(text_time)) self.dt = time return time
[docs] def from_text2(self, text_time): """ Month DD, YYYY """ months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ] #get rid of double spacing, if it exists text_time = text_time.replace(" ", " ") (month_s, day_s, year_s) = text_time.split(' ') if month_s in months: month = months.index(month_s) + 1 #print month else: print "Couldn't find: %s" % month_s month = None #get rid of ',' after date if re.search(',', day_s): day = int(day_s[:-1]) else: day = int(day_s) year = int(year_s) #print "Day: %s, Year: %s" % (day, year) compact = "%s%02d%02d" % (year, month, day) #try: self.dt = datetime(*(strptime(compact, "%Y%m%d")[0:6])) self.accuracy = 'day' #stamp = Timestamp(compact=compact) #except: # print "error making Timestamp from: %s" % compact
[docs] def from_gps(self, text_time, offset=-4): """ take a string of the gps format: apply a delta for local time zone return a python datetime object """ if len(text_time) == 20: self.dt = datetime(*(strptime(text_time, "%Y-%m-%dT%H:%M:%SZ")[0:6])) self.accuracy = 'second' else: #some other format self.dt = None print "Unknown gps date format: %s" % text_time exit() off = timedelta(hours=offset) self.dt += off return self.dt #was tstamp_to_time
[docs] def from_apple_compact(self, text_time): """ take a string of the format: YYYYMMDDTHHMMSS (where T is the character 'T') return a python datetime object """ #get rid of trailing Z if len(text_time) == 16: text_time = text_time[:15] if len(text_time) == 15: self.dt = datetime(*(strptime(text_time, "%Y%m%dT%H%M%S")[0:6])) self.accuracy = 'second' elif len(text_time) == 13: self.dt = datetime(*(strptime(text_time, "%Y%m%dT%H%M")[0:6])) self.accuracy = 'minute' elif len(text_time) == 11: self.dt = datetime(*(strptime(text_time, "%Y%m%dT%H")[0:6])) self.accuracy = 'hour' elif len(text_time) == 8: self.dt = datetime(*(strptime(text_time, "%Y%m%d")[0:6])) self.accuracy = 'day' elif len(text_time) == 6: self.dt = datetime(*(strptime(text_time, "%Y%m")[0:6])) self.accuracy = 'month' elif len(text_time) == 4: self.dt = datetime(*(strptime(text_time, "%Y")[0:6])) self.accuracy = 'year' else: #some other format self.dt = None return self.dt
[docs] def apple_compact(self, accuracy=None): """ take a python datetime object return a string representation of that time YYYYMMDDTHHMMSS """ return self.dt.strftime("%Y%m%dT%H%M%S")
[docs] def from_google_calendar(self, text_time): """ take a string of the format: YYYY-MM-DDTHH:MM:SS.000-04:00 (where T is the character 'T') return a python datetime object Note that timezone feature is not yet working """ ## if len(text_time) == 29: ## #TODO: incorporate time zone when available ## #self.dt = datetime(*(strptime(text_time, "%Y-%m-%dT%H:%M:%S.000-0.:00")[0:6])) ## #note: ## #TypeError: can't compare offset-naive and offset-aware datetimes ## from dateutil.parser import parse ## self.dt = parse(text_time) ## elif len(text_time) == 19: if len(text_time) == 29: #discarding timezone in this case: text_time = text_time[:19] if len(text_time) == 19: self.dt = datetime(*(strptime(text_time, "%Y-%m-%dT%H:%M:%S")[0:6])) self.accuracy = 'second' elif len(text_time) == 16: self.dt = datetime(*(strptime(text_time, "%Y-%m-%dT%H:%M")[0:6])) self.accuracy = 'minute' elif len(text_time) == 13: self.dt = datetime(*(strptime(text_time, "%Y-%m-%dT%H")[0:6])) self.accuracy = 'hour' elif len(text_time) == 10: self.dt = datetime(*(strptime(text_time, "%Y-%m-%d")[0:6])) self.accuracy = 'day' elif len(text_time) == 7: self.dt = datetime(*(strptime(text_time, "%Y-%m")[0:6])) self.accuracy = 'month' elif len(text_time) == 4: self.dt = datetime(*(strptime(text_time, "%Y")[0:6])) self.accuracy = 'year' else: #some other format self.dt = None return self.dt
[docs] def google_calendar(self, accuracy=None): """ take a python datetime object return a string representation of that time YYYYMMDDTHHMMSS """ return self.dt.strftime("%Y-%m-%dT%H:%M:%S") ## def from_blog(self, text): ## """ ## the following format is often used in blogs ## """ ## pass ## def from_format(self, text, format): ## """ ## use the supplied format to parse the text ## """ ## pass
[docs] def now(self): """ update the timestamp to be the current time (when now() is called) """ self.dt = datetime.now()
[docs] def future(self, years=0, weeks=0, days=0, hours=0, minutes=0, seconds=0): """ return a new Timestamp object that is in the future according to parameters months are not included since it is tricky to convert those into days (months are not consistent in length) and days are what we need to boil the distance down to. could revisit that in the future (hehe) the tricky cases will be when the function is called on the 31st of a month, and told to go into the future to a month that does not have 31 days. Should it roll back to the 30th of that future month? (or 28th in Feb?) or should it go forward? """ future = self.dt if years: year = timedelta(365) future = future + (years * year) if weeks: week = timedelta(7) future = future + (weeks * week) if days: future = future + timedelta(days) if hours: future = future + timedelta(hours=hours) if minutes: future = future + timedelta(minutes=minutes) if seconds: future = future + timedelta(seconds=seconds) return Timestamp(future)
[docs] def past(self, years=0, weeks=0, days=0, hours=0, minutes=0, seconds=0): """ return a new Timestamp object that is in the past according to parameters """ past = self.dt if years: year = timedelta(365) past = past - (years * year) if weeks: week = timedelta(7) past = past - (weeks * week) if days: past = past - timedelta(days) if hours: past = past - timedelta(hours=hours) if minutes: past = past - timedelta(minutes=minutes) if seconds: past = past - timedelta(seconds=seconds) return Timestamp(past)
[docs] def future_month(self, months=1): """ because future and past does not handle months, handle this separately """ next_month = self.month + months if next_month >= 13: years = next_month / 12 next_month = next_month % 12 #next_month = 1 year = self.year + years else: year = self.year next_compact = "%s%02d" % (year, next_month) next_month_stamp = Timestamp(compact=next_compact) return next_month_stamp
def past_month(self, months=1): prior_month = self.month - months if prior_month <= 0: years = 1 + (abs(prior_month) / 12) year = self.year - years prior_month = 12 - (abs(prior_month) % 12) else: year = self.year prior_compact = "%s%02d" % (year, prior_month) prior_month_stamp = Timestamp(compact=prior_compact) return prior_month_stamp
[docs] def is_in(self, timerange): """ check if we are contained in the given timerange this should be equivalent to: timerange.has(timestamp) """ #print "Datetime: %s" % self.datetime #print "Start Datetime: %s" % timerange.start.datetime #print "End Datetime: %s" % timerange.end.datetime #print type(self.datetime) #print type(self.dt) #print type(timerange.start.datetime) if ( (self.datetime > timerange.start.datetime) and (self.datetime < timerange.end.datetime) ): return True else: return False # the following are picked up by __getattr__() # which pulls them from the self.dt (datetime) ## def year(self): ## """ ## return a string for our year (YYYY) ## """ ## return self.dt.strftime("%Y") ## def month(self): ## """ ## return a string for our month (MM) ## """ ## return self.dt.strftime("%m") ## def day(self): ## """ ## return a string for our day (DD) ## """ ## return self.dt.strftime("%d") ## def hour(self): ## """ ## return a string for our hour (HH) (24 hour) ## """ ## return self.dt.strftime("%H") ## def minute(self): ## """ ## return a string for our minute (MM) ## """ ## return self.dt.strftime("%M") ## def second(self): ## """ ## return a string for our second (SS) ## """ ## return self.dt.strftime("%S")
[docs]class Timerange(object): """ Timerange holds a start and end python datetime trange can either be a string representing two timestamps separated by a '-' or just a simple tstamp string in which case it will be evaluated as a range based on accuracy *2011.07.06 19:21:53 start can be either a text based string range "start-end" or a timestamp object representing the start of the range *2011.07.06 22:15:06 merging in RelativeRange functions Relative range was a class to quickly get ranges relative to 'now' (or start) sometimes these are more complex than just 'future' and 'past' on Timestamp results in timeranges from specific functions """ def __init__(self, start, end=None, name='', end_is_now=False): """ remember that start can be a string based range value: 'YYYYMMDD-YYYYMMDD' """ #this may get set in a number of different places self.end = None if isinstance(start, str) or isinstance(start, unicode): self.from_text(start) else: #this should handle all different cases self.start = Timestamp(start) if end: #this should handle #all of the different conditions / formats / types #that end could be in self.end = Timestamp(end) elif end_is_now: #end = datetime.now() self.end = Timestamp() elif not self.end: #default should set self.end now self.default() #temp = self.default() #self.end = temp.end else: #end was not passed in, #but self.end must have been set elsewhere (from_text()) #all should be ok then pass #print type(self) #print "Name: ->%s<-" % name #name could be a string indicating the type of relation #e.g. #last month, this month, next_month self._name = name #name is a property on the cycle.Month #can't set a property in parent class in that case def _get_name(self): return self._name name = property(_get_name) def __str__(self): if self.start == self.end: return self.start.compact() else: return '-'.join( [self.start.compact(), self.end.compact()] ) def as_tuple(self): return (self.start, self.end)
[docs] def has(self, timestamp): """ return true if timestamp is in our range see also Timestamp.is_in(Timerange) """ pass
[docs] def from_text(self, trange): """ will work with either a simple timestamp string or a string with a - separating two timestamp strings check the tstamp for a range of times split if found return the range start and end """ start = '' end = '' #check for a '-' indicating a YYYYMMDD-YYYYMMDD string if re.search('-', trange): (start, end) = trange.split('-') else: start = trange #(start, default_end) = self.from_tstamp(start) self.start = Timestamp(compact=start) #if we found an explicit end in the string, use it if end: #end = Timestamp().from_compact(end) self.end = Timestamp(compact=end) #end = ts.time #end = tstamp_to_time(end) else: self.default() #end = self.end #self.start = start #self.end = end return (self.start, self.end)
[docs] def biggest_cycle(self): """ determine what the biggest cycle is in our range... i.e. year, month, day """ #can't do anything if there is no end in the range if not self.end: return None else: diff = self.end.datetime - self.start.datetime if diff.days >= 365: print "YEAR" return "year" elif diff.days >= 31: print "MONTH" return "month" elif diff.days >= 28: print "MONTH (maybe)" diff_m = self.end.month - self.start.month #should check here if the month and days increment accordingly if (diff_m == 1) and (self.end.day >= self.start.day): return "month" elif (diff_m > 1): return "month" elif diff.days >= 7: print "WEEK" return "week" elif diff.days >= 1: print "DAY" return "day" else: return "hour"
[docs] def months(self, overlap_edges=True): """ return a list of all months contained in self (this could also be an iterator) if overlap_edges is set, may extend beyond the current range rounding to the nearest full month on each end """ #rr = RelativeRange() current = self.start.round("month") end = self.end.round("month") months = [] while str(current) != str(end): #month = rr.month(current) month = self.month(current) months.append(month) current = current.future_month(months=1) return months
[docs] def days(self, overlap_edges=True): """ return a list of all days contained in self (this could also be an iterator) if overlap_edges is set, may extend beyond the current range rounding to the nearest full day on each end """ #rr = RelativeRange() current = self.start.round("day") end = self.end.round("day") days = [] while str(current) != str(end): #day = rr.day(current) day = self.day(current) days.append(day) current = current.future(days=1) return days #Relative Range functions #the following functions could be used independent of a Timerange context #but keeping them here for easier access
[docs] def default(self): """ checks the accuracy of the start timestamp calls correct method automatically to generate a range based on that accuracy """ accuracy = self.start.accuracy if accuracy and accuracy != "second": if accuracy == "year": return self.year() elif accuracy == "month": return self.month() elif accuracy == "day": return self.day() elif accuracy == "hour": return self.hour() elif accuracy == "minute": return self.minute() else: #print "Invalid accuracy for automatic Timerange: %s" % self.start.accuracy #if we can't make an intelligent choice about the range #based on the accuracy of the start #then we can just default to now self.end = Timestamp() return self
[docs] def year(self, timestamp=None): """ return a range for the year that timestamp falls in """ if timestamp: #want to make sure we normalize to an acutal Timestamp object timestamp = Timestamp(timestamp) else: timestamp = self.start start_compact = "%s" % (timestamp.year) end_compact = "%s1231235959" % (timestamp.year) self.end = Timestamp(compact=end_compact) return Timerange("%s-%s" % (start_compact, end_compact))
[docs] def month(self, timestamp=None): """ return a range for the month that timestamp falls in """ if timestamp: #want to make sure we normalize to an acutal Timestamp object timestamp = Timestamp(timestamp) else: timestamp = self.start #start_compact = "%s%02d" % (timestamp.year, timestamp.month) #month_start_stamp = Timestamp(compact=start_compact) month_start_stamp = timestamp.round(accuracy="month") next_month_stamp = timestamp.future_month() #print "Next month: %s" % next_month #print "Next month stamp: %s" % next_month_stamp sec = timedelta(seconds=1) month_end = next_month_stamp.datetime - sec #print month_end month_end_stamp = Timestamp(month_end) self.end = month_end_stamp return Timerange(start=month_start_stamp, end=month_end_stamp)
[docs] def week(self, timestamp=None, week_start=0): """ uses date.weekday() to determine position in the week Monday is 0, (default week_start) if another day should be used, specify in week_start """ if timestamp: #want to make sure we normalize to an acutal Timestamp object timestamp = Timestamp(timestamp) else: timestamp = self.start #round timestamp to the beginning of the day: day = timestamp.round(accuracy='day') timestamp = day date = timestamp.datetime.date() weekday = date.weekday() if weekday >= week_start: go_back = weekday - week_start go_forward = 6 - weekday else: go_back = weekday - week_start + 6 + 1 go_forward = week_start - weekday - 1 go_forward += 1 #print "back: %s, forward: %s" % (go_back, go_forward) week_start_stamp = timestamp.past(days=go_back) week_end_plus = timestamp.future(days=go_forward) sec = timedelta(seconds=1) week_end = week_end_plus.datetime - sec #print month_end week_end_stamp = Timestamp(week_end) self.end = week_end_stamp return Timerange(start=week_start_stamp, end=week_end_stamp)
[docs] def day(self, timestamp=None): """ return a range for the day that timestamp falls in """ if timestamp: #want to make sure we normalize to an acutal Timestamp object timestamp = Timestamp(timestamp) else: timestamp = self.start today_start_stamp = timestamp.round(accuracy="day") tomorrow_stamp = timestamp.future(days=1) sec = timedelta(seconds=1) today_end = tomorrow_stamp.datetime - sec today_end_stamp = Timestamp(today_end) self.end = today_end_stamp return Timerange(start=today_start_stamp, end=today_end_stamp)
[docs] def hour(self, timestamp=None): """ return a range for the day that timestamp falls in """ if timestamp: #want to make sure we normalize to an acutal Timestamp object timestamp = Timestamp(timestamp) else: timestamp = self.start start_stamp = timestamp.round(accuracy="hour") next_stamp = timestamp.future(hours=1) sec = timedelta(seconds=1) hour_end = next_stamp.datetime - sec end_stamp = Timestamp(hour_end) self.end = end_stamp return Timerange(start=start_stamp, end=end_stamp)
[docs] def minute(self, timestamp=None): """ return a range for the minute that timestamp falls in """ if timestamp: #want to make sure we normalize to an acutal Timestamp object timestamp = Timestamp(timestamp) else: timestamp = self.start start_stamp = timestamp.round(accuracy="minute") next_stamp = timestamp.future(minutes=1) sec = timedelta(seconds=1) minute_end = next_stamp.datetime - sec end_stamp = Timestamp(minute_end) self.end = end_stamp return Timerange(start=start_stamp, end=end_stamp)
[docs] def year_past(self, timestamp=None): """the last 12 months""" if timestamp: #want to make sure we normalize to an acutal Timestamp object timestamp = Timestamp(timestamp) else: timestamp = self.start end_compact = timestamp.compact() last_year = timestamp.past(years=1) start_compact = last_year.compact() return Timerange("%s-%s" % (start_compact, end_compact))
def this_month(self): return self.month(self.start) def last_month(self): #last_compact = "%s%02d" % (self.start. last_month_ts = self.start.past_month() return self.month(last_month_ts) def next_month(self): #last_compact = "%s%02d" % (self.start. next_month_ts = self.start.future_month() return self.month(next_month_ts) #should see future and past operations on Timestamp now #def this_week_last_year(today=date.today()): def this_week_last_year(today=None): if not today: today = datetime.combine(date.today(), dttime(0)) #today = datetime.datetime.now() #could use date to same effect, #but that would require exceptions/checks in timestamp for type #today.hour = 0 #today.minute = 0 #today.second = 0 #these are read-only attributes year = timedelta(365) last_year = today - year start = last_year - timedelta(4) end = last_year + timedelta(4) #stamp = start.strftime("%Y%m%d") + '-' + end.strftime("%Y%m%d") return Timerange(start=start, end=end) #stamp = str(tr) #return stamp
class Year(object): def __init__(self): self.months = []
[docs]class Month(Timerange): """ using this to hold a collection of days (or weeks?) to ultimately render those days, and their content (summary) to some other representation (HTML, areaui, etc) """ def __init__(self, tstamp=None, **kwargs): Timerange.__init__(self, tstamp, **kwargs) #test to make sure we were sent an actual month, #rr = RelativeRange(self.start) #rr = Timerange(self.start, name="relative") #rr_month = rr.month() rr_month = self.month() if rr_month.start.datetime != self.start.datetime: print "Moving start from: %s to: %s" % (self.start, rr_month.start) self.start = rr_month.start if rr_month.end.datetime != self.end.datetime: print "Moving end from: %s to: %s" % (self.end, rr_month.end) self.end = rr_month.end #start = timerange.start #end = timerange.end #timerange = timerange #using a dict for easier (more intuitive) access to the days self.days = {} #print range(start.day, end.day+1) for i in range(self.start.day, self.end.day+1): day_stamp = "%s%02d%02d" % ( self.start.year, self.start.month, i ) day = Day(day_stamp) self.days[i] = day #print i def _get_name(self): #months = [ "January" return self.start.strftime("%B") name = property(_get_name)
class Week(object): def __init__(self): self.days = []
[docs]class Day(Timerange): """ days are a 24 hour period starting at midnight (00:00) and ending at 23:59... have a number within a month and a name and number within a week have a number within a year These are a time range too """ def __init__(self, tstamp=None, **kwargs): Timerange.__init__(self, tstamp, **kwargs) #test to make sure we were sent an actual month, #rr = RelativeRange(self.start) rr = Timerange(self.start) rr_day = rr.day() if rr_day.start.datetime != self.start.datetime: #print "Moving start from: %s to: %s" % (self.start, rr_day.start) self.start = rr_day.start if rr_day.end.datetime != self.end.datetime: #print "Moving end from: %s to: %s" % (self.end, rr_day.end) self.end = rr_day.end self.number = self.start.datetime.day self.hours = [] self.items = [] ## #was tstamp_to_timerange ## def from_tstamp(self, text_time): ## """ ## take a string of the format: ## YYYYMMDDHHMMSS ## return a tuple of python datetime objects ## based on the format supplied ## can assume different ranges based on the degree of accuracy ## we want to assume the difference ## """ ## if len(text_time) == 14: ## start = datetime(*(strptime(text_time, "%Y%m%d%H%M%S")[0:6])) ## end = start ## elif len(text_time) == 12: ## start = datetime(*(strptime(text_time, "%Y%m%d%H%M")[0:6])) ## delta = timedelta(minutes=1) ## end = start+delta ## elif len(text_time) == 10: ## start = datetime(*(strptime(text_time, "%Y%m%d%H")[0:6])) ## delta = timedelta(hours=1) ## end = start+delta ## elif len(text_time) == 8: ## start = datetime(*(strptime(text_time, "%Y%m%d")[0:6])) ## #this differs from RelativeRange that uses same date, 23:59:59 instead ## delta = timedelta(days=1) ## end = start+delta ## elif len(text_time) == 6: ## start = datetime(*(strptime(text_time, "%Y%m")[0:6])) ## month = start.month ## if month == 12: ## month = 1 ## else: ## month += 1 ## #there is no concept of months in timedelta: ## #delta = timedelta(months=1) ## #end = start+delta ## end = datetime(start.year, month, 1) ## elif len(text_time) == 4: ## start = datetime(*(strptime(text_time, "%Y")[0:6])) ## delta = timedelta(days=365) ## end = start+delta ## else: ## #some other format ## #start = None ## #end = None ## raise AttributeError, "Unknown timerange format: %s" % text_time ## self.start = Timestamp(start) ## self.end = Timestamp(end) ## return (self.start, self.end) ## #class RelativeRange(Timerange): ## class RelativeRange(object): ## """ ## class to quickly get ranges relative to 'now' ## sometimes these are more complex than just 'future' and 'past' on Timestamp ## return timeranges from specific functions ## """ ## def __init__(self, timestamp=None, name=''): ## #Timerange.__init__(self) ## #name should be a string indicating the type of relation ## #e.g. ## #last month, this month, next_month ## self.name = name ## if not timestamp: ## self.ts = Timestamp() ## else: ## self.ts = timestamp