Skip to content

feat(placeholder): Adds age() function for timestamp evaluation in placeholders#1550

Open
jkellerer wants to merge 7 commits intoTwiN:masterfrom
jkellerer:feat/age-function
Open

feat(placeholder): Adds age() function for timestamp evaluation in placeholders#1550
jkellerer wants to merge 7 commits intoTwiN:masterfrom
jkellerer:feat/age-function

Conversation

@jkellerer
Copy link

@jkellerer jkellerer commented Feb 13, 2026

Summary

#916

Implemented using https://github.com/markusmobius/go-dateparser as it parses the most common timestamp formats thus we don't need to specify a pattern for parsing.

Implemented without any 3rd party dependencies

Supported formats:

  • Unix timestamp (int64 & float64, seconds or milliseconds) e.g.: 1771235569, 17712355691231, 17712355691.231 & 1.7712355691231e12
  • Duration
  • Date formats like listed in time package with relaxed syntax for RFC3339 and additional support for access log formats (e.g. used by nginx)

Checklist

  • Tested and/or added tests to validate that the changes work as intended, if applicable.
  • Updated documentation in README.md, if applicable.

@github-actions github-actions bot added the feature New feature or request label Feb 13, 2026
@CherryDT
Copy link

Something doesn't seem to be working. According to the documentation of that dateparse thing, timestamps should work.

However, for an endpoint that returns lastUpdate: 1771548534964, with the check running at 2026/02/20 00:50:27 UTC = 1771548627000, I get a failure for age([BODY].lastUpdate) < 5m with ✗ age([BODY].lastUpdate) (6377274) < 5m...

However, it should be only a difference of 92036 and hence an age of 1m32s which is <5m!

@jkellerer
Copy link
Author

jkellerer commented Feb 20, 2026

@CherryDT: Just enhanced the tests to cover 3 variants of unix timestamps (and the tests succeeded). While time without zone or offset is interpreted as local time, this doesn't matter for unix timestamps which are UTC by definition (and the new tests cover this).

6377274 is 106 minutes... this doesn't seem to be explainable by timezone issues. Could it be that clocks are out of sync?

E.g. the following test covers your example from above and succeeds: d228f92

@CherryDT
Copy link

Hm, no, the times are fine, I also added a "check" for the raw value to see what is being compared, and it does not make sense.

✗ age([BODY].lastUpdate) (6377010) < 5m
✓ [BODY].lastUpdate (1771581792479) > 0 (0s)

Time on the server is OK:

ubuntu@ip-172-26-3-124:~$ date +%s%N | cut -b1-13
1771581916673

Should be only 2m difference:

> 1771581916673-1771581792479
124194

Also I think that number 6377010 is strange for another reason because other time values show in hours and minutes, why does age not?

✓ [DOMAIN_EXPIRATION] (4706h9m54s) > 72h

@CherryDT
Copy link

CherryDT commented Feb 20, 2026

Try yourself with this check:

  - name: timestamp-test
    interval: 10s
    url: "https://time.jakcodex.io/api/time"
    conditions:
      - "age([BODY].epoch) < 5m"
      - "[BODY].epoch > 0"
    ui:
      resolve-successful-conditions: true
Timestamp
2026-02-20 11:19:05
Response Time
353ms
Conditions
✗
age([BODY].epoch) (6377010) < 5m
✓
[BODY].epoch (1771582745811) > 0 (0s)

The "Timestamp" text displayed above is in my local browser time, which is UTC+1, so that would be 10:19:05 UTC = 1771582745000 which is correct. If you compare this to 1771582745811, you get a difference of 811 and not again 6377010.

(Noteworthy: Here we again get 6377010 even though now the timestamp is the current time, while before it was a few minutes behind... I mean, this time the real age should have been <1s, before it was 92s, 120s, etc., yet the output is still 6377010 or just a few milliseconds above or below it, so it can't be a simple offset!)

@CherryDT
Copy link

I found the problem and fixed it here: jkellerer#1

jkellerer and others added 2 commits February 20, 2026 14:28
@jkellerer
Copy link
Author

@CherryDT thanks a lot for the fix. I've merged it and integrated the changes in the already existing block to deal with formats the library doesn't parse directly.

@jkellerer
Copy link
Author

The following could be a showstopper: markusmobius/go-dateparser#20

Maybe the parsing lib needs to be switched or made optional.

@CherryDT
Copy link

Hm, true... for me it's OK but it could be a problem elsewhere... We don't need so many variations though, I feel like 95% of the use cases would be covered by just being able to parse UNIX timestamps in seconds or milliseconds (assuming seconds if the value is small enough - if this causes an issue for dates before February 1970, we'd need to allow selecting which one it is) and ISO8601 (note this, we probably have to use both). We could allow specifying a custom format if needed, just using time.parse then. I very rarely saw anything other than those three formats used in APIs.

@jkellerer
Copy link
Author

@CherryDT , yes I think the same. I'm about to update the PR with own parsing of the most common formats. Basically allow the ones we have in the test but remove the relative and localised.

- Refactored `parseAge` to support durations, unix timestamps and custom layouts directly.
- Added `dateparser.go` to handle date parsing with regex and custom formats.
- Modified tests in `placeholder_test.go` to reflect all changes.
- Removed external dependencies.
@jkellerer
Copy link
Author

@CherryDT , is the updated implementation working for you?

@CherryDT
Copy link

@CherryDT , is the updated implementation working for you?

Yes it works, thanks!

@nalgeon
Copy link

nalgeon commented Mar 17, 2026

@TwiN, could you please share your thoughts on this PR? I think it would be a really useful addition to Gatus.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants