Python time comparison at midnight - python

I have to save the time in AM PM format.
But i am having trouble in deciding how to enter midnight time.
Suppose the time is 9PM to 6AM next morning. I have to divide it into day to day basis . Like this
t1 = datetime.datetime.strptime('09:00PM', '%I:%M%p').time()
t2 = datetime.datetime.strptime('12:00AM', '%I:%M%p').time()
t3 = datetime.datetime.strptime('06:00AM', '%I:%M%p').time()
Now i want to know whether the t2 should be
12:00 AM or 11.59PM
If i do 12:00AM then i can't compare if 9pm > 12am but 11.59 looks odd or may be it is right way

You should always use 00:00 (or 12:00 AM) to represent midnight.
Using 23:59 (or 11:59 PM) is problematic for a couple of reasons:
Precision matters in the comparison. Is 23:59:01 not before midnight? What about 23:59:59.9999?
Duration calculation will be thrown off by whatever precision you chose. Consider that 10:00 pm to midnight is 2 hours, not 1 hour and 59 minutes.
To avoid these problems, you should always treat time intervals as half-open intervals. That is, the range has an inclusive start, and an exclusive end. In interval notation: [start, end)
Now with regard to crossing the midnight boundary:
When you are comparing times that are associated with a date, you can just compare directly:
[2015-01-01T21:00, 2015-01-02T06:00) = 9 hours
2015-01-01T21:00 < 2015-01-02T06:00
When you do not have a date, you can determine duration, but you cannot determine order!
[21:00, 06:00) = 9 hours
21:00 < 06:00 OR 21:00 > 06:00
The best you can do is determine whether a time is between the points covered by the range.
Both 23:00 and 01:00 are in the range [21:00, 06:00)
21:00 is also in that range, but 06:00 is NOT.
Think about a clock. It's modeled as a circle, not as a straight line.
To calculate duration of a time-only interval that can cross midnight, use the following pseudocode:
if (start <= end)
duration = end - start
else
duration = end - start + 24_hours
Or more simply:
duration = (end - start + 24_hours) % 24_hours
To determine whether a time-only value falls within a time-only interval that can cross midnight, use this pseudocode:
if (start <= end)
is_between = start <= value AND end > value
else
is_between = start <= value OR end > value
Note that in the above pseudocode, I am referring to the magnitude of the values, as compared numerically - not the logical time values which, as said earlier, cannot be compared independently without a reference date.
Also, much of this is covered in my Date and Time Fundamentals course on Pluralsight (towards the very end, in "Working With Ranges").

How about making t1 = 09:00PM, t2 = 11.59PM, t3 = 12:00AM and t4 = 06:00AM. Then you have definite time ranges per day. Of course, adding the date would make time differences evident as well.

Related

Python datetime only returning negative numbers?

I have the following code:
commit_date = get_last_commit_date() # returns datetime obj: 2022-08-25 13:32:12
pr_date = datetime.fromisoformat(ISO_CODE) # returns 2022-08-24 19:15:07
Notice how commit_date is Aug. 25 and pr_date is Aug. 24. I want to find the difference in days which should be 1 day.
Now doing print((pr_date - commit_date).days) will give -1 days. So naturally I swap them and do print((commit_date - pr_date).days) but it reports 0 days? How can it be 0 days? Why isn't it reporting 1 day time difference?
This is a rounding error; since the dates are not at the exact same time, the difference between the two days can end up being on a different day depending on the order you subtract them.
i.e
2022-08-24 19:15:07 - 2022-08-25 13:32:12 = {0}-{0}-{-1} {5}:{42}{55}
and
2022-08-25 13:32:12 - 2022-08-24 19:15:07 = {0}-{0}-{0} {18}:{17}{05}
The difference of hours between 13-19 means the hour is -6. Negative hours in a day means it is a day before, so the difference is 0 days and 18 hours instead of 1 for the second calculation.
If you're trying to find the difference in days as opposed to total difference in date+time, you wanna subtract the "day" attributes of the individual datetimes as opposed to the entire date+time.
If you do
print((pr_date.day - commit_date.day))
>> -1
print((commit_date.day - pr_date.day))
>> 1
You get the right difference between days. If you use absolute value i.e
print(abs(pr_date.day - commit_date.day))
>> 1
The order of the days doesn't matter, and you can find the difference like that.

How do I calculate the actual number of hours in a day according to a local calendar in Python?

I'd like to write a small function that can calculate the number of hours in any given day, for any time zone. The obvious approach was to count the hours between the first instant of the day and the next day. Unfortunately, whatever day I choose, this approach always says that a day is 24 hours long.
In the UK, the clocks are advanced at 1am in March by 1 hour. That means the 28th March 2021 should have 23 hours. The time-range from 1am to 2am will not have existed that day.
Likewise, on the 31st October 2021 the clock is pushed back at 1am, so that day will have 25 hours. The time-range midnight to 1am will have occurred twice in that day.
import datetime
import pytz
# When do the clocks change?
# https://www.gov.uk/when-do-the-clocks-change
day0=datetime.datetime(2021,3,28, tzinfo=pytz.timezone("Europe/London"))
day1=datetime.datetime(2021,3,29, tzinfo=pytz.timezone("Europe/London"))
delta = day1-day0
print(delta)
hours = delta / datetime.timedelta(hours=1)
print(hours)
This script gives output that seems incorrect:
1 day, 0:00:00
24.0
Is there a simpler way to get the number of hours in a particular day, that gives the right answer?
Ideally this should be able to account for daylight savings, leap-years and even leap seconds.
Part of the issue is "using the tzinfo argument of the standard datetime constructors ‘’does not work’’ with pytz for many timezones."
So we can work around that by using timezone.localize() with a local naive time (no tz):
London = pytz.timezone("Europe/London")
day0 = London.localize(datetime.datetime(2021,3,28))
day1 = London.localize(datetime.datetime(2021,3,29))
(day1 - day0).total_seconds() / 60 / 60 # in hours
# 23.0
And for 31st October:
day0 = London.localize(datetime.datetime(2021, 10, 31))
day1 = London.localize(datetime.datetime(2021, 11, 1))
(day1 - day0).total_seconds() / 60 / 60
# 25.0

How to add and subtract times in python [duplicate]

This question already has answers here:
How to compare times of the day?
(8 answers)
python time subtraction
(1 answer)
Closed 6 years ago.
I want to write a simple timesheet script. My input file looks like
9:00 17:00
10:45 12:35
11:00 15:00
I would like to read it in, compute the number of hours worked per day and then sum those hours up. When the day started before 12:00 and ended after 13:00 I would like to subtract half an hour too for lunch break.
My attempt so far is:
import sys
from datetime import datetime
gap = datetime.strptime('00:30','%H:%M')
hours = []
for line in sys.stdin:
(start_time,end_time) = line.split()
start_time = datetime.strptime(start_time, '%H:%M')
end_time = datetime.strptime(end_time, '%H:%M')
#if start_time before 12:00 and end_time after 13:00 then subtract gap
hours.append(end_time-start_time)
print sum(hours)
I don't know how to get the if statement line to work and summing the hours doesn't seem to work either as you can't sum datetime.timedelta types.
Thanks to the link in the comments, replacing sum(hours) with reduce(operator.add, hours) works.
The remaining part is how to test if start_time is before 12:00 and end_time is after 13:00 and if so to reduce the timedelta by half an hour.
You are using incorrect code (and syntax) in your if statement.
if start_time < datetime.strptime('12:00', '%H:%M') and end_time > datetime.strptime('13:00', '%H:%M'):
delta_hours = end_time.hour - start_time.hour)
delta_minutes = end_time.minutes - start_time.minutes)
# Do whatever your want with it now.
# Substraction of the break is not implemented in this example, it depends on how you want to save it.
Time delta might be worth looking into as well, it can use basic operations like
a = timedelta(...)
b = timedelta(...)
c = b - a - gap

Python: Calculate hours within a specific time period

I'm working on a script that calculates my salary, for each of my work days, since we don't get the plan send out electronic.
We do earn extra withing some time periods.
every hour i earn 61.86 DKK, but at within some time periods i earn extra money, as seen below.
(For simplicity i have calculated the time in 24h cycle, since that what i am used to)
Weekdays (18:00 - 06:00) 12.20 DKK
Saturday (15:00 - 24:00) 21.65 DKK
Sunday (whole day) 24.50 DKK
So fare i have worked out, how to calculate the extra money and the hourly rate fine. Although my problem is, if i have a work guard that starts 20:00 and ends next day 4:00 then it will give me and error. I have an IF statement that activates if the hour is above 18(which is when i get extra in the weekdays) then i subtract the hour count with 18 to get, how many hours that's i need to earn extra.
if d2.isoweekday() <= 5: #If its a weekday
if d2.hour >= 18:
extra += ((d2.hour - 18) * weekdaySalary) + ((d2.minute / 60) * weekdaySalary)
How do i detect, exact how many hours that's between a specific period?
like if i have to dates
26-12-2014 17:00
27-12-2014 08:00
i need a way to see how many of those work hours is within the time period(18:00-06:00).
how can this be done?
it's like having 2 diffrent date ranges.
1st - for when i get extra.
2rd - for when i actually work.
26-12-2014 17:00
18:00 - extra money period start here
|
|how do i get the time between these to points?
|
06:00 - extra money period ends here
27-12-2014 08:00
it could also be like this
26-12-2014 17:00
18:00 - extra money period start here
|
|how do i get the time between these to points?
|
27-12-2014 04:00
06:00 - extra money period ends here
Every answer is highly appreciated, spent so much time trying to figure out with no really result.
Based on the two ranges you provided, presuming they are when your shift starts and ends, the following will calculate pay from start of shift to end, increasing by basic rate or basic rate plus extra pay based on the time of day:
def calculate_ot(t1,t2):
t1 = datetime.strptime(t1, "%d-%m-%Y %H:%M")
t2 = datetime.strptime(t2, "%d-%m-%Y %H:%M")
days = ((t2 - t1).days * 86400)
hours, rem = divmod((t2 - t1).seconds + days, 3600)
start_ot = datetime.strptime("18:00 {}".format(t1.date()), "%H:%M %Y-%m-%d")
end_ot = datetime.strptime("06:00 {}".format(t2.date()), "%H:%M %Y-%m-%d")
total_pay = 0
for x in range(hours): # loop in total hour time difference
# if we are within the range of extras pay increase by normal rate plus ot
if start_ot <= t1 < end_ot and t1 < t2:
total_pay += 62 # or 62.20 you have conflicting examples
else:
# else just add basic pay rate
total_pay == 50
t1 += timedelta(hours=1) # keep adding another hour
return total_pay

Given a date range how to calculate the number of weekends partially or wholly within that range?

Given a date range how to calculate the number of weekends partially or wholly within that range?
(A few definitions as requested:
take 'weekend' to mean Saturday and Sunday.
The date range is inclusive i.e. the end date is part of the range
'wholly or partially' means that any part of the weekend falling within the date range means the whole weekend is counted.)
To simplify I imagine you only actually need to know the duration and what day of the week the initial day is...
I darn well now it's going to involve doing integer division by 7 and some logic to add 1 depending on the remainder but I can't quite work out what...
extra points for answers in Python ;-)
Edit
Here's my final code.
Weekends are Friday and Saturday (as we are counting nights stayed) and days are 0-indexed starting from Monday. I used onebyone's algorithm and Tom's code layout. Thanks a lot folks.
def calc_weekends(start_day, duration):
days_until_weekend = [5, 4, 3, 2, 1, 1, 6]
adjusted_duration = duration - days_until_weekend[start_day]
if adjusted_duration < 0:
weekends = 0
else:
weekends = (adjusted_duration/7)+1
if start_day == 5 and duration % 7 == 0: #Saturday to Saturday is an exception
weekends += 1
return weekends
if __name__ == "__main__":
days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
for start_day in range(0,7):
for duration in range(1,16):
print "%s to %s (%s days): %s weekends" % (days[start_day], days[(start_day+duration) % 7], duration, calc_weekends(start_day, duration))
print
General approach for this kind of thing:
For each day of the week, figure out how many days are required before a period starting on that day "contains a weekend". For instance, if "contains a weekend" means "contains both the Saturday and the Sunday", then we have the following table:
Sunday: 8
Monday: 7
Tuesday: 6
Wednesday: 5
Thursday: 4
Friday: 3
Saturday: 2
For "partially or wholly", we have:
Sunday: 1
Monday: 6
Tuesday: 5
Wednesday: 4
Thursday: 3
Friday: 2
Saturday: 1
Obviously this doesn't have to be coded as a table, now that it's obvious what it looks like.
Then, given the day-of-week of the start of your period, subtract[*] the magic value from the length of the period in days (probably start-end+1, to include both fenceposts). If the result is less than 0, it contains 0 weekends. If it is equal to or greater than 0, then it contains (at least) 1 weekend.
Then you have to deal with the remaining days. In the first case this is easy, one extra weekend per full 7 days. This is also true in the second case for every starting day except Sunday, which only requires 6 more days to include another weekend. So in the second case for periods starting on Sunday you could count 1 weekend at the start of the period, then subtract 1 from the length and recalculate from Monday.
More generally, what's happening here for "whole or part" weekends is that we're checking to see whether we start midway through the interesting bit (the "weekend"). If so, we can either:
1) Count one, move the start date to the end of the interesting bit, and recalculate.
2) Move the start date back to the beginning of the interesting bit, and recalculate.
In the case of weekends, there's only one special case which starts midway, so (1) looks good. But if you were getting the date as a date+time in seconds rather than day, or if you were interested in 5-day working weeks rather than 2-day weekends, then (2) might be simpler to understand.
[*] Unless you're using unsigned types, of course.
My general approach for this sort of thing: don't start messing around trying to reimplement your own date logic - it's hard, ie. you'll screw it up for the edge cases and look bad. Hint: if you have mod 7 arithmetic anywhere in your program, or are treating dates as integers anywhere in your program: you fail. If I saw the "accepted solution" anywhere in (or even near) my codebase, someone would need to start over. It beggars the imagination that anyone who considers themselves a programmer would vote that answer up.
Instead, use the built in date/time logic that comes with Python:
First, get a list of all of the days that you're interested in:
from datetime import date, timedelta
FRI = 5; SAT = 6
# a couple of random test dates
now = date.today()
start_date = now - timedelta(57)
end_date = now - timedelta(13)
print start_date, '...', end_date # debug
days = [date.fromordinal(d) for d in
range( start_date.toordinal(),
end_date.toordinal()+1 )]
Next, filter down to just the days which are weekends. In your case you're interested in Friday and Saturday nights, which are 5 and 6. (Notice how I'm not trying to roll this part into the previous list comprehension, since that'd be hard to verify as correct).
weekend_days = [d for d in days if d.weekday() in (FRI,SAT)]
for day in weekend_days: # debug
print day, day.weekday() # debug
Finally, you want to figure out how many weekends are in your list. This is the tricky part, but there are really only four cases to consider, one for each end for either Friday or Saturday. Concrete examples help make it clearer, plus this is really the sort of thing you want documented in your code:
num_weekends = len(weekend_days) // 2
# if we start on Friday and end on Saturday we're ok,
# otherwise add one weekend
#
# F,S|F,S|F,S ==3 and 3we, +0
# F,S|F,S|F ==2 but 3we, +1
# S|F,S|F,S ==2 but 3we, +1
# S|F,S|F ==2 but 3we, +1
ends = (weekend_days[0].weekday(), weekend_days[-1].weekday())
if ends != (FRI, SAT):
num_weekends += 1
print num_weekends # your answer
Shorter, clearer and easier to understand means that you can have more confidence in your code, and can get on with more interesting problems.
To count whole weekends, just adjust the number of days so that you start on a Monday, then divide by seven. (Note that if the start day is a weekday, add days to move to the previous Monday, and if it is on a weekend, subtract days to move to the next Monday since you already missed this weekend.)
days = {"Saturday":-2, "Sunday":-1, "Monday":0, "Tuesday":1, "Wednesday":2, "Thursday":3, "Friday":4}
def n_full_weekends(n_days, start_day):
n_days += days[start_day]
if n_days <= 0:
n_weekends = 0
else:
n_weekends = n_days//7
return n_weekends
if __name__ == "__main__":
tests = [("Tuesday", 10, 1), ("Monday", 7, 1), ("Wednesday", 21, 3), ("Saturday", 1, 0), ("Friday", 1, 0),
("Friday", 3, 1), ("Wednesday", 3, 0), ("Sunday", 8, 1), ("Sunday", 21, 2)]
for start_day, n_days, expected in tests:
print start_day, n_days, expected, n_full_weekends(n_days, start_day)
If you want to know partial weekends (or weeks), just look at the fractional part of the division by seven.
You would need external logic beside raw math. You need to have a calendar library (or if you have a decent amount of time implement it yourself) to define what a weekend, what day of the week you start on, end on, etc.
Take a look at Python's calendar class.
Without a logical definition of days in your code, a pure mathematical methods would fail on corner case, like a interval of 1 day or, I believe, anything lower then a full week (or lower then 6 days if you allowed partials).

Categories

Resources