Yes.  Use resources instead. Read the docs. $ZPARENT value is also a "resource" that's getting created with the process, so children processes can communicate events to a parent process easily. As processes you want to pass messages between are not parent and child,  you need to create and use explicitly named resources.

You can use %SerialObject for that:

Class Utils.Serial Extends %SerialObject
{

Property Payload As %String;

/// zw ##class(Utils.Serial).Test()
ClassMethod Test(input As %String = {$lb(1,2,3,",",5)}) As %String
{
    
    set obj = ##class(Utils.Serial).%New()
    set obj.Payload = input
    do obj.%SerializeObject(.str)
    kill (str)
    
    set obj = ##class(Utils.Serial).%Open(str)
    return obj.Payload
}
}

For example:

zw ##class(Utils.Serial).Test("1,2,3,,,5")
>"1,2,3,,,5"

zw ##class(Utils.Serial).Test($lb(1,2,3,",",5))
>$lb(1,2,3,",",5)

Here it is:

/// Returns MDX string used to create pivot.<br>
/// <b>pPivotName</b> - fullname of pivot. Eg: "KPIs & Plugins/HoleFoods.pivot". Case insensitive.<br>
/// <b>pStatus</b> - Status of query execution.<br>
/// <b>MDX</b> - MDX query with filters<br> /// <b>BaseMDX</b> - MDX query without filters<br>
ClassMethod GetMdx(pPivotName As %String, Output MDX, Output BaseMDX) As %Status
{
    #dim tPivot As %DeepSee.Dashboard.Pivot
    #dim tPivotTable As %DeepSee.Component.pivotTable
    set MDX = ""
    set BaseMDX = ""

    set tPivot = ##class(%DeepSee.UserLibrary.Utils).%OpenFolderItem(pPivotName,.pStatus)
    return:'$IsObject(tPivot) $$$OK
    return:$$$ISERR(pStatus) pStatus

    set tPivotTable = ##class(%DeepSee.Component.pivotTable).%New()
    set pStatus = tPivot.%CopyToComponent(tPivotTable)
    return:$$$ISERR(pStatus) pStatus

     // returns tQueryText - mdx without filters
    set rs = tPivotTable.%CreateResultSet(.pStatus, .tParms, .tFilterInfo, .tAdvancedFilters, .BaseMDX)
    
    return:$$$ISERR(pStatus) pStatus

    set pStatus = tPivotTable.%GetFilterInfo(.tFilterInfo, .tAdvancedFilters)
    //return:$$$ISERR(pStatus) pStatus

    if (($d(tFilterInfo)=0) &&($d(tAdvancedFilters)=0)) {
        set MDX = BaseMDX // no filters, so we're good
    } else {
        // returns tQueryText - mdx with filters
        set rs = tPivotTable.%CreateResultSet(.pStatus, .tParms, .tFilterInfo, .tAdvancedFilters, .MDX)
        return:$$$ISERR(pStatus) pStatus
    }

    // Remove \n
    set MDX = $TR(MDX, $C(10), "")
    set BaseMDX = $TR(BaseMDX, $C(10), "")

    return pStatus
}

It is a part of MDX2JSON REST API.

Save timestamp with user timezone (get timezone from request), for example (returns server timezone):

write $ZDATETIME($HOROLOG, 1, 5)

Return timestamp to a client, there are a lot of js libraries which automatically convert incoming timestamp with a different timezone into user's local time.

The advantage of this approach is that you only need to convert timezone once, the rest would be done on client.

Check out ISO 8601  for timestamp formats with timezone support and which js understands.

This approach works best, when you don't need to access your application data from the systems without support for this type of timestamp.