DATETIME-TZ function handling DST

Posted by Akshay Guleria on 04-Sep-2019 12:12

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?

Posted by Akshay Guleria on 12-Sep-2019 08:22

Thanks [mention:679595a048594a6a8dc3cbd6c5c182f2:e9ed411860ed4f2ba0265705b8793d05] 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

Posted by Peter Judge on 04-Sep-2019 12:24

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.
 
 

Posted by Akshay Guleria on 04-Sep-2019 12:50

Thanks [mention:9e4ee96fac634b8f91b580e1fb4f7e71:e9ed411860ed4f2ba0265705b8793d05] 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?

Posted by Peter Judge on 04-Sep-2019 13:04

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.
 
 

Posted by Akshay Guleria on 04-Sep-2019 14:31

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.

Posted by tbergman on 04-Sep-2019 14:42

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.

Posted by Akshay Guleria on 04-Sep-2019 15:36

Thanks [mention:3bdfc97448124857a5b85c01a3d4da63:e9ed411860ed4f2ba0265705b8793d05]. I'm looking for something that works at least on Linux/Unix environment.

Posted by dbeavon on 05-Sep-2019 13:09

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.

Posted by Robin Brown on 05-Sep-2019 14:23

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.

Posted by Akshay Guleria on 12-Sep-2019 08:22

Thanks [mention:679595a048594a6a8dc3cbd6c5c182f2:e9ed411860ed4f2ba0265705b8793d05] 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.

This thread is closed