CacheThings
CacheThing provides a fast, in-memory interface for storing and retrieving key-value data. It’s ideal for caching results from expensive or time-consuming operations, such as slow queries.
Because CacheThing operates in memory, its contents are cleared when ThingWorx restarts. In high-availability (HA) environments, each ThingWorx node maintains its own independent cache. As a result, cache entries created or updated on one node aren’t automatically shared with other nodes. This includes cache invalidation calls, such as PurgeCache, DeleteEntry, and DeleteEntryByKey.
You can use CacheThing to cache various types of data, including:
Results of expensive or frequent queries to Database Things
Results of expensive or frequent queries to ValueStreams or DataTables
Results of slow network calls, for example, calls made with ContentLoaderFunctions
Expensive calculated values
Contents of frequently used, small files from FileRepositories
Using CacheThing can significantly improve performance and reduce resource usage compared to accessing external systems like databases or network resources.
Usage Guidelines
CacheThing is not intended to be used as a permanent datastore. Any data cached in CacheThing should be reproducible from the original source.
Data in CacheThing can be either Expired or Evicted. Services that use CacheThing should be designed to retrieve data from the original source if the cache returns no result. See the examples for more details.
Configuration
Each CacheThing must be configured with a DataShape that includes a primary key. The primary key is used to retrieve entries from the cache.
Composite primary keys are supported, but they prevent the use of the ByKey convenience services Expiration Policy and Global Maximum Size listed below.
Expiration Policy
The Expiration Policy defines the Time To Live (TTL) for each cache entry. You can configure expiration time using the Cache Entry Expiration Time (Seconds) setting. After an entry expiration expires, retrieving the value returns an empty result, similar to if it were deleted from the cache.
Supported expiration policies:
Cache Entry Expiration Policy
Description
Never
The cache entry is never removed based on the amount of time it has spent in the cache. However, it can still be evicted if the cache reaches the maximum size configuration or by using the DeleteEntry service. The Cache Entry Expiration Time (Seconds) setting is ignored when the Never expiration policy is configured.
Expiration Time Since Last Accessed
The entry is removed if it hasn’t been read within the specified expiration time.
Expiration Time Since Created
The entry is removed after the specified time has passed since it was created.
Expiration Time Since Last Modified
The entry is removed if it hasn’t been modified within the specified expiration time.
Expiration Time Since Last Touched
The entry is removed if it hasn’t been accessed or modified within the specified expiration time.
For more information on Interface Expiration Policy, see Interface ExpiryPolicy.
Cache Maximum Size (Eviction)
Cache entries are automatically evicted (deleted) when the cache reaches the size specified in the Cache Maximum Size (MB) setting. The eviction process removes the least frequently used (LFU) entries to make room for new entries. Once an entry is evicted, retrieving it returns an empty result, similar to if it were deleted.
If a large entry is added, multiple smaller entries may be evicted to accommodate it. To avoid this, use the EstimateEntrySize service to check the size of an entry before adding it to the cache.
Cache Eviction and Expiration operate independently:
Eviction is based on cache size and usage frequency.
Expiration is based on time, as defined by the configured expiration policy.
Global Maximum Size
The Platform Subsystem includes a setting called Maximum Size shared between all CacheThings per ThingWorx node (MB). This setting limits the combined size of all CacheThings on a single node to prevent excessive memory usage. Disabled CacheThings are not included in this limit.
This configuration prevents the following actions:
Creating a new CacheThing that exceeds the maximum size
Updating a CacheThing’s Cache Maximum Size (MB) that exceeds the maximum size
Importing CacheThings (either as entity imports or as part of an extension) that exceed the maximum size, which causes the entire import to fail
Reducing the maximum size below the current combined Cache Maximum Size (MB) of all CacheThings
Activating a CacheThing that exceeds the global limit
Changing Cache Configuration
Any changes to a cache’s configuration automatically purge the cache to maintain internal consistency. Editing any of the following will purge the cache:
Data Shape—Includes modifying the configured DataShape, such as adding or removing fields.
Cache Entry Expiration Policy
Cache Entry Expiration Time (Seconds)
Cache Maximum Size (MB)
Cache Performs Loading of Missing Entries
Services
The following services are available on CacheThing. In high-availability (HA) environments, these services affect the cache on the ThingWorx node where the service is executed.
The *ByKey convenience services listed below are available only when the cache's DataShape has a single primary key. If the cache's DataShape includes multiple primary keys, use the full InfoTable input version of the service.
Service Name
Description
PutEntry
Adds an entry to the cache.
Input Value—One-row InfoTable using the CacheThing's configured DataShape.
GetEntry
Returns a one-row InfoTable result from the cache.
If the entry is not found (a cache miss), and the cache is not configured with Cache Performs Loading of Missing Entries , an empty InfoTable is returned. If the cache is configured with that option, the LoadEntry service is called automatically, and its result is returned.
Cache misses can occur if the entry was never added to the cache, was evicted, or expired.
Input Value—One-row InfoTable using the CacheThing's configured DataShape. Only the Primary Key values should be populated.
GetEntryByKey
Returns a one-row InfoTable result from the cache.
If the entry is not found, an empty InfoTable is returned. Cache misses can occur if the entry was never added to the cache, was evicted, or expired.
Input Value—String representing the primary key of the entry being retrieved. This service is available only if the DataShape has a single primary key. It works best with String primary keys but also supports most types with a clear String conversion.
LoadEntry
Used to automatically fetch cache data from the data source when a cache miss occurs.
This service must be overridden if the Cache Performs Loading of Missing Entries is enabled.
DeleteEntry
Deletes an entry from the cache.
In a high-availability environment, this only affects the node where the DeleteEntry service is called.
Input Value—One-row InfoTable using the CacheThing's configured DataShape. Only the Primary Key values should be populated.
DeleteEntryByKey
Deletes an entry from the cache.
In a high-availability environment, this only affects the node where the DeleteEntryByKey service is called.
Input Value—String representing the primary key of the entry being retrieved. This service is available only if the DataShape has a single primary key.
PurgeCache
Removes all cache entries.
In a high-availability environment, this only affects the node where the PurgeCache service is called.
GetDataShape
Returns the cache's configured DataShape.
SetDataShape
Sets the DataShape field in the cache's Configuration Table. Changing this value will automatically purge the cache.
GetEstimatedEntryCount
Returns the estimated number of entries in the cache. Ongoing PUT and DELETE operations, as well as Expiration or Evictions processes, may affect accuracy.
EstimateEntrySize
Returns the estimated size of an entry if it were added to the cache. This does not put the entry into the cache and does not require any entries already in the cache. This service can be useful to estimate an appropriate Cache Maximum Size (MB) or to prevent large entries from being added to the cache.
Input Value—One-row InfoTable using the CacheThing's configured DataShape.
Overriding the LoadEntry Service
To enable the Cache Performs Loading of Missing Entries (read-through caching), you must override the LoadEntry service on CacheThing. This service is called internally when a cache miss occurs, allowing the cache to retrieve and store data from the source.
Calling LoadEntry service directly does not add entries to the cache. Entries are only added when LoadEntry is invoked internally by GetEntry during a cache miss.
Input
The input is a one-row InfoTable that matches the parameters of the corresponding GetEntry call. Typically, only the primary key fields are populated.
Service Implementation
The LoadEntry service should retrieve or generate the value to be cached from the data source. This could include database queries, web service calls, or generated values. Avoid updating application state, such as properties or files, within the service.
Do not call other caching services, such as GetEntry or PutEntry, from within LoadEntry. The cache automatically handles storing the returned value after a cache miss.
Output
The output must be an InfoTable with the same DataShape as the input. Populate the non-primary key fields with the retrieved data. Any exceptions are propagated to the calling GetEntry call.
It is best practice to create a new InfoTable for the Output result InfoTable. While it may be possible to reuse and populate the input values InfoTable, doing so can cause unexpected behavior due to limitations in how InfoTables handle certain BaseTypes in Javascript. See the examples for more details. using LoadEntry.
Read-Through Caching
Selecting the Cache Performs Loading of Missing Entries checkbox enables read-through caching. In this mode, the cache automatically loads missing entry values using the LoadEntry service on the CacheThing when a cache miss occurs.
To support this behavior, you must override the LoadEntry service on CacheThing to query the source data and return the appropriate value. When the GetEntry service does not find a value in the cache, the cache calls the LoadEntry service to retrieve the value.
Read-through caching can help mitigate thundering herd scenarios. If multiple parallel GetEntry requests are made for the same key before it is populated, the backing LoadEntry service is only called once. After the LoadEntry call completes, all pending GetEntry requests return the retrieved value.
See the Overriding LoadEntry Service section above for details providing a LoadEntry service implementation.
Metrics
Several cache-related metrics are available. In high-availability (HA) environments, caches are independent for each node. Metrics should be processed per node using the platform and cache_name labels.
The following cache metrics are available:
Cache Metrics
Description
thingworx_cache_hit_rate
Ratio of cache requests that resulted in an entry being found in the cache
thingworx_cache_hits
Number of times cache lookup methods have returned a cached value
thingworx_cache_request_count
Number of times cache lookup methods have returned either a cached or uncached value
thingworx_cache_miss_rate
Ratio of cache lookup methods that have returned an uncached (newly loaded) value or null
thingworx_cache_misses
Number of times cache lookup methods have returned an uncached (newly loaded) value or null
thingworx_cache_eviction_count
Number of times an entry has been evicted
thingworx_cache_average_load_penalty
Average time spent loading new values
thingworx_cache_weighted_size
Approximate current size of the cache in bytes
thingworx_cache_max_weight
Maximum size of the cache in bytes before eviction
thingworx_cache_estimated_entry_count
Estimated number of entries in the cache
thingworx_cache_global_max_size
Global maximum configured size for all caches in bytes
Alerts (Available in PTC Cloud Hosted Environments)
The following alerts are available in PTC Cloud Hosted Environments:
thingworxEntityCacheMissRate—Alerts when a cache's miss rate exceeds 80% for 1 hour.
thingworxEntityCacheEvictions—Alerts when a cache's eviction rate exceeds 80% for 30 minutes.
Examples and Best Practices
Implementing services that use CacheThing effectively requires attention to detail. Consider the following best practices:
The cache is cleared when the ThingWorx node restarts.
Entries may be evicted or expire from the cache without user action. Do not assume that entires will remain in the cache indefinitely.
Use cache metrics to validate that cache sizing is appropriate.
Avoid storing all data under a single entry or creating very large cache entries.
In high-availability (HA) environments, each node has an independent cache. Adding, deleting, or purging a cache on one node does not affect all ThingWorx nodes.
When Cache Performs Loading of Missing Entries is disabled (non-read-through cache):
1. Retrieve an entry from the cache using the primary key.
2. Check if the result is populated (the returned InfoTable has one row).
3. If the result is found, use it.
4. If the result is not found:
Fetch or generate the required entry from the source data (for example, Database Thing, DataTable, or ContentLoader).
Add the entry to the cache using PutEntry.
Return the newly cached entry.
When Cache Performs Loading of Missing Entries is enabled (read-through cache):
1. Override the LoadEntry service on the CacheThing.
2. Retrieve an entry from the cache using the primary key.
If the entry is not found, LoadEntry is automatically called to populate the value.
Example Using TimesTwo Service 
The TimesTwo service takes a number, multiplies it by 2, and stores the result in a cache. To set this up, follow these steps:
1. Create a DataShape (TimesTwoDataShape) with the following fields: operand: (LONG, primaryKey) and timesTwoValue: (LONG).
2. Create a CacheThing (MyTimesTwoCache) with the configured TimesTwoDataShape.
3. Add the TimesTwo service to MyTimesTwoCache:
Input—x (a LONG number)
Output—result (a LONG number)
// Input is x as a LONG
// Output is result as LONG

// MyTimesTwoCache CacheThing is configured with a DataShape with two fields:
// operand: (LONG, primaryKey)
// timesTwoValue: (LONG)

result = -1;

// Check the cache first, to see if we have already calculated x*2
// GetEntryByKey is a convenience method that takes the String value the configured DataShape's Primary Key ("key" in this example)
// cacheResult is an InfoTable with the DataShape of the cache
let cacheResult = Things["MyTimesTwoCache"].GetEntryByKey({ operand: x.toString() });

if (cacheResult.getRowCount() === 0) {
// Cache didn't have a result, so calculate it
let timesTwo = x*2;

// Put the result in the cache for the next time we need this multiple
// operand and value are from the Cache's configured DataShape
let cacheInput = getInfoTableForPut(x, timesTwo);
Things["MyTimesTwoCache"].PutEntry({
values: cacheInput
});

// Turn ScriptLogger to debug mode to see the logger messages
logger.debug("PutEntry the following entry into the MyTimesTwoCache: {operand: " + x + ", timesTwoValue: " + timesTwo + "}");

// Set the global result Output variable
result = timesTwo;
} else {
// If results are found, they are always in the first row of the InfoTable
let row = cacheResult.getRow(0);
logger.debug("Found the following entry already in MyTimesTwoCache: {operand: " + row.operand + ", timesTwoValue: " + row.timesTwoValue + "}");

result = row.timesTwoValue;
}

function getInfoTableForPut(operand, timesTwoValue) {
let infoTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: Things["MyTimesTwoCache"].GetDataShape()
});
infoTable.AddRow({"operand": operand, "timesTwoValue ": timesTwoValue });
return infoTable;
}
Example Caching Service Wrapper 
This example demonstrates how to manually cache the results of the QueryImplementingThingsV2 service.
1. Create a DataShape named Cached_QueryImplementingThingsV2_DataShape with the following fields:
maxItems: (NUMBER, primaryKey)
nameMask: (STRING, primaryKey)
query: (QUERY, primaryKey)
isSortFirst: (BOOLEAN, primaryKey)
result: (INFOTABLE, DataShape RootEntityListV2)
2. Create a CacheThing named Cached_QueryImplementingThingsV2 and assign Cached_QueryImplementingThingsV2_DataShape as its DataShape.
3. To observe cache expiration behavior, configure a short ExpirationTime and set an ExpirationPolicy other than Never on the Cached_ReadThrough_QueryImplementingThingsV2 Thing.
4. Create a service named CachingQueryImplementingThingsV2 with the following parameters:
Inputs
maxItems: (NUMBER, required)
nameMask: (STRING, required)
query: (QUERY, required)
isSortFirst: (BOOLEAN, required)
Output result: (INFOTABLE, DataShape RootEntityListV2)
/* 
This Service queries QueryImplementingThingsV2 and caches the results as needed
Service Name: CachingQueryImplementingThingsV2 (Overridden on CacheThing)
Input: values InfoTable with Cached_QueryImplementingThingsV2_DataShape
Cached_QueryImplementingThingsV2_DataShape:
maxItems: NUMBER, Primary Key
nameMask: STRING, Primary Key
query: QUERY, Primary Key
isSortFirst: BOOLEAN, Primary Key
result: INFOTABLE, DataShape RootEntityListV2 (NOT a Primary Key!)
*/

let cacheEntryInfoTable = getCacheEntryInfoTable();
cacheEntryInfoTable.AddRow({
maxItems: maxItems /* NUMBER */,
nameMask: nameMask /* STRING */,
query: query /* QUERY */,
isSortFirst: isSortFirst /* BOOLEAN */
});

let getEntryResult = me.GetEntry({values: cacheEntryInfoTable});

if (getEntryResult.getRowCount() === 0) {
// Cache didn't have results, so call QueryImplementingThingsV2
let queryResult = ThingTemplates["GenericThing"].QueryImplementingThingsV2({
maxItems: maxItems /* NUMBER */,
nameMask: nameMask /* STRING */,
query: query /* QUERY */,
isSortFirst: isSortFirst /* BOOLEAN */
});

// Add the result to the cache, with the primaryKey parameters used to uniquely identify the query.
let newCacheEntryInfoTable = getCacheEntryInfoTable();
newCacheEntryInfoTable.AddRow({
maxItems: maxItems /* NUMBER */,
nameMask: nameMask /* STRING */,
query: query /* QUERY */,
isSortFirst: isSortFirst /* BOOLEAN */,
result: queryResult /* INFOTABLE */
});
me.PutEntry({
values: newCacheEntryInfoTable
});
logger.debug("Entry not found in Cache, retrieved from QueryImplementingThingsV2:" + queryResult.ToJSON());
result = queryResult;
} else {
logger.debug("Found entry from Cache:" + getEntryResult.ToJSON());
let row = getEntryResult.getRow(0);
result = row.result;
}


function getCacheEntryInfoTable() {
return Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: "Cached_QueryImplementingThingsV2_DataShape"
});
}
Example Caching Service Wrapper as Read-Through Cache 
This example demonstrates how to automatically cache the results of the QueryImplementingThingsV2 service using a read-through cache. After overriding the LoadEntry service, you can use the GetEntry service to automatically load QueryImplementingThingsV2 query results into the cache.
1. Create a DataShape named Cached_QueryImplementingThingsV2_DataShape with the following fields:
maxItems: (NUMBER, primaryKey)
nameMask: (STRING, primaryKey)
query: (QUERY, primaryKey)
isSortFirst: (BOOLEAN, primaryKey)
result: (INFOTABLE, DataShape RootEntityListV2)
2. Create a CacheThing named Cached_ReadThrough_QueryImplementingThingsV2. Assign the Cached_QueryImplementingThingsV2_DataShape as its DataShape and enable the Cache Performs Loading of Missing Entries option.
3. To observe cache expiration behavior, configure a short ExpirationTime and set an ExpirationPolicy other than Never on the Cached_ReadThrough_QueryImplementingThingsV2 Thing.
4. Go to the Services tab, and select Override next to the LoadEntry service, with the following example:
/* 
This Service simply proxies a query to QueryImplementingThingsV2 for Read Through caching
Service Name: LoadEntry (Overridden on CacheThing)
Input: values InfoTable with Cached_QueryImplementingThingsV2_DataShape
Cached_QueryImplementingThingsV2_DataShape:
maxItems: NUMBER, Primary Key
nameMask: STRING, Primary Key
query: QUERY, Primary Key
isSortFirst: BOOLEAN, Primary Key
result: INFOTABLE, DataShape RootEntityListV2 (NOT a Primary Key!)
*/

let queryResult = ThingTemplates["GenericThing"].QueryImplementingThingsV2({
maxItems: values.maxItems /* NUMBER */,
nameMask: values.nameMask /* STRING */,
query: values.query /* QUERY */,
isSortFirst: values.isSortFirst /* BOOLEAN */
});

// Copy the results into a new InfoTable.
// This prevents some internal ThingWorx casting issues with some InfoTables
let resultTable = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: me.GetDataShape() // Returns Cached_QueryImplementingThingsV2_DataShape
});

resultTable.AddRow({
maxItems: values.maxItems /* NUMBER {"defaultValue":500} */,
nameMask: values.nameMask /* STRING */,
query: values.query /* QUERY */,
isSortFirst: values.isSortFirst /* BOOLEAN */,
result: queryResult
});

let result = resultTable;
Example Using DataTableQuery 
This example demonstrates how to use a working BetweenDatesDataTableQuery service to find the largest value in a DataTable between two dates and store the results in a cache. Unique calls to CachingBetweenDatesDataTableQuery initially query the backing DataTable, but subsequent identical calls use the cache instead.
To set this up, follow these steps:
1. Create a DataShape (TimeValueDataShape), with the following fields: longKey: (LONG, primaryKey) and dateKey: (DATETIME, primaryKey).
2. Create a DataTable (DemoDataTable) with the TimeValueDataShape DataShape.
3. Create a DataShape (TimeSpanValueCacheDataShape) with the following fields: startDate: (DATETIME, primaryKey) , endDate: (DATETIME, primaryKey), and longValue: (LONG).
4. Create a CacheThing (CacheThingDemo) with the configured TimeSpanValueCacheDataShape DataShape.
5. Create the following services on CacheThingDemo:
CachingBetweenDatesDataTableQuery — Inputs: StartDate and EndDate as DATETIME. Outputs: result as LONG
BetweenDatesDataTableQuery —Inputs: StartDate and EndDate as DATETIME. Outputs: result as LONG
* 
This service automatically populates demo data into the DataTable in the queried date range.
6. Optional. Configure a short ExpirationTime with an ExpirationPolicy other than Never on the CacheThingDemo to observe entries being Expired from the cache.
CachingBetweenDatesDataTableQuery
/* Reads the Cached results of BetweenDatesDataTableQuery Service.
If Cache doesn't have the result, call BetweenDatesDataTableQuery and load results into Cache.

Service Name: CachingBetweenDatesDataTableQuery
Inputs: StartDate[DATETIME, required], EndDate[DATETIME, required]
Output: LONG
*/

// This example uses CacheThing.GetEntry, which requires a one-row infoTable populated with the CacheThing's DataShapes primaryKey getEntryByKey
let cacheEntryInfoTable = getCacheEntryInfoTable();
cacheEntryInfoTable.AddRow({"startDate": StartDate, "endDate": EndDate});

let getEntryResult = Things["CacheThingDemo"].GetEntry({
values: cacheEntryInfoTable
});

if (getEntryResult.getRowCount() === 0) {
// Cache didn't have results, so go to backing DataTable, by calling the BetweenDatesDataTableQuery Service
let queryResult = Things["CacheThingDemo"].BetweenDatesDataTableQuery({
StartDate: StartDate,
EndDate: EndDate
});

// Turn ScriptLogger to debug mode to see the logger messages
logger.debug("PutEntry called for BetweenDatesQueryCache: {StartDate: " + StartDate + ", EndDate: " + EndDate + " value: " + queryResult + "}");
if (!queryResult || queryResult < 0) {
// BetweenDatesDataTableQuery should always populate with demo data if no data is in the StartDate - EndDate range
throw new Error("BetweenDatesDataTableQuery did not return results");
}
let newCacheEntryInfoTable = getCacheEntryInfoTable();
newCacheEntryInfoTable.AddRow({"startDate": StartDate, "endDate": EndDate, "longValue": queryResult});
Things["CacheThingDemo"].PutEntry({
values: newCacheEntryInfoTable
});
result = queryResult;
} else {
let row = getEntryResult.getRow(0);
logger.debug("Found the following entry already in Cache : {StartDate: " + row.startDate + ", EndDate: " + row.endDate + ", LongValue: " + row.longValue + "}");
result = row.longValue;
}

function getCacheEntryInfoTable() {
return Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: "TimeSpanValueCacheDataShape"
});
}
BetweenDatesDataTableQuery
/*  This service queries a range of dates, based on the dateKey column, and returns the greatest longKey value found.
If there are no entries between StartDate and EndDate this Service dynamically populates demo data into demoDataTable, and returns that result.

Service Name: BetweenDatesDataTableQuery
Inputs: StartDate[DATETIME, required], EndDate[DATETIME, required]
Output: LONG
*/
if (StartDate >= EndDate) {
throw Error("StartDate must be before EndDate");
}

let dataTable = Things["DemoDataTable"];

let queryResult = queryGreatestLongKeyBetweenDates(dataTable, StartDate, EndDate);

if (queryResult.getRowCount() === 0) {
// If we don't get any results, first populate the date range with random values, then retry
// Turn ScriptLogger to debug mode to see the logger messages
logger.debug("No values found between [" + StartDate + "] and [" + EndDate + "], populating 100 demo values and re-querying");
populateDemoDataTable(dataTable, StartDate, EndDate);
// Re-query the now populated DateTable
queryResult = queryGreatestLongKeyBetweenDates(dataTable, StartDate, EndDate);
result = queryResult.getRow(0).get("longKey");
} else {
result = queryResult.getRow(0).get("longKey");
}

// Queries the DataTable for the largest longKey column value between the given dates
// Returns: One-row InfoTable with the found row.
function queryGreatestLongKeyBetweenDates(dateTable, startDate, endDate) {
let greatestBetweenDatesQuery = {
"filters": {
"type": "Between",
"fieldName": "dateKey",
"from": startDate,
"to": endDate
},
"sorts": [{
"fieldName": "longKey",
"isAscending": false
}]
};

// Run the Query to find the greatest Value Between Two Dates
let queryResult = dataTable.QueryDataTableEntries({
maxItems: 1,
query: greatestBetweenDatesQuery,
});

logger.debug("Queried between [" + StartDate + "] and [" + EndDate + "], found:" + queryResult.toJSON());
return queryResult;
}

function populateDemoDataTable(dataTable, startDate, endDate) {
// add 100 random entries between startDate and endDate
for (let entry = 0; entry < 100; entry++) {
let randomTimeBetween = randomDate(startDate, endDate);
let entry = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName: "InfoTable",
dataShapeName: "TimeValueDataShape"
});
entry.AddRow({
"longKey": Math.floor(Math.random() * 1000),
"dateKey": randomTimeBetween
});
Things["DemoDataTable"].AddDataTableEntry({
values: entry /* INFOTABLE */ ,
});
}
}

function randomDate(startDate, endDate) {
let randomTime = Math.random() * (endDate.getTime() - startDate.getTime()) + startDate.getTime();
return new Date(randomTime);
}
Was this helpful?