Search code examples
prologswi-prolog

"strptime" in SWI-Prolog


How to convert atom '2015-12-15T05 PST' to timestamp or datetime?

I've tried parse_time/3

?- parse_time('2015-12-15T05 PST', '%Y-%m-%dT%H %Z', Stamp)
false.

and format_time/3

?- format_time('2015-12-15T05 PST', '%Y-%m-%dT%H %Z', date(Y,M,D,H,M,S,O,TZ,DST)).
ERROR: format_time/3: Arguments are not sufficiently instantiated

Solution

  • According to the documentation, the mode of format_time/3 can't really help you, because it's expecting everything to be passed in:

    format_time(+Out, +Format, +StampOrDateTime)

    This means you supply each of the arguments. You want to see something with a - prefix which means it's handing something back, which seems like it would mean parse_time/3, but there the documentation says:

    Supported formats for Text are in the table below.

    and it then proceeds to list exactly two options, rfc_1123 and iso_8601, neither of which really match your format. There does not seem to be a way to supply a format code here, which I find really puzzling, because there are underlying Unix libraries here that can certainly do this.

    However, the problem can be solved with everyone's favorite tool: definite clause grammars! Here's my solution:

    :- use_module(library(dcg/basics)).
    
    myformat(date(Y,M,D,H,_,_,_,TZ,_)) -->
        integer(Y), "-", integer(M), "-", integer(D),
        "T", integer(H), " ", timezone(TZ).
    
    timezone('UTC') --> "UTC".
    timezone('UTC') --> "GMT".
    timezone(-18000) --> "PST".
    timezone(Secs) -->
        [Sign], digit(H0), digit(H1), digit(M0), digit(M1),
        {
         (Sign = 0'+ ; Sign = 0'-),
         number_codes(Hour, [H0,H1]),
         number_codes(Minutes, [M0, M1]),
         (Sign = 0'+
         -> Secs is Hour * 3600 + Minutes * 60
         ;  Secs is -(Hour * 3600 + Minutes * 60))
        }.
    
    my_time_parse(Atom, Date) :-
        atom_codes(Atom, Codes),
        phrase(myformat(Date), Codes).
    

    I'm sure the learned will see ways to improve this code but it did the trick for me with your sample data. You will unfortunately need to either enumerate timezones or find a better source of data on them (perhaps parsing the system timezone definitions?) but if you know a-priori that you only need to handle PST, you can try it. Here's an example:

    ?- my_time_parse('2015-12-15T05 PST', Date).
    Date = date(2015, 12, 15, 5, _G3204, _G3205, _G3206, -18000, _G3208) ;
    false.
    

    Hope this helps!