I'm trying to calculate the nth weekday for a given date. For example, I should be able to calculate the 3rd wednesday in the month for a given date.
I have written 2 versions of a function that is supposed to do that:
from datetime import datetime, timedelta
### version 1
def nth_weekday(the_date, nth_week, week_day):
temp = the_date.replace(day=1)
adj = (nth_week-1)*7 + temp.weekday()-week_day
return temp + timedelta(days=adj)
### version 2
def nth_weekday(the_date, nth_week, week_day):
temp = the_date.replace(day=1)
adj = temp.weekday()-week_day
temp += timedelta(days=adj)
temp += timedelta(weeks=nth_week)
return temp
Console output
# Calculate the 3rd Friday for the date 2011-08-09
x=nth_weekday(datetime(year=2011,month=8,day=9),3,4)
print 'output:',x.strftime('%d%b%y')
# output: 11Aug11 (Expected: '19Aug11')
The logic in both functions is obviously wrong, but I can't seem to locate the bug - can anyone spot what is wrong with the code - and how do I fix it to return the correct value?
Your problem is here:
adj = temp.weekday()-week_day
First of all, you are subtracting things the wrong way: you need to subtract the actual day from the desired one, not the other way around.
Second, you need to ensure that the result of the subtraction is not negative - it should be put in the range 0-6 using % 7.
The result:
adj = (week_day - temp.weekday()) % 7
In addition, in your second version, you need to add nth_week-1 weeks like you do in your first version.
Complete example:
def nth_weekday(the_date, nth_week, week_day):
temp = the_date.replace(day=1)
adj = (week_day - temp.weekday()) % 7
temp += timedelta(days=adj)
temp += timedelta(weeks=nth_week-1)
return temp
>>> nth_weekday(datetime(2011,8,9), 3, 4)
datetime.datetime(2011, 8, 19, 0, 0)
one-liner
You can find the nth weekday with a one liner that uses calendar from the standard library.
import calendar
calendar.Calendar(x).monthdatescalendar(year, month)[n][0]
where:
x : the integer representing your weekday (0 is Monday)
n : the 'nth' part of your question
year, month : the integers year and month
This will return a datetime.date object.
broken down
It can be broken down this way:
calendar.Calendar(x)
creates a calendar object with weekdays starting on your required weekday.
.monthdatescalendar(year, month)
returns all the calendar days of that month.
[n][0]
returns the 0 indexed value of the nth week (the first day of that week, which starts on the xth day).
why it works
The reason for starting the week on your required weekday is that by default 0 (Monday) will be used as the first day of the week and if the month starts on a Wednesday, calendar will consider the first week to start on the first occurrence of Monday (ie. week 2) and you'll be a week behind.
example
If you were to need the third Saturday of September 2013 (that month's US stock option expiry day), you would use the following:
calendar.Calendar(5).monthdatescalendar(2013,9)[3][0]
The problem with the one-liner with the most votes is it doesn't work.
It can however be used as a basis for refinement:
You see this is what you get:
c = calendar.Calendar(calendar.SUNDAY).monthdatescalendar(2018, 7)
for c2 in c:
print(c2[0])
2018-07-01
2018-07-08
2018-07-15
2018-07-22
2018-07-29
c = calendar.Calendar(calendar.SUNDAY).monthdatescalendar(2018, 8)
for c2 in c:
print(c2[0])
2018-07-29
2018-08-05
2018-08-12
2018-08-19
2018-08-26
If you think about it it's trying to organise the calendars into nested lists to print a weeks worth of dates at a time. So stragglers from other months come into play. By using a new list of valid days that fall in the month - this does the trick.
Answer with appended list
import calendar
import datetime
def get_nth_DOW_for_YY_MM(dow, yy, mm, nth) -> datetime.date:
#dow - Python Cal - 6 Sun 0 Mon ... 5 Sat
#nth is 1 based... -1. is ok for last.
i = -1 if nth == -1 or nth == 5 else nth -1
valid_days = []
for d in calendar.Calendar(dow).monthdatescalendar(yy, mm):
if d[0].month == mm:
valid_days.append(d[0])
return valid_days[i]
So here's how it could be called:
firstSundayInJuly2018 = get_nth_DOW_for_YY_MM(calendar.SUNDAY, 2018, 7, 1)
firstSundayInAugust2018 = get_nth_DOW_for_YY_MM(calendar.SUNDAY, 2018, 8, 1)
print(firstSundayInJuly2018)
print(firstSundayInAugust2018)
And here is the output:
2018-07-01
2018-08-05
get_nth_DOW_for_YY_MM() can be refactored using lambda expressions like so:
Answer with lambda expression refactoring
import calendar
import datetime
def get_nth_DOW_for_YY_MM(dow, yy, mm, nth) -> datetime.date:
#dow - Python Cal - 6 Sun 0 Mon ... 5 Sat
#nth is 1 based... -1. is ok for last.
i = -1 if nth == -1 or nth == 5 else nth -1
return list(filter(lambda x: x.month == mm, \
list(map(lambda x: x[0], \
calendar.Calendar(dow).monthdatescalendar(yy, mm) \
)) \
))[i]
The one-liner answer does not seem to work if the target day falls on the first of the month. For instance, if you want the 2nd Friday of every month, then the one-liner approach
calendar.Calendar(4).monthdatescalendar(year, month)[2][0]
for March 2013 will return March 15th 2013 when it should be March 8th 2013. Perhaps add in a check like
if date(year, month, 1).weekday() == x:
delivery_date.append(calendar.Calendar(x).monthdatescalendar(year, month)[n-1][0])
else:
delivery_date.append(calendar.Calendar(x).monthdatescalendar(year, month)[n][0])
Alternatively this will work for Python 2, returns the occurance of weekday in the said month, i.e if 16 June 2018 is the input, then returns the occurance of the day on 16th June 2018
You may substitute the month/year/date integers to anything you might want - right now it's getting the input / date from the system via datetime
Omit out print statements or use pass where they're not needed
import calendar
import datetime
import pprint
month_number = int(datetime.datetime.now().strftime('%m'))
year_number = int(datetime.datetime.now().strftime('%Y'))
date_number = int(datetime.datetime.now().strftime('%d'))
day_ofweek = str(datetime.datetime.now().strftime('%A'))
def weekday_occurance():
print "\nFinding current date here\n"
for week in xrange(5):
try:
calendar.monthcalendar(year_number, month_number)[week].index(date_number)
occurance = week + 1
print "Date %s of month %s and year %s is %s #%s in this month." % (date_number,month_number,year_number,day_ofweek,occurance)
return occurance
break
except ValueError as e:
print "The date specified is %s which is week %s" % (e,week)
myocc = weekday_occurance()
print myocc
A little tweak would make the one-liner work correctly:
import calendar
calendar.Calendar((weekday+1)%7).monthdatescalendar(year, month)[n_th][-1]
Here n_th should be interpreted as c-style, e.g. 0 is the first index.
Example: to find 1st Sunday in July 2018 one could type:
>>> calendar.Calendar(0).monthdatescalendar(2018, 7)[0][-1]
datetime.date(2018, 7, 1)
People here seem to like one-liner, I will propose below.
import calendar
[cal[0] for cal in calendar.Calendar(x).monthdatescalendar(year, month) if cal[0].month == month][n]
The relativedelta module that's an extension from the Python dateutil package (pip install python-dateutil) does exactly what you want:
from dateutil import relativedelta
import datetime
def nth_weekday(the_date, nth_week, week_day):
return the_date.replace(day=1) + relativedelta.relativedelta(
weekday=week_day(nth_week)
)
print(nth_weekday(datetime.date.today(), 3, relativedelta.FR))
The key part here evaluates to weekday=relativedelta.FR(3): the third Friday of the month. Here are the relevant part of the docs for the weekday parameter,
weekday:
One of the weekday instances (MO, TU, etc) available in the
relativedelta module. These instances may receive a parameter N,
specifying the Nth weekday, which could be positive or negative
(like MO(+1) or MO(-2)).
Related
I am creating an app where I have two values, a committee starting date for e-g 2022,01,02 and for how many months it will continue here it is (4months). Now I am saving some data in my database month wise and also these dates will save too. now the issue is I am getting right result if the number of month is less than or equal to 12 using this.
number of memebrs = 12
starting date = 2022,01,01
for i in range(1,17):
print('date', (2022,i,10))
but the issue comes when the months are greater than 12 so than date start printing 2022,01,13 which is false because I also want to increment the year to 2023, I feel like this is not really a good idea very inefficient looking way. can anyone tell me is there any other way to do this.
While you can use the datetime library to handle dates, it doesn't provide any methods to increase dates month by month.
Now, previous suggestions/answers suggest you increase the year when month == 12, but that will cause December to be skipped. Also, your code doesn't consider any given month in the starting date. So a better solution would be:
>>> year = 2022
>>> month = 7
>>> day = 23
>>>
>>> for i in range(1, 8):
... month += 1
... if month == 13:
... month = 1
... year += 1
... print(f'{year}-{month}-{day}')
...
2022-8-23
2022-9-23
2022-10-23
2022-11-23
2022-12-23
2023-1-23
2023-2-23
you could do something like this:
date = [2022,1,10]
for i in range(1,17):
if i%12==0:
date[0]+=1
date[1]=1
print('date', (time[0],i,time[2]))
By the tone of your question i think you are a beginner, so i won't
recommend you to use datetime module and i appreciate that you tried to do it on your own.
What i dont appreciate is that why cant you just use if statements and create variables for year and date
yr = 2022
dt = 1
for i in range(1,17):
print('date', (yr,i,dt))
if i % 12 == 0:
yr += 1
mn = 1
I also want to share the modern aproach using datetime module. But it requires some modules.
In your cmd enter the command
pip install python-dateutil
Once installed close cmd and refresh your ide
this is the code you may want to use
from datetime import datetime
from dateutil.relativedelta import relativedelta
date_time = datetime(2022, 1, 1) #Creating a Date object
for i in range(1, 17):
date = date_time.date()
print(date)
date_time = date_time + relativedelta(months=1)
I have been trying to calculate the date for next Friday the 13th in Python 3, but Idk how to make it more efficient.
from datetime import date, timedelta
from calendar import monthrange
def is_friday_13th(date):
# Returns bool value for the given date if it is Friday The 13th
return date.day == 13 and date.weekday() == 4
def max_days(date):
# Returns Number of Days for the given month
return monthrange(date.year, date.month)[1]
def friday_the_13th():
# Returns next Friday the 13th
# If today is Friday the 13th, returns today's date.
today = date.today()
result = today
if result.day < 13:
result += timedelta(days=13-result.day)
found = is_friday_13th(result)
while not found:
result += timedelta(days=max_days(result) - result.day)
result += timedelta(days=13)
found = is_friday_13th(result)
return f"{result.strftime('%Y-%m-%d')}"
if __name__ == "__main__":
print(friday_the_13th())
I feel like using the monthrange function of the calendar module makes it less efficient and this problem can be solved without it but I'm struggling to do it.
I read the solutions written by others and I don't understand how to solve this problem more efficiently and write more Pythonically.
How about this
from datetime import date, timedelta
def friday13s(from_date=date.today()):
d = from_date + timedelta(13 - from_date.day) # clamp date to the 13th
def increment_month(d):
mm = 1 if d.month == 12 else d.month + 1
yy = d.year + 1 if mm == 1 else d.year
return date(yy, mm, d.day)
if from_date > d:
d = increment_month(d)
while True:
if d.weekday() == 4:
yield d
d = increment_month(d)
usage:
from itertools import islice
for d in islice(friday13s(), 10):
print(d)
prints
2021-08-13
2022-05-13
2023-01-13
2023-10-13
2024-09-13
2024-12-13
2025-06-13
2026-02-13
2026-03-13
2026-11-13
Note that increment_month() is not a general-purpose way to increment the month in a date. It works fine if the day is the 13th, but it will fail if the day is >= 29th. It's only fit for the purpose of the particular task it is used in here.
There are more elegant ways of incrementing the month with libraries like dateutil, but when using only built-ins from the Python standard library, doing some legwork is necessary. Luckily, the date arithmetic for this task is not complicated. Adding a whole extra library just to reduce the above by two or three lines seems excessive.
I want to parse a date string and manipulate the year, month, date in cases where I either get '00' for month or day or in cases where I get a day beyond the possible days of that year/month. Given a '2012-00-00' or a '2020-02-31', I get a ValueError. What I want, is to catch the error and then turn the former into '2012-01-01' and the latter to '2020-02-29'. No results on Google so far.
Clarification: I use try/except/ValueError... what I want is to parse out the year, month, day and fix the day or month when they are having a ValueError... without having to code the parsing and regular expressions myself... which defeats the purpose of using a library to begin with.
# Try dateutjil
blah = dateutil.parser.parse(date_string, fuzzy=True)
print(blah)
# Try datetime
date_object = datetime.strptime(date_string, date_format)
return_date_string = date_object.date().strftime('%Y-%m-%d')
I know you don't want to parse the date yourself but I think you will probably have to. One option would be to split the incoming string into its component year, month and day parts and check them against valid values, adjusting as required. You can then create a date from that and call strftime to get a valid date string:
from datetime import datetime, date
import calendar
def parse_date(dt):
[y, m, d] = map(int, dt.split('-'))
# optional error checking on y
# ...
# check month
m = 1 if m == 0 else 12 if m > 12 else m
# check day
last = calendar.monthrange(y, m)[-1]
d = 1 if d == 0 else last if d > last else d
return date(y, m, d).strftime('%Y-%m-%d')
print(parse_date('2012-00-00'))
print(parse_date('2020-02-31'))
Output:
2012-01-01
2020-02-29
I'm trying to write a program that will give me a nice .txt file displaying this year's calendar, however, I don't want to use the calendar function, but datetime.
I want it to have the following format (I want to have the first 3 letter of every day of the week next to it):
Tue Jan 01
Wed Jan 02
all the way to
Tue Dec 31
(basically 365 lines altogether with 10 characters in every line, and every line ends in a newline "\n").
This is what I have gathered going from various stackflow questions, tutorials and modules. So far no success.
import datetime
from datetime import date
textfile = file('new_file.txt','wt')
your_date.isoformat()
your_date.strftime("%A %d. %B %Y")
My main issue is that I am unfamiliar with how I get python to give me a range of dates (here being the year 2013, but it could also be just any increment in time such as June 2011 to December 2014) and of course to print the day of the week next to it. This way it could be adapted to any time period you might need this small calendar for. I was thinking maybe assigning every day's number (1 being Monday, 2 being Tuesday is the pattern if I'm correct) the first 3 letters of a day in the week so it's all neat and of the same length.
Here's one way to do the looping:
inc = datetime.timedelta(days=1)
curr = datetime.date(2013, 1, 1)
end = datetime.date(2014, 1, 1)
while curr < end:
# print out the date and whatnot
curr += inc
#!/usr/bin/env python3
import sys
from datetime import date, timedelta
def gen_calendar(year=None):
if year is None:
year = date.today().year
current = date(year, 1, 1)
delta = timedelta(days=1)
while current.year == year:
yield current.strftime('%a %b %d')
current += delta
if __name__ == '__main__':
year = None
if len(sys.argv) > 1:
year = int(sys.argv[1])
for str_date in gen_calendar(year):
print(str_date)
this will print to stdout so you can redirect it to a file from the console, which is the usual but if you want to write to the file directly from the script replace the last two lines with this:
with open('calendar.txt', 'w') as f:
for str_date in gen_calendar(year):
print(str_date, file=f)
How can I print the next year if the current year is given in python using the simplest code, possibly in one line using datetime module.
Both date and datetime objects have a year attribute, which is a number. Just add 1:
>>> from datetime import date
>>> print date.today().year + 1
2013
If you have the current year in a variable, just add 1 directly, no need to bother with the datetime module:
>>> year = 2012
>>> print year + 1
2013
If you have the date in a string, just select the 4 digits that represent the year and pass it to int:
>>> date = '2012-06-26'
>>> print int(date[:4]) + 1
2013
Year arithmetic is exceedingly simple, make it an integer and just add 1. It doesn't get much simpler than that.
If, however, you are working with a whole date, and you need the same date but one year later, use the components to create a new date object with the year incremented by one:
>>> today = date.today()
>>> print date(today.year + 1, today.month, today.day)
2013-06-26
or you can use the .replace function, which returns a copy with the field you specify changed:
>>> print today.replace(year=today.year + 1)
2013-06-26
Note that this can get a little tricky when today is February 29th in a leap year. The absolute, fail-safe correct way to work this one is thus:
def nextyear(dt):
try:
return dt.replace(year=dt.year+1)
except ValueError:
# February 29th in a leap year
# Add 365 days instead to arrive at March 1st
return dt + timedelta(days=365)
here is another simple way...
import datetime
x = datetime.datetime.now()
print(x.year+1)