modifying a time.struct_time

Discussion in 'Python' started by Ulrich Eckhardt, Dec 16, 2011.

  1. 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
    Ulrich Eckhardt, Dec 16, 2011
    #1
    1. Advertising

  2. Ulrich Eckhardt

    Mazen Harake Guest

    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.time()

    1324031491.026137
    >>> time.time() + 3600 # Add an hour

    1324035105.082003
    >>> time.gmtime(time.time() + 3600)

    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)
    >>>


    On 16 December 2011 10:45, Ulrich Eckhardt
    <> wrote:
    > 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
    >
    > --
    > http://mail.python.org/mailman/listinfo/python-list
    Mazen Harake, Dec 16, 2011
    #2
    1. Advertising

  3. On Fri, Dec 16, 2011 at 8:45 PM, Ulrich Eckhardt
    <> wrote:
    > 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
    Chris Angelico, Dec 16, 2011
    #3
  4. On Fri, 16 Dec 2011 10:45:22 +0100, Ulrich Eckhardt wrote:

    > 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.



    --
    Steven
    Steven D'Aprano, Dec 16, 2011
    #4
  5. Ulrich Eckhardt

    Tim Golden Guest

    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
    Tim Golden, Dec 16, 2011
    #5
  6. 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
    Ulrich Eckhardt, Dec 16, 2011
    #6
  7. On Sat, Dec 17, 2011 at 12:32 AM, Ulrich Eckhardt
    <> wrote:
    > 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
    Chris Angelico, Dec 16, 2011
    #7
  8. On Fri, Dec 16, 2011 at 10:44 AM, Chris Angelico <> wrote:
    > On Fri, Dec 16, 2011 at 8:45 PM, Ulrich Eckhardt
    > <> wrote:
    >> 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
    > --


    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
    Jason Friedman, Dec 24, 2011
    #8
  9. On Sat, Dec 24, 2011 at 5:04 PM, Jason Friedman <> wrote:
    > 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
    Chris Angelico, Dec 24, 2011
    #9
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. ladygrinningsoul

    Modifying a file's time stamp

    ladygrinningsoul, Dec 5, 2004, in forum: Perl
    Replies:
    1
    Views:
    552
    ladygrinningsoul
    Dec 6, 2004
  2. Victor Bazarov
    Replies:
    4
    Views:
    395
    =?ISO-8859-1?Q?Ney_Andr=E9_de_Mello_Zunino?=
    Feb 14, 2005
  3. Thomas Weholt

    Adding and modifying methods at run-time

    Thomas Weholt, Apr 30, 2004, in forum: Python
    Replies:
    2
    Views:
    283
    Thomas Weholt
    May 2, 2004
  4. flamesrock
    Replies:
    8
    Views:
    450
    Hendrik van Rooyen
    Nov 24, 2006
  5. Florian Lindner

    Convert from/to struct_time

    Florian Lindner, Apr 22, 2007, in forum: Python
    Replies:
    1
    Views:
    296
    Diez B. Roggisch
    Apr 22, 2007
Loading...

Share This Page