DATETIME-TZ function handling DST - Forum - OpenEdge Development - Progress Community

DATETIME-TZ function handling DST

 Forum

DATETIME-TZ function handling DST

This question is answered

It seems that that the DATETIME-TZ function can figure out the DST timezone if 

  1. SESSION:TIMEZONE is not set i.e. ?.
  2. OS timezone is a DST timezone. I’m using ‘Europe/Berlin’ for testing.

Now using the below example code we can see that the DATETIME-TZ function figures out the correct timezone of each date, accurate to their DST timezone.

def var ldtWinterTS as datetime-tz.
def var ldtSummerTS as datetime-tz.

ldtWinterTS = DATETIME-TZ(02, 24, 2019, 0, 0, 0).
ldtSummerTS = DATETIME-TZ(06, 19, 2019, 0, 0, 0).

DISP ldtWinterTS LABEL "Winter TS" SKIP
     ldtSummerTS LABEL "Summer TS" SKIP
     WITH SIDE-LABEL.
RESULT:
Winter TS: 24/02/2019 00:00:00.000+01:00 Summer TS: 19/06/2019 00:00:00.000+02:00

I would like to know 

  1. how exactly the DATETIME-TZ function figures out the DST timezones? Any pointer to implementation/code would be nice to have.
  2. can this feature be reused somehow in OE application/implementations?
Verified Answer
  • Thanks for your reply. I ended up doing something similar. It's direct call to OS in order to use the tzdb (Olson db). The usage of this in our case not extensive (and only limited to unix/linux os) so it is not much of an overkill for us. This method can be used to convert any timezone timestamp with correct timezone offset, even when the stored UTC timestamp was in a different timezone due to DST rules.

       METHOD PUBLIC STATIC DATETIME-TZ ConvertIsoToLocal(icISOTimeStamp  AS CHAR, icTZCanonicalId AS CHAR):
       /* Convert ISO format (YYYY-MM-DDTHH:MM:SS+Z) timestamps to
          desired timezone's timestamp. input tz should be a valid canonical id */
          DEF VAR lcDateFormat AS CHAR        NO-UNDO.
          DEF VAR lcResult     AS CHAR        NO-UNDO.
          DEF VAR lcCommand    AS CHAR        NO-UNDO.
          DEF VAR lcArguments  AS CHAR        NO-UNDO.
          DEF VAR lcTSFormat   AS CHAR        NO-UNDO.
    
          lcDateFormat = SESSION:DATE-FORMAT.
          SESSION:DATE-FORMAT = "ymd".
    
          /* if OS is not unix/linux then this method cannot evaluate.
             hence return original timestamp */
          IF OPSYS <> "UNIX" THEN
             RETURN DATETIME-TZ(icISOTimeStamp).
    
          /* prepare the command and format of timestamp */
          lcTSFormat = "+'%Y-%m-%d %H:%M:%S.%3N%:z'".
          lcCommand = SUBST("TZ='&1' date", icTZCanonicalId).
          lcArguments = SUBST("-d '&1' &2", icISOTimeStamp,
                                            lcTSFormat).
    
          /* call the os date function and capture the resulting timestamp */
          INPUT THROUGH VALUE(lcCommand) VALUE(lcArguments) NO-ECHO.
          IMPORT UNFORMATTED lcResult.
          INPUT CLOSE.
    
          RETURN DATETIME-TZ(lcResult).
    
          FINALLY:
             SESSION:DATE-FORMAT = lcDateFormat.
          END FINALLY.
       END METHOD.
All Replies
  • The SESSION:TIMEZONE uses the SESSION:TIME-SOURCE as a source. docs.progress.com/.../TIME-SOURCE-attribute.html and by default will use the OS.
     
     
  • Thanks for the answer. Yes I understand it will use the SESSION:TIME-SOURCE as a source but what I am after is:

    1. how exactly the DATETIME-TZ function figures out the DST timezones? Is this implemented and written in Progress ABL or does it use some OS library underneath? Any pointer to implementation/code would be nice to have.

    2. can this feature be reused somehow in OE application/implementations?

  • I expect that it just gets the current time per the OS. The OS knows what the appropriate/current time-zone is.
     
    I don't believe there's any way in the ABL to know whether the timezone is DST or not. You'd have to call an OS function (or maybe there's something in .NET that you can use on Windows.
     
     
  • If there is a call to OS function (or similar functionality) then it would be nice to know more about the implementation or actual code if possible somehow. It might help resolve a lot of datetime-tz related issue where DST needs to be considered for UI displays while timestamps are stored in UTC format. Any info would be really helpful.

  • If you're using Windows, the following .Net Progress code might give you some ideas.

    IF System.TimeZone:CurrentTimeZone:IsDaylightSavingTime(System.DateTime:Now)

     THEN MESSAGE System.TimeZone:CurrentTimeZone:DaylightName

       VIEW-AS ALERT-BOX.

    ELSE MESSAGE System.TimeZone:CurrentTimeZone:StandardName

       VIEW-AS ALERT-BOX.

  • Thanks . I'm looking for something that works at least on Linux/Unix environment.

  • I'm curious about the precise use-case.  You simply said: "... resolve a lot of datetime-tz related issue where DST needs to be considered for UI displays ...".

    Are you saying that you want to be able to visually indicate to the user whether the related point of time was daylight savings or not?  This is a bit confusing to me.  Normally it is up to the user to pick their "preference" of timezone, usually in the hosting OS.  Once they've selected it, the user should know for themselves what the rules are for their own timezone.  

    For example, if I live in EST5EDT and I have a very short business trip to another timezone that is three hours away (eg. PST8PDT) then I might continue keeping displaying my times in EST5EDT if I desire ...  it is my own choice ... nothing forces me to change the visual representations of my date/times (eg in my audit logs or whatever).

    I suspect that the best information you can get out of OE is the offset from UTC, as you are already aware (+01:00 or +02:00)

    RESULT:

    Winter TS: 24/02/2019 00:00:00.000+01:00

    Summer TS: 19/06/2019 00:00:00.000+02:00

    ...  the problem is that timezone stuff works very differently from one OS to another.  If you want to do better than displaying "+01:00" then you probably just shell out to the OS:

    (host:/home/me)date
    Thu Sep  5 09:03:32 EDT 2019

    Personally I think it might be overkill, if your users already have a way to select their preference of timezone in the OS - assuming they understand how that selected timezone behaves.

  • When the DATETIME-TZ function is not supplied a timezone, we calculate the timezone using the c-runtime localtime function:

    struct tm *localtime(const time_t *timer)

    The tm structure has a member named tm_isdst, indicating whether the date/time value is in daylight savings time.  The OS provides this information.

  • Thanks for your reply. I ended up doing something similar. It's direct call to OS in order to use the tzdb (Olson db). The usage of this in our case not extensive (and only limited to unix/linux os) so it is not much of an overkill for us. This method can be used to convert any timezone timestamp with correct timezone offset, even when the stored UTC timestamp was in a different timezone due to DST rules.

       METHOD PUBLIC STATIC DATETIME-TZ ConvertIsoToLocal(icISOTimeStamp  AS CHAR, icTZCanonicalId AS CHAR):
       /* Convert ISO format (YYYY-MM-DDTHH:MM:SS+Z) timestamps to
          desired timezone's timestamp. input tz should be a valid canonical id */
          DEF VAR lcDateFormat AS CHAR        NO-UNDO.
          DEF VAR lcResult     AS CHAR        NO-UNDO.
          DEF VAR lcCommand    AS CHAR        NO-UNDO.
          DEF VAR lcArguments  AS CHAR        NO-UNDO.
          DEF VAR lcTSFormat   AS CHAR        NO-UNDO.
    
          lcDateFormat = SESSION:DATE-FORMAT.
          SESSION:DATE-FORMAT = "ymd".
    
          /* if OS is not unix/linux then this method cannot evaluate.
             hence return original timestamp */
          IF OPSYS <> "UNIX" THEN
             RETURN DATETIME-TZ(icISOTimeStamp).
    
          /* prepare the command and format of timestamp */
          lcTSFormat = "+'%Y-%m-%d %H:%M:%S.%3N%:z'".
          lcCommand = SUBST("TZ='&1' date", icTZCanonicalId).
          lcArguments = SUBST("-d '&1' &2", icISOTimeStamp,
                                            lcTSFormat).
    
          /* call the os date function and capture the resulting timestamp */
          INPUT THROUGH VALUE(lcCommand) VALUE(lcArguments) NO-ECHO.
          IMPORT UNFORMATTED lcResult.
          INPUT CLOSE.
    
          RETURN DATETIME-TZ(lcResult).
    
          FINALLY:
             SESSION:DATE-FORMAT = lcDateFormat.
          END FINALLY.
       END METHOD.