1
# -*- test-case-name: epsilon.test.test_extime -*-
3
Extended date/time formatting and miscellaneous functionality.
5
See the class 'Time' for details.
11
from email.Utils import parsedate_tz
13
_EPOCH = datetime.datetime.utcfromtimestamp(0)
16
class InvalidPrecision(Exception):
18
L{Time.asHumanly} was passed an invalid precision value.
23
def sanitizeStructTime(struct):
25
Convert struct_time tuples with possibly invalid values to valid
26
ones by substituting the closest valid value.
28
maxValues = (9999, 12, 31, 23, 59, 59)
29
minValues = (1, 1, 1, 0, 0, 0)
31
for value, maxValue, minValue in zip(struct[:6], maxValues, minValues):
32
newstruct.append(max(minValue, min(value, maxValue)))
33
return tuple(newstruct) + struct[6:]
35
def _timedeltaToSignHrMin(offset):
37
Return a (sign, hour, minute) triple for the offset described by timedelta.
39
sign is a string, either "+" or "-". In the case of 0 offset, sign is "+".
41
minutes = round((offset.days * 3600000000 * 24
42
+ offset.seconds * 1000000
43
+ offset.microseconds)
50
return (sign, minutes // 60, minutes % 60)
52
def _timedeltaToSeconds(offset):
54
Convert a datetime.timedelta instance to simply a number of seconds.
56
For example, you can specify purely second intervals with timedelta's
59
>>> td = datetime.timedelta(seconds=99999999)
61
but then you can't get them out again:
68
>>> import epsilon.extime
69
>>> epsilon.extime._timedeltaToSeconds(td)
72
@param offset: a L{datetime.timedelta} representing an interval that we
73
wish to know the total number of seconds for.
75
@return: a number of seconds
78
return ((offset.days * 60*60*24) +
80
(offset.microseconds * 1e-6))
82
class FixedOffset(datetime.tzinfo):
83
_zeroOffset = datetime.timedelta()
85
def __init__(self, hours, minutes):
86
self.offset = datetime.timedelta(minutes = hours * 60 + minutes)
88
def utcoffset(self, dt):
92
return _timedeltaToSignHrMin(self.offset)
95
return self._zeroOffset
98
return '<%s.%s object at 0x%x offset %r>' % (
99
self.__module__, type(self).__name__, id(self), self.offset)
104
"""An object representing a well defined instant in time.
106
A Time object unambiguously addresses some time, independent of timezones,
107
contorted base-60 counting schemes, leap seconds, and the effects of
108
general relativity. It provides methods for returning a representation of
109
this time in various ways that a human or a programmer might find more
110
useful in various applications.
112
Every Time instance has an attribute 'resolution'. This can be ignored, or
113
the instance can be considered to address a span of time. This resolution
114
is determined by the value used to initalize the instance, or the
115
resolution of the internal representation, whichever is greater. It is
116
mostly useful when using input formats that allow the specification of
117
whole days or weeks. For example, ISO 8601 allows one to state a time as,
118
"2005-W03", meaning "the third week of 2005". In this case the resolution
119
is set to one week. Other formats are considered to express only an instant
120
in time, such as a POSIX timestamp, because the resolution of the time is
121
limited only by the hardware's representation of a real number.
123
Timezones are significant only for instances with a resolution greater than
124
one day. When the timezone is insignificant, the result of methods like
125
asISO8601TimeAndDate is the same for any given tzinfo parameter. Sort order
126
is determined by the start of the period in UTC. For example, "today" sorts
127
after "midnight today, central Europe", and before "midnight today, US
128
Eastern". For applications that need to store a mix of timezone dependent
129
and independent instances, it may be wise to store them separately, since
130
the time between the start and end of today in the local timezone may not
131
include the start of today in UTC, and thus not independent instances
132
addressing the whole day. In other words, the desired sort order (the one
133
where just "Monday" sorts before any more precise time in "Monday", and
134
after any in "Sunday") of Time instances is dependant on the timezone
137
Date arithmetic and boolean operations operate on instants in time, not
138
periods. In this case, the start of the period is used as the value, and
139
the result has a resolution of 0.
141
For containment tests with the 'in' operator, the period addressed by the
144
The methods beginning with 'from' are constructors to create instances from
145
various formats. Some of them are textual formats, and others are other
146
time types commonly found in Python code.
148
Likewise, methods beginning with 'as' return the represented time in
149
various formats. Some of these methods should try to reflect the resolution
150
of the instance. However, they don't yet.
152
For formats with both a constructor and a formatter, d == fromFu(d.asFu())
154
@type resolution: datetime.timedelta
155
@ivar resolution: the length of the period to which this instance could
156
refer. For example, "Today, 13:38" could refer to any time between 13:38
157
until but not including 13:39. In this case resolution would be
158
timedelta(minutes=1).
161
# the instance variable _time is the internal representation of time. It
162
# is a naive datetime object which is always UTC. A UTC tzinfo would be
163
# great, if one existed, and anyway it complicates pickling.
166
class Precision(object):
172
Precision.MINUTES: '%I:%M %p',
173
Precision.SECONDS: '%I:%M:%S %p'}
175
rfc2822Weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
177
rfc2822Months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
178
'Sep', 'Oct', 'Nov', 'Dec']
180
resolution = datetime.timedelta.resolution
183
# Methods to create new instances
187
"""Return a new Time instance representing the time now.
189
See also the fromFu methods to create new instances from other types of
192
self._time = datetime.datetime.utcnow()
195
def _fromWeekday(klass, match, tzinfo, now):
196
weekday = klass.weekdays.index(match.group('weekday').lower())
197
dtnow = now.asDatetime().replace(
198
hour=0, minute=0, second=0, microsecond=0)
199
daysInFuture = (weekday - dtnow.weekday()) % len(klass.weekdays)
200
if daysInFuture == 0:
202
self = klass.fromDatetime(dtnow + datetime.timedelta(days=daysInFuture))
203
assert self.asDatetime().weekday() == weekday
204
self.resolution = datetime.timedelta(days=1)
208
def _fromTodayOrTomorrow(klass, match, tzinfo, now):
209
dtnow = now.asDatetime().replace(
210
hour=0, minute=0, second=0, microsecond=0)
211
when = match.group(0).lower()
212
if when == 'tomorrow':
213
dtnow += datetime.timedelta(days=1)
214
elif when == 'yesterday':
215
dtnow -= datetime.timedelta(days=1)
217
assert when == 'today'
218
self = klass.fromDatetime(dtnow)
219
self.resolution = datetime.timedelta(days=1)
223
def _fromTime(klass, match, tzinfo, now):
224
minute = int(match.group('minute'))
225
hour = int(match.group('hour'))
226
ampm = (match.group('ampm') or '').lower()
228
if not 1 <= hour <= 12:
229
raise ValueError, 'hour %i is not in 1..12' % (hour,)
230
if hour == 12 and ampm == 'am':
234
if not 0 <= hour <= 23:
235
raise ValueError, 'hour %i is not in 0..23' % (hour,)
237
dtnow = now.asDatetime(tzinfo).replace(second=0, microsecond=0)
238
dtthen = dtnow.replace(hour=hour, minute=minute)
240
dtthen += datetime.timedelta(days=1)
242
self = klass.fromDatetime(dtthen)
243
self.resolution = datetime.timedelta(minutes=1)
247
def _fromNoonOrMidnight(klass, match, tzinfo, now):
248
when = match.group(0).lower()
252
assert when == 'midnight'
254
dtnow = now.asDatetime(tzinfo).replace(
255
minute=0, second=0, microsecond=0)
256
dtthen = dtnow.replace(hour=hour)
258
dtthen += datetime.timedelta(days=1)
260
self = klass.fromDatetime(dtthen)
261
self.resolution = datetime.timedelta(minutes=1)
264
def _fromNow(klass, match, tzinfo, now):
265
# coerce our 'now' argument to an instant
266
return now + datetime.timedelta(0)
268
weekdays = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday',
269
'saturday', 'sunday']
285
""", re.IGNORECASE | re.VERBOSE),
287
(re.compile(r"\b(today|tomorrow|yesterday)\b", re.IGNORECASE),
288
_fromTodayOrTomorrow),
291
(?P<hour>\d{1,2}):(?P<minute>\d{2})
292
(\s*(?P<ampm>am|pm))?
294
""", re.IGNORECASE | re.VERBOSE),
296
(re.compile(r"\b(noon|midnight)\b", re.IGNORECASE),
297
_fromNoonOrMidnight),
298
(re.compile(r"\b(now)\b", re.IGNORECASE),
302
_fromWeekday = classmethod(_fromWeekday)
303
_fromTodayOrTomorrow = classmethod(_fromTodayOrTomorrow)
304
_fromTime = classmethod(_fromTime)
305
_fromNoonOrMidnight = classmethod(_fromNoonOrMidnight)
306
_fromNow = classmethod(_fromNow)
309
def fromHumanly(klass, humanStr, tzinfo=None, now=None):
310
"""Return a new Time instance from a string a human might type.
312
@param humanStr: the string to be parsed.
314
@param tzinfo: A tzinfo instance indicating the timezone to assume if
315
none is specified in humanStr. If None, assume UTC.
317
@param now: A Time instance to be considered "now" for when
318
interpreting relative dates like "tomorrow". If None, use the real now.
320
Total crap now, it just supports weekdays, "today" and "tomorrow" for
321
now. This is pretty insufficient and useless, but good enough for some
322
demo functionality, or something.
324
humanStr = humanStr.strip()
328
tzinfo = FixedOffset(0, 0)
330
for pattern, creator in klass.humanlyPatterns:
331
match = pattern.match(humanStr)
333
or match.span()[1] != len(humanStr):
336
return creator(klass, match, tzinfo, now)
339
raise ValueError, 'could not parse date: %r' % (humanStr,)
341
fromHumanly = classmethod(fromHumanly)
344
iso8601pattern = re.compile(r"""
347
# a year may optionally be followed by one of:
350
# - a specific day, and an optional time
351
# a specific day is one of:
354
# - a day of the year
356
-? (?P<month1> \d{2})
358
-? W (?P<week1> \d{2})
361
-? (?P<month2> \d{2})
364
-? W (?P<week2> \d{2})
367
-? (?P<dayofyear> \d{3})
372
:? (?P<minute> \d{2})
374
:? (?P<second> \d{2})
376
[\.,] (?P<fractionalsec> \d+)
383
(?P<tzhour> [+\-]\d{2})
393
def fromISO8601TimeAndDate(klass, iso8601string, tzinfo=None):
394
"""Return a new Time instance from a string formated as in ISO 8601.
396
If the given string contains no timezone, it is assumed to be in the
397
timezone specified by the parameter `tzinfo`, or UTC if tzinfo is None.
398
An input string with an explicit timezone will always override tzinfo.
400
If the given iso8601string does not contain all parts of the time, they
401
will default to 0 in the timezone given by `tzinfo`.
403
WARNING: this function is incomplete. ISO is dumb and their standards
404
are not free. Only a subset of all valid ISO 8601 dates are parsed,
405
because I can't find a formal description of the format. However,
406
common ones should work.
409
def calculateTimezone():
410
if groups['zulu'] == 'Z':
411
return FixedOffset(0, 0)
413
tzhour = groups.pop('tzhour')
414
tzmin = groups.pop('tzmin')
415
if tzhour is not None:
416
return FixedOffset(int(tzhour), int(tzmin or 0))
417
return tzinfo or FixedOffset(0, 0)
420
groups['month'] = groups['month1'] or groups['month2']
421
groups['week'] = groups['week1'] or groups['week2']
422
# don't include fractional seconds, because it's not an integer.
423
defaultTo0 = ['hour', 'minute', 'second']
424
defaultTo1 = ['month', 'day', 'week', 'weekday', 'dayofyear']
425
if groups['fractionalsec'] is None:
426
groups['fractionalsec'] = '0'
427
for key in defaultTo0:
428
if groups[key] is None:
430
for key in defaultTo1:
431
if groups[key] is None:
433
groups['fractionalsec'] = float('.'+groups['fractionalsec'])
434
for key in defaultTo0 + defaultTo1 + ['year']:
435
groups[key] = int(groups[key])
437
for group, min, max in [
438
# some years have only 52 weeks
446
# Sometime in the 22nd century AD, two leap seconds will be
447
# required every year. In the 25th century AD, four every
448
# year. We'll ignore that for now though because it would be
449
# tricky to get right and we certainly don't need it for our
450
# target applications. In other words, post-singularity
451
# Martian users, please do not rely on this code for
452
# compatibility with Greater Galactic Protectorate of Earth
453
# date/time formatting! Apologies, but no library I know of in
454
# Python is sufficient for processing their dates and times
455
# without ADA bindings to get the radiation-safety zone counter
459
# don't forget leap years
460
('dayofyear', 1, 366)]:
461
if not min <= groups[group] <= max:
462
raise ValueError, '%s must be in %i..%i' % (group, min, max)
464
def determineResolution():
465
if match.group('fractionalsec') is not None:
466
return max(datetime.timedelta.resolution,
468
microseconds=1 * 10 ** -len(
469
match.group('fractionalsec')) * 1000000))
471
for testGroup, resolution in [
472
('second', datetime.timedelta(seconds=1)),
473
('minute', datetime.timedelta(minutes=1)),
474
('hour', datetime.timedelta(hours=1)),
475
('weekday', datetime.timedelta(days=1)),
476
('dayofyear', datetime.timedelta(days=1)),
477
('day', datetime.timedelta(days=1)),
478
('week1', datetime.timedelta(weeks=1)),
479
('week2', datetime.timedelta(weeks=1))]:
480
if match.group(testGroup) is not None:
483
if match.group('month1') is not None \
484
or match.group('month2') is not None:
485
if self._time.month == 12:
486
return datetime.timedelta(days=31)
487
nextMonth = self._time.replace(month=self._time.month+1)
488
return nextMonth - self._time
490
nextYear = self._time.replace(year=self._time.year+1)
491
return nextYear - self._time
493
def calculateDtime(tzinfo):
494
"""Calculate a datetime for the start of the addressed period."""
496
if match.group('week1') is not None \
497
or match.group('week2') is not None:
498
if not 0 < groups['week'] <= 53:
500
'week must be in 1..53 (was %i)' % (groups['week'],))
501
dtime = datetime.datetime(
508
int(round(groups['fractionalsec'] * 1000000)),
511
dtime -= datetime.timedelta(days = dtime.weekday())
512
dtime += datetime.timedelta(
513
days = (groups['week']-1) * 7 + groups['weekday'] - 1)
514
if dtime.isocalendar() != (
515
groups['year'], groups['week'], groups['weekday']):
516
# actually the problem could be an error in my logic, but
517
# nothing should cause this but requesting week 53 of a
518
# year with 52 weeks.
519
raise ValueError('year %04i has no week %02i' %
520
(groups['year'], groups['week']))
523
if match.group('dayofyear') is not None:
524
dtime = datetime.datetime(
531
int(round(groups['fractionalsec'] * 1000000)),
534
dtime += datetime.timedelta(days=groups['dayofyear']-1)
535
if dtime.year != groups['year']:
537
'year %04i has no day of year %03i' %
538
(groups['year'], groups['dayofyear']))
542
return datetime.datetime(
549
int(round(groups['fractionalsec'] * 1000000)),
554
match = klass.iso8601pattern.match(iso8601string)
557
'%r could not be parsed as an ISO 8601 date and time' %
560
groups = match.groupdict()
562
if match.group('hour') is not None:
563
timezone = calculateTimezone()
566
self = klass.fromDatetime(calculateDtime(timezone))
567
self.resolution = determineResolution()
570
fromISO8601TimeAndDate = classmethod(fromISO8601TimeAndDate)
572
def fromStructTime(klass, structTime, tzinfo=None):
573
"""Return a new Time instance from a time.struct_time.
575
If tzinfo is None, structTime is in UTC. Otherwise, tzinfo is a
576
datetime.tzinfo instance coresponding to the timezone in which
579
Many of the functions in the standard time module return these things.
580
This will also work with a plain 9-tuple, for parity with the time
581
module. The last three elements, or tm_wday, tm_yday, and tm_isdst are
584
dtime = datetime.datetime(tzinfo=tzinfo, *structTime[:6])
585
self = klass.fromDatetime(dtime)
586
self.resolution = datetime.timedelta(seconds=1)
589
fromStructTime = classmethod(fromStructTime)
591
def fromDatetime(klass, dtime):
592
"""Return a new Time instance from a datetime.datetime instance.
594
If the datetime instance does not have an associated timezone, it is
597
self = klass.__new__(klass)
598
if dtime.tzinfo is not None:
599
self._time = dtime.astimezone(FixedOffset(0, 0)).replace(tzinfo=None)
602
self.resolution = datetime.timedelta.resolution
605
fromDatetime = classmethod(fromDatetime)
607
def fromPOSIXTimestamp(klass, secs):
608
"""Return a new Time instance from seconds since the POSIX epoch.
610
The POSIX epoch is midnight Jan 1, 1970 UTC. According to POSIX, leap
611
seconds don't exist, so one UTC day is exactly 86400 seconds, even if
614
@param secs: a number of seconds, represented as an integer, long or
617
self = klass.fromDatetime(_EPOCH + datetime.timedelta(seconds=secs))
618
self.resolution = datetime.timedelta()
621
fromPOSIXTimestamp = classmethod(fromPOSIXTimestamp)
623
def fromRFC2822(klass, rfc822string):
625
Return a new Time instance from a string formated as described in RFC 2822.
627
@type rfc822string: str
629
@raise ValueError: if the timestamp is not formatted properly (or if
630
certain obsoleted elements of the specification are used).
632
@return: a new L{Time}
635
# parsedate_tz is going to give us a "struct_time plus", a 10-tuple
636
# containing the 9 values a struct_time would, i.e.: (tm_year, tm_mon,
637
# tm_day, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst), plus a
638
# bonus "offset", which is an offset (in _seconds_, of all things).
640
maybeStructTimePlus = parsedate_tz(rfc822string)
642
if maybeStructTimePlus is None:
643
raise ValueError, 'could not parse RFC 2822 date %r' % (rfc822string,)
644
structTimePlus = sanitizeStructTime(maybeStructTimePlus)
645
offsetInSeconds = structTimePlus[-1]
646
if offsetInSeconds is None:
648
self = klass.fromStructTime(
652
minutes=offsetInSeconds // 60))
653
self.resolution = datetime.timedelta(seconds=1)
656
fromRFC2822 = classmethod(fromRFC2822)
659
# Methods to produce various formats
662
def asPOSIXTimestamp(self):
663
"""Return this time as a timestamp as specified by POSIX.
665
This timestamp is the count of the number of seconds since Midnight,
666
Jan 1 1970 UTC, ignoring leap seconds.
668
mytimedelta = self._time - _EPOCH
669
return _timedeltaToSeconds(mytimedelta)
671
def asDatetime(self, tzinfo=None):
672
"""Return this time as an aware datetime.datetime instance.
674
The returned datetime object has the specified tzinfo, or a tzinfo
675
describing UTC if the tzinfo parameter is None.
678
tzinfo = FixedOffset(0, 0)
680
if not self.isTimezoneDependent():
681
return self._time.replace(tzinfo=tzinfo)
683
return self._time.replace(tzinfo=FixedOffset(0, 0)).astimezone(tzinfo)
685
def asNaiveDatetime(self, tzinfo=None):
686
"""Return this time as a naive datetime.datetime instance.
688
The returned datetime object has its tzinfo set to None, but is in the
689
timezone given by the tzinfo parameter, or UTC if the parameter is
692
return self.asDatetime(tzinfo).replace(tzinfo=None)
694
def asRFC2822(self, tzinfo=None, includeDayOfWeek=True):
695
"""Return this Time formatted as specified in RFC 2822.
697
RFC 2822 specifies the format of email messages.
699
RFC 2822 says times in email addresses should reflect the local
700
timezone. If tzinfo is a datetime.tzinfo instance, the returned
701
formatted string will reflect that timezone. Otherwise, the timezone
702
will be '-0000', which RFC 2822 defines as UTC, but with an unknown
705
RFC 2822 states that the weekday is optional. The parameter
706
includeDayOfWeek indicates whether or not to include it.
708
dtime = self.asDatetime(tzinfo)
713
rfcoffset = '%s%02i%02i' % _timedeltaToSignHrMin(dtime.utcoffset())
717
rfcstring += self.rfc2822Weekdays[dtime.weekday()] + ', '
719
rfcstring += '%i %s %4i %02i:%02i:%02i %s' % (
721
self.rfc2822Months[dtime.month - 1],
730
def asISO8601TimeAndDate(self, includeDelimiters=True, tzinfo=None,
731
includeTimezone=True):
732
"""Return this time formatted as specified by ISO 8861.
734
ISO 8601 allows optional dashes to delimit dates and colons to delimit
735
times. The parameter includeDelimiters (default True) defines the
736
inclusion of these delimiters in the output.
738
If tzinfo is a datetime.tzinfo instance, the output time will be in the
739
timezone given. If it is None (the default), then the timezone string
740
will not be included in the output, and the time will be in UTC.
742
The includeTimezone parameter coresponds to the inclusion of an
743
explicit timezone. The default is True.
745
if not self.isTimezoneDependent():
747
dtime = self.asDatetime(tzinfo)
749
if includeDelimiters:
753
dateSep = timeSep = ''
757
timezone = '+00%s00' % (timeSep,)
759
sign, hour, min = _timedeltaToSignHrMin(dtime.utcoffset())
760
timezone = '%s%02i%s%02i' % (sign, hour, timeSep, min)
764
microsecond = ('%06i' % (dtime.microsecond,)).rstrip('0')
766
microsecond = '.' + microsecond
769
('%04i' % (dtime.year,), datetime.timedelta(days=366)),
770
('%s%02i' % (dateSep, dtime.month), datetime.timedelta(days=31)),
771
('%s%02i' % (dateSep, dtime.day), datetime.timedelta(days=1)),
772
('T', datetime.timedelta(hours=1)),
773
('%02i' % (dtime.hour,), datetime.timedelta(hours=1)),
774
('%s%02i' % (timeSep, dtime.minute), datetime.timedelta(minutes=1)),
775
('%s%02i' % (timeSep, dtime.second), datetime.timedelta(seconds=1)),
776
(microsecond, datetime.timedelta(microseconds=1)),
777
(timezone, datetime.timedelta(hours=1))
781
for part, minResolution in parts:
782
if self.resolution <= minResolution:
787
def asStructTime(self, tzinfo=None):
788
"""Return this time represented as a time.struct_time.
790
tzinfo is a datetime.tzinfo instance coresponding to the desired
791
timezone of the output. If is is the default None, UTC is assumed.
793
dtime = self.asDatetime(tzinfo)
795
return dtime.utctimetuple()
797
return dtime.timetuple()
799
def asHumanly(self, tzinfo=None, now=None, precision=Precision.MINUTES):
800
"""Return this time as a short string, tailored to the current time.
802
Parts of the date that can be assumed are omitted. Consequently, the
803
output string depends on the current time. This is the format used for
804
displaying dates in most user visible places in the quotient web UI.
806
By default, the current time is determined by the system clock. The
807
current time used for formatting the time can be changed by providing a
808
Time instance as the parameter 'now'.
810
@param precision: The smallest unit of time that will be represented
811
in the returned string. Valid values are L{Time.Precision.MINUTES} and
812
L{Time.Precision.SECONDS}.
814
@raise InvalidPrecision: if the specified precision is not either
815
L{Time.Precision.MINUTES} or L{Time.Precision.SECONDS}.
818
timeFormat = Time._timeFormat[precision]
820
raise InvalidPrecision(
821
'Use Time.Precision.MINUTES or Time.Precision.SECONDS')
824
now = Time().asDatetime(tzinfo)
826
now = now.asDatetime(tzinfo)
827
dtime = self.asDatetime(tzinfo)
830
if dtime.date() == now.date():
833
return dtime.strftime(timeFormat).lower()
835
res = str(dtime.date().day) + dtime.strftime(' %b') # day + month
837
if not dtime.date().year == now.date().year:
838
res += dtime.strftime(' %Y')
839
if not self.isAllDay():
840
res += dtime.strftime(', %s' % (timeFormat,)).lower()
844
# methods to return related times
847
def getBounds(self, tzinfo=None):
849
Return a pair describing the bounds of self.
851
This returns a pair (min, max) of Time instances. It is not quite the
852
same as (self, self + self.resolution). This is because timezones are
853
insignificant for instances with a resolution greater or equal to 1
856
To illustrate the problem, consider a Time instance::
858
T = Time.fromHumanly('today', tzinfo=anything)
860
This will return an equivalent instance independent of the tzinfo used.
861
The hour, minute, and second of this instance are 0, and its resolution
864
Now say we have a sorted list of times, and we want to get all times
865
for 'today', where whoever said 'today' is in a timezone that's 5 hours
866
ahead of UTC. The start of 'today' in this timezone is UTC 05:00. The
867
example instance T above is before this, but obviously it is today.
869
The min and max times this returns are such that all potentially
870
matching instances are within this range. However, this range might
871
contain unmatching instances.
873
As an example of this, if 'today' is April first 2005, then
874
Time.fromISO8601TimeAndDate('2005-04-01T00:00:00') sorts in the same
875
place as T from above, but is not in the UTC+5 'today'.
879
if self.resolution >= datetime.timedelta(days=1) \
880
and tzinfo is not None:
881
time = self._time.replace(tzinfo=tzinfo)
886
min(self.fromDatetime(time), self.fromDatetime(self._time)),
887
max(self.fromDatetime(time + self.resolution),
888
self.fromDatetime(self._time + self.resolution))
892
"""Return a Time instance representing the day of the start of self.
894
The returned new instance will be set to midnight of the day containing
895
the first instant of self in the specified timezone, and have a
896
resolution of datetime.timedelta(days=1).
898
day = self.__class__.fromDatetime(self.asDatetime().replace(
899
hour=0, minute=0, second=0, microsecond=0))
900
day.resolution = datetime.timedelta(days=1)
908
"""Return True iff this instance represents exactly all day."""
909
return self.resolution == datetime.timedelta(days=1)
911
def isTimezoneDependent(self):
912
"""Return True iff timezone is relevant for this instance.
914
Timezone is only relevent for instances with a resolution better than
917
return self.resolution < datetime.timedelta(days=1)
920
# other magic methods
923
def __cmp__(self, other):
924
if not isinstance(other, Time):
925
raise TypeError("Cannot meaningfully compare %r with %r" % (self, other))
926
return cmp(self._time, other._time)
928
def __eq__(self, other):
929
if isinstance(other, Time):
930
return cmp(self._time, other._time) == 0
933
def __ne__(self, other):
934
return not (self == other)
937
return 'extime.Time.fromDatetime(%r)' % (self._time,)
939
__str__ = asISO8601TimeAndDate
941
def __contains__(self, other):
942
"""Test if another Time instance is entirely within the period addressed by this one."""
943
if not isinstance(other, Time):
945
'%r is not a Time instance; can not test for containment'
947
if other._time < self._time:
949
if self._time + self.resolution < other._time + other.resolution:
953
def __add__(self, addend):
954
if not isinstance(addend, datetime.timedelta):
955
raise TypeError, 'expected a datetime.timedelta instance'
956
return Time.fromDatetime(self._time + addend)
958
def __sub__(self, subtrahend):
960
Implement subtraction of an interval or another time from this one.
962
@type subtrahend: L{datetime.timedelta} or L{Time}
964
@param subtrahend: The object to be subtracted from this one.
966
@rtype: L{datetime.timedelta} or L{Time}
968
@return: If C{subtrahend} is a L{datetime.timedelta}, the result is
969
a L{Time} instance which is offset from this one by that amount. If
970
C{subtrahend} is a L{Time}, the result is a L{datetime.timedelta}
971
instance which gives the difference between it and this L{Time}
974
if isinstance(subtrahend, datetime.timedelta):
975
return Time.fromDatetime(self._time - subtrahend)
977
if isinstance(subtrahend, Time):
978
return self.asDatetime() - subtrahend.asDatetime()
980
return NotImplemented