23

When dealing with times and dates in python, you will stumble across the time.struct_time object:

st = time.strptime("23.10.2012", "%d.%m.%Y")
print st
time.struct_time(tm_year=2012, tm_mon=10, tm_mday=23, tm_hour=0, tm_min=0,
                 tm_sec=0, tm_wday=1, tm_yday=297, tm_isdst=-1)

Now as this struct does not support item assignment (i.e. you cannot do something like st[1]+=1) how else is it possible to increase, say, the number of the month.

Solutions suggest to convert this time_struct into seconds and add the corresponding number of seconds, but this does not look nice. You also need to know how many days are in a month, or if the year is a leap year or not. I want an easy way to obtain a time_struct to be e.g.

time.struct_time(tm_year=2012, tm_mon=11, tm_mday=23, tm_hour=0, tm_min=0,
                 tm_sec=0, tm_wday=1, tm_yday=297, tm_isdst=-1)

with just the month increased by one. Creating a time_struct from scratch would be fine—but how? What ways are there?

martineau
  • 119,623
  • 25
  • 170
  • 301
Alex
  • 41,580
  • 88
  • 260
  • 469
  • How about converting the date to datetime.date and using datetime.timedelta for manupulating time differences? – Dhara Oct 23 '12 at 13:34
  • What is the timedelta for a month? It is different, depending on the month, the year... – Alex Oct 23 '12 at 13:43

6 Answers6

25

Use the datetime module instead, which has a far richer set of objects to handle date(time) arithmetic:

import datetime
adate = datetime.datetime.strptime("23.10.2012", "%d.%m.%Y").date()
adate + datetime.timedelta(days=30)

You can use the excellent python-dateutil add-on module to get an ever richer set of options for dealing with deltas:

from dateutil.relativedelta import relativedelta
adate + relativedelta(months=1)

relativedelta knows about leap years, month lengths, etc. and will do the right thing when adding a month to, say, January 30th (you end up with February 28th or 29th, it won't cross month boundaries).

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • But don't you still need to know the number of days in the month for this to work -- accounting for leap years, etc.? – mgilson Oct 23 '12 at 13:36
  • leap years etc are all handled internally. – specialscope Oct 23 '12 at 13:38
  • @specialscope -- no they're not. I understand that `datetime` knows about leap years, etc. But OP doesn't want to add 30 days to the date -- OP wants to add 1 month to the date. Say you're on February 23rd. OP wants to get to March 23rd. How do you do it? Do you add 28, 29, 30 or 31 days? You first need to check what month it is, then you need to figure out how many days that month has (taking leap years into account) ... (as the `dateutil` option here would do) – mgilson Oct 23 '12 at 13:40
  • 2
    @mgilson: that's why I suggested using `dateutil.relativedelta`. – Martijn Pieters Oct 23 '12 at 13:44
  • @ mgilson you need to define what 1 month is, if you merely want to reach march 23 you can just add 1 to the month of datetime. The problem is what happens if you add -1 day to march 1. – specialscope Oct 23 '12 at 13:47
  • @specialscope -- Adding 1 to the month doesn't work in december. Basically, this is just a messy problem. `dateutil` deals with all of the various cases, but I don't think there's a "clean" way to do it in the standard library – mgilson Oct 23 '12 at 13:53
15

So... the schema for the tuple underlying time_struct is given in the documentation here: http://docs.python.org/2/library/time.html#time.struct_time

And even though it's read-only as a time_struct, you can use casting to list() to get at that tuple directly (remembering to wrap-around your month after you increment it, and keep the end range in 1-12 rather than 0-11). Then the time.struct_time() function will convert a valid 9-tuple back into a time_struct:

st = time.strptime("23.10.2012", "%d.%m.%Y")
st_writable = list(st)
st_writable[1] = (st_writable[1] + 1)%12 + 1
st = time.struct_time(tuple(st_writable))
Christine
  • 151
  • 1
  • 2
  • 3
    The conversion to a `list` could be avoided by doing a little `tuple` arithmetic i.e. `st = time.struct_time((st[0],) + ((st[1]+1)%12 + 1,) + st[2:])`. – martineau Jan 14 '18 at 03:25
3

You can first convert it to datetime, then add a timedelta offset to it:

from time import mktime
from datetime import datetime

dt = datetime.fromtimestamp(mktime(struct))
timedelta = datetime.timedelta(seconds=10)
dt = dt + timedelta

References:

  1. How do you convert a Python time.struct_time object into a datetime object?
  2. http://docs.python.org/library/datetime.html
Community
  • 1
  • 1
Dhara
  • 6,587
  • 2
  • 31
  • 46
  • What is the timedelta for a month? It is different, depending on the month, the year... – Alex Oct 23 '12 at 13:43
  • alex if you are merely looking a way to change month you can do. newdate=datetime.datetime(dt.year,dt.month+1,dt.day,0,0) – specialscope Oct 23 '12 at 13:49
  • @specialscope -- Doesn't that fail if `dt.month` is 12? – mgilson Oct 23 '12 at 13:51
  • it indeed does and it does in many other instances like January 30 and so on. Hence advancing by days is standard way of doing things, by month is extremely vague and unhelpful in most situations. – specialscope Oct 23 '12 at 13:55
  • @Alex Why would you rate this negatively? The solution works for most situations, you can specify: days, seconds, microseconds, milliseconds, minutes, hours, weeks. – Dhara Oct 23 '12 at 14:31
  • 1
    As @specialscope points out, advancing by months is extremely vague: Example, when you add a month to October 31st, do you mean always to add 30 days or, look for the same date in the next month? What happens if it doesn't exist (No November 31st)? – Dhara Oct 23 '12 at 14:32
3

If you want to avoid datetime all together and stick with time, you can convert to unix timestamp and then subtract the number of seconds you need. To get a time object for 24 hours ago:

time.gmtime(time.mktime(time.gmtime()) - 86400)

However, as OP pointed out this is not good for working with months and should be reserved for seconds, hours, and days.

Andy Fraley
  • 1,043
  • 9
  • 16
0

Here is a solution with the time module only (not datetime). It uses tuple arithmetic as proposed by martineau, but with all terms specified.

import time
st = time.strptime("23.10.2012", "%d.%m.%Y")
st = time.struct_time(
    (st.tm_year,)
    + (st.tm_mon % 12 + 1,)
    + (st.tm_mday,)
    + (st.tm_hour,)
    + (st.tm_min,)
    + (st.tm_sec,)
    + (st.tm_wday,)
    + (st.tm_yday,)
    + (st.tm_isdst,)
)
print(time.strftime("%d.%m.%Y", st))
# 23.11.2012

You can also create the tuple element by element.

import time
st = time.strptime("23.10.2012", "%d.%m.%Y")
st = time.struct_time((
    st.tm_year
    ,st.tm_mon % 12 + 1
    ,st.tm_mday
    ,st.tm_hour
    ,st.tm_min
    ,st.tm_sec
    ,st.tm_wday
    ,st.tm_yday
    ,st.tm_isdst
))
print(time.strftime("%d.%m.%Y", st))
# 23.11.2012

Or in an even more compact way.

import time
st = time.strptime("23.10.2012", "%d.%m.%Y")
st = time.struct_time((
    st.tm_year
    ,st.tm_mon % 12 + 1
    ) + st[2:]
)
print(time.strftime("%d.%m.%Y", st))
# 23.11.2012
nico
  • 1,130
  • 2
  • 12
  • 26
-1

This question of the OP seems to be still unanswered:

Creating a time_struct from scratch would be fine—but how?

It can be done without importing any extra modules, but at the cost of taking an indirect and less efficient route:

def new_date( y, m, d ):
    return time.strptime( \
        str(y) + str(m).rjust(2,'0') + str(d).rjust(2,'0'), "%Y%m%d" );

or use use str.format() for brevity:

def new_date( y, m, d ):
    return time.strptime( "{0}{1:0>2}{2:0>2}".format(y, m, d), "%Y%m%d");

It is indeed starge that the module implementing a type has not provided a direct means of creating instances of that type...

Anton Shepelev
  • 922
  • 9
  • 19