Forum OpenACS Q&A: formatting timestamps

Collapse
Posted by Radam Batnag on

What is the "best practices" way of formatting PostgreSQL timestamps?

I tried util_AnsiDatetoPrettydate but it drops the time field.

I tried dt_ansi_to_pretty but I get an error like this:

  unable to convert date-time string "2002-04-25 11:22:00+08"  while executing clock scan $ansi_date  (dt_ansi_to_pretty line 6)  ...  

I worked around this by splitting the PostgreSQL date into a date component and a time component:

  select cast(start_timestamp as date) as start_date, cast(start_timestamp as time)...  

Then I use

  set start_time_str [dt_ansi_to_pretty $start_date]  append start_time_str " $start_time"  

Is this a bug in dt_ansi_to_pretty? Or is there something wrong with the way I get my date from PostgreSQL?

Collapse
Posted by Don Baccus on
Hmmm ... presumably dt_ansi_to_pretty is supposed to replace util_AnsiDatetoPrettyDate but isn't doing the truncation of the timestamp that's necessary to make it work.

AnsiDatetoPrettyDate is meant to only return the date portion.  The best way to format timestamps in both Oracle and PostgreSQL is to use the "to_char" function.  The PG version's a subset of the Oracle version but the functionality's complete enough for most needs.  "to_char()" takes a format string that gives you complete control over how the format of the resulting string.

Collapse
Posted by Jonathan Ellis on
Reformat the date outside of the database is silly. Not sure why that was the ACS approach, because it's twice as much work as just having the DB spit out the format you want in the first place. (First you have to parse the db's format, then reformat.)

I wanted to have my timestamps displayed with just the hour/minute info if it was today's date, give the full date. And I want the timezone, since I have a lot of non-US users. I couldn't do that at all with the existing tcl functions, and as I said, trying to extend them to do that is IMO the wrong approach. Here's the PG function I use instead:

drop function pretty_timestamp (timestamp);
create function pretty_timestamp (timestamp) returns varchar as '
    spi_exec "select to_char(''$1''::timestamp, ''Month DD YYYY HH:MI AM TZ'') as s"
    set s_date [string range $s 0 16] ;# month is padded so it's always this long
    spi_exec "select to_char(now(), ''Month DD YYYY'') as now_date"

    #todo: trim zeros from hours (but not minutes)

    if ![string compare $s_date $now_date] {
        # same day; just return time
        return [string range $s 18 end]
    }
    # different day; return date + time
    regsub -all " +" $s " " s ;# rm space padding from month name
    set s_list [split $s]
    set trimmed_day [string trim [lindex $s_list 1] "0"]
    return [join [lreplace $s_list 1 1 $trimmed_day]]
' LANGUAGE 'pltcl'
;
Collapse
Posted by Jonathan Ellis on
and here it is without apostrophes in the comments that I added while posting, in case you tried it and it broke (oops :)
create function pretty_timestamp (timestamp) returns varchar as '
    spi_exec "select to_char(''$1''::timestamp, ''Month DD YYYY HH:MI AM TZ'') as s"
    set s_date [string range $s 0 16] ;# month is padded so it is always this long
    spi_exec "select to_char(now(), ''Month DD YYYY'') as now_date"

    #todo: trim hours (but not minutes)

    if ![string compare $s_date $now_date] {
        # same day -- just return time
        return [string range $s 18 end]
    }
    # different day -- return date + time
    regsub -all " +" $s " " s ;# rm space padding from month name
    set s_list [split $s]
    set trimmed_day [string trimleft [lindex $s_list 1] "0"]
    return [join [lreplace $s_list 1 1 $trimmed_day]]
' LANGUAGE 'pltcl'
;
Collapse
Posted by Radam Batnag on
Don, Jonathan,

Thanks for the guidance.

On a related note...
The documentation for the dt_* procs are included in the API documentation, but there is too little HOW in the API docs,
and almost no WHY.

For example, there's no "reformatting the date outside the
database is silly" in the documentation. I'm thinking of something
like the classic book Peter Norton's PC Progremmer's Solver.

Here's my first stab at it:
http://www.geocities.com/radam_avatar/oacs-cookbook.html.

Are other people working on something like this? Let me know so that I can just contribute to their efforts.

Again, thanks for the tips!

Collapse
Posted by Don Baccus on
We need all the documentation we can get, of course.

There is one instance when formatting the date time outside the database isn't silly.  That's when the date's not being extracted from the database in the first place!

datetime used to use the database to do all its date stuff.  But much of the code there is used to build datetime widgets, like the one that displays all the days of the month in calendar format.  You don't need the database to do that, just as you don't need the database in order to add two Tcl vars together.

The time to do date formatting in the database is during the query that extracts the date(s).  You should always do this if possible.  If you can't do that, though, or if the database isn't involved ("Next Month" isn't stored in a databsae table, it is calculated based on today's date), then using the dt_* utilities is the right thing to do.

Collapse
Posted by Ola Hansson on
Jonathan, If you want to remove the padding from, let's say, month and hour it can be done with the "FM" prefix:
<br><br>
spi_exec "select to_char(''$1''::timestamp, ''<b>FM</b>Month DD YYYY <b>FM</b>HH:MI AM TZ'') as s"
<br><br>
This works in PG 7.1.3 and beyond. (Maybe before that too)...
Collapse
Posted by Ola Hansson on
Reformat the date outside of the database is silly. Not sure why that was the ACS approach, because it's twice as much work as just having the DB spit out the format you want in the first place. (First you have to parse the db's format, then reformat.)
Just saying: "select * from foo" and then do the reformatting with a proc is somewhat quicker than specifying a bazillion columns, like you have to do if you let the db do the formatting...

I'm not sure if this justifies "silly" behaviour but might be an explanation😊
Collapse
Posted by Stephen . on
To enable internationalisation I presume the thing to do is pass in the format string as a variable. There might be problems with the object creation pl/pgsql functions which have already run up against the 16 parameter limit.

It's also a pain in a query which you want sorted by date, where the textual representation of that date sorts in some other order.

Collapse
Posted by Jonathan Ellis on
Ola, I use the fixed width with month and day b/c that makes it easy to separate the date and time info without extra calls to to_char or regexp.  I did try the other way but to my mind the code was messier.  However, I didn't know it could apply to hour as well; I changed my proc to use FMHH.  Thanks. :)
Collapse
Posted by Tilmann Singer on
Wouldn't it be nice to have a few centrally defined pl/sql functions that return some frequently used date formats, instead of repeatedly using to_char? This would also be a step  towards internationalization of the toolkit. For example for a german site I find myself rewriting a lot of to_char(some_date, 'MM/DD/YYYY HH12:MI PM') to to_char(some_date, 'DD.MM.YYYY HH24:MI'), while it would be much nicer to just change a function like oacs_dt_short in only one place.

I am sure this has been discussed before, but I couldn't find where.

Collapse
Posted by Don Baccus on
No, it's not been discussed before but it's an excellent point.  Would you by any chance have time to consider making a pass over the sources and making such a change?  As you say there are a handful of format strings that stereotypically get used over and over.