Python timezone delta gives different dates when subtracted from date and datetime - python

I wrote a python function that gives me datetime or date in the past, with reference to current date.
def get_past_date(no_of_days, date_only=False):
"""Returns timezone aware Datetime object in past based on no_of_days provided"""
if date_only:
return timezone.datetime.today().date() - timezone.timedelta(no_of_days)
past = timezone.datetime.today() - timezone.timedelta(no_of_days)
return timezone.make_aware(past, timezone=pytz.timezone(settings.TIME_ZONE))
The problem is:
timezone.datetime.today().date() - timezone.timedelta(no_of_days)
and
timezone.datetime.today() - timezone.timedelta(no_of_days)
return different date for same input (no_of_days)
timezone.datetime.today() returns a date that is 1 day earlier than timezone.datetime.today().date()
timezone.datetime.today() - timezone.timedelta(6 * 365 / 12)
= datetime.datetime(2018, 1, 1, 21, 12, 43, 741750)
timezone.datetime.today().date() - timezone.timedelta(6 * 365 / 12)
= datetime.date(2018, 1, 2)
Am I missing something here?

The issue here is that you're subtracting an non-integral number of days (6 * 365 / 12 = 182.5). But the smallest unit of a date is a single day, and as described in the documentation, timedelta units smaller than the day are ignored when operating on dates.
So the date operation is equivalent to subtracting 182 days, while the datetime operation is subtracting 182.5 days.
An analogy would be:
184.0 - 182.5 = 1.5
int(184.0) - int(182.5) = 2

Related

How do I calculate year fraction from datetime objects in Python?

I have two dates expressed as datetime objects and trying to calculate the time between the two dates in fractions of years (equivalent to the Excel yearfrac function).
Using relativedelta I can get the number of years, number of months and number of days between the dates, but not the fraction of years, and I can also subtract the dates to get the number of days, but dividing by 365.25 doesn't seem to get me the answer I would expect.
start_date = dt.datetime(2010, 12, 31)
end_date = dt.datetime(2019, 5, 16);
delta = relativedelta(end_date, start_date);
print(delta)
This is the output I get:
relativedelta(years=+8, months=+4, days=+16)
What I am looking for is: 8.38
If I use the following code:
delta = (end_date - start_date)/365.25
print(delta)
I get output of:
8 days, 8:56:10.841889
I just did the math of 8 + ((months * 30) + days)/365 = 8.3726. This is assuming 30 days in all months and 365 days in a year. Less precise but can fit on one line. What do you get when you divide by the number 365.25 that makes it wrong? How precise does it have to be?
If you need absolute precision, I would simply do:
from datetime import date
d0 = date(2010, 12, 31)
d1 = date(2019, 5, 16)
delta = d1 - d0
delta_fraction = delta.days/365.25
print(delta_fraction)
# Output: 8.72348
EDIT
This is a simplified solution assuming 365.25 days in a year (you can use 365.2425 days to be accurate up to the 400 year exception) to account for leap years. If you require it to match exactly excel's output, your probably better off writing a vba macro for excel
One thing to remember is that datetime.datetime objects have the subtraction operator defined, returning a datetime.timedelta object. And datetime.timedelta objects have the division operator defined, so you can get a ratio between a timedelta and either a common year (365 days) or the average length of all common and leap years (365 days, 5 hours, 49 minutes and 12 seconds).
import datetime as dt
start_date = dt.datetime(2010, 12, 31)
end_date = dt.datetime(2019, 5, 16)
print(round((end_date-start_date)/dt.timedelta(365,0,0,0),2)) #8.38
print(round((end_date-start_date)/dt.timedelta(365,5,49,12),2)) #8.38

Adding dates with python

I was trying a simple date calculation by adding a certain number of days to a datetime.
import datetime
from dateutil.relativedelta import relativedelta
initial = datetime.date(2019, 3, 5)
delta = relativedelta(day=60)
print(f"Initial date: {initial.strftime('%d-%m-%Y')}")
new_dt = initial + delta
print(f"Final date: {new_dt.strftime('%d-%m-%Y')}")
However my output is:
Initial date: 05-03-2019
Final date: 31-03-2019
What is wrong here?
delta = relativedelta(day=60)
new_dt = initial + delta
The day of initial is set (not incremented but set) to 60 but since there are only 31 days in that month it is set to 31.
https://dateutil.readthedocs.io/en/stable/relativedelta.html
If your intention is to increment the date by 60 days use
delta = datetime.timedelta(days=60)
OR
delta = relativedelta(days=60)
instead of
delta = relativedelta(day=60)

Can I make any format mask I like for datetime Python

Given a date and time in a specific format can I use datetime with a format mask that suits to read in the values, when the values aren't all that typical? For example how would I create a mask that works with the following:
08264.51782528
08 = last 2 digits of year (will be within last 60 years so if the 2 digits are above current (eg. 18, then assume they're in the 20th century)
264 = number of days
51782528 = decimal representation of how far through the day (0 = midnight, 0.5 = noon, 0.999988 = 1 second to midnight the following day)
Look into timedelta.
from datetime import datetime, timedelta
# get year, day, and day_percent
if year >= 58:
year += 1900
else:
year += 2000
date = datetime(year, 1, 1) + timedelta(days=day-1) + (timedelta(days=1) * day_percent)
This assumes day 1 is the first day of the year (January 1st).

Python construct datetime having weekday with other time parameters

I need to construct datetime object from different parameters. For example, I get these parameters:
integer number from 0 to 6. This one indicates weekday.
hour and minutes in float format (from 0.0 to 24.0).
And then I know which week of the current year it is going to be. So, for example, let say that datetime should be from 2014-07-18 00:00:00 to 2014-07-24 23:59:00 (seconds can be ignored and left at 00). So to get exact datetime I need to use above defined parameters.
Let say I would get these parameters 4 (meaning Friday), and 9.5 (meaning 09:30).
So by doing such construction, I should get a date that would be: 2014-07-22 09:30:00. How could accomplish such thing?
Do I need to somehow get for example day of the month by knowing which is the week of the year and which weekday it is?
P.S. A bit more detailed example of what I'm trying to accomplish
from datetime import datetime
today = datetime.today() #using this to get the week I'll be working with.
today = today.replace(day=?) #how to get which day I
#need to enter by having weekday and knowing that week is the present one?
You could do something like that, if your parameters are weekday and t (time):
from datetime import timedelta
monday = today - timedelta(days=today.weekday())
result = (monday + timedelta(days=weekday)).replace(hour=int(t), minutes=int((t - int(t)) * 60))
If you have a starting date, use the relative value of the datetime.datetime.weekday() value to construct a timedelta() object that'll put you onto the right weekday, then replace the hour and minutes:
from datetime import timedelta
def relative_date(reference, weekday, timevalue):
hour, minute = divmod(timevalue, 1)
minute *= 60
days = reference.weekday() - weekday
return (reference - timedelta(days=days)).replace(
hour=int(hour), minute=int(minute), second=0, microsecond=0)
Demo:
>>> from datetime import timedelta, datetime
>>> def relative_date(reference, weekday, timevalue):
... hour, minute = divmod(timevalue, 1)
... minute *= 60
... days = reference.weekday() - weekday
... return (reference - timedelta(days=days)).replace(
... hour=int(hour), minute=int(minute), second=0, microsecond=0)
...
>>> relative_date(datetime.now(), 4, 9.5)
datetime.datetime(2014, 8, 22, 9, 30)
>>> relative_date(datetime.now() - timedelta(days=30), 6, 11.75)
datetime.datetime(2014, 7, 27, 11, 45)
I would use timedelta to add the difference between weekdays to the datetime
from datetime import datetime, timedelta
friday = 4
today = datetime.now()
friday_this_week = today + timedelta(friday - today.weekday())
In your case just replace today with a date that is in the week you want.

Extract day of year and Julian day from a string date

I have a string "2012.11.07" in python. I need to convert it to date object and then get an integer value of day of year and also Julian day. Is it possible?
First, you can convert it to a datetime.datetime object like this:
>>> import datetime
>>> fmt = '%Y.%m.%d'
>>> s = '2012.11.07'
>>> dt = datetime.datetime.strptime(s, fmt)
>>> dt
datetime.datetime(2012, 11, 7, 0, 0)
Then you can use the methods on datetime to get what you want… except that datetime doesn't have the function you want directly, so you need to convert to a time tuple
>>> tt = dt.timetuple()
>>> tt.tm_yday
312
The term "Julian day" has a few different meanings. If you're looking for 2012312, you have to do that indirectly, e.g., one of the following.
>>> int('%d%03d' % (tt.tm_year, tt.tm_yday))
2012312
>>> tt.tm_year * 1000 + tt.tm_yday
2012312
If you're looking for a different meaning, you should be able to figure it out from here. For example, if you want the "days since 1 Jan 4713 BC" meaning, and you have a formula that requires Gregorian year and day in year, you've got those two values above to plug in. (If you have a formula that takes Gregorian year, month, and day, you don't even need the timetuple step.) If you can't work out where to go from there, ask for further details.
If you don't have a formula—and maybe even if you already do—your best bet is probably to look around PyPI and ActiveState for pre-existing modules. For example, a quick search turned up something called jdcal. I'd never seen it before, but a quick pip install jdcal and a brief skim of the readme, and I was able to do this:
>>> sum(jdcal.gcal2jd(dt.year, dt.month, dt.day))
2456238.5
That's the same result that the USN Julian date converter gave me.
If you want integral Julian day, instead of fractional Julian date, you have to decide which direction you want to round—toward 0, toward negative infinity, rounding noon up to the next day, rounding noon toward even days, etc. (Note that Julian date is defined as starting since noon on 1 Jan 4713BC, so half of 7 Nov 2012 is 2456238, the other half is 2456239, and only you know which one of those you want…) For example, to round toward 0:
>>> int(sum(jdcal.gcal2jd(dt.year, dt.month, dt.day)))
2456238
To get the Julian day, use the datetime.date.toordinal method and add a fixed offset.
The Julian day is the number of days since January 1, 4713 BC at 12:00 in the proleptic Julian calendar, or November 24, 4714 BC at 12:00 in the proleptic Gregorian calendar. Note that each Julian day starts at noon, not midnight.
The toordinal function returns the number of days since December 31, 1 BC at 00:00 in the proleptic Gregorian calendar (in other words, January 1, 1 AD at 00:00 is the start of day 1, not day 0). Note that 1 BC directly precedes 1 AD, there was no year 0 since the number zero wasn't invented until many centuries later.
import datetime
datetime.date(1,1,1).toordinal()
# 1
Simply add 1721424.5 to the result of toordinal to get the Julian day.
Another answer already explained how to parse the string you started with and turn it into a datetime.date object. So you can find the Julian day as follows:
import datetime
my_date = datetime.date(2012,11,7) # time = 00:00:00
my_date.toordinal() + 1721424.5
# 2456238.5
To simplify the initial steps of abarnert's answer:
from dateutil import parser
s = '2012.11.07'
dt = parser.parse(s)
then apply the rest of abanert's answer.
This functionality (conversion of date strings to Julian date/time) is also present in the astropy module. Please refer to their documentation for complete details. The astropy implementation is especially handy for easy conversions to Julian time, as opposed to just the Julian date.
Example solution for the original question:
>>> import astropy.time
>>> import dateutil.parser
>>> dt = dateutil.parser.parse('2012.11.07')
>>> time = astropy.time.Time(dt)
>>> time.jd
2456238.5
>>> int(time.jd)
2456238
For quick computations, you could find day of year and Julian day number using only stdlib datetime module:
#!/usr/bin/env python3
from datetime import datetime, timedelta
DAY = timedelta(1)
JULIAN_EPOCH = datetime(2000, 1, 1, 12) # noon (the epoch name is unrelated)
J2000_JD = timedelta(2451545) # julian epoch in julian dates
dt = datetime.strptime("2012.11.07", "%Y.%m.%d") # get datetime object
day_of_year = (dt - datetime(dt.year, 1, 1)) // DAY + 1 # Jan the 1st is day 1
julian_day = (dt.replace(hour=12) - JULIAN_EPOCH + J2000_JD) // DAY
print(day_of_year, julian_day)
# 312 2456239
Another way to get day_of_year:
import time
day_of_year = time.strptime("2012.11.07", "%Y.%m.%d").tm_yday
julian_day in the code above is "the Julian day number associated with the solar day -- the number assigned to a day in a continuous count of days beginning with the Julian day number 0 assigned to the day starting at Greenwich mean noon on 1 January 4713 BC, Julian proleptic calendar -4712".
The time module documentation uses the term "Julian day" differently:
Jn The Julian day n (1 <= n <= 365). Leap days are not counted, so in
all years February 28 is day 59 and March 1 is day 60.
n The
zero-based Julian day (0 <= n <= 365). Leap days are counted, and it
is possible to refer to February 29.
i.e., the zero-based Julian day is day_of_year - 1 here. And the first one (Jn) is day_of_year - (calendar.isleap(dt.year) and day_of_year > 60) -- the days starting with March 1 are shifted to exclude the leap day.
There is also a related term: Julian date.
Julian day number is an integer. Julian date is inherently fractional: "The Julian Date (JD) of any instant is the Julian day number for the preceding noon plus the fraction of the day since that instant."
In general, to avoid handling edge cases yourself, use a library to compute Julian day as suggested by #abarnert.
According to this article there is an unpublished one-line formula created by Fliegel and Van Flandern to calculate an Gregorian Date to an Julian Date:
JD = 367 * year - 7 * (year + (month + 9)/12)/4 - 3 * ((year + (month - 9)/7)/100 + 1)/4 + 275 * month/9 + day + 1721029
This was compacted by P. M. Muller and R. N. Wimberly of the Jet Propulsion Laboratory, Pasadena, California for dates after March of 1900 to:
JD = 367 * year - 7 * (year + (month + 9)/12)/4 + 275 * month/9 + day + 1721014
These formulas are off by 0.5, so just subtract 0.5 from the formulas.
Use some string manupulation to actually extract the data and you will be good
>>> year, month, day = map(int,"2018.11.02".split("."))
>>> 367 * year - 7 * (year + (month + 9)/12)/4 + 275 * month/9 + day + 1721014 - 0.5
2458424.5
I import datetime lib, and use strftime to extract 'julian day', year, month, day...
import datetime as dt
my_date = dt.datetime.strptime('2012.11.07', '%Y.%m.%d')
jld_str = my_date.strftime('%j') # '312'
jld_int = int(jld_str) # 312
From the above examples, here is the one liner (non-Julian):
import datetime
doy = datetime.datetime.strptime('2014-01-01', '%Y-%m-%d').timetuple().tm_yday
def JulianDate_to_date(y, jd):
month = 1
while jd - calendar.monthrange(y,month)[1] > 0 and month <= 12:
jd = jd - calendar.monthrange(y,month)[1]
month += 1
date = datetime.date(y,month,jd).strftime("%m/%d/%Y")
return date
While the answer of #FGol provides self-contained formulae, it should be noted that those formulae are only valid if division rounds towards zero (so-called "truncated division"), which is language-dependent.
Python, for example, implements rounding towards -infinity, which is quite different. To use the formulae given in Python, you can do something like this:
def trunc_div(a, b):
"""Implement 'truncated division' in Python."""
return (a // b) if a >= 0 else -(-a // b)
def formula1(year, month, day):
"""Convert Gregorian date to julian day number."""
return 367 * year - trunc_div(7 * (year + trunc_div(month + 9, 12)), 4) - trunc_div(3 * (trunc_div(year + trunc_div(month - 9, 7), 100) + 1), 4) + trunc_div(275 * month, 9) + day + 1721029
def formula2(year, month, day):
"""Convert Gregorian date to julian day number (simplified); only valid for dates from March 1900 and beyond."""
return 367 * year - trunc_div(7 * (year + trunc_div(month + 9, 12)), 4) + trunc_div(275 * month, 9) + day + 1721014

Categories

Resources