I have a list of strings as follows:
4:00-5:00PM
11:00-2:00PM
12:00-1:00PM
11:00-1:00AM
and I'm trying to find an effective way to create two time objects (I suppose this is the only way to keep track of a range of time, which I will later combine with a date object). It is clear to humans what we mean we say 11:00-1:00AM, but wondering what's an effective way to convert this to:
datetime.time(23, 0)
datetime.time(1, 0)
My current approach is to take the first time, and create a PM and AM version, take the timedelta with the end time (which is specified), and take the shorter of the two differences to be the correct one.
Here is a simple implementation.
>>> def timeRange(timestr):
... t1, t2 = timestr.split("-")
... timeFormat = "%I:%M%p"
... t1AM = datetime.datetime.strptime(t1 + "AM", timeFormat)
... t1PM = datetime.datetime.strptime(t1 + "PM", timeFormat)
... t2 = datetime.datetime.strptime(t2, timeFormat)
...
... if (t2 - t1AM).seconds < (t2-t1PM).seconds:
... return t1AM.time(), t2.time()
... else:
... return t1PM.time(), t2.time()
>>> timeRange("11:00-2:00PM")
(datetime.time(11, 0), datetime.time(14, 0))
>>> timeRange("4:00-5:00PM")
(datetime.time(16, 0), datetime.time(17, 0))
>>> timeRange("11:00-1:00AM")
(datetime.time(23, 0), datetime.time(1, 0))
>>> timeRange("11:00-2:00PM")
(datetime.time(11, 0), datetime.time(14, 0))
>>> timeRange("12:00-1:00PM")
(datetime.time(12, 0), datetime.time(13, 0))
This returns a time object, but you could roll that into a datetime object if you need to.
There are two assumptions implicit in your question:
You are looking for the shortest possible duration between these times
The first time is the earlier of the two.
If these assumptions are true, then a quick optimization would be:
If the first time is larger than the second (eg 11:00-1:00AM or 11:00-1:00PM) then the earlier time is the 'opposite' AM/PM indicator of the second. Otherwise, the AM/PM of the first time is the same.
This works even for the largest time period, eg 6:00-6:00AM (6:00PM-6:00AM is shorter than 6:00AM-6:00AM)
A second observation is that you cannot use the simple time object because
An AM->PM duration takes place over a day boundary (midnight). This is probably screwing up the computation of your timedelta in this condition.
Therefore I think you have to use datetime, or wrap all this up in a structure that states the time in the first time object is the previous day.
Hope this helps :)
Related
How can I convert YYYY-MM-DD hh:mm:ss format to integer in python?
for example 2014-02-12 20:51:14 -> to integer.
I only know how to convert hh:mm:ss but not yyyy-mm-dd hh:mm:ss
def time_to_num(time_str):
hh, mm , ss = map(int, time_str.split(':'))
return ss + 60*(mm + 60*hh)
It depends on what the integer is supposed to encode. You could convert the date to a number of milliseconds from some previous time. People often do this affixed to 12:00 am January 1 1970, or 1900, etc., and measure time as an integer number of milliseconds from that point. The datetime module (or others like it) will have functions that do this for you: for example, you can use int(datetime.datetime.utcnow().timestamp()).
If you want to semantically encode the year, month, and day, one way to do it is to multiply those components by order-of-magnitude values large enough to juxtapose them within the integer digits:
2012-06-13 --> 20120613 = 10,000 * (2012) + 100 * (6) + 1*(13)
def to_integer(dt_time):
return 10000*dt_time.year + 100*dt_time.month + dt_time.day
E.g.
In [1]: import datetime
In [2]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:def to_integer(dt_time):
: return 10000*dt_time.year + 100*dt_time.month + dt_time.day
: # Or take the appropriate chars from a string date representation.
:--
In [3]: to_integer(datetime.date(2012, 6, 13))
Out[3]: 20120613
If you also want minutes and seconds, then just include further orders of magnitude as needed to display the digits.
I've encountered this second method very often in legacy systems, especially systems that pull date-based data out of legacy SQL databases.
It is very bad. You end up writing a lot of hacky code for aligning dates, computing month or day offsets as they would appear in the integer format (e.g. resetting the month back to 1 as you pass December, then incrementing the year value), and boiler plate for converting to and from the integer format all over.
Unless such a convention lives in a deep, low-level, and thoroughly tested section of the API you're working on, such that everyone who ever consumes the data really can count on this integer representation and all of its helper functions, then you end up with lots of people re-writing basic date-handling routines all over the place.
It's generally much better to leave the value in a date context, like datetime.date, for as long as you possibly can, so that the operations upon it are expressed in a natural, date-based context, and not some lone developer's personal hack into an integer.
I think I have a shortcut for that:
# Importing datetime.
from datetime import datetime
# Creating a datetime object so we can test.
a = datetime.now()
# Converting a to string in the desired format (YYYYMMDD) using strftime
# and then to int.
a = int(a.strftime('%Y%m%d'))
This in an example that can be used for example to feed a database key, I sometimes use instead of using AUTOINCREMENT options.
import datetime
dt = datetime.datetime.now()
seq = int(dt.strftime("%Y%m%d%H%M%S"))
The other answers focused on a human-readable representation with int(mydate.strftime("%Y%m%d%H%M%S")). But this makes you lose a lot, including normal integer semantics and arithmetics, therefore I would prefer something like bash date's "seconds since the epoch (1970-01-01 UTC)".
As a reference, you could use the following bash command to get 1392234674 as a result:
date +%s --date="2014-02-12 20:51:14"
As ely hinted in the accepted answer, just a plain number representation is unmistakeable and by far easier to handle and parse, especially programmatically. Plus conversion from and to human-readable is an easy oneliner both ways.
To do the same thing in python, you can use datetime.timestamp() as djvg commented. For other methods you can consider the edit history.
Here is a simple date -> second conversion tool:
def time_to_int(dateobj):
total = int(dateobj.strftime('%S'))
total += int(dateobj.strftime('%M')) * 60
total += int(dateobj.strftime('%H')) * 60 * 60
total += (int(dateobj.strftime('%j')) - 1) * 60 * 60 * 24
total += (int(dateobj.strftime('%Y')) - 1970) * 60 * 60 * 24 * 365
return total
(Effectively a UNIX timestamp calculator)
Example use:
from datetime import datetime
x = datetime(1970, 1, 1)
time_to_int(x)
Output: 0
x = datetime(2021, 12, 31)
time_to_int(x)
Output: 1639785600
x = datetime(2022, 1, 1)
time_to_int(x)
Output: 1639872000
x = datetime(2022, 1, 2)
time_to_int(x)
Output: 1639958400
When converting datetime to integers one must keep in mind the tens, hundreds and thousands.... like
"2018-11-03" must be like 20181103 in int
for that you have to
2018*10000 + 100* 11 + 3
Similarly another example,
"2018-11-03 10:02:05" must be like 20181103100205 in int
Explanatory Code
dt = datetime(2018,11,3,10,2,5)
print (dt)
#print (dt.timestamp()) # unix representation ... not useful when converting to int
print (dt.strftime("%Y-%m-%d"))
print (dt.year*10000 + dt.month* 100 + dt.day)
print (int(dt.strftime("%Y%m%d")))
print (dt.strftime("%Y-%m-%d %H:%M:%S"))
print (dt.year*10000000000 + dt.month* 100000000 +dt.day * 1000000 + dt.hour*10000 + dt.minute*100 + dt.second)
print (int(dt.strftime("%Y%m%d%H%M%S")))
General Function
To avoid that doing manually use below function
def datetime_to_int(dt):
return int(dt.strftime("%Y%m%d%H%M%S"))
df.Date = df.Date.str.replace('-', '').astype(int)
How can you express an infinite timedelta in Python?
Up to now we use datetime from stdlib, but we could switch, if it is only supported in a third party library.
PostgreSQL does support it. And it would be handy in my case. I want to use it to express an not finished interval.
I could use None, but an infinite timedelta could avoid a lot of conditions (if statements).
I found this: http://initd.org/psycopg/docs/usage.html#infinite-dates-handling
PostgreSQL can store the representation of an “infinite” date,
timestamp, or interval. Infinite dates are not available to Python, so
these objects are mapped to date.max, datetime.max, interval.max.
Unfortunately the mapping cannot be bidirectional so these dates will
be stored back into the database with their values, such as
9999-12-31.
Short answer: not supported.
An "unfinished interval" can be expressed with datetime (and possibly subclass it to contain a flag stating start or end) - that's what I would do for a quick implementation.
When you have the interval's corresponding end or start), get the timedelta using their difference:
>>> from datetime import datetime, timedelta
# represent infinite time from 1/1/2014 onwards
>>> start = datetime(2014, 1, 1)
# stop that time
>>> end = datetime(2015, 1, 1)
>>> interval = end - start
>>> interval
datetime.timedelta(365)
If you have the time to do this "the right way" you can create a speacialized timedelta-like class with two datetime objects (start and end). If one of them is missing you can assume it's an infinite timedelta and treat it as such.
Example:
from datetime import timedelta, datetime
class SpecialTimedelta(object):
def __init__(self, start=None, end=None):
self.start = start
self.end = end
if start and not isinstance(start, datetime):
raise TypeError("start has to be of type datetime.datetime")
if end and not isinstance(start, datetime):
raise TypeError("end has to be of type datetime.datetime")
def timedelta(self):
if not (self.start and self.end):
# possibly split cases
return "infinite"
# alternatives:
# return self.start or self.end
# raise OverflowError("Cannot quantiate infinite time with timedelta")
return self.end - self.start
print SpecialTimedelta(datetime(1986, 1, 1), datetime(2015, 1, 1)).timedelta()
# out: 10592 days, 0:00:00
print SpecialTimedelta(datetime(1986, 1, 1)).timedelta()
# out: infinite
The typical way to express an unfinished interval is to use a datetime type's maximum value or NULL for the ending time.
As it was shown, you cannot really perform calculations involving the infinite value and get meaningful results.
Thus, NULL is better in this regard by simply not allowing you to.
I'm trying to increase the time.
I want to get an hour format like this: 13:30:45,123 (in Java: "HH:mm:ss,SSS"), but Python displays 13:30:45,123456 ("%H:%M:%S,%f")(microseconds of 6 digits).
I read on the web and found possible solutions like:
from datetime import datetime
hour = datetime.utcnow().strftime('%H:%M:%S,%f')[:-3]
print(hour)
The output is: 04:33:16,123
But it's a bad solution, because if the hour is for example: 01:49:56,020706, the output is: 01:49:56,020, that the right should be: 01:49:56,021 (rounded).
The real purpose is that if I increase the milliseconds, even reaching rounds the seconds.
Example: (I want to increase 500 microseconds)
If the Input: 00:01:48,557, the Output should be: 00:01:49,057
The code of the program in Java (working good) is:
SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss,SSS");
System.out.print("Input the time: ");
t1 = in.next();
Date d = df.parse(t1);
Calendar cal = Calendar.getInstance();
cal.setTime(d);
cal.add(Calendar.MILLISECOND, 500);//here increase the milliseconds (microseconds)
t2 = df.format(cal.getTime());
System.out.print("The Output (+500): "+t2);
I don't know if exists in Python something like SimpleDateFormat (in Java).
As to addition, you can add 500ms to your datetime object, using a timedelta object:
from datetime import datetime, timedelta
t1 = datetime.utcnow()
t2 = t1 + timedelta(milliseconds=500)
So as long as you're working with datetime objects instead of strings, you can easily do all the time-operations you'd like.
So we're left with the question of how to format the time when you want to display it.
As you pointed out, the [:-3]-trick seems to be the common solution, and seems to me it should work fine. If you really care about rounding correctly to the closest round millisecond, you can use the following "rounding trick":
You must have seen this trick in the past, for floats:
def round(x):
return int(x + 0.5)
The same idea (i.e. adding 0.5) can also be applied to datetimes:
def format_dt(t):
tr = t + timedelta(milliseconds=0.5)
return tr.strftime('%H:%M:%S,%f')[:-3]
You can round of digits using decimal
from decimal import Decimal
ts = datetime.utcnow()
sec = Decimal(ts.strftime('%S.%f'))
print ts.strftime('%H:%M:')+str(round(sec, 3))
I have an object that represents an event, and I have many types of those objects.
I want to find events that are close in time in python.
The nicest way I could find to do it is:
joined_events = [[event_a.time, event_a.description, event_b.time, event_b.description] for event_a in events_a for event_b in events_b if abs(event_a.time - event_b.time) < datetime.timedelta(hours = 1)]
But I hoped there was a better method of doing it.
Beauty is in the eye of the beholder, but hopefully the is a little less ugly to you :)
import datetime
from itertools import product
delta = datetime.timedelta(hours=1)
joined_events = [
[a.time, a.description, b.time, b.description]
for a, b in product(events_a, events_b) if abs(a.time - b.time) < delta
]
I couldn't find a better solution so I wrote one:
https://github.com/tomirendo/Grouper
Its main purpose is to group object by their relation and replace the itertools.groupby function.
import datetime,random,Grouper
groups_of_dates = Grouper.groupby(list_of_dates,relation= Grouper.time_delta(hours = 3),as_iterable=False)
#Get a list of lists, grouping
#consecutive events that are less than 3 hours apart.
To solve the problem I had, You can do this:
results = [[a,b]
for a,b in itertools.product(events_a ,events_b)
if Grouper.time_delta(attr="time",hours = 3)(a,b)]
For a more general solution, you can use the AND,OR and difference methods:
groups_of_dates = Grouper.groupby(list_of_dates,
relation= Grouper.AND(Grouper.time_delta(hours = 3),
Grouper.difference(3,attr="loc")),
as_iterable=False)
I am getting time data in string format like this, 'HH:MM', for example '13:33' would be 13 hours and 33 minutes.
So, I used this code to get time object and it works great
datetime.datetime.strptime('13:33', '%H:%M').time()
However, I now have new problem.New strings started coming in representing more than 24 hours and datetime.datetime.strptime('25:33', '%H:%M').time() will simply fail.What's your suggestion?
A datetime.time object represents a (local) time of day, independent of any particular day.
You shouldn't use it to represent an elapsed time, like you appear to be.
More appropriate might be a datetime.timedelta:
A timedelta object represents a duration, the difference between two dates or times.
class datetime.timedelta([days[, seconds[, microseconds[, milliseconds[, minutes[, hours[, weeks]]]]]]])
All arguments are optional and default to 0. Arguments may be ints, longs, or floats, and may be positive or negative.
An example:
>>> from datetime import timedelta
>>> d = timedelta(hours=25,minutes=10)
>>> d
datetime.timedelta(1, 4200) #Days, seconds
When you say "it will simply fail", I assume you want to know when it will fail. Here's one approach:
>>> import datetime
>>>
>>> time_strs = [
... "13:33",
... "25:33",
... "12:88"
... ]
>>>
>>> for s in time_strs:
... try:
... print datetime.datetime.strptime(s, '%H:%M').time()
... except ValueError:
... print "Bad time: {0}".format(s)
...
13:33:00
Bad time: 25:33
Bad time: 12:88
You'll have to do this manually, alas.
def parse_stopwatch(s):
hours, minutes = s.split(':', 1)
return datetime.time(hour=int(hours), minute=int(minutes))
This is really dumb, granted. You could automatically have more than 60 minutes convert to hours, or get all fancy with a regex, or add support for days or seconds. But you'll have to be a bit more specific about where this data is coming from and what it's supposed to represent. :)