Is there a native way to convert to UTC time in UniVerse 11.2.4+?

1.1k views Asked by At

The release notes for UniVerse version 11.2.4 mention local time zone configuration, but it is in the context of auditing. This is the quote:

Local time zone configuration

Prior to UniVerse 11.2.4, the date and time data stored in the audit log records was based on UTC only. Beginning at UniVerse 11.2.4, UniVerse adds the date and time data based on local timezone to audit log records. The data is stored in location 19 for each record. The dictionary name for this data field is TZINFO. For more information, see UniVerse Security Features.

Since UniVerse seems capable of working with time zones natively, does this mean there might be a way to easily generate UTC-formatted date/time stamps from my EST/EDT values?

I am sending data to a system that wants dates formatted in ISO-8601 Date/Time format yyyy-MMddTHH:mm:ssZ, like 2015-06-02T15:55:22Z, with the time zone and Daylight Saving Time offsets accounted for.

I dug through the Security Features guide, and found this:

UniVerse also adds a globally cataloged program to help users to obtain date and time information from the audit log (which is called by the above two I-descriptor fields):

SUBROUTINE GETLOCALTIME
(
RESULT ;* OUT: output
TZOFF ;* IN: time zone offset
DATE ;* IN: UTC date
TIME ;* IN: UTC time
OP ;* IN: operation
;* 1: get local date
;* 2: get local time
;* 3: get local timezone id
;* 4: get local timezone daylight saving flag
)

(Since I'm not using the auditing capabilities of UniVerse, I don't think I can do much with this, nor could I locate the subroutine.)

I have also played with the popular(?) DATE.UTILITY program from PickWiki, but its calculation of Daylight Saving Time start/end dates seem off. I will save those issues for another question.

This is getting long-winded but I'm hoping someone can point me in the right direction if there's a new OCONV() parameter or something I could use.

Just in case it matters, I'm running on Windows Server 2008 R2.

Thanks!

2

There are 2 answers

0
Van Amburg On

Time is a complicated thing. Socially we have accepted that it not only acceptable to alter it 2 times a year, we have mandated it! This is all well and good for us meat machines who only want to understand time when it is convenient for us however it does cause us to get grumpy when out reporting "looks funny".

The solution to your problem is not exceptionally easy, especially if you are working with already recorded dates. Dates and times in Universe are generally recorded based on local system time. If this is something that you are trying to do going forward you have to note what the offset is at the time of the transaction or simply stamp things SYSTEM(99), which complicated pretty much all other reporting you will need to do. Either way, this is a complicated matter and it still likely to be somewhat imperfect.

Here is a little something that might help you if you are the one in charge of recording dates, going forward.

SECONDS.SINCE.GMT.01.01.1970 = SYSTEM(99) 
CRT SECONDS.SINCE.GMT.01.01.1970:" Seconds since GMT Epoch Began"

NUMBER.OF.DAYS.SINCE.01.01.1970 = DATE() -732 
;* Day 0 in Pick is 12/31/1967 because Dick Pick so we subtract 732 from the pick date

SECONDS.SINCE.MIDNIGHT.LOCAL= TIME()
SECS.PER.DAY = 24 * 60 * 60
LOCAL.SECONDS.SINCE.GMT.01.01.1970 = NUMBER.OF.DAYS.SINCE.01.01.1970 * SECS.PER.DAY + FIELD(SECONDS.SINCE.MIDNIGHT.LOCAL,".",1) 
;*I drop the precision
CRT LOCAL.SECONDS.SINCE.GMT.01.01.1970: " Seconds since 01/01/1970 in local time"

OFFSET = (LOCAL.SECONDS.SINCE.GMT.01.01.1970 - SECONDS.SINCE.GMT.01.01.1970)
CRT "CURRENT.OFFSET IS ":INT((OFFSET / 60 )/ 60)
END

Which outputs the following on my system which is currently PDT (even though OCONV(DATE(),'DZ') reports it as PST.

1434472817 Seconds since GMT Epoch Began
1434447617 Seconds since 01/01/1970 in local time
CURRENT.OFFSET IS -7

Hopefully you have found this helpful.

0
Greg On

Thanks for the clues. Here's my implementation:

  SUBROUTINE FORMAT.ISO.8601 (IDATE, ITIME, RESULT, ERR.TEXT)

  * Don't step on the caller's variables.
  IN.DATE = IDATE
  IN.TIME = ITIME

  * Initialize the outbound variable.
  RESULT = ''

  IF NOT(NUM(IN.DATE)) THEN
     ERR.TEXT = 'Non-numeric internal date ' : DQUOTE(IN.DATE) : ' when numeric required.'
     RETURN
  END

  IF NOT(NUM(IN.DATE)) THEN
     ERR.TEXT = 'Non-numeric internal time ' : DQUOTE(IN.TIME) : ' when numeric required.'
     RETURN
  END

  * SYSTEM(99) is based on 1/1/1970.
  SECONDS.SINCE.GMT.01.01.1970 = SYSTEM(99)

  * Day 0 in Pick is 12/31/1967
  * Subtract 732 to equalize the starting dates.
  NUMBER.OF.DAYS.SINCE.01.01.1970 = DATE() - 732

  SECONDS.SINCE.MIDNIGHT.LOCAL= TIME()
  SECS.PER.DAY = 24 * 60 * 60
  LOCAL.SECONDS.SINCE.GMT.01.01.1970 = NUMBER.OF.DAYS.SINCE.01.01.1970 * SECS.PER.DAY + FIELD(SECONDS.SINCE.MIDNIGHT.LOCAL,".",1)

  OFFSET = LOCAL.SECONDS.SINCE.GMT.01.01.1970 - SECONDS.SINCE.GMT.01.01.1970

  OFFSET = INT((OFFSET / 60 )/ 60)

  OTIME = OCONV(IN.TIME, 'MTS')
  IF OTIME = '' THEN
     ERR.TEXT = 'Bad internal time ' : DQUOTE(IN.TIME) : '.'
     RETURN
  END

  HOURS = FIELD(OTIME, ':', 1)
  MINUTES = FIELD(OTIME, ':', 2)
  SECONDS = FIELD(OTIME, ':', 3)

  HOURS -= OFFSET
  IF HOURS >= 24 THEN
     IN.DATE += 1
     HOURS = HOURS - 24
  END
  HOURS = HOURS 'R%2'

  ODATE = OCONV(IN.DATE, 'D4/')
  IF ODATE = '' THEN
     ERR.TEXT = 'Bad internal date ' : DQUOTE(IN.DATE) : '.'
     RETURN
  END

  DMONTH = FIELD(ODATE, '/', 1)
  DDAY = FIELD(ODATE, '/',2)
  DYEAR = FIELD(ODATE, '/',3)

  RESULT = DYEAR : '-' : DMONTH : '-' : DDAY : 'T' : HOURS : ':' : MINUTES : ':' : SECONDS : 'Z'

  RETURN

END

Here's my test harness:

  CRT 'Testing right now.'
  IDATE = DATE()
  ITIME = TIME()
  CALL FORMAT.ISO.8601 (IDATE, ITIME, RESULT, ERR.TEXT)
  IF ERR.TEXT THEN
     CRT 'ERR.TEXT: ' : ERR.TEXT
  END ELSE
     CRT 'RESULT: ' : RESULT
  END

  CRT
  CRT 'Testing an hour ago.'
  IDATE = DATE()
  ITIME = TIME()
  ITIME = ITIME - (60*60)
  IF ITIME < 0 THEN
     ITIME += (24*60*60)
     IDATE -= 1
  END
  CALL FORMAT.ISO.8601 (IDATE, ITIME, RESULT, ERR.TEXT)
  IF ERR.TEXT THEN
     CRT 'ERR.TEXT: ' : ERR.TEXT
  END ELSE
     CRT 'RESULT: ' : RESULT
  END

  CRT
  CRT 'Testing an hour from now.'
  IDATE = DATE()
  ITIME = TIME()
  ITIME = ITIME + (60*60)
  IF ITIME > (24*60*60) THEN
     ITIME -= (24*60*60)
     IDATE += 1
  END
  CALL FORMAT.ISO.8601 (IDATE, ITIME, RESULT, ERR.TEXT)
  IF ERR.TEXT THEN
     CRT 'ERR.TEXT: ' : ERR.TEXT
  END ELSE
     CRT 'RESULT: ' : RESULT
  END

END

Here's my test run:

  >T$FORMAT.ISO.8601
  Testing right now.
  RESULT: 2017-03-29T00:47:22Z

  Testing an hour ago.
  RESULT: 2017-03-28T23:47:22Z

  Testing an hour from now.
  RESULT: 2017-03-29T01:47:22Z