Resolving system.tag.storeTagHistory Timestamp Errors

Karen Mitchell4 min read
Other ManufacturerOther TopicTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Why does storeTagHistory reject a numeric or string Timestamp?

A blank Timestamp works because the function stamps each sample with the current time, and a supplied value fails because the argument is typed as a list of Date objects. The milliseconds-since-1970 number you see in the history database is the storage form, not the input form. Jython hands the gateway whatever object you build, and neither an integer nor a string is converted to a Date on the way in.

The typical failed attempt is a list of strings in various standard formats. No string format works, because the problem is the object type, not the text layout. Every attempt to find the right format fails until the value becomes a Date.

Which Timestamp inputs work and which fail?

Timestamp argument Result Reason
Omitted or blank Runs; sample stored at call time Function defaults to now, stored as milliseconds since 1970
Integer milliseconds since 1970 Error A number is not a Date object
List of strings, any format Error Strings are not parsed by the function
A single Date, not in a list Rejected The argument is a list; the square brackets are the critical part
[system.date.now()] Runs List containing a Date object
[date] built with system.date.getDate and system.date.setTime Runs List containing an arbitrary Date object

Which way should you build the Date objects?

Build Dates with the system.date.* functions and pick the function by what your source data looks like. Two approaches cover the cases.

Source data Conversion Precision Notes
Date parts (year, month, day, h, m, s) system.date.getDate then system.date.setTime Seconds Month is 0-indexed in getDate
Epoch milliseconds system.date.fromMillis Milliseconds Keeps sub-second resolution; confirm the function exists in your version's script help
Timestamp strings system.date.parse with a format pattern Per pattern Convert every string first, then wrap the results in a list

Use getDate/setTime when you assemble a time from discrete fields and one-second resolution is enough. Use fromMillis when the samples already carry epoch milliseconds, since it avoids splitting the value into parts and keeps the milliseconds. Use parse only when the source is text, and treat it as the first stage of the same pipeline: string to Date, then Date into the list.

How do you build the call?

  1. Convert each source timestamp to a Date object. Do not pass the raw string or number.
  2. Collect the Dates into a Jython list, one Date per sample you are storing.
  3. Pass that list as the Timestamp argument and leave every other argument as it was in the working blank-timestamp call.
date = system.date.getDate(2018, 6, 29)   # year, month (0-indexed), day
date = system.date.setTime(date, 1, 37, 44)  # hours, minutes, seconds

system.tag.storeTagHistory(<other arguments unchanged>, [date])

For several samples, build the list in a loop so the position of each Date matches the position of its path and value in the other list arguments:

dates = []
for row in sourceRows:
    dates.append(system.date.fromMillis(row[0]))

system.tag.storeTagHistory(<other arguments unchanged>, dates)

Keep the Date list the same length as the values list. A one-element list holding a single Date is still a list and satisfies the argument type.

What still puts samples at the wrong time?

Symptom in the history data Cause Fix
Sample lands one month later than intended getDate month is 0-indexed, so 6 is July, not June Subtract 1 from the calendar month before the call
Backfilled rows all stamped at run time Timestamp argument omitted, so the function used now Pass the Date list
Sub-second detail lost setTime takes hours, minutes, seconds only Build the Date from epoch milliseconds with system.date.fromMillis
Parsed strings shifted by a fixed number of hours Text has no zone, so it is interpreted in the script's default time zone Parse with a format that matches the source and check the gateway zone
Error persists after conversion A string or number is still inside the list Print type(item) for each element before the call

How do you confirm the rows landed at the supplied times?

  1. In the Script Console, print each element of the list and check it displays as a date, not a string or number.
  2. Run the call with a single sample and a timestamp well away from the current time, so a fallback to now is obvious.
  3. Query the history for that window, for example with system.tag.queryTagHistory or a chart bound to the same history provider, and compare the returned timestamp against the Date you supplied.
  4. Repeat with the full batch and confirm the row count in the window equals the number of Dates in the list.

FAQ

Why does storeTagHistory throw an error when I pass milliseconds since 1970?

The Timestamp argument takes a list of Date objects, and an integer is not a Date. Convert the value with system.date.fromMillis and wrap the result in a list.

Why does storeTagHistory need square brackets around the timestamp?

The argument is a list even when you store one sample, so a bare Date is rejected. Write [date] for a single sample.

Why does my backfilled sample land in the wrong month?

system.date.getDate counts months from 0, so getDate(2018, 6, 29) is a July date. Pass the calendar month minus 1.

Why does leaving Timestamp blank write the current time?

With no timestamp supplied, the function stamps the sample with now and stores it as milliseconds since 1970. Historical backfill needs an explicit Date list to keep the original sample times.

Back to blog