modifying a time.struct_time

U

Ulrich Eckhardt

Hi!

I'm trying to create a struct_time that is e.g. one year ahead or a
month back in order to test some parsing/formatting code with different
dates.

Now, the straightforward approach is

t = time.localtime()
t.tm_year += 1

This fails with "TypeError: readonly attribute". This kind-of makes
sense, as an immutable object allows you to use it as key in a dict.


The second approach is this:

l = list(t) # convert to a sequence
l[0] += 1 # increment year
t = time.struct_time(l) # convert to a struct_time

This works but is ugly, because the code relies on the order inside the
list and uses magic numbers to access them. The order is AFAICT not
accessible programmatically but only documented, and not even in a way
that makes clear that it is part of the API and as such actualy
guaranteed. I could try to assert that the indices match using "if l[0]
is t.tm_year", but this is still ugly.


The next approach I tried was to simply create a derived class:

class my_time(time.struct_time):
pass

This fails again with "TypeError: Error when calling the metaclass
bases, type 'time.struct_time' is not an acceptable base type. I could
try to encapsulate a struct_time and delegate attribute access to it in
order to do this, but it also seems overkill. Also, using an immutable
type as a baseclass and delegating access to members seems like hackery
to me, prone to fail in situations where it is least expected.


Then I tried duck typing. If it quacks like a duck, it better not be a
crocodile! This looks like this:

struct my_time(object): pass
t = my_time()
t.tm_year = 2012
t.tm_month = 12
t.tm... # other fields accordingly
time.mktime(t)

This fails with "TypeError: argument must be 9-item sequence, not
my_time". I thought about using a collections.namedtuple, because a
namedtuple is a tuple and therefore also a sequence, but that only leads
me back to the problem that time.mktime() takes a sequence and the order
of the sequence is not accessible programmatically.


A last approach was to convert the thing to a dict and back. Alas, there
is no conversion to a dict, otherwise

d = dict(t)
d['tm_year'] += 1
t = time.struct_time(d)

would have been a straightforward approach.


Does anyone have a suggestion how to solve this elegantly and
pythonically? Also, what I'm wondering is if the lack of a clear way
should be considered a bug or not.


Cheers!

Uli
 
M

Mazen Harake

Hi,

Easiest way is to change the time to seconds, add as many seconds as a
year/month/week/day/hour/minutes represent and then transform it back.

E.g.
time.struct_time(tm_year=2011, tm_mon=12, tm_mday=16, tm_hour=11,
tm_min=31, tm_sec=57, tm_wday=4, tm_yday=350, tm_isdst=0)

Hi!

I'm trying to create a struct_time that is e.g. one year ahead or a month
back in order to test some parsing/formatting code with different dates.

Now, the straightforward approach is

 t = time.localtime()
 t.tm_year += 1

This fails with "TypeError: readonly attribute". This kind-of makes sense,
as an immutable object allows you to use it as key in a dict.


The second approach is this:

 l = list(t) # convert to a sequence
 l[0] += 1 # increment year
 t = time.struct_time(l) # convert to a struct_time

This works but is ugly, because the code relies on the order inside the list
and uses magic numbers to access them. The order is AFAICT not accessible
programmatically but only documented, and not even in a way that makes clear
that it is part of the API and as such actualy guaranteed. I could try to
assert that the indices match using "if l[0] is t.tm_year", but this is
still ugly.


The next approach I tried was to simply create a derived class:

 class my_time(time.struct_time):
     pass

This fails again with "TypeError: Error when calling the metaclass bases,
type 'time.struct_time' is not an acceptable base type. I could try to
encapsulate a struct_time and delegate attribute access to it in order todo
this, but it also seems overkill. Also, using an immutable type as a
baseclass and delegating access to members seems like hackery to me, prone
to fail in situations where it is least expected.


Then I tried duck typing. If it quacks like a duck, it better not be a
crocodile! This looks like this:

 struct my_time(object): pass
 t = my_time()
 t.tm_year = 2012
 t.tm_month = 12
 t.tm... # other fields accordingly
 time.mktime(t)

This fails with "TypeError: argument must be 9-item sequence, not my_time".
I thought about using a collections.namedtuple, because a namedtuple is a
tuple and therefore also a sequence, but that only leads me back to the
problem that time.mktime() takes a sequence and the order of the sequenceis
not accessible programmatically.


A last approach was to convert the thing to a dict and back. Alas, there is
no conversion to a dict, otherwise

 d = dict(t)
 d['tm_year'] += 1
 t = time.struct_time(d)

would have been a straightforward approach.


Does anyone have a suggestion how to solve this elegantly and pythonically?
Also, what I'm wondering is if the lack of a clear way should be considered
a bug or not.


Cheers!

Uli
 
C

Chris Angelico

I'm trying to create a struct_time that is e.g. one year ahead or a month
back in order to test some parsing/formatting code with different dates.

Do you need it to be one exact calendar year, or would it make sense
to add/subtract integers from a Unix time?

t = time.time() + 365*86400 # Not actually a year ahead, it's 365 days ahead
t = time.localtime(t) # if you want a struct_time

ChrisA
 
S

Steven D'Aprano

Hi!

I'm trying to create a struct_time that is e.g. one year ahead or a
month back in order to test some parsing/formatting code with different
dates. [...]
The second approach is this:

l = list(t) # convert to a sequence
l[0] += 1 # increment year
t = time.struct_time(l) # convert to a struct_time

This works but is ugly, because the code relies on the order inside the
list and uses magic numbers to access them. The order is AFAICT not
accessible programmatically but only documented, and not even in a way
that makes clear that it is part of the API and as such actualy
guaranteed. I could try to assert that the indices match using "if l[0]
is t.tm_year", but this is still ugly.

Feel free to propose a feature enhancement to time.struct_time, but the
order of the fields is stable and won't change. So ugly or not, that way
is guaranteed to work.


[...]
Then I tried duck typing. If it quacks like a duck, it better not be a
crocodile! This looks like this:

struct my_time(object): pass
"struct"?


[...]
Does anyone have a suggestion how to solve this elegantly and
pythonically? Also, what I'm wondering is if the lack of a clear way
should be considered a bug or not.

Not a bug, but it does seem a very old and inelegant API more suited to
hairy C programmers gathered around a smokey fire in a cave chewing on
old dinosaur bones, and not worthy of space-age Python coders flying
around on anti-gravity belts.
 
T

Tim Golden

On 16/12/2011 10:44, Steven D'Aprano wrote:
[ on time.struct_time ]
Not a bug, but it does seem a very old and inelegant API more suited to
hairy C programmers gathered around a smokey fire in a cave chewing on
old dinosaur bones, and not worthy of space-age Python coders flying
around on anti-gravity belts.

+1 QOTW

TJG
 
U

Ulrich Eckhardt

Am 16.12.2011 10:45, schrieb Ulrich Eckhardt:
I'm trying to create a struct_time that is e.g. one year ahead or a
month back in order to test some parsing/formatting code with different
dates.

There is something I stumbled across that helps and that is the datetime
module, which seems more reasonably pythonic, and which allows
operations on dates like adding a

Concerning the idea to use seconds, I'd rather not, because already the
number of seconds per minute ranges from 60 to 62, and it doesn't get
better with things like months (28...31 days), years (365...366 days)
and all other types built upon them.

Considering the question if the current state is buggy, I'm definitely
+1 on it. I do understand that this API is not going to change, but
explicitly documenting in "help(time)" that the order is fixed and
possibly making the order programmatically available are not changes but
useful additions, IMHO. Also, conversion from/to a dict and perhaps a
link to the datetime module would have saved me some futile attempts.

Thanks to all responders, I wish you a happy weekend!

Uli
 
C

Chris Angelico

Concerning the idea to use seconds, I'd rather not, because already the
number of seconds per minute ranges from 60 to 62, and it doesn't get better
with things like months (28...31 days), years (365...366 days) and all other
types built upon them.

Right, which is why I asked how important the difference between "365
days" and "1 year" is. Obviously if your goal is one entire calendar
year, then you don't want to duplicate the work of figuring out how
many seconds that is.

ChrisA
 
J

Jason Friedman

Do you need it to be one exact calendar year, or would it make sense
to add/subtract integers from a Unix time?

t = time.time() + 365*86400   # Not actually a year ahead, it's 365 days ahead
t = time.localtime(t)  # if you want a struct_time

ChrisA
--

Not particularly elegant, but I believe accurate and relying only on
the stated struct_time contract:

#!/usr/bin/env python
# 2.7.2
import time, itertools
def is_local_time_different_by_one_year(time1, time2):
if abs(time1.tm_year - time2.tm_year) != 1:
return False
if abs(time1.tm_mon - time2.tm_mon ) != 0:
return False
if abs(time1.tm_mday - time2.tm_mday) != 0:
return False
if abs(time1.tm_hour - time2.tm_hour) != 0:
return False
if abs(time1.tm_min - time2.tm_min ) != 0:
return False
if abs(time1.tm_sec - time2.tm_sec ) != 0:
return False
return True

t = time.time()
time1 = time.localtime(t)
print("Local time is {}.".format(time1))
for i in itertools.count(0):
t += 1 # Add one second until we have reached next year
time2 = time.localtime(t)
if is_local_time_different_by_one_year(time1, time2):
print("One year later is {}".format(time2))
break


Not exactly a speed demon, either:
$ time python timediff.py
Local time is time.struct_time(tm_year=2011, tm_mon=12, tm_mday=24,
tm_hour=5, tm_min=57, tm_sec=44, tm_wday=5, tm_yday=358, tm_isdst=0).
One year later is time.struct_time(tm_year=2012, tm_mon=12,
tm_mday=24, tm_hour=5, tm_min=57, tm_sec=44, tm_wday=0, tm_yday=359,
tm_isdst=0)

real 3m8.922s
user 2m2.470s
sys 1m1.760s
 
C

Chris Angelico

Not particularly elegant, but I believe accurate and relying only on
the stated struct_time contract:

Funny! But a binary search would be better, I think.


t = time.time()
time1 = time.localtime(t)
print("Local time is {}.".format(time1))
est1 = t + 365*86400
est2 = est1 + 86400 # Assume that the year is between 365 and 366 days
long (widen this if assumption not valid)
while True:
est = (est1 + est2) / 2
time2 = time.localtime(t)
cmp = is_local_time_different_by_one_year(time1, time2):
if cmp<0: est1 = est
elif cmp>0: est2 = est
else:
print("One year later is {}".format(time2))
break

Could do with a few more improvements. Also, it requires that the
comparison function return -1, 0, or 1, in the same way that strcmp()
does (not a difficult enhancement, but I'm off to Christmas dinner
with the family so I'll leave it as an exercise for the reader).

ChrisA
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,575
Members
45,053
Latest member
billing-software

Latest Threads

Top