Python datetime difference between .localize and tzinfo - python

Why do these two lines produce different results?
>>> import pytz
>>> from datetime import datetime
>>> local_tz = pytz.timezone("America/Los_Angeles")
>>> d1 = local_tz.localize(datetime(2015, 8, 1, 0, 0, 0, 0)) # line 1
>>> d2 = datetime(2015, 8, 1, 0, 0, 0, 0, local_tz) # line 2
>>> d1 == d2
False
What's the reason for the difference, and which should I use to localize a datetime?

When you create d2 = datetime(2015, 8, 1, 0, 0, 0, 0, local_tz), it does not handle daylight saving time (DST) correctly. local_tz.localize() does.
d1 is
>>> local_tz.localize(datetime(2015, 8, 1, 0, 0, 0, 0))
datetime.datetime(
2015, 8, 1, 0, 0,
tzinfo=<DstTzInfo 'America/Los_Angeles' PDT-1 day, 17:00:00 DST>
)
d2 is
>>> datetime(2015, 8, 1, 0, 0, 0, 0, local_tz)
datetime.datetime(
2015, 8, 1, 0, 0,
tzinfo=<DstTzInfo 'America/Los_Angeles' LMT-1 day, 16:07:00 STD>
)
You can see that they are not representing the same time.
d2 way is fine if you are going to work with UTC. UTC does not have daylight saving time (DST) transitions to deal with.
The correct way to handle timezone is to use local_tz.localize() to support daylight saving time (DST)
More information and additional examples can be found here:
http://pytz.sourceforge.net/#localized-times-and-date-arithmetic

Related

Modify days counter on datetime [Python]

I have this code and I want to count the days between 2 dates.
from datetime import date, datetime
checkin= datetime(2022, 1, 30, 1, 15, 00)
checkout= datetime(2022, 1, 31, 0, 0, 00)
count_days= (checkout - checkin).days
In this case, the result of count_days result is 0, because in an operation with 2 datetimes, it takes into account hours, minutes and seconds.
I want the result to be 1 because is +1 day of difference. Type of variables must be datetimes. Thanks!
Convert them to dates first, with the date method.
from datetime import date, datetime
checkin = datetime(2022, 1, 30, 1, 15, 00)
checkout = datetime(2022, 1, 31, 0, 0, 00)
count_days = (checkout.date() - checkin.date()).days
Could you do something like this?
(assuming you want a minimum since the solution you have is similar)
from datetime import date, datetime
check_in= datetime(2022, 1, 30, 1, 15, 00)
check_out= datetime(2022, 1, 31, 0, 0, 00)
# Number of days between two dates (min 1 day)
days = (check_out - check_in).days + 1
print(days)

Why is only one date under DST influence?

I've been reading the pytz and datetime module documentation but I can't figure out why one date is under DST and the other is not.
import pytz
import datetime
mytz = pytz.timezone('America/New_York')
od = datetime.datetime(2021, 7, 1, 4, 0)
mytz.localize(od)
# Out: datetime.datetime(2021, 7, 1, 4, 0, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)
mytz.localize(od).dst()
# Out: datetime.timedelta(0, 3600)
dt = datetime.datetime(2089, 7, 1, 4, 0)
mytz.localize(dt)
# Out: datetime.datetime(2089, 7, 1, 4, 0, tzinfo=<DstTzInfo 'America/New_York' EST-1 day, 19:00:00 STD>)
mytz.localize(dt).dst()
# Out: datetime.timedelta(0)
If you look at the source of the time zone rules, you find that they can have a keyword "max" specified that "is used to extend a rule’s application into the indefinite future" ref. For the US, you can find that here. Unless otherwise specified, DST just continues to be applied during the specified period of the year. But keep in mind that this does not mean that it will actually be the case in the future, since time zones are subject to political decisions.
As an addition to #balmy 's comment suggesting this is a deficiency of pytz, Python 3.9's zoneinfo gives the result to be expected from the above:
import datetime
from zoneinfo import ZoneInfo
od = datetime.datetime(2021, 7, 1, 4, 0, tzinfo=ZoneInfo('America/New_York'))
print(od.dst())
# 1:00:00
dt = datetime.datetime(2089, 7, 1, 4, 0, tzinfo=ZoneInfo('America/New_York'))
print(dt.dst())
# 1:00:00

rrule weekly for three weeks, then one week not, repeat

I'm using rrule from python dateutil and don't know how to create an rruleset for the following example:
Monday, three weeks in a row. Then a week not, then again three weeks in a row, one not, and so on.
Any advice on creating an rrule(set) for this?
One way to do this is to use an rruleset with a WEEKLY rrule and a corresponding exrule for every 4th week:
from dateutil.rrule import rrule, rruleset
from dateutil.rrule import WEEKLY
from dateutil.relativedelta import relativedelta
from datetime import datetime, timedelta
dtstart = datetime(2011, 1, 1)
rrset = rruleset()
weekly_rule = rrule(freq=WEEKLY, dtstart=dtstart)
every_4_weeks = rrule(freq=WEEKLY, interval=4,
dtstart=dtstart + relativedelta(weeks=4))
rrset.rrule(weekly_rule)
rrset.exrule(every_4_weeks)
rrset.between(dtstart, dtstart + timedelta(days=65))
The result:
[datetime.datetime(2011, 1, 8, 0, 0),
datetime.datetime(2011, 1, 15, 0, 0),
datetime.datetime(2011, 1, 22, 0, 0),
datetime.datetime(2011, 2, 5, 0, 0),
datetime.datetime(2011, 2, 12, 0, 0),
datetime.datetime(2011, 2, 19, 0, 0),
datetime.datetime(2011, 3, 5, 0, 0)]
The way it works is weekly_rule generates one date per week, and the every_4_weeks generates every 4th week, starting with the 4th week after dtstart. That gives you a 3-on 1-off schedule.

Python 3: maketime()

Rookie question:
the following works:
import time
# create time
dztupel = 1971, 1, 1, 0, 0, 1, 0, 0, 0
print(time.strftime("%d.%m.%Y %H:%M:%S", dztupel))
damals = time.mktime(dztupel)
# output
lt = time.localtime(damals)
wtage = ["Montag", "Dienstag", "Mittwoch","Donnerstag","Freitag","Samstag", "Sonntag"]
wtagnr = lt[6]
print("Das ist ein", wtage[wtagnr])
tag_des_jahres = lt[7]
print("Der {0:d}. Tag des Jahres".format(tag_des_jahres))
but:
dztupel = 1970, 1, 1, 0, 0, 1, 0, 0, 0
does not work,at least not at windows 10. edit: I get out of range error.
But time should start at January 1st 1970 at 0 hour 0 min and 0 seconds. shouldn't it ?
In your second snippet, check out what the time.mktime() function returns, given that dztupel represents a datetime of 11:01am UTC on 1/1/1969 (shows as one hour ahead because of BST (i.e., UTC+0100) locally on my system):
>>> import time
>>> dztupel = 1970, 1, 1, 0, 0, 1, 0, 0, 0 # In BST locally for me, remember, so one hour less seconds than printed EPOCH seconds
>>> time.mktime(dztupel) # This command
-3599.0 # seconds after (i.e., before as is negative) 1/1/1970 UTC0000
It's negative because EPOCH time (which time.mktike is printing, in seconds) starts at UTC midnight on 1/1/1970:
>>> dztupel = 1970, 1, 1, 1, 0, 0, 0, 0, 0 # 1/1/1970 BST0100 == 1/1/1970 UTC0000
>>> time.mktime(dztupel)
0.0 # seconds after 1/1/1970 UTC0000
Hence 0.0, as it's 0 seconds since dztupel = 1970, 1, 1, 1, 0, 0, 0, 0, 0 since BST 0100 on 1/1/1970, or since UTC midnight on 1/1/1970.
Really, we want to print as UTC, so instead of time.localtime(), use time.gmtime():
>>> dztupel = 1970, 1, 1, 0, 0, 1, 0, 0, 0
>>> time.gmtime(time.mktime(dztupel))
time.struct_time(tm_year=1969, tm_mon=12, tm_mday=31, tm_hour=23, tm_min=0, tm_sec=1, tm_wday=2, tm_yday=365, tm_isdst=0)
Then use strftime() to format it:
>>> gmt = time.gmtime(time.mktime(dztupel))
>>> time.strftime('%Y-%m-%d %H:%M:%S', gmt)
'1969-12-31 23:00:01'

Sorting datetime objects while ignoring the year?

I have a list of birthdays stored in datetime objects. How would one go about sorting these in Python using only the month and day arguments?
For example,
[
datetime.datetime(1983, 1, 1, 0, 0)
datetime.datetime(1996, 1, 13, 0 ,0)
datetime.datetime(1976, 2, 6, 0, 0)
...
]
Thanks! :)
You can use month and day to create a value that can be used for sorting:
birthdays.sort(key = lambda d: (d.month, d.day))
l.sort(key = lambda x: x.timetuple()[1:3])
If the dates are stored as strings—you say they aren't, although it looks like they are—you might use dateutil's parser:
>>> from dateutil.parser import parse
>>> from pprint import pprint
>>> bd = ['February 6, 1976','January 13, 1996','January 1, 1983']
>>> bd = [parse(i) for i in bd]
>>> pprint(bd)
[datetime.datetime(1976, 2, 6, 0, 0),
datetime.datetime(1996, 1, 13, 0, 0),
datetime.datetime(1983, 1, 1, 0, 0)]
>>> bd.sort(key = lambda d: (d.month, d.day)) # from sth's answer
>>> pprint(bd)
[datetime.datetime(1983, 1, 1, 0, 0),
datetime.datetime(1996, 1, 13, 0, 0),
datetime.datetime(1976, 2, 6, 0, 0)]
If your dates are in different formats, you might give fuzzy parsing a shot:
>>> bd = [parse(i,fuzzy=True) for i in bd] # replace line 4 above with this line

Categories

Resources