I am running sql query in python, I have a requirement to dynamically generate dates and append it to my sql query.This script runs on every Monday. If the week of Monday falls between two months then I have to restrict the date range till last of the previous month (i.e 30th or 31st). Any Ideas on how to achieve this ?
I tried to get the weeknumber and their respective dates but I couldn't find the exact function which will return me list of dates with corresponding week number
You can use the following code to get the week number between two months:
import datetime
def get_weeks_between_dates(start_date, end_date):
start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d')
end_date = datetime.datetime.strptime(end_date, '%Y-%m-%d')
week_number = (end_date - start_date).days / 7
return week_number
start_date = '2017-01-01'
end_date = '2017-02-01'
print(get_weeks_between_dates(start_date, end_date))
Find the possibly truncated week starting at a given date and not extending beyond the end of the month.
If what you mean is "the seven days starting with the day the script is run", you could use the following:
from datetime import date, timedelta, datetime as dt
from typing import Tuple
def one_week_from(base: None | date = None) -> Tuple[date, date]:
'''Returns a tuple consisting of the given starting date (default today)
and the end of the week starting at that date, or the last day of the
month, whichever comes first. (That is, a week truncated at the end of
the month.)
'''
if base is None:
base = dt.now().date()
if base.month < 12:
next_first = date(base.year, base.month + 1, 1)
else:
next_first = date(base.year + 1, 1, 1))
return (base, min(base + timedelta(6), next_first - timedelta(1)))
If necessary, you could use a function like this to find a Monday:
# Same imports as above
def monday_not_later_than(base: None | date = None) -> date:
'''Returns the last Monday no later than the given date (default: today).
(That is, the given date if it is a Monday; otherwise the previous
Monday.)
'''
if base is None:
base = dt.now().date()
return base - timedelta(base.weekday())
Get the ISO week number
If you have a datetime.date or datetime.datetime object, you can use the isocalendar() member function to get a tuple consisting of the year, week_number and iso_day_of_week for that date. (Use datetime.datetime.now() to get the current day, but watch out for time zone artefacts.)
The ISO week always starts with a Monday; it is counted in the year which contains the Wednesday of the week. The ISO weekday is from 1 to 7 (Monday == 1), unlike the datetime.date.weekday which ranges from 0 to 6 (Monday == 0).
I have a date in the format "YYYY-MM-DD", for example "2022-03-09". It is the 68th day of the year 2022. Is there a way to get the 68th day of 2021? More generally, if I have a date in "YYYY-MM-DD" format, which is the N th day of the year, is there a quick way to calculate the N th day of the previous year?
You could do:
from datetime import datetime, timedelta
date_string = '2020-03-09'
# Parse the string to a datetime.date object
date = datetime.strptime(date_string, '%Y-%m-%d')
# Get the number N of the day in the year
N = date.timetuple().tm_yday
# Take 1st of january of last year and add N-1 days
N_last_year = (
date.replace(year=date.year-1, month=1, day=1) +
timedelta(days=N-1)
)
print(N_last_year.date())
Or, another funny solution based on leap years. It is based on the fact that the Nth day of last year is the same as this year's, except if the date is after 29th february and there is a leap year somewhere.
from datetime import datetime, timedelta
def leap(year: int) -> bool:
# Returns True if year is a leap year, False otherwise
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
date_string = '2020-03-09'
# Parse the string to a datetime.date object
date = datetime.strptime(date_string, '%Y-%m-%d')
# Get the number N of the day in the year
N = date.timetuple().tm_yday
date_last_year = date.replace(year=date.year-1)
# Do the cool thing
if date.month >= 3:
if leap(date.year):
date_last_year += timedelta(days=1)
elif leap(date.year-1):
date_last_year += timedelta(days=-1)
print(date_last_year.date())
The datetime objects will you with that (https://docs.python.org/3/library/datetime.html).
Indeed, you can compute the time lap between your date and the beginning of the year. Then you can add this difference to the beginning of the next year.
import datetime
date_string = "2022-03-09"
date_object = datetime.datetime.strptime(date_string, "%Y-%m-%d").date()
beginning_of_year = datetime.date(date_object.year, 1, 1)
time_difference = date_object - beginning_of_year
beginning_of_last_year = datetime.date(date_object.year - 1, 1, 1)
same_day_last_year = beginning_of_last_year + time_difference
print(same_day_last_year)
However, the result is disappointing, since it is in fact the same date...
I think you can do something like
from datetime import timedelta, date
year = timedelta(days=365)
today = date.fromisoformat("2022-03-09")
print(today - year)
This program is supposed to take the date_string parameter in the format of "year-month-day", and use the add_year function to calculate the next year that this date will occur (it's 4 years later for the 29th of February during Leap Year, and 1 year later for all other dates).
There's an error in the code which is resulting in the wrong answer. Instead of the actual date in the result, the code generates "yyyy-mm-dd" every single time.
import datetime
from datetime import date
def add_year(date_obj):
try:
new_date_obj = date_obj.replace(year = date_obj.year + 1)
except ValueError:
# This gets executed when the above method fails,
# which means that we're making a Leap Year calculation
new_date_obj = date_obj.replace(year = date_obj.year + 4)
return new_date_obj
def next_date(date_string):
# Convert the argument from string to date object
date_obj = datetime.datetime.strptime(date_string, r"%Y-%m-%d")
next_date_obj = add_year(date_obj)
# Convert the datetime object to string,
# in the format of "yyyy-mm-dd"
next_date_string = next_date_obj.strftime("yyyy-mm-dd")
return next_date_string
today = date.today() # Get today's date
print(next_date(str(today)))
# Should return a year from today, unless today is Leap Day
print(next_date("2021-01-01")) # Should return 2022-01-01
print(next_date("2020-02-29")) # Should return 2024-02-29
import datetime
from datetime import date
def add_year(date_obj):
try:
new_date_obj = date_obj.replace(year = date_obj.year + 1)
except ValueError:
# This gets executed when the above method fails,
# which means that we're making a Leap Year calculation
new_date_obj = date_obj.replace(year = date_obj.year + 4)
return new_date_obj
def next_date(date_string):
# Convert the argument from string to date object
date_obj = datetime.datetime.strptime(date_string, r"%Y-%m-%d")
next_date_obj = add_year(date_obj)
# Convert the datetime object to string,
# in the format of "yyyy-mm-dd"
next_date_string = next_date_obj.strftime("%Y-%m-%d")
return next_date_string
today = date.today() # Get today's date
print(next_date(str(today)))
# Should return a year from today, unless today is Leap Day
print(next_date("2021-01-01")) # Should return 2022-01-01
print(next_date("2020-02-29")) # Should return 2024-02-29
I have the following date range:
begin: 2018-02-15
end: 2018-04-23
I want to achieve the following:
["2018-02-15 - 2018-02-28", "2018-03-01 - 2018-03-31", "2018-04-01 - 2018-04-23"]
Essentially, I want to divide a given date range into months. I can't think of a way to accomplish this in Python.
I have considered the solution here, however, this splits the date range based on a specified interval. I want to be able to split a date range dynamically.
Hence, given a date range from 15 February 2018 to 23 April 2018, I want to be able to get the individual months in the range, like so:
15 February 2018 to 28 February 2018
01 March 2018 to 31 March 2018
01 April 2018 to 23 April 2018
In a loop; starting at the first day continually add one day till you get to the end date; whenever the month changes save the dates.
import datetime
begin = '2018-02-15'
end = '2018-04-23'
dt_start = datetime.datetime.strptime(begin, '%Y-%m-%d')
dt_end = datetime.datetime.strptime(end, '%Y-%m-%d')
one_day = datetime.timedelta(1)
start_dates = [dt_start]
end_dates = []
today = dt_start
while today <= dt_end:
#print(today)
tomorrow = today + one_day
if tomorrow.month != today.month:
start_dates.append(tomorrow)
end_dates.append(today)
today = tomorrow
end_dates.append(dt_end)
out_fmt = '%d %B %Y'
for start, end in zip(start_dates,end_dates):
print('{} to {}'.format(start.strftime(out_fmt), end.strftime(out_fmt)))
Result:
>>>
15 February 2018 to 28 February 2018
01 March 2018 to 31 March 2018
01 April 2018 to 23 April 2018
>>>
You could probably figure out a way to get a range of months between the start and end dates; create a datetime object for the first day of each of those months store them and the days just prior to them. Dates spanning a change of year might be problematic though.
To work with convenient date objects, always use the standard module datetime. This wraps your string formatted dates, and allows easier calculations as well as tailored output formatting.
Unfortunately, it seems to miss one important piece of information: the last day of each month, given a year (which is necessary for Februari). There is an additional module calendar which returns the last day for a month, but since this is all you need of it and there is a simple datetime based function that does the same thing, I chose the latter.
With that, you can set any begin date and append it to your list, together with its last day of that month, then set begin to the next month's 1st and continue until you pass end.
A caveat/finetuning: I realized it would not work if both begin and end fall inside the same month. That needs an interim check, so I changed my initial while begin < end to while True and moved the check for crossing the end date into a separate line.
Also, to cross a year needs a separate test again, because else the statement month+1 will fail on December.
import datetime
# borrowed from https://stackoverflow.com/a/13565185
# as noted there, the calendar module has a function of its own
def last_day_of_month(any_day):
next_month = any_day.replace(day=28) + datetime.timedelta(days=4) # this will never fail
return next_month - datetime.timedelta(days=next_month.day)
begin = "2018-02-15"
end = "2018-04-23"
def monthlist(begin,end):
begin = datetime.datetime.strptime(begin, "%Y-%m-%d")
end = datetime.datetime.strptime(end, "%Y-%m-%d")
result = []
while True:
if begin.month == 12:
next_month = begin.replace(year=begin.year+1,month=1, day=1)
else:
next_month = begin.replace(month=begin.month+1, day=1)
if next_month > end:
break
result.append ([begin.strftime("%Y-%m-%d"),last_day_of_month(begin).strftime("%Y-%m-%d")])
begin = next_month
result.append ([begin.strftime("%Y-%m-%d"),end.strftime("%Y-%m-%d")])
return result
date_list = monthlist(begin,end)
print (date_list)
results in
[ ['2018-02-15', '2018-02-28'],
['2018-03-01', '2018-03-31'],
['2018-04-01', '2018-04-23'] ]
(slightly formatted for readability only)
If you don't mind using pandas, there's a nice helper date_range that will achieve what you want:
import pandas as pd
start = pd.Timestamp('20180215')
end = pd.Timestamp('20180423')
parts = list(pd.date_range(start, end, freq='M'))
# parts = [Timestamp('2018-02-28 00:00:00', freq='M'), Timestamp('2018-03-31 00:00:00', freq='M')]
if start != parts[0]:
parts.insert(0, start)
if end != parts[-1]:
parts.append(end)
parts[0] -= pd.Timedelta('1d') # we add back one day later
pairs = zip(map(lambda d: d + pd.Timedelta('1d'), parts[:-1]), parts[1:])
pairs_str = list(map(lambda t: t[0].strftime('%Y-%m-%d') + ' - ' + t[1].strftime('%Y-%m-%d'), pairs))
# pairs_str = ['2018-02-15 - 2018-02-28', '2018-03-01 - 2018-03-31', '2018-04-01 - 2018-04-23']
Using python calendar and accounting for change of the year
import calendar
from datetime import datetime
begin = '2018-02-15'
end= '2018-04-23'
begin_year, begin_month, begin_date = [int(i) for i in begin.split("-")]
end_year, end_month, end_date = [int(i) for i in end.split("-")]
years = end_year - begin_year
# if date range contains more than single year, we calculate total months
if years:
months = (12 - begin_month) + end_month + (12 * (years - 1))
else:
months = end_month - begin_month
dates = []
month = begin_month
year = begin_year
def create_datetime_object(y, m, d):
return datetime.strptime('{}-{}-{}'.format(y, m, d), '%Y-%m-%d')
# append the first date
dates.append(create_datetime_object(begin_year, begin_month, begin_date))
for i in range(months+1):
days_in_month = calendar.monthrange(year, month)[-1]
if month == begin_month and year == begin_year:
dates.append(create_datetime_object(begin_year, begin_month, days_in_month))
elif month == end_month and year == end_year:
dates.append(create_datetime_object(end_year, end_month, 1))
else:
dates.append(create_datetime_object(year, month, 1))
dates.append(create_datetime_object(year, month, days_in_month))
if month == 12:
month = 0
year += 1
month += 1
# append the last date
dates.append(create_datetime_object(end_year, end_month, end_date))
And to get a list in the question, we could do something like -
dates = [datetime.strftime(dt, '%Y-%m-%d') for dt in dates]
I had to do a similar manipulation and ended up building this function. I tested it on different use cases (different years, same month...) and it's working well.
It is inspired from S.Lott answer here
Creating a range of dates in Python
import datetime
def get_segments(start_date, end_date):
"""
Divides input date range into associated months periods
Example:
Input: start_date = 2018-02-15
end_date = 2018-04-23
Output:
["2018-02-15 - 2018-02-28",
"2018-03-01 - 2018-03-31",
"2018-04-01 - 2018-04-23"]
"""
curr_date = start_date
curr_month = start_date.strftime("%m")
segments = []
loop = (curr_date!=end_date)
days_increment = 1
while loop:
# Get incremented date with 1 day
curr_date = start_date + datetime.timedelta(days=days_increment)
# Get associated month
prev_month = curr_month
curr_month = curr_date.strftime("%m")
# Add to segments if new month
if prev_month!=curr_month:
# get start of segment
if not segments:
start_segment = start_date
else:
start_segment = segments[-1][1] + datetime.timedelta(days=1)
# get end of segment
end_segment = curr_date - datetime.timedelta(days=1)
# define and add segment
segment = [start_segment, end_segment]
segments.append(segment)
# stop if last day reached
loop = (curr_date!=end_date)
# increment added days
days_increment += 1
if not segments or segments[-1][1]!=end_date:
if not segments:
start_last_segment = start_date
else:
start_last_segment = segments[-1][1] + datetime.timedelta(days=1)
last_segment = [start_last_segment, end_date]
segments.append(last_segment)
for i in range(len(segments)):
segments[i][0] = segments[i][0].strftime("%Y-%m-%d")
segments[i][1] = segments[i][1].strftime("%Y-%m-%d")
return segments
Here is an example:
start_date = datetime.datetime(2020, 5, 27)
end_date = datetime.datetime(2021, 3, 1)
segments = get_segments(start_date, end_date)
for seg in segments:
print(seg)
Output:
['2020-05-27', '2020-05-31']
['2020-06-01', '2020-06-30']
['2020-07-01', '2020-07-31']
['2020-08-01', '2020-08-31']
['2020-09-01', '2020-09-30']
['2020-10-01', '2020-10-31']
['2020-11-01', '2020-11-30']
['2020-12-01', '2020-12-31']
['2021-01-01', '2021-01-31']
['2021-02-01', '2021-02-28']
['2021-03-01', '2021-03-01']
I extend the solution by #wwii
Now you will not have duplicate start and/or end dates
def date_range_split_monthly(begin, end):
dt_start = datetime.strptime(begin, '%Y-%m-%d')
dt_end = datetime.strptime(end, '%Y-%m-%d')
one_day = timedelta(1)
start_dates = [dt_start]
end_dates = []
today = dt_start
while today <= dt_end:
# print(today)
tomorrow = today + one_day
if tomorrow.month != today.month:
if tomorrow <= dt_end:
start_dates.append(tomorrow)
end_dates.append(today)
today = tomorrow
end_dates.append(dt_end)
return start_dates, end_dates
For people using Pendulum :
import pendulum
start = pendulum.now().subtract(months=6)
end = pendulum.today()
period = pendulum.period(start, end)
time_ranges = list(period.range("months"))
arr = []
for index, dt in enumerate(time_ranges):
if index < len(time_ranges) - 1:
start_range = time_ranges[index].format("YYYY-MM-D")
end_range = time_ranges[index + 1].format("YYYY-MM-D")
litt = F"{start_range} - {end_range}"
print(litt)
arr.append(litt)
print(arr)
More about period here
i'm quoting the comment of Kiran Subbaraman, just with addition of the exact keyword (otherwise, whole months will be returned even if ranges fall beyond the start or the end).
#!pip install arrow
from arrow import Arrow
Arrow.span_range('month', start, end, exact=True)
I'm using Python's datetime module for dates.
I'd like to be able to get a datetime for the (say) 3rd Wednesday in May of a given year.
Is there an easy way of doing this without significant external dependencies?
def get_derired_date(day_number,day,month,year):
day = day.upper()
month_dict={
"MONDAY":0,
"TUESDAY":1,
"WEDNESDAY":2,
"THURSDAY":3,
"FRIDAY":4,
"SATURDAY":5,
"SUNDAY":6
}
datestring = "{0}-{1}".format(year,month)
dt = datetime.strptime(datestring, '%Y-%m')
first_day_of_month = datetime(dt.year, dt.month, 1)
month_diff_value = month_dict[day]
final_date = first_day_of_month + timedelta(days=((month_diff_value-calendar.monthrange(dt.year,dt.month)[0])+7)%7)
return final_date
get_derired_date(1,"Friday",2,2016)