# function to convert from Local time zone date/time to
# Universal Coordinated Time, UCT, formly
# known as GMT. Environment string of form:
# SET TZ=EST5EDT
# necssary.
# Parameters:
#    sjdn  == local date as Julian Day Number
#    lsecs == local time as returned by '_time'
#             built-in function, seconds since midnight
# Returns
#    gmtime == array
#              gmtime[0] == Julian Day Number, UCT
#              gmtime[1] == seconds since midnight UCT
#              gmtime[2] == Time Zone name
#              gmtime[3] == Daylight Savings Time Zone name, if any
#
function to_UCT(sjdn,lsecs)
{
    local TZ = /TZ{_w}*={_w}*/;
    local tzn = /([A-Za-z]{3,3})/;
    local digits = /({_d}{1,2})/;
    local hrs = /([-+]?){digits}/;
    local min_sec = /(:{digits})?/;
    local time_zone_setting = /{TZ}{tzn}{hrs}{min_sec}{min_sec}{tzn}?/;
    local tz;
    local conversion_factor, sign;
    local gmtime;

    # determine if time zone information in environment, specifically
    # environment variable 'TZ'
    for ( tz in ENVIRON ) {
        if ( ENVIRON[tz] ~~ time_zone_setting ) {
            # Found time zone environment variable with time zone correction.
            # Correction in tagged strings for "ENVIRON[tz]"
            # time zone name in:                     ENVIRON[tz][<1>][<1>]
            # correction sign in:                    ENVIRON[tz][<1>][<2>]
            # hour correction in:                    ENVIRON[tz][<1>][<3>]
            # optional minute correction in:         ENVIRON[tz][<2>][<2>]
            # optional seconds correction in:        ENVIRON[tz][<2>][<3>]
            # optional daylight saving time name in: ENVIRON[tz][<1>][<6>]
            gmtime[2] = ENVIRON[tz][<1>][<1>];
            gmtime[3] = ENVIRON[tz][<1>][<6>];
            sign = (ENVIRON[tz][<1>][<2>] >< "1") + 0;
            conversion_factor = ((ENVIRON[tz][<1>][<3>] * 60) + ENVIRON[tz][<2>][<2>]) * 60
                                + ENVIRON[tz][<2>][<3>];
            conversion_factor *= sign;
            break;
        }
    } ## endfor

    if ( conversion_factor ) {
        # test for DST in local time zone
        if ( gmtime[3] ) {
            conversion_factor += DST_Adjust(sjdn,lsecs) * 3600;
        } ## endif

        if ( (gmtime[1] = lsecs + conversion_factor) < 0 ) gmtime[1] += 86400;
          else gmtime[1] %= 86400;

        gmtime[0] = sjdn;

        if ( (conversion_factor > 0) && (gmtime[1] < lsecs) ) {
            gmtime[0]++;
          } else if ( (conversion_factor < 0) && (gmtime[1] > lsecs) ) {
            gmtime[0]--;
        } ## endif
    } ## endif

    return gmtime;
}

# Function to determine if Daylight Savings Time, DST,
# in effect. In the US, DST starts at 0200 hours on the
# first Sunday in April and ends at 0200 Hours on the last
# Sunday of October
function DST_Adjust(sjdn,lsecs)
{
    # break specified JDN into calender date
    local cdate = jdn(sjdn);
    local AS, OS;
    local DST_adj;

    # compute JDN for 1st Sunday of April
    AS = jdn(cdate[0],3,31) + month_day_date(4,1,0,cdate[0]);

    # compute JDN for last Sunday of October
    OS = jdn(cdate[0],9,30) + last_dow_of_month(10,0,cdate[0]);

    # assume DST in effect
    DST_adj = 1;

    # test if assumption correct
    if ( (sjdn < AS) || (sjdn > OS) ) DST_adj = 0;
      else if ( (sjdn == AS) && (lsecs < 7200) ) DST_adj = 0;
      else if ( (sjdn == OS) && (lsecs > 7200) ) DST_adj = 0;

    return DST_adj;
}


#
# Function to compute day of month for the nth occurrence
# of a specified day of the week, dow, for the month and
# year specified.
# Parameters:
#   month, 1 <= m <= 12
#   occurrence, 1 <= w <= 5
#   day of week (0 == Sunday, 6 == Saturday)
#   year
#     e.g., want day of month for the second Thursday
#           in September for 1996. Pass:
#       month      == 9 (September)
#       occurrence == 2
#       dow        == 4 (Thursday)
#       year       == 1996
function month_day_date(month,occur,dow,year)
{
    local day_incr = (occur - 1) * 7,
          dow_first = (auto_jdn(year,month,1) + 1) % 7;
    local addit = (dow_first <= dow) ? 1 : 8;

    return (dow - dow_first + addit + day_incr);
}


# Function to compute day of month for the day of week, dow, specified
# Parameters:
#  month, 1 <= month <= 12
#  dow,   0 <= dow <= 6
#  year
#     E.g., find last thursday in month of October, 1996. Pass:
#       month == 10
#       dow   == 4
#       year  == 1996
function last_dow_of_month(month,dow,year)
{
    local jdn_first = auto_jdn(year,month,1);   # compute JDN of first day of desired month
    local dow_first = (jdn_first + 1) % 7;      # compute DoW of first day of desired month
    local jdn_nfirst,               # JDN of first day of next month
          dow_last,                 # DoW of last day of desired month
          dm,                       # number of days in desired month
          nm = (month + 1) % 13,    # next month number
          ny = year,                # year of next month
          addit;                    # additive factor

    # set year and month number for month after desired month, next month
    if ( !nm ) {
        nm++;
        ny++;
    } ## endif

    # compute JDN for first day of next month
    jdn_nfirst = auto_jdn(ny,nm,1);

    # compute DOW for last day of desired month
    dow_last = jdn_nfirst % 7;

    # compute number of days in desired month
    dm = jdn_nfirst - jdn_first;

    addit = (dow <= dow_last) ? 0 : 7;

    return (dm - addit - (dow_last - dow));
}