4

Given a pair of str objects representing an ISO 8601 time and time zone:

time_str = '09:30'

time_zone_str = 'America/New_York'

How can these 2 strings be parsed into a time (not datetime) object?

Note: It's obviously possible to split the time_str by ':' and use the time constructor but then the parsing would be a little tricky to count the number of elements in the resulting list to know the resolution (minute, second, microsecond) of the str. This is because ISO 8601 allows for different representations:

 time_str_short = '09:30'

 time_str_long = '09:30:00'

Thank you in advance for your consideration and response.

Ramón J Romero y Vigil
  • 17,373
  • 7
  • 77
  • 125
  • I agree that once you convert the object to a time object, you can't tell what the resolution of the original data was, but isn't that true regardless of how you create the time object? – Kevin Jun 28 '18 at 13:37
  • 2
    Python 3.7 has `time.fromisoformat`… – deceze Jun 28 '18 at 13:38
  • @Kevin It is definitely true regardless of how you create, but I was referring to creation itself. If I split `09:30` and `09:30:00` I have 2 lists with different lengths... – Ramón J Romero y Vigil Jun 28 '18 at 13:42
  • So, the strings could have different formats? Sometimes `09:00`, or ``09:00:00`, or maybe `09:00:00.000`? – tobias_k Jun 28 '18 at 13:53
  • [The `.time()` method of `datetime` objects](https://docs.python.org/3/library/datetime.html#datetime.datetime.time) lets you parse to a `datetime`, then extract only the `time` portion. Reduces it to a solved problem (parsing to `datetime` via `strptime`). – ShadowRanger Jun 28 '18 at 13:54
  • @tobias_k correct, I want to handle all properly formatted time strings. – Ramón J Romero y Vigil Jun 28 '18 at 14:00
  • @ShadowRanger So would I pass the `time_zone_str` to the `datetime` constructor? – Ramón J Romero y Vigil Jun 28 '18 at 14:00
  • In this case I feel inclined to close as "too broad", sorry. What are "all properly formatted time strings"? You could have a list of 10 different time formats and test each in turn until one "fits", but your "manual" approach might actually be simpler. Also, it's not really clear what to do with the time zone. – tobias_k Jun 28 '18 at 14:01
  • @tobias_k Updated my question accordingly. `ISO 8601` allows for several different time formats. – Ramón J Romero y Vigil Jun 28 '18 at 14:06
  • Timezones are highly date dependent, so no, you can't just take a timezone and a time. – Martijn Pieters Jun 28 '18 at 14:07
  • @RamonJRomeroyVigil: the problem is that that only supports simple offset timezones. Your stated timezone is not a simple offset. – Martijn Pieters Jun 28 '18 at 14:16
  • @MartijnPieters The following code works for me in py36: `datetime.time(9, 30, tzinfo=pytz.timezone('America/New_York'))` – Ramón J Romero y Vigil Jun 28 '18 at 14:19
  • 1
    That code doesn't actually work @RamonJRomeroyVigil ... per the pytz docs you can't pass a pytz object as tzinfo because it needs to know the date. If you actually print that to STDOUT you'll see a weird offset. That's why I didn't do it this way in my answer below. The pytz docs make it very clear that you need to use either `localize` or `astimezone` for this to work. I know it's very weird and annoying, but time is very complicated. – Matthew Story Jun 28 '18 at 14:23

3 Answers3

2

The answer to "can I do this?" (with a timezone) is both yes and no. Firstly let's convert the string to a time object. As one commenter mentioned, you can do this in python 3.7 with the fromisoformat method:

from datetime import time
time.fromisoformat("09:30")

If you are not using 3.7, you can do this by creating a datetime and then converting to a time object:

from datetime import datetime, time
as_time = datetime.datetime.strptime("09:00", "%H:%M").time()

Now to deal with the timezone. As the timezone is a name, we can use the very convenient pytz module to convert it to a tzinfo object:

pytz.timezone('America/New_York')

At this point you're probably tempted to just pass it to the time constructor as the tzinfo argument, but unfortunately that does not work with pytz:

Unfortunately using the tzinfo argument of the standard datetime constructors ‘’does not work’’ with pytz for many timezones. ~ http://pytz.sourceforge.net/

So we will have to use the localize method of the newly created tzinfo object. But unfortunately we will still not be able to successfully localize the time object with this timezone. The reason for this is that pytz needs to know the date in order to determine if this timezone is in daylight savings time or not. As we have not provided the date, achieving this is quite impossible and you will get odd results like:

>>> pytz.timezone('America/New_York').localize(as_dt).isoformat()
'1900-01-01T09:00:00-04:56'

Note the -04:56 offset, which is gibberish. There are a few options for getting to what you ultimately want.

One option is to assume the time is a time today:

as_time = datetime.datetime.strptime("09:00", "%H:%M").time()
tz = pytz.timezone('America/New_York')
local_time = tz.localize(datetime.datetime.now().replace(hour=as_time.hour, minute=as_time.minute))

The other option is to use naive timezone offsets rather than timezone names:

from datetime import timezone, timedelta  
naive_tz = timezone(timedelta(hours=5))
datetime.time(9, 30).replace(tz_info=naive_tz)

But I would not recommend this method as it's quite brittle and would require some intermediate steps to derive from the TZ location name that are non-trivial.

Matthew Story
  • 3,573
  • 15
  • 26
1

A timezone without a date is meaningless, so no, you can't use both to produce a time object. While the standard library time object does support having a tzinfo attribute, the 'timezone' object is not really a timezone, but merely a time offset.

A timezone is more than just an offset from UTC. Timezone offsets are date-dependent, and because such details as the Daylight Savings winter / summer time distinction is partly the result of political decisions, what dates the timezone offset changes is also dependent on the year.

To be explicit, America/New_York is a timezone, not a time offset. The exact offset from UTC depends on the date; it'll be minus 4 hours in summer, 5 hours in winter!

So for a timezone such as America/New_York, you need to pick a date too. If you don't care about the date, pick a fixed date so your offset is at least consistent. If you are converting a lot of time stamps, store the timezone offset once as a timedelta(), then use that timedelta to shift time() objects to the right offset.

To parse just a timestring, pretend there is a date attached by using the datetime.strptime() method, then extract the time object:

from datetime import datetime

try:
    timeobject = datetime.strptime(time_str, '%H:%M').time()
except ValueError:
    # input includes seconds, perhaps
    timeobject = datetime.strptime(time_str, '%H:%M:%S').time()

To update the time given a timezone, get a timezone database that supports your timezone string first; the pytz library is regularly updated.

from pytz import timezone

timezone = pytz.timezone(time_zone_str)

How you use it depends on what you are trying to do. If the input time is not in UTC, you can simply attach the timezone to a datetime() object with the datetime.combine()method, after which you can move it to the UTC timezone:

dt_in_timezone = datetime.combine(datetime.now(), timeobject, timezone)
utc_timeobject = dt_in_timezone.astimezone(pytz.UTC).time()

This assumes that 'today' is good enough to determine the correct offset.

If your time is a UTC timestamp, combine it with the UTC timezone, then use the pytz timezone; effectively the reverse:

dt_in_utc = datetime.combine(datetime.now(), timeobject, pytz.UTC)
timeobject_in_timezone = dt_in_timezone.astimezone(timezone).time()

To store just the offset for bulk application, pass in a reference date to the timezone.utcoffset() method:

utc_offset = timezone.utcoffset(datetime.now())

after which you can add this to any datetime object as needed to move from UTC to local time, or subtract it to go from local to UTC. Note that I said datetime, as time objects also don't support timedelta arithmetic; a timedelta can be larger than the number of seconds left in the day or the number of seconds since midnight, after all, so adding or subtracting could shift days as well as the time:

# new time after shifting
(datetime.combine(datetime.now(), timeobject) + utc_offset).time()

For completion sake, you can't pass in a pytz timezone to a time object; it just doesn't have any effect on the time. The timezone object returns None for the UTC offset in that case, because it can't give any meaningful answer without a date:

>>> from datetime import time
>>> from pytz import timezone
>>> tz = timezone('America/New_York')
>>> time_with_zone = time(12, 34, tzinfo=tz)
>>> time_with_zone
datetime.time(12, 34, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)
>>> time_with_zone.utcoffset()
>>> time_with_zone.utcoffset() is None
True
>>> tz.utcoffset(None) is None    # what time_with_zone.utcoffset() does under the hood
None

So for all intents an purposes, time_with_zone is just another naive time object as the tzinfo object attached doesn't actually have any effect.

Moreover, because there is no date to determine the correct timezone information, pytz selects the earliest known 'New York' timezone, and that's not exactly a recent one; look closely at that tzinfo representation:

tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>
                                     ^^^^^^^^^^^^^^^^^^^^^^^

That's the timezone introduced in 1883 by the railroads; the 'modern' EST timezone was not introduced until the 20th century. This is why timezone objects are usually passed in a date when determining the offset:

>>> tz.utcoffset(datetime(1883, 6, 28))
datetime.timedelta(-1, 68640)
>>> tz.utcoffset(datetime(1918, 6, 28))
datetime.timedelta(-1, 72000)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
-1

Hope it works for you,

import datetime

# Hello World program in Python

print "Hello World!\n"

time_str = '09:30'

time_zone_str = 'America/New_York'

s = "I am looking for a course in Paris!" 

print(s)

print(datetime.datetime.strptime(time_str, '%H:%M').time())
print(datetime.time(3, 55))

Thanks

Utkarsh Dubey
  • 703
  • 11
  • 31