Skip to content

Time processing

Basic Concepts

AppCubeTime processing in the system is complex, especially when multiple time zones are involved. Its complexity lies not in the complexity of the interface, but in the variety of scenarios in which scripts are used.AppCubeThere are three trigger modes:

  1. After the user logs in,httpThe request directly or indirectly triggers the script. (Indirect triggering modes include flow invoking and triggering.)
  2. Script triggered by the scheduled task directly or indirectly.
  3. Scripts triggered by events directly or indirectly.

The time zone used in the execution context is different in the three cases:

Time zone of the login user in the first case, organization time zone of the tenant in the second case, and UTC time zone in the third case.

AppCubeThere are four time zones in the system:

  • User Time Zone

The user's default time zone isLocalIt is easy to mislead people into thinking that this is the time zone of the operating system where the browser is located, but it isAppCubeTime zone of the operating system on the server. You are advised to change the time zone to the following:

user_timezone_shanghai

The time zone in the script execution triggered by a normal request is the user time zone.

  • Tenant organization time zone

organization_timezone

Tenant organization time zone, which is used by the script execution context triggered by the scheduled task.

  • Server OS Time Zone

AppCubeBy default, the backend script engine of the uses this time zone.

  • Client OS Time Zone (Browser)

AppCubeThe backend scripting engine does not use this time zone. OnlyAppCubeThe frontend script uses this time zone.

After we know the information about the time zone, let's look at it again.AppCubeTime type involved in.

javascriptTime type ofDate

We first have to be clearjavascriptMediumDateDefinition of the type:

DateCreates a JavaScript Date instance that renders a moment in time. The Date object is based on the Unix Time Stamp, which is the number of milliseconds since January 1, 1970 (UTC).

From the definition we can see,DateThe object itself indicates yes: the number of milliseconds elapsed since January 1, 1970 (UTC). There is no concept of time zone.

The concept of when and sometimes zone. Simply put:

Is to turn a string into aDateInstance, or from aDateThe concept of a time zone becomes apparent as the instance gets a time string.

const date1 = new Date('December 17, 1995 03:24:00');
console.log(date1.toString());

const date2 = new Date('1995-12-17T03:24:00');
console.log(date2.toLocaleString('zh-CN', { timeZone: 'UTC' }));

AppCubeBy default, the backend script engine of the uses the UTC time zone, as shown in.new Date(...),JSON.parse, JSON.stringifyFunction,

AppCubein the back-end script engineconsole.logPrintDateWhen a variable is instanced, the script execution context time zone is used.

console.logThe output time string uses the script execution context timezone because it is notjavascriptFor the standard library, it'sAppCubeExtended implementation of,AppCubeThe script engine uses the time zone information of the execution context when printing logs.

AppCube time type.DateTo be associated with theDatetime

AppCubeObject field type support inDateTo be associated with theDatetimeType, both of which are mapped in the back-end scripting engine tojavascriptof theDateType. However, the processing of the two types in the time zone is different:

  • object'sDateIndicates the year, month, and day information. There is no time zone. The date character string transferred by the client is invoked.yyyy-MM-ddAll are processed as character strings of the UTC time zone.
  • object'sDatetimeIndicates the year, month, day, hour, minute, and second information. The time zone is a date character string transferred by the client.yyyy-MM-dd HH:mm:ssStrings that are treated as different time zones in different script triggering processes.

Writing an Example Script

Let's use an example script as an example:

ts
export class Input {
    @action.param({ type: "Date" })
    inDate: Date;

    @action.param({ type: "Datetime" })
    inDatetime: Date;
}

export class Output {
    @action.param({ type: "Date" })
    outDate: Date;

    @action.param({ type: "Datetime" })
    outDatetime: Date;
}

export class DateDemo {
    @action.method({ input: "Input", output: "Output", description: "do a operation" })
    run(input: Input): Output {
        let output = new Output();

        console.log("input date = ", input.inDate);
        console.log("input datetime = ", input.inDatetime);

        output.outDate = new Date("2021-07-01 06:30:30");
        output.outDatetime = new Date("2021-07-01 06:30:30");

        console.log("output date = ", output.outDate);
        console.log("output datetime = ", output.outDatetime);

        return output;
    }
}

Execute the script.

  • Request parameter
ts
{
    "inDate": "2021-07-01 06:30:30",
    "inDatetime": "2021-07-01 06:30:30"    
}

DateYou can also enter a character string containing the hour, hour, and second. When the backend script is executed, the parameter is directly deleted.

  • Script running output log
ts
0728 11:10:25.314|debug|vm[18]>>> Build #AppCube Core 1.3.7 on amd64
Built on 2021-07-27 20:14:22  
Commit #4c0246172d
0728 11:10:25.314|debug|vm[18]>>> node:  2
0728 11:10:25.314|debug|vm[18]>>> script:  my__datedemo 1.0.1 DateDemo.run
0728 11:10:25.314|debug|vm[18]>>> locale:  zh_CN
0728 11:10:25.314|debug|vm[18]>>> timezone:  (GMT+08:00) China Standard Time (Asia/Shanghai)
0728 11:10:25.314|debug|vm[18]>>> input date =  2021-07-01T08:00:00+08:00 (my__datedemo.ts:22)
0728 11:10:25.314|debug|vm[18]>>> input datetime =  2021-07-01T06:30:30+08:00 (my__datedemo.ts:23)
0728 11:10:25.314|debug|vm[18]>>> output date =  2021-07-01T14:30:30+08:00 (my__datedemo.ts:28)
0728 11:10:25.314|debug|vm[18]>>> output datetime =  2021-07-01T14:30:30+08:00 (my__datedemo.ts:29)
  1. From theinput date = 2021-07-01T08:00:00+08:00Informationally, the input parameterinDateThe hour, minute and second of is truncated into2021-07-01, and is treated as a UTC date string.
  2. From theinput datetime = 2021-07-01T06:30:30+08:00Informationally, the input parameterinDatetimeUsed as a user time zone string: GMT+8 character string
  3. From theoutput date = 2021-07-01T14:30:30+08:00Informationally,new Date("2021-07-01 06:30:30")The input parameter is used asUTCTime string,console.logThe print result is converted into a character string of GMT+8 hours.
  4. According to the logs,AppCubein the back-end script engineconsole.logPrintDateWhen a variable is instanced, the script execution context time zone, that is, the user time zone, is used.
  • Returned information
ts
{
    "outDate": "2021-07-01 08:00:00",
    "outDatetime": "2021-07-01 14:30:30"
}

From theoutDateAccording to the returned result, the hour, hour, and second are truncated. and the character string converted to the user time zone is returned. Technically, it should be returned.2021-07-01That's right. but because the underlying libraryjsonSerialization does not distinguish the object engineDateTo be associated with theDatetimeType, so the format string with hour, hour, and second is returned.

From theoutDatetimeAccording to the returned result of,new Date("2021-07-01 06:30:30")The input parameter is used as theUTCIn addition to the time string, the returned time zone is converted to GMT+8, which adds eight hours.

The preceding figure shows the time zone processing for the script after the user logs in to the system. When an event is triggered or a scheduled task is triggered, the preceding results are different: The time zone in the script execution context affects the processing of time strings.

Date module

In order to handle the time zone processing of the above complicated date and time,AppCubeOffered date module, The module deals specifically with these issues.

  • format
ts
function format(date: Date, layout: string, timezone?: TimeZones): string

Formats the Date type as a string. If the time zone is not specifiedtimezone, obtain the time zone information based on the following rules:

  • The user time zone is used in the script triggered by the user HTTP request.
  • Use the tenant organization time zone in the script triggered by the scheduled task.
  • Use the UTC time zone in the script triggered by an event.
ts
import { getTimeZone } from 'context';
import { format } from 'date';

let result = format(new Date(), 'yyyy-MM-dd HH:mm:ss', getTimeZone());

in that above example,timezoneUse the time zone of the script execution context:getTimeZone(), which is the same as the default value.

  • parse
ts
function parse(dateStr: string): Date

Parses a date string into a variable of the Date type based on the specified format string. Obtain the time zone information as follows:

  • UserhttpIn the script triggered by the request, the user time zone is used.
  • The tenant time zone is used in the script triggered by the scheduled task.
  • In the event-triggered script, use theUTCTime zone
  • toDate
ts
function toDate(date: string, layout: string, timezone?: TimeZones): Date

Parses a date string into a variable of the Date type based on the specified format string. If the time zone is not specifiedtimezone, obtain the time zone information based on the following rules:

  • UserhttpIn the script triggered by the request, the user time zone is used.
  • The tenant time zone is used in the script triggered by the scheduled task.
  • In the event-triggered script, use theUTCTime zone
ts
import { getOrganizationTimeZone } from 'context';
import { toDate } from 'date';

let date = toDate('2018-08-08 20:08:08', 'yyyy-MM-dd HH:mm:ss', getOrganizationTimeZone());

In the preceding example, the tenant organization time zone is used, and a time string is used.2018-08-08 20:08:08Converted into ajavascriptof theDateType instance.

With the above knowledge in mind, we can begin to discuss how a script that processes time can be executed consistently and successfully in different execution contexts. In general, there are three strategies:

Time string with time zone information

In the event triggering and scriptinghttpThe time character strings of input and output parameters such as requests contain the time zone information. For example:

ts
{
    "inDate": "2021-07-01T06:30:30+08:00",
    "inDatetime": "2021-07-01T06:30:30+08:00"    
}

AppCubeThe time zone information in the time string is used preferentially. However, this can only solve the problem of input and output parameters, not the problem.new DateThe time zone of the script standard library is incorrect. In addition, many interconnected third-party systems may not support the character string format with the time zone.

Determine different execution contexts and perform special processing.

contextThe module provides the following interfaces:

ts
/**
 *Script Execution Triggering Request Type
 */
export enum RequestType {
    /**
     *RESTful request of the login user
     */
    User = 0,
    /**
     *Event-triggered request
     */
    Event = 1,
    /**
     *Request triggered by a scheduled task
     */
    Task = 2
}

function getRequestType(): RequestType

Based on the execution context type of the script, the code performs different special processing on the time character string. The example is provided in the third case.

Use the tenant organization time zone in a unified manner.

Now we use thedateThe module rewrites the previous example script:

ts
import * as date from 'date';
import * as context from 'context';

export class Input {
    @action.param({ type: "Date" })
    inDate: Date;

    @action.param({ type: "Datetime" })
    inDatetime: Date;
}

export class Output {
    @action.param({ type: "Date" })
    outDate: Date;

    @action.param({ type: "Datetime" })
    outDatetime: Date;
}


const dateLayout = 'yyyy-MM-dd HH:mm:ss';

export class DateDemo {
    @action.method({ input: "Input", output: "Output", description: "do a operation" })
    run(input: Input): Output {
        let output = new Output();

        console.log("input date = ", input.inDate);

        let inDatetime = context2OrganizationTime(input.inDatetime);
        console.log("input datetime = ", inDatetime);

        output.outDate = newOrganizationTime("2021-07-01 06:30:30");
        output.outDatetime = newOrganizationTime("2021-07-01 06:30:30");

        console.log("output date = ", output.outDate);
        console.log("output datetime = ", output.outDatetime);

        return output;
    }
}

function newOrganizationTime(dateStr: string): Date {
    return date.toDate(dateStr, dateLayout, context.getOrganizationTimeZone())
}

function context2OrganizationTime(d: Date): Date {
    //Correction time input parameter, which may be incorrect in event-triggered execution scenarios.
    //This judgment processing is not required actually. However, adding this function can improve performance and reduce the number of time objects to be created.
    if (context.getRequestType() != context.RequestType.Event) {
        return d;
    }

    let dateStr = date.format(d, dateLayout, context.getTimeZone());
    return date.toDate(dateStr, dateLayout, context.getOrganizationTimeZone());
}

Compared with the previous version, the code in this version has the following changes:

  • For input parameters, callcontext2OrganizationTimeFunction to convert from the script execution context time zone to the tenant organization time zone
  • Use thedate.toDateIn place of thenew Date

The execution information of the script code of this version is as follows:

  • Request parameter
ts
{
    "inDate": "2021-07-01 06:30:30",
    "inDatetime": "2021-07-01 06:30:30"    
}
  • Script running output log
bash
0728 14:39:33.924|debug|vm[22]>>> Build #AppCube Core 1.3.7 on amd64
Built on 2021-07-27 20:14:22  
Commit #4c0246172d
0728 14:39:33.924|debug|vm[22]>>> node:  2
0728 14:39:33.924|debug|vm[22]>>> script:  my__orgdatedemo 1.0.1 DateDemo.run
0728 14:39:33.924|debug|vm[22]>>> locale:  zh_CN
0728 14:39:33.924|debug|vm[22]>>> timezone:  (GMT+08:00) China Standard Time (Asia/Shanghai)
0728 14:39:33.925|debug|vm[22]>>> input date =  2021-07-01T08:00:00+08:00 (my__orgdatedemo.ts:28)
0728 14:39:33.925|debug|vm[22]>>> input datetime =  2021-07-01T06:30:30+08:00 (my__orgdatedemo.ts:31)
0728 14:39:33.925|debug|vm[22]>>> output date =  2021-07-01T06:30:30+08:00 (my__orgdatedemo.ts:36)
0728 14:39:33.925|debug|vm[22]>>> output datetime =  2021-07-01T06:30:30+08:00 (my__orgdatedemo.ts:37)
  • Returned information
ts
{
    "outDate": "2021-07-01 08:00:00",
    "outDatetime": "2021-07-01 06:30:30"
}

The following information is displayed in the log:output datetime = 2021-07-01T06:30:30+08:00and return value"outDatetime": "2021-07-01 06:30:30"Informationally, the originalnew DateThe problem of adding eight hours is gone.

Secondary Log Informationinput datetime = 2021-07-01T06:30:30+08:00Come and see, putinDatetimeAfter the tenant organization time zone is changed, the result is still correct. The output is the same regardless of the scenario in which the script is executed.

The prerequisite for this solution is that the user time zone is the same as the tenant organization time zone.