Python and check if current datetime is in specific range - python

I am trying to do something similar to this, but i want to specify the start date and end date by actual weekday names and times. For example, I want to check if the current datetime (datetime.datetime.now()) is in between Tuesday at 4:30pm and Thursday at 11:45am. This would update weekly so it has to be by Tuesday/Thursday mentality.
I have thought about how to do the weekdays (but i don't know how to wrap the time part into it):
TimeNow = datetime.datetime.now()
if TimeNow.weekday() >= 1 and TimeNow.weekday() <= 3:
#runcodehere
Any thoughts on how i would do this?

Neatest way is to use the amount of minutes elapsed in a week:
def mins_in_week(day, hour, minute):
return day * 24 * 60 + hour * 60 + minute
if (mins_in_week(1, 16, 30) <
mins_in_week(TimeNow.weekday(), TimeNow.hour, TimeNow.minute) <
mins_in_week(3, 11, 45)):
....

It's not very neat but something like this should work:
TimeNow = datetime.datetime.now()
if (TimeNow.weekday() == 1 and ((TimeNow.hour() == 4 and TimeNow.minute >= 30) or TimeNow.hour > 4)) or (TimeNow.weekday() == 2) or (TimeNow.weekday() == 3 and (TimeNow.hour() < 11 or (TimeNow.hour() == 11 and TimeNow.minute <= 45)):
#runcodehere

You can use a combination of and and or, and have different conditions for each day:
import datetime
TimeNow = datetime.datetime.now()
day_now = TimeNow.weekday()
time_now = TimeNow.hour*60 + TimeNow.minute
if (day_now == 1 and time_now > 990) or (day_now == 2) or (day_now == 3 and time_now < 705):
# do something

Related

How to convert a date to a number of days from 0001-01-01?

How can I convert any date to just number of days? This is what I tried:
import datetime
import calendar
def leap_day_counter(yr):
leap_days = 0
# since 1582 11 days are missing
if yr >= 1582:
leap_days += 11
for specific_year in range(1, yr):
if calendar.isleap(specific_year):
leap_days += 1
return leap_days
def month_to_day(yr, mth):
all_days = 0
for specific_month in range(1, mth+1):
days_in_month = calendar.monthrange(yr, specific_month)
all_days += days_in_month[1]
return all_days
date = datetime.datetime.now()
days_passed = ((date.year * 365) + leap_day_counter(date.year)) + month_to_day(date.year, date.month) + date.day
print(days_passed)
I got 737 158 days but according to https://www.timeanddate.com/date/durationresult.html I should have 736 755 days. Do I miss something? Is there easier way to do this?
This helps
from datetime import date
d0 = date(2000, 1, 01)
d1 = date.today()
delta = d1 - d0
print delta.days
Are the amount of days in a year correct for you?
01/01/0001 - 01/01/2018 has 736,696, you say there is 737,060. This is roughly 1 year too many.
(date.year - 1) * 365
After fixing the above, we should check if 01/01/0001 - 01/02/2018 works.
The website says 736,727, where you say 736,754. Which is about the entire month of February too many.
for specific_month in range(1, mth)
You have one too many leap years.
for specific_year in range(1, yr)
You can also simplify this code to:
def leap_day_counter(y):
y -= 1
return y//4 - y//100 + y//400
This is now the same as datetime.datetime.now().toordinal().
The number of days between two dates can be calculated as below: For more see here. Hope this may help
>>>enddate = "2018/03/12" +" 23:59"
>>>enddate = datetime.strptime(enddate, "%Y/%m/%d %H:%M")
>>>(enddate-datetime.now()).days
12
Update:edit
>>>import datetime
>>>checkdate = datetime.datetime.strptime("0001-01-01", "%Y-%m-%d")
>>>days = (datetime.datetime.now()-checkdate).days
>>>days
736757
2 days difference because start days and end date are excluded.

How many "premium" hours between datetimes?

This is part of a bigger problem we're facing but the problem at the moment is splitting time between two datetimes into two rates based on when those hours are in the day. It's quite arbitrary but we treat 7am-7pm as normal hours and the opposite 12 hours as premium.
So for any given pair of datetimes, we need to grade these down so that we know how many normal hours, or how many premium hours there were in that period. A couple of examples:
If we took the next 24 hours, I'd expect an exact split of 12 hours.
> start = datetime.datetime.now()
> end = start + datetime.timedelta(1)
> split_hours(start, end)
(datetime.timedelta(0, 43200), datetime.timedelta(0, 43200))
If we took the next 12 hours, at 20:26, I'd expect 1h26 normal and 10h34m premium rate:
> start = datetime.datetime(2017, 11, 6, 20, 26, 0)
> end = start + datetime.timedelta(hours=12)
> split_hours(start, end)
(datetime.timedelta(0, 5160), datetime.timedelta(0, 38040))
"How do I do that?" is my question. Sorry. I've been thinking through this most of the day but only ever got as far as the following napkin algorithm:
Split range into distinct-date datetime ranges (how?!) and for each:
Count hours before 7am and after 7pm as premium
Count hours between 7am and 7pm
Total them up.
But even there I don't know how to split things up.
There is also a natural extension —that I'll almost certainly have to implement at some point— that also grades weekend hours as premium too. If I could split time (as in my napkin algorithm) it would be easy to tack on but I still don't like how clumsy that "code" is). If your answer covers that too, you can have my firstborn. Well, no, you can have a bounty or something.
I'm doing this in Python without any real library limits (if eg Pandas Just Does™ this) but if you want to submit a raw C or Pseudo code answer, I'm sure I'll be able to read it.
We could:
generate a range of datetime between start and end
loop that range and calculate normal seconds (the length - normal = premium)
Here is the code:
import datetime
def split_hours(start, end):
# Total seconds
length = int((end-start).total_seconds())
# Generator with datetime objects
s = (start + datetime.timedelta(seconds=i) for i in range(length))
# Calculate normal and premium
# normal when hour > 7 AM, smaller than 7 PM and weekday not sat,sun
normal = sum(7 <= i.hour < 19 and i.weekday() not in [5,6] for i in s)
premium = length - normal
d = dict(normal=normal,
premium=premium,
total=dict(h=length/3600,m=length/60,s=length))
return d
And now we can do some tests:
start = datetime.datetime.now()
end1 = start + datetime.timedelta(hours=12)
end2 = start + datetime.timedelta(days=1)
end3 = start + datetime.timedelta(days=24)
print(split_hours(start,end1))
print(split_hours(start,end2))
print(split_hours(start,end3))
Returns:
# 12 hours
{'total': {'h': 12.0, 's': 43200, 'm': 720.0}, 'premium': 26131, 'normal': 17069}
# 1 days / 24 hours
{'total': {'h': 24.0, 's': 86400, 'm': 1440.0}, 'premium': 43200, 'normal': 43200}
# 7 days
{'total': {'h': 168.0, 's': 604800, 'm': 10080.0}, 'premium': 388800, 'normal': 216000}
That would be my approach:
from datetime import datetime, timedelta
def is_premium_time_period(start_time, end_time):
start_time = datetime.strptime(start_time, "%d-%m-%Y %H:%M")
end_time = datetime.strptime(end_time, "%d-%m-%Y %H:%M")
seconds = (end_time - start_time).total_seconds()
minutes = int(seconds / 60)
premium_minutes = 0
regular_minutes = 0
for minute in range(minutes):
premium_start = datetime.strptime("19:00 {}".format(start_time.date()), "%H:%M %Y-%m-%d")
premium_end = premium_start + timedelta(hours=12)
previous_start = premium_start - timedelta(hours=24)
previous_end = previous_start + timedelta(hours=12)
if premium_start <= start_time < premium_end or previous_start <= start_time < previous_end:
premium_minutes += 1
else:
regular_minutes += 1
start_time += timedelta(minutes=1)
_premium_hours = premium_minutes / 60
_regular_hours = regular_minutes / 60
return _premium_hours, _regular_hours
datetime_01 = "06-11-2017 14:17"
datetime_02 = "06-11-2017 19:20"
datetime_03 = "05-11-2017 02:39"
datetime_04 = "11-11-2017 08:39"
print(is_premium_time_period(datetime_01, datetime_02))
print(is_premium_time_period(datetime_03, datetime_04))
EDIT: I'm sorry, I forgot to post what it returns:
It returns:
(0.3333333333333333, 4.716666666666667)
(76.35, 73.65)
Meaning (premium_hours, regular_hours)

Count number of sundays in current month

How can I get the numberof Sundays of the current month in Python?
Anyone got any idea about this?
This gives you the number of sundays in a current month as you wanted:
import calendar
from datetime import datetime
In [367]: len([1 for i in calendar.monthcalendar(datetime.now().year,
datetime.now().month) if i[6] != 0])
Out[367]: 4
I happened to need a solution for this, but was unsatisfactory with the solutions here, so I came up with my own:
import calendar
year = 2016
month = 3
day_to_count = calendar.SUNDAY
matrix = calendar.monthcalendar(year,month)
num_days = sum(1 for x in matrix if x[day_to_count] != 0)
I'd do it like this:
import datetime
today = datetime.date.today()
day = datetime.date(today.year, today.month, 1)
single_day = datetime.timedelta(days=1)
sundays = 0
while day.month == today.month:
if day.weekday() == 6:
sundays += 1
day += single_day
print 'Sundays:', sundays
My take: (saves having to worry about being in the right month etc...)
from calendar import weekday, monthrange, SUNDAY
y, m = 2012, 10
days = [weekday(y, m, d+1) for d in range(*monthrange(y, m))]
print days.count(SUNDAY)
Or, as #mgilson has pointed out, you can do away with the list-comp, and wrap it all up as a generator:
sum(1 for d in range(*monthrange(y,m)) if weekday(y,m,d+1)==SUNDAY)
And I suppose, you could throw in a:
from collections import Counter
days = Counter(weekday(y, m, d + 1) for d in range(*monthrange(y, m)))
print days[SUNDAY]
Another example using calendar and datetime:
import datetime
import calendar
today = datetime.date.today()
m = today.month
y = today.year
sum(1 for week in calendar.monthcalendar(y,m) if week[-1])
Perhaps a slightly faster way to do it would be:
first_day,month_len = monthrange(y,m)
date_of_first_sun = 1+6-first_day
print sum(1 for x in range(date_of_first_sun,month_len+1,7))
You can do this using ISO week numbers:
from datetime import date
bom = date.today().replace(day=1) # first day of current month
eom = (date(bom.year, 12, 31) if bom.month == 12 else
(bom.replace(month=bom.month + 1) - 1)) # last day of current month
_, b_week, _ = bom.isocalendar()
_, e_week, e_weekday = eom.isocalendar()
num_sundays = (e_week - b_week) + (1 if e_weekday == 7 else 0)
In general for a particular day of the week (1 = Monday, 7 = Sunday) the calculation is:
num_days = ((e_week - b_week) +
(-1 if b_weekday > day else 0) +
( 1 if e_weekday >= day else 0))
import calendar
MONTH = 10
sundays = 0
cal = calendar.Calendar()
for day in cal.itermonthdates(2012, MONTH):
if day.weekday() == 6 and day.month == MONTH:
sundays += 1
PAY ATTENTION:
Here are the Calendar.itermonthdates's docs:
Return an iterator for one month. The iterator will yield datetime.date
values and will always iterate through complete weeks, so it will yield
dates outside the specified month.
That's why day.month == MONTH is needed
If you want the weekdays to be in range 0-6, use day.weekday(),
if you want them to be in range 1-7, use day.isoweekday()
My solution.
The following was inspired by #Lingson's answer, but I think it does lesser loops.
import calendar
def get_number_of_weekdays(year: int, month: int) -> list:
main_calendar = calendar.monthcalendar(year, month)
number_of_weeks = len(main_calendar)
number_of_weekdays = []
for i in range(7):
number_of_weekday = number_of_weeks
if main_calendar[0][i] == 0:
number_of_weekday -= 1
if main_calendar[-1][i] == 0:
number_of_weekday -= 1
number_of_weekdays.append(number_of_weekday)
return sum(number_of_weekdays) # In my application I needed the number of each weekday, so you could return just the list to do that.

Add n business days to a given date ignoring holidays and weekends in python

I'm trying to add n (integer) working days to a given date, the date addition has to avoid the holidays and weekends (it's not included in the working days)
Skipping weekends would be pretty easy doing something like this:
import datetime
def date_by_adding_business_days(from_date, add_days):
business_days_to_add = add_days
current_date = from_date
while business_days_to_add > 0:
current_date += datetime.timedelta(days=1)
weekday = current_date.weekday()
if weekday >= 5: # sunday = 6
continue
business_days_to_add -= 1
return current_date
#demo:
print '10 business days from today:'
print date_by_adding_business_days(datetime.date.today(), 10)
The problem with holidays is that they vary a lot by country or even by region, religion, etc. You would need a list/set of holidays for your use case and then skip them in a similar way. A starting point may be the calendar feed that Apple publishes for iCal (in the ics format), the one for the US would be http://files.apple.com/calendars/US32Holidays.ics
You could use the icalendar module to parse this.
If you don't mind using a 3rd party library then dateutil is handy
from dateutil.rrule import *
print "In 4 business days, it's", rrule(DAILY, byweekday=(MO,TU,WE,TH,FR))[4]
You can also look at rruleset and using .exdate() to provide the holidays to skip those in the calculation, and optionally there's a cache option to avoid re-calculating that might be worth looking in to.
There is no real shortcut to do this. Try this approach:
Create a class which has a method skip(self, d) which returns True for dates that should be skipped.
Create a dictionary in the class which contains all holidays as date objects. Don't use datetime or similar because the fractions of a day will kill you.
Return True for any date that is in the dictionary or d.weekday() >= 5
To add N days, use this method:
def advance(d, days):
delta = datetime.timedelta(1)
for x in range(days):
d = d + delta
while holidayHelper.skip(d):
d = d + delta
return d
Thanks based on omz code i made some little changes ...it maybe helpful for other users:
import datetime
def date_by_adding_business_days(from_date, add_days,holidays):
business_days_to_add = add_days
current_date = from_date
while business_days_to_add > 0:
current_date += datetime.timedelta(days=1)
weekday = current_date.weekday()
if weekday >= 5: # sunday = 6
continue
if current_date in holidays:
continue
business_days_to_add -= 1
return current_date
#demo:
Holidays =[datetime.datetime(2012,10,3),datetime.datetime(2012,10,4)]
print date_by_adding_business_days(datetime.datetime(2012,10,2), 10,Holidays)
I wanted a solution that wasn't O(N) and it looked like a fun bit of code golf. Here's what I banged out in case anyone's interested. Works for positive and negative numbers. Let me know if I missed anything.
def add_business_days(d, business_days_to_add):
num_whole_weeks = business_days_to_add / 5
extra_days = num_whole_weeks * 2
first_weekday = d.weekday()
remainder_days = business_days_to_add % 5
natural_day = first_weekday + remainder_days
if natural_day > 4:
if first_weekday == 5:
extra_days += 1
elif first_weekday != 6:
extra_days += 2
return d + timedelta(business_days_to_add + extra_days)
I know it does not handle holidays, but I found this solution more helpful because it is constant in time. It consists of counting the number of whole weeks, adding holidays is a little more complex. I hope it can help somebody :)
def add_days(days):
today = datetime.date.today()
weekday = today.weekday() + ceil(days)
complete_weeks = weekday // 7
added_days = weekday + complete_weeks * 2
return today + datetime.timedelta(days=added_days)
This will take some work since there isn't any defined construct for holidays in any library (by my knowledge at least). You will need to create your own enumeration of those.
Checking for weekend days is done easily by calling .weekday() < 6 on your datetime object.
Refactoring omz code, and using holidays package, this is what I use to add business days taking into account the country's holidays
import datetime
import holidays
def today_is_holiday(date):
isHoliday = date.date() in [key for key in holidays.EN(years = date.year).keys()]
isWeekend = date.weekday() >= 5
return isWeekend or isHoliday
def date_by_adding_business_days(from_date, add_days):
business_days_to_add = add_days
current_date = from_date
while business_days_to_add > 0:
current_date += datetime.timedelta(days=1)
if today_is_holiday(current_date):
continue
business_days_to_add -= 1
return current_date
Hope this helps. It's not O(N) but O(holidays). Also, holidays only works when the offset is positive.
def add_working_days(start, working_days, holidays=()):
"""
Add working_days to start start date , skipping weekends and holidays.
:param start: the date to start from
:type start: datetime.datetime|datetime.date
:param working_days: offset in working days you want to add (can be negative)
:type working_days: int
:param holidays: iterator of datetime.datetime of datetime.date instances
:type holidays: iter(datetime.date|datetime.datetime)
:return: the new date wroking_days date from now
:rtype: datetime.datetime
:raise:
ValueError if working_days < 0 and holidays
"""
assert isinstance(start, (datetime.date, datetime.datetime)), 'start should be a datetime instance'
assert isinstance(working_days, int)
if working_days < 0 and holidays:
raise ValueError('Holidays and a negative offset is not implemented. ')
if working_days == 0:
return start
# first just add the days
new_date = start + datetime.timedelta(working_days)
# now compensate for the weekends.
# the days is 2 times plus the amount of weeks are included in the offset added to the day of the week
# from the start. This compensates for adding 1 to a friday because 4+1 // 5 = 1
new_date += datetime.timedelta(2 * ((working_days + start.weekday()) // 5))
# now compensate for the holidays
# process only the relevant dates so order the list and abort the handling when the holiday is no longer
# relevant. Check each holiday not being in a weekend, otherwise we don't mind because we skip them anyway
# next, if a holiday is found, just add 1 to the date, using the add_working_days function to compensate for
# weekends. Don't pass the holiday to avoid recursion more then 1 call deep.
for hday in sorted(holidays):
if hday < start:
# ignore holidays before start, we don't care
continue
if hday.weekday() > 4:
# skip holidays in weekends
continue
if hday <= new_date:
# only work with holidays up to and including the current new_date.
# increment using recursion to compensate for weekends
new_date = add_working_days(new_date, 1)
else:
break
return new_date
If someone needs to add/substract days, extending #omz's answer:
def add_business_days(from_date, ndays):
business_days_to_add = abs(ndays)
current_date = from_date
sign = ndays/abs(ndays)
while business_days_to_add > 0:
current_date += datetime.timedelta(sign * 1)
weekday = current_date.weekday()
if weekday >= 5: # sunday = 6
continue
business_days_to_add -= 1
return current_date
similar to #omz solution but recursively:
def add_days_skipping_weekends(start_date, days):
if not days:
return start_date
start_date += timedelta(days=1)
if start_date.weekday() < 5:
days -= 1
return add_days_skipping_weekends(start_date, days)
If you are interested in using NumPy, then you can follow the solution below:
import numpy as np
from datetime import datetime, timedelta
def get_future_date_excluding_weekends(date,no_of_days):
"""This methods return future date by adding given number of days excluding
weekends"""
future_date = date + timedelta(no_of_days)
no_of_busy_days = int(np.busday_count(date.date(),future_date.date()))
if no_of_busy_days != no_of_days:
extend_future_date_by = no_of_days - no_of_busy_days
future_date = future_date + timedelta(extend_future_date_by)
return future_date
This is the best solution because it has O(1) complexity (no loop) and no 3-rd party, but it does not take into account the holidays:
def add_working_days_to_date(self, start_date, days_to_add):
from datetime import timedelta
start_weekday = start_date.weekday()
# first week
total_days = start_weekday + days_to_add
if total_days < 5:
return start_date + timedelta(days=total_days)
else:
# first week
total_days = 7 - start_weekday
days_to_add -= 5 - start_weekday
# middle whole weeks
whole_weeks = days_to_add // 5
remaining_days = days_to_add % 5
total_days += whole_weeks * 7
days_to_add -= whole_weeks * 5
# last week
total_days += remaining_days
return start_date + timedelta(days=total_days)
Even though this does not fully solves your problem, I wanted to let it here because the solutions found on the internet for adding working days to dates, all of them have O(n) complexity.
Keep in mind that, if you want to add 500 days to a date, you will go through a loop and make the same set of computations 500 times. The above approach operates in the same amount of time, no matter how many days you have.
This was heavily tested.
Use numpy (you can skip holidays too):
np.busday_offset(
np.datetime64('2022-12-08'),
offsets=range(12),
roll='following',
weekmask="1111100",
holidays=[])
Result:
array(['2022-12-08', '2022-12-09', '2022-12-12', '2022-12-13',
'2022-12-14', '2022-12-15', '2022-12-16', '2022-12-19',
'2022-12-20', '2022-12-21', '2022-12-22', '2022-12-23'],
dtype='datetime64[D]')
I am using following code to handle business date delta. For holidays, you need to create your own list to skip.
today = datetime.now()
t_1 = today - BDay(1)
t_5 = today - BDay(5)
t_1_str = datetime.strftime(t_1,"%Y%m%d")

Find time until a date in Python

What's the best way to find the time until a date. I would like to know the years, months, days and hours.
I was hoping somebody had a nice function. I want to do something like: This comment was posted 2month and three days ago or this comment was posted 1year 5months ago.
datetime module, datetime and timedelta objects, it will give you days and seconds.
In [5]: datetime.datetime(2009, 10, 19) - datetime.datetime.now()
Out[5]: datetime.timedelta(2, 5274, 16000)
In [6]: td = datetime.datetime(2009, 10, 19) - datetime.datetime.now()
In [7]: td.days
Out[7]: 2
In [8]: td.seconds
Out[8]: 5262
You should use dateutil.relativedelta.
from dateutil.relativedelta import relativedelta
import datetime
today = datetime.date.today()
rd = relativedelta(today, datetime.date(2001,1,1))
print "comment created %(years)d years, %(months)d months, %(days)d days ago" % rd.__dict__
Let's asume you have the future datetime in a variable named eta:
(eta - datetime.datetime.now()).total_seconds()
Datetime difference results in a timedelta object, which happens to implement a method named total_seconds. That's it :)
I was looking for something more like this ... which took some hard work to find.
import datetime
SECOND = 1
MINUTE = 60 * SECOND
HOUR = 60 * MINUTE
DAY = 24 * HOUR
MONTH = 30 * DAY
def get_relative_time(dt):
now = datetime.datetime.now()
delta_time = dt - now
delta = delta_time.days * DAY + delta_time.seconds
minutes = delta / MINUTE
hours = delta / HOUR
days = delta / DAY
if delta < 0:
return "already happened"
if delta < 1 * MINUTE:
if delta == 1:
return "one second to go"
else:
return str(delta) + " seconds to go"
if delta < 2 * MINUTE:
return "a minute ago"
if delta < 45 * MINUTE:
return str(minutes) + " minutes to go"
if delta < 90 * MINUTE:
return "an hour ago"
if delta < 24 * HOUR:
return str(hours) + " hours to go"
if delta < 48 * HOUR:
return "yesterday"
if delta < 30 * DAY:
return str(days) + " days to go"
if delta < 12 * MONTH:
months = delta / MONTH
if months <= 1:
return "one month to go"
else:
return str(months) + " months to go"
else:
years = days / 365.0
if years <= 1:
return "one year to go"
else:
return str(years) + " years to go"
may be you want something like this:
import datetime
today = datetime.date.today()
futdate = datetime.date(2016, 8, 10)
now = datetime.datetime.now()
mnight = now.replace(hour=0, minute=0, second=0, microsecond=0)
seconds = (mnight - now).seconds
days = (futdate - today).days
hms = str(datetime.timedelta(seconds=seconds))
print ("%d days %s" % (days, hms))

Categories