14

I need to parse times in a character format like "1:36:22 PM".

I've tried various permutations of as.POSIXct and strptime but just can't get there. For example, this fails to pick up the importance of PM:

t <- "1:36:22 PM"
as.POSIXct(t, format = "%H:%M:%S")
# [1] "2016-09-08 01:36:22 BST"

and this fails:

strptime(t, "%H:%M:%S")
# [1] NA

Curiously, for reasons I can't understand, dropping the seconds from the input may work:

t <- "1:30:00 PM"
strptime(t, "%I:%M %p")
# [1] NA
 
t <- "1:30 PM"
strptime(t, "%I:%M %p")
# [1] "2016-09-08 13:30:00 BST"

All I want is for this to work:

t <- "1:30:00 PM"
SOME COMMAND HERE HERE
# [1] "13:30:00"

Any ideas?

Henrik
  • 61,039
  • 13
  • 131
  • 152
IanW
  • 161
  • 1
  • 1
  • 4

3 Answers3

15

Have you tried using the lubridate package? It can handle AM/PM using the %p indicator in the "parse_date_time" function.

library(lubridate)
t <- "1:36:22 PM"
parse_date_time(t, '%I:%M:%S %p')
[1] "2016-09-08 13:36:22 UTC"

From the lubridate documentation:

%I: Hours as decimal number (01–12 or 1–12).

%M: Minute as decimal number (00–59 or 0–59).

%S: Second as decimal number (00–61 or 0–61).

%P: AM/PM indicator in the locale. Used in conjunction with I and not with H. An empty string in some locales.

gillespiecd
  • 189
  • 4
  • 4
    These abbreviations are part of base R. See the documentation for `strptime` to see all the date-time abbreviations available. – aosmith Sep 08 '16 at 21:02
  • 1
    In `parse_date_time` you don't need the `%` symbols; [it ignores them anyway](http://stackoverflow.com/documentation/r/1157/date-and-time/7018/parsing-dates-and-datetimes-from-strings-with-lubridate#t=201609082306537640757). – alistaire Sep 08 '16 at 23:08
  • using the command `ymd_hms` from `lubridate`, you don't even need to specify the formatting as long as you enter a reasonable date time value. It automatically converts from AM/PM to 24hr time. – user3386170 Jan 20 '20 at 22:42
8

Use the conversion specifications %I and %p. See Details section in ?strptime:

%I Hours as decimal number (01–12).>

%p AM/PM indicator in the locale. Used in conjunction with %I and not with %H.

strptime(t, "%I:%M:%S %p")
Henrik
  • 61,039
  • 13
  • 131
  • 152
Shenglin Chen
  • 4,430
  • 9
  • 10
  • 1
    This answer turned up in the low quality review queue, presumably because you don't provide any explanation of the code. If this code answers the question, consider adding adding some text explaining the code in your answer. This way, you are far more likely to get more upvotes — and help the questioner learn something new. – lmo Sep 08 '16 at 22:04
  • 1
    ... also, it looks like you have `%l` (lower-case L) rather than `%I` (upper-case I) in your answer – Ben Bolker Sep 08 '16 at 22:11
0

The function below can round any time string to hours, minutes, seconds by setting parmeter 'unit', default is the smallest format of ts. Parameter 'ts' is the time string you want to round, the fuction accept multiple time formats. With parmeter 'rnd' you can set the number you want to round at, default is 1. With parmeter 'frm' you can set the format of the output string, as default the function takes the format of ts. Just play with the function and you will see how easy it will be. The function even convert time strings between 12 and 24 hours format and vice versa. All this with a minimum on import.

def toNearestTime(ts, unit=None, rnd=1, frm=None):

    ''' round to the Nearest Time
    param ts = auto recognize the most time patterns : not date-time
    param unit = specify unit wich must be rounded 'sec' or 'min' or 'hour', default is the smallest unit of ts :
    param rnd = to which number you will round, the default is 1 :
    param frm = the output (return) format of the time string you want, as default the function take the ts format'''

    from time import strftime, gmtime, strptime
    ustp = ["AM", 'PM']

    if any(e in ts for e in ustp):
        time12_pat = {'%I:%M%p': 'm', '%I:%M %p': 'm', '%I:%M:%S%p': 'se', '%I:%M:%S %p': 'se', '%I%M%p': 'm', '%I%M %p': 'm', '%I%M%S%p': 'se', '%I%M%S %p': 'se'}
        for p, u in time12_pat.items():
            try:
                ts = strftime('%H:%M:%S', strptime(ts, p))
                break
            except:
                continue
        sf = p
        unit = time12_pat[sf] if unit is None else unit
    else:
        time24_pat = {'%H:%M': 'm', '%H:%M:%S': 'se', '%H': 'h', '%H%M': 'm', '%H%M%S': 'se'}
        for p, u in time24_pat.items():
            try:
                ts = strftime('%H:%M:%S', strptime(ts, p))
                break
            except:
                continue
        sf = p
        unit = time24_pat[sf] if unit is None else unit
    if 'se' in unit.lower():
        frm = sf if frm is None else frm
    elif 'm' in unit.lower():
        frm = sf if frm is None else frm
        rnd = rnd * 60
    elif 'h' in unit.lower():
        frm = p if frm is None else frm
        rnd = rnd * 3600
    secs = sum(int(x) * 60 ** i for i, x in enumerate(reversed(ts.split(':'))))
    rtm = int(round(secs / rnd, 0) * rnd)
    nt = strftime(frm, gmtime(rtm))
    return nt

Call function as follow: Round to nearest 5 minutes with ouput format = hh:mm as follow

ts = '02:27:29'
nt = toNearestTime(ts, unit='min', rnd=5, frm='%H:%M')
print(nt)
output: '02:25'

Or round to nearest 30 minutes with ouput 12 hour format hh:mm:ss as follow

ts = '11:14PM'
nt = toNearestTime(ts, rnd=30, frm='%I:%M:%S %p')
print(nt)
output: '11:00:00 PM'

Or convert from 12 hour format to 24 hour format as follow

ts = '1:30:00 PM'
nt = toNearestTime(ts, frm='%H:%M:%S')
print(nt)
output: '13:30:00'