View Raw SPL
/****************************************************************************
*                                                                           *
*   TZCONVERT.SPL Copyright 2024 (C) DSP Development Corporation            *
*                                                                           *
*   Author:       Randy Race                                                *
*                                                                           *
*   Synopsis:     Convert date time to a target time zone                   *
*                                                                           *
*   Revisions:    1 Nov 2024     RRR     Creation                           *
*                                                                           *
****************************************************************************/


#if @HELP_TZCONVERT

    TZCONVERT

    Purpose: Converts date time values to a target time zone.

    Syntax:  TZCONVERT(date, time, "targzone", "srczone")

                  date - Optional. A string or series of dates, the local
                         date. Defaults to the current date.

                  time - Optional. A string or series of times, the local
                         time. Defaults to the current time.

              targzone - Optional. A string, the target time zone. Defaults
                         to the current time zone.

               srczone - Optional. A string, the source time zone. Defaults
                         to the current time zone.


    Returns: A string or series, the date and time of the input values
             in the target time zone.

    Example:
             tzconvert()

             Returns the current date and time.
            
    Example:
             tzconvert("America/Los_Angeles")

             Returns the current date and time in the US Pacific
             time zone.
            
    Example:
             tzconvert("1-1-2024", "12:00:00", "UTC", "America/New_York")

             Returns "1-01-2024 17:00:00", the corresponding UTC date time
             of noon on January 1, 2024 in the US Eastern time zone.

    Example:
             local2utc("1-1-2024", "12:00:00", "America/New_York")

             Same as above.

    Example:
             tzconvert("1-1-2024", "12:00:00", "UTC", "America/Los_Angeles")

             Returns "1-01-2024 20:00:00", the corresponding UTC date time
             of noon January 1, 2024 in the US Pacific time zone.

    Example:
             tzconvert("1-1-2024", "12:00:00", "America/Los_Angeles", "America/New_York")

             Returns "1-01-2024 9:00:00", the corresponding date time in
             the US Pacific time zone of noon, January 1, 2024 in the Eastern
             time zone.

   Example:
             dt =  {{julstr("2024-11-03"), todstr("22:30:00")},
                    {julstr("2024-11-03"), todstr("23:30:00")}};

             W1: dt;setvunits(w0, "date", 1, 1);setvunits(w0, "time", 1, 2)
             W2: tzconvert(W1, "America/New_York", "America/Chicago")

             W2 contains the date time:

               11-03-2024  23:30:00
               11-04-2024  00:30:00

             W2 converts the date times of W1 in the US Central time zone
             to date time values in the Eastern time zone.

   Example:
             W1: {{ymdhms2dt(2024, 11, 3, 22, 30, 0)},
                  {ymdhms2dt(2024, 11, 2, 23, 30, 0)}};

             W2: tzconvert(W1, "America/New_York", "America/Chicago")

             Same as above, except the date time values are created by
             YMDHMS2DT.

             W2 contains the date time:

               11-03-2024  23:30:00
               11-04-2024  00:30:00

    Remarks:
             UTC stands for Coordinated Universal Time and is a reference
             time for all time zones worldwide. UTC is historically linked
             to Greenwich Mean Time (GMT), but is more precise.

             TZCONVERT converts date and times to date and time values
             within a specified time.

             The TARGZONE and SRCZONE strings should adhere to the the IANA
             timezone identifiers available at:

               https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

             and:
      
               https://www.iana.org/time-zones

             If TARGZONE is blank, the local time zone is used.

             If SRCZONE is blank or unspecified, the local time zone is used.

             See LOCAL2UTC and UTC2LOCAL to convert to and from UTC date time
             values.

    See Also:
             CLOCK
             GETDATE
             GETDT
             GETTIME
             LOCAL2UTC
             UTC2LOCAL
             YMDHMS2DT
#endif


/* convert date/time string to date/time string in specified timezone */
tzconvert(srcdate = "", srctime = "", targzone = "", srczone = "")
{
        local targdate, targtime, isstring, dt;

        /* get args as series */
        (srcdate, srctime, targzone, srczone, isstring) = tzconvert_parse_args(srcdate, srctime, targzone, srczone);

        if (outargc > 1)
        {
                (targdate, targtime) = tzconvert_iterate(srcdate, srctime, targzone, srczone, isstring);

                return(targdate, targtime);
        }
        else
        {
                dt = tzconvert_iterate(srcdate, srctime, targzone, srczone, isstring);

                return(dt);
        }
}


/* normalize date/time settings */
tzconvert_dt(set)
{
        static dt_date, dt_time, isset;

        if (set)
        {
                dt_date = setconfig("dt_date_format", 0);
                dt_time = setconfig("dt_time_format", 0);
                isset   = 1;
        }
        else if (isset)
        {
                setconfig("dt_date_format", dt_date);
                setconfig("dt_time_format", dt_time);
                isset = 0;
        }
}



/* get args as series */
tzconvert_parse_args(argv)
{
        local date, time, srctz, targtz, j, str, isscalar = 0;

        date = time = srctz = targtz = {};

        loop (j = 1..argc)
        {
                if (isstring(getargv(j)))
                {
                        str = getargv(j);

                        if (isdatestr(str))
                        {
                                date = {julstr(str)};
                                isscalar++;
                        }
                        else if (istimestr(str))
                        {
                                time = {todmsecstr(str)};
                                isscalar++;
                        }
                        else if (not(isstring(targtz)))
                        {
                                targtz = str;
                        }
                        else if (not(isstring(srctz)))
                        {
                                srctz = str;
                        }
                        else if (strlen(str) > 0)
                        {
                                error(sprintf("%s - unexpected string '%s'", __CALLER__, str));
                        }
                }
                else if (isarray(getargv(j)))
                {
                        if (isempty(date))
                        {
                                date = getargv(j);
                        }
                        else if (isempty(time))
                        {
                                time = getargv(j);
                        }
                        else
                        {
                                error(sprintf("%s - unexpected series input", __CALLER__));
                        }
                }
                else
                {
                        error(sprintf("%s - unexpected input for argument %d", __CALLER__, j));
                }
        }

        if (numcols(date) > 1 && isempty(time))
        {
                time = date[.., 2..2..end];
                date = date[.., 1..2..end];
        }

        if (isempty(date))
        {
                isscalar++;
                date = {julstr(getdate())};
        }

        if (isempty(time))
        {
                isscalar++;
                time = {todmsecstr(gettime(13))};
        }

        if (isempty(srctz))
        {
                srctz = "";
        }

        if (isempty(targtz))
        {
                targtz = "";
        }

        isscalar = (isscalar == 2);

        return(date, time, targtz, srctz, isscalar);
}


/* iterate conversion */
ITERATE tzconvert_iterate(srcdate, srctime, targzone, srczone, isstring)
{
        local utcdate, utctime, locdate, loctime, msec;

        /* default format */
        tzconvert_dt(1);

        if (not(isstring(srczone)))
        {
                error(sprintf("%s - source timezone must be a string", __CALLER__));
        }

        /* convert to UTC using source timezone */
        (utcdate, utctime) = local2utc(srcdate, srctime, srczone);

        /* convert to local date/time using target timezone */
        (locdate, loctime) = utc2local(utcdate, utctime, targzone);

        /* restore format */
        tzconvert_dt(0);

        /* Real Time if time has fractional part */
        msec = any((loctime - int(loctime)) % 1);

        if (isstring)
        {
                /* return strings */
                locdate = strjul(locdate[1]);
                loctime = (msec) ? strtodmsec(loctime[1]) : strtod(loctime[1]);

                if (outargc > 1)
                {
                        return(locdate, loctime);
                }
                else
                {
                        return(locdate + " " + loctime);
                }
        }
        else
        {
                setmatrix(locdate, 0);
                setmatrix(loctime, 0);

                locdate.vunits = "Date";
                loctime.vunits = (msec) ? "Real Time" : "Time";

                if (outargc > 1)
                {
                        return(locdate, loctime);
                }
                else
                {
                        return(ravel(locdate, loctime));
                }
        }
}



/* error handler */
tzconvert_error(errnum, errmes)
{
        tzconvert_dt(0);

        error(errmes);
}