Getting syntax error for the following line trying to use assert to test.
#test is_valid_date for April 4, 2014 and Januarary 3, 2012
print assert(is_valid_date(2014,4,4))
print assert(is_valid_date(2012,1,3))
Shouldn't the assert return true for the above if the function is_valid_date returns true?
Here is the actual is_valid_date implementation.
def is_valid_date(year, month, day):
"""
Inputs:
year - an integer representing the year
month - an integer representing the month
day - an integer representing the day
Returns:
True if year-month-day is a valid date and
False otherwise
"""
if year > datetime.MINYEAR and year < datetime.MAXYEAR:
if month >= 1 and month <= 12:
d = days_in_month(year, month)
if day >= 1 and day <= d:
return True
return False
assert is not a function, it's a statement, and as such cannot be used inside the expression for print.
What you probably want to do is this:
is_valid = is_valid_date(2014, 4, 4)
print is_valid
assert is_valid
That is, first execute the print statement, then execute the assert statement (although, for a function returning only True or False there's no much benefit in printing the return value before the assertion).
If you find yourself writing code like that often, you can consider writing your own utility function:
def verbose_assert(value):
print value
assert value
verbose_assert(is_valid_date(2014, 4, 4))
Or even like this:
def assert_is_valid_date(*args):
is_valid = is_valid_date(*args)
print is_valid
assert is_valid
assert_is_valid_date(2014, 4, 4)
What do you expect assert to return and why would you want to print that value? Your usage is just not very idiomatic. Take out the print and add a description of the thing you are testing to the assert as its second argument.
assert is_valid_date(2014,4,4), "2014,4,4 is a valid date tuple"
assert is_valid_date(2012,1,3), "2012,1,3 is a valid date tuple"
Tangentially, maybe you want to refactor your function to avoid the arrow antipattern
def is_valid_date(year, month, day):
"""
Inputs:
year - an integer representing the year
month - an integer representing the month
day - an integer representing the day
Returns:
True if year-month-day is a valid date and
False otherwise
"""
if year <= datetime.MINYEAR:
return False
if year >= datetime.MAXYEAR:
return False
if month < 1:
return False
if month > 12:
return False
if day < 1:
return False
if day > days_in_month(year, month):
return False
return True
This is perhaps overdoing it, but you'll notice how adding new conditions is now easy, and adding debug prints to see exactly where the code is rejecting an input is now very straightforward.
Related
I am not using any datetime module. I created my own functions to calculate the day, month, and year. I want to calculate the refunds based on the date. If the date is invalid, it should ask the user to try again until a date is true.
year = 0
month = 0
day = 0
money_owed = 0
def if_leap_year(year):
if (year % 400 == 0): return 366
elif (year % 100 == 0): return 365
elif (year % 4 == 0): return 366
else:
return 365
#print(if_leap_year(year))
def days_in_month(month, year):
if month in {1, 3, 5, 7, 8, 10, 12}:
return 31
if month == 2:
if if_leap_year(year):
return 29
return 28
return 30
#print(days_in_month(month, year))
def is_valid_date(year, month, day):
if days_in_month(month, year)<day:#checks if the given day is possible
given the month
return False
else:
return True
def days_left_in_year(month, day, year):
daysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31]
daysLeft = (if_leap_year(year) if month < 3 else 365) -
sum(daysInMonth[:month - 1]) - day
return daysLeft
def refund_period():
month = int(input("Enter the month of the year: "))
day = int(input("Enter the day of the year: "))
year = int(input("Enter the year to determine the number of days: "))
if is_valid_date(year , month , day):
money_owed = (days_left_in_year(month, day, year) /
if_leap_year(year)) * 278
return round(money_owed, 2)
else:
print("Your date is invalid, try again.")
while is_valid_date(year, month, day):
print('you will be refunded','$', + refund_period())
break
else:
print("Your date is invalid, try again.")
I am getting:
you will be refunded $ -8.38
even though the calculation shouldn't be performed since the date is invalid
You are setting year =0 , month =0, day = 0 in first loop.
Also the while is not clear. All your functions return an int so never validate if the date is correct.
Maybe you can create a function to validate the date something like this :
def is_valid_date(year , month , day):
if month <1 or month >12: #Validate a allowed month
return False
if day <1 or day > days_in_month(month, year): # validate an allowed day for the month
return False
return True
and you can change this function :
def refund_period():
month = int(input("Enter the month of the year: "))
day = int(input("Enter the day of the year: "))
year = int(input("Enter the year to determine the number of days: "))
if is_valid_date(year , month , day):
money_owed = (days_left_in_year(month, day, year) / if_leap_year(year)) * 278
return round(money_owed, 2)
else :
print("Your date is invalid, try again.")
Just a couple of comments:
You are getting the year, month, and day using input() so you don't need to create global variables for that.
you don't need to ask if if_leap_year(year) == 365 or 366 because this function returns 365 or 366 so you can use it directly when you calculate the money_owned, as I do.
Also you can use if_leap_year(year) instead
(if_leap_year(year) if month < 3 else 365) . That functions return 366 or 365, you dont need to validate again.
And you can use list comprehension for you daysInMonth variable inside days_left_in_year function :
daysInMonth = [days_in_month(m, year) for m in range(1,13)]
Your while loop is not comparing the function value but just checking if the object exists. Instead of conditions like while days_left_in_year(month, day, year), use conditions like while days_left_in_year(month, day, year)<30 (assuming you wanted to deny refunds on orders older than 30 days.
To validate dates, add the following function under your comment #print(days_in_month(month, year)):
def is_valid_date(year, month, day)
if days_in_month(month, year)<day:#checks if the given day is possible given the month
return False
else:
return True
then your condition should look something like this:
if ((is_valid_date(year, month, day) == True) and (month<13)):
print('you will be refunded','$', + refund_period())
else:
print("Your date is invalid, try again.")
I want to check if a string matches a certain string format by using lists like this:
# Creating the lists
year = ["%02d" % x for x in range(2015, 2100)]
month = ["%02d" % x for x in range(13)]
day = ["%02d" % x for x in range(32)]
# Checking if the string is in a certain format
if "2017-7-16" == f"{year}-{month}-{day}":
print("a")
else:
print("b")
But that doesn't seem to be the way to do it, is it even possible?
I'd recommend you to use date.fromisoformat() to convert string to date object and then check if date is after 2015.
Code:
from datetime import date
fn = "2016-10-28"
assert 2015 <= date.fromisoformat(fn).year <= 2100
Upd.
If your filenames is not in ISO format (not zero-padded month) you can split string, convert each item to int and pass into a date() constructor:
assert 2015 <= date(*map(int, fn.split("-"))).year <= 2100
Upd. #2
Also you can use datetime.strptime():
from datetime import datetime
fn = "2016-10-28"
assert 2015 <= datetime.strptime(fn, "%Y-%m-%d").year <= 2100
You can convert the string to a date object and then compare to bounds also in date objects. This has the benefit that things like wrong day count for the month are caught.
import datetime
def in_bounds(datestr, mindate=datetime.date(2015, 1, 1),
maxdate=datetime.date(2100,12,31)):
print('try', datestr)
try:
mydate = datetime.datetime.strptime(datestr,"%Y-%m-%d").date()
print('got', mydate)
if not mindate <= mydate <= maxdate:
print('out of bounds')
raise ValueError()
except ValueError:
print("fail")
else:
print("pass")
test = ["2015-2-11", # good
"2015-1-01", # good
"2100-12-31", # good
"2014-12-31", # low
"2101-1-1", # high
"2020-9-31", # 30 days in september
"2020-13-2", # bad month
"These are the times", # not a date
]
for datestr in test:
in_bounds(datestr)
Here's a simple solution:
year = ["%02d" % x for x in range(2015, 2100)]
month = ["%02d" % x for x in range(13)]
day = ["%02d" % x for x in range(32)]
if any("2017-7-16" == f"{y}-{m}-{d}" for y in year for m in month for d in day):
print("a")
else:
print("b")
Output:
b
You can always use regular expressions!
import re
# Our date-format pattern
# The curved brackets indicate how many to match.
dt_pattern = r"""
\d{4} # Look for four numbers
- # Look for a dash
\d{2}- # Look for two numbers
- # Look for a dash
\d{2} # Look for two
"""
# That pattern looks like this when not verbose:
# r"\d{4}-\d{2}-\d{2}"
# Compile with re.X flag to handle verbose pattern layout from above.
p = re.compile(dt_pattern, flags = re.X)
Test incorrect format
if p.match("2017-7-16" ):
print("a")
else:
print("b")
Output:
b
Test correct format
if p.match("2017-07-16" ):
print("a")
else:
print("b")
Output:
a
I'm trying to write an if else statement in shorthand.
An if else to check a value and increment if it has not reached a value, otherwise minus a set number.
As an if else this works, but trying to do this as a shorthand version and am stuck.
Is the issue you cant use -= or += in this context? Any help to understand would be appreciated.
Have tried day-=7 if day == 7 else day+=1 - but know this is wrong as the var is already referenced on the left.
If else works fine
day = 5
if day == 7:
day-=7
else:
day+=1
Trying to write this in shorthand and I get an error on the right day+=1 as this is clearly incorrect. Looking for some advice on how to increment the day value if day != 7
day = 5
day-=7 if day == 7 else +=1
The +=1 throws an error.
Expect day to be 6
day += -7 if day == 7 else 1
The way you read this is "Add negative 7 to day if day == 7, otherwise, add 1 to day"
Dagorodir's original answer does not work, because it will subtract (day + 1) from the current value of day if day != 7. So using your example with the the starting value of 5 for day, the result of running the code from the other answer is -1.
You are right in saying that you cannot use the assignment operators -= or += in some places in the context of a "conditional expression" in Python. My understanding is that the shorthand if-else is an expression, not a statement, as seen in your initial example. You cannot make assignments on the right hand side but instead specify the return value if false, and the return value (or assignment to the variable) if true on the left.
In your second attempt, on the right hand side, you have used the assignment operator without a variable. If you wanted the evaluation of the condition to take more complex values, you could assign to variables:
day = 5
ret_false = day + 1
ret_true = day - 7
ret_true if day == 7 else ret_false
Refer to Samantha's answer for more elegant solution: the += increment assignment operator is used for evaluation of either True or False in the expression - so -7 is necessary on the left, while += 1 is assigned otherwise. I repeat the inverse of the solution in her answer to illustrate the mechanics of the syntax; using the decremental -= operator on the left enforces its use on the right -
day -= 7 if day == 7 else -1
Following the above logic, my original suggestion below uses the decremental -= assignment operator on the left; day-7 is returned if true, while day-(day+1) is returned if false. Thanks to Samantha for pointing this out.
Original
Try:
day -= 7 if day == 7 else day + 1
There are many questions regarding this: here and here, for example.
To me this is the most readable, using modulo arithmetic
day = 5
day = (day + 1) % 7
print(day)
# result 6
day = 6
day = (day + 1) % 7
print(day)
# result 0
I have two option for you:
day = day + (-7 if day == 7 else 1)
OR
day = day -7 if day == 7 else day + 1
I have defined a procedure in python which outputs a number, and would like to add the results of calling the procedure on two different inputs. However, when I try to perform arithmetic on the result of a procedure, I am presented with the error message,
TypeError: unsupported operand type(s) for -: 'NoneType' and
'NoneType'.
I tried using the int() function, but apparently this cannot operate on 'NoneType' results. How would I go about adding the two results?
The segment of the code in question is:
def leapYear(year):
if year % 4 != 0:
year = 365
else:
if year % 100 != 0:
year = 366
else:
if year % 400 != 0:
year = 365
else:
year = 366
def daysBetweenDates(year1, month1, day1, year2, month2, day2):
dpY = leapYear(year2) - leapYear(year1)
It's part of my attempted solution for a problem on Udacity (I'm relatively new to coding).
You need to explicitly return the result of the function you wish to use. Therefore, you need to add the following line to the end of your leapYear function:
return year
with a single level of indentation.
Complete example:
def leapYear(year):
if year % 4 != 0:
year = 365
else:
if year % 100 != 0:
year = 366
else:
if year % 400 != 0:
year = 365
else:
year = 366
return year
def daysBetweenDates(year1, month1, day1, year2, month2, day2):
return leapYear(year2) - leapYear(year1)
If a value is not returned explicitly, a Python function returns a None value.
That being said, you can make your life easier by using the datetime module, and in particular the datetime.timedelta objects.
You forgot to return year in leapYear() procedure, by default it will return None.
Use this:
def leapYear(year):
if year % 4 != 0:
year = 365
else:
if year % 100 != 0:
year = 366
else:
if year % 400 != 0:
year = 365
else:
year = 366
return year
def daysBetweenDates(year1, month1, day1, year2, month2, day2):
dpY = leapYear(year2) - leapYear(year1)
This question already has answers here:
calculate the difference between two datetime.date() dates in years and months
(3 answers)
Closed 9 years ago.
There are many posts about finding the difference between two dates but the values involved start and end with different formatting than those I am using eg:
a = 01/01/10 # dd/mm/yy format
b = 01/01/05 # dd/mm/yy format
So I am after the difference in years, months and days between a and b where the output required is in the format x years, x months, x days (if required) format.
I'm reading the datetime documentation and have had a crack at figuring it out (note: admittedly newbie code ahead, i was trying to piece together all the demo's there so had to make a few modifications):
from datetime import datetime as dt
# calculate duration between two dates
# later date
later_date = '01/01/10'.replace('/', '')
# reverse the order
later_date = "".join(reversed([later_datet[i:i+2] for i in range(0, len(later_date), 2)]))
# separate with commas every two numbers
later_date = ','.join(later_date[i:i+2] for i in range(0, len(later_date), 2))
# convert to datetime object
later_date = dt.strptime(later_date, "%y,%m,%d")
# earlier date
earlier_date = '01/01/05'.replace('/','')
# reverse the order
earlier_date = "".join(reversed([earlier_date[i:i+2] for i in range(0, len(earlier_date), 2)]))
# separate with commas every two numbers
earlier_date = ','.join(earlier_date[i:i+2] for i in range(0, len(earlier_date), 2))
# convert to datetime object
earlier_date = dt.strptime(earlier_date, "%y,%m,%d")
duration = later date - earlier_date
print duration
print type(duration)
is outputting:
1826 days, 0:00:00
<type 'datetime.timedelta'>
So I think i am somewhat close to getting the correct data, but now i need to convert it into the x years, x months, x days (if required) format.
Edit/Solution:
I have put some code together and am testing now, I think it is working for all date combinations but if anyone notices a bug please let me know:
"""
this code calculates the duration between two dates (a later and earlier date)
in the format dd/mm/yy and returns the duration in years, months and days with
correct formatting in regards to the plurality of the year/s, month/s, and day/s
and the punctuation required dependent on whether one or more values are returned
ie multiple values are separated by ',' whereas a singular value is terminated by '.'.
"""
# imported libraries
from datetime import datetime as dt
from dateutil import relativedelta
import sys
# initial date objects
later_date = '01/01/10'
earlier_date = '01/01/05'
# convert dates to required format
a_date = dt.strptime(later_date, '%d/%m/%y')
b_date = dt.strptime(earlier_date, '%d/%m/%y')
# get duration using dateutil
duration = relativedelta.relativedelta(a_date, b_date)
# check if number of years is not false ie != 0
if duration.years != 0:
years = duration.years
else:
years = False
# check if number of months is not false ie != 0
if duration.months != 0:
months = duration.months
else:
months = False
# check if number of days is not false ie != 0
if duration.days != 0:
days = duration.days
else:
days = False
# add values to a list
date_list = [years,months,days]
# count instances of False in the list
false_count = date_list.count(False)
# iterate over list with enumeration performing value and
# boolean checking to predicate plurality and punctuality
# requirements.
for n, _ in enumerate(date_list):
# year/s - single or plural, lone value or more
if _ != False and n == 0:
single_year = date_list[0] == 1
# if single and not lone
if single_year == True and false_count != 2:
sys.stdout.write(str(_)+' year, ')
# if single and lone
elif single_year == True and false_count == 2:
sys.stdout.write(str(_)+' year.')
# if not single and not lone
elif single_year == False and false_count != 2:
sys.stdout.write(str(_)+' years, ')
# if not single but lone
elif single_year == False and false_count == 2:
sys.stdout.write(str(_)+' years.')
# if there are no years, still provide value for possible later concatenation
if _ == False and n == 0:
datasetduration_y = ''
# month/s - single or plural, lone value or more
if _ != False and n == 1:
single_month = date_list[1] == 1
# if single and not lone
if single_month == True and false_count != 2:
sys.stdout.write(str(_)+' month, ')
# if single and lone
elif single_month == True and false_count == 2:
sys.stdout.write(str(_)+' month.')
# if not single and not lone and there are days
elif single_month == False and false_count != 2 and date_list[2] != False:
sys.stdout.write(str(_)+' months, ')
# if not single and not lone and there are no days
elif single_month == False and false_count != 2 and date_list[2] == False:
sys.stdout.write(str(_)+' months.')
# if not single but lone
elif single_month == False and false_count == 2:
sys.stdout.write(str(_)+' months.')
# if there are no months, still provide value for possible later concatenation
if _ == False and n == 1:
datasetduration_m = ''
# day/s - single or plural, lone value or more
if _ != False and n == 2:
single_day = date_list[2] == 1
# if single and not lone
if single_day == True and false_count != 2:
sys.stdout.write(str(_)+' day.')
# if single and lone
elif single_day == True and false_count == 2:
sys.stdout.write(str(_)+' day.')
# if not single and not lone
elif single_day == False and false_count != 2:
sys.stdout.write(str(_)+' days.')
# if not single but lone
elif single_day == False and false_count == 2:
sys.stdout.write(str(_)+' days.')
# if there are no days, still provide value for possible later concatenation
if _ == False and n == 2:
datasetduration_d = ''
Well, here we go. This is not a datetime-related solution. But, I think this should still get you what you are you asking...
Number of years: 1826/365. See how many whole years have passed.
Number of months: (1826%365)/30. Of the remaining days, how many months. (Here, we are ignoring the specific month lengths (Jan=31, Feb=28, etc) and just using 30days/month).
Number of days: (1826%365)%30. Of the remaining days, how many days.
This should do the trick
>>> from datetime import datetime as dt
>>> a = '01/01/2010'
>>> b = '01/01/2005'
>>> a_date = dt.strptime(a, '%d/%m/%Y') # Use capital Y for year with century
>>> b_date = dt.strptime(b, '%d/%m/%Y')
>>> td = a_date - b_date
>>> td
datetime.timedelta(1826)
You now have a timedelta object that contains the number of days between a and b.
It's not quite clear what it means to express this in years, months and days, since none of "year" or "month" or "day" are a standardized unit of time - years and months have a variable number of days, and days have a variable number of seconds. In this case I think it is best to leave the difference expressed in days, which you can get by doing
>>> td.days
1826
If you really want to express the number of days, years and months, then you could operate as follows
>>> nyears = a_date.year - b_date.year
>>> nmonths = a_date.month - b_data.month
>>> ndays = a_date.day - b_data.day
Which gives you the difference in years, days and months, subject to the proviso that they can be negative. There are a few options for dealing with that. One way is to allow negative differences in number of days, months and years (which makes perfect mathematical sense). Another option is to adjust them:
>>> if ndays < 0:
ndays = ndays + 30
nmonths = nmonths - 1
>>> if nmonths < 0:
nmonths = nmonths + 12
nyears = nyears - 1
A trivial answer:
years, remainder = divmod(duration.days, 365)
months, days = divmod(remainder, 30)
print "{} years, {} months {} days".format(years,months,days)