Skip to content

Commit

Permalink
core: allow unix timestamp to be anchor time parameters
Browse files Browse the repository at this point in the history
Updates the date and range parsing to allow a unix timestamp
value to be an absolute time anchor for the time parameters.
This is mostly a convenience if generating the parameters via
substitution, but there is a need to have some offset.

For now the ISO date time patterns do not support the offset
behavior. The parsing is trickier because they already use
`-` for the year-month-day as well as for time zone offsets.
  • Loading branch information
brharrington committed Dec 4, 2024
1 parent bab3e62 commit 0fd4d09
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 13 deletions.
42 changes: 29 additions & 13 deletions atlas-core/src/main/scala/com/netflix/atlas/core/util/Strings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ object Strings {
/**
* Date relative to a given reference point.
*/
private val RelativeDate = """^([a-z]+)([\-+])(.+)$""".r
private val RelativeDate = """^([a-z]+)([\-+])([^\-+]+)$""".r

/**
* Named date such as `epoch` or `now`.
Expand All @@ -60,6 +60,11 @@ object Strings {
*/
private val UnixDate = """^([0-9]+)$""".r

/**
* Unix data in seconds since the epoch.
*/
private val UnixDateWithOp = """^([0-9]+)([\-+])([^\-+]+)$""".r

/**
* When parsing a timestamp string, timestamps after this point will be treated as
* milliseconds rather than seconds. This time if treated as millis represents
Expand Down Expand Up @@ -461,21 +466,13 @@ object Strings {
): ZonedDateTime = {
str match {
case RelativeDate(r, op, p) =>
op match {
case "-" => parseRefVar(refs, r).minus(parseDuration(p))
case "+" => parseRefVar(refs, r).plus(parseDuration(p))
case _ => throw new IllegalArgumentException("invalid operation " + op)
}
applyDateOffset(parseRefVar(refs, r), op, p)
case NamedDate(r) =>
parseRefVar(refs, r)
case UnixDate(d) =>
val t = d.toLong match {
case v if v <= secondsCutoff => Instant.ofEpochSecond(v)
case v if v <= millisCutoff => Instant.ofEpochMilli(v)
case v if v <= microsCutoff => ofEpoch(v, 1_000_000L, 1_000L)
case v => ofEpoch(v, 1_000_000_000L, 1L)
}
ZonedDateTime.ofInstant(t, tz)
parseUnixDate(d, tz)
case UnixDateWithOp(d, op, p) =>
applyDateOffset(parseUnixDate(d, tz), op, p)
case str =>
try IsoDateTimeParser.parse(str, tz)
catch {
Expand All @@ -484,6 +481,25 @@ object Strings {
}
}

private def parseUnixDate(d: String, tz: ZoneId): ZonedDateTime = {
val t = d.toLong match {
case v if v <= secondsCutoff => Instant.ofEpochSecond(v)
case v if v <= millisCutoff => Instant.ofEpochMilli(v)
case v if v <= microsCutoff => ofEpoch(v, 1_000_000L, 1_000L)
case v => ofEpoch(v, 1_000_000_000L, 1L)
}
ZonedDateTime.ofInstant(t, tz)
}

private def applyDateOffset(t: ZonedDateTime, op: String, p: String): ZonedDateTime = {
val d = parseDuration(p)
op match {
case "-" => t.minus(d)
case "+" => t.plus(d)
case _ => throw new IllegalArgumentException("invalid operation " + op)
}
}

private def ofEpoch(v: Long, f1: Long, f2: Long): Instant = {
Instant.ofEpochSecond(v / f1, (v % f1) * f2)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,20 @@ class StringsSuite extends FunSuite {
assertEquals(received, ref)
}

test("parseDate, unix with sub") {
val ref = ZonedDateTime.of(2012, 2, 1, 3, 0, 0, 0, ZoneOffset.UTC)
val refStr = "%d-3h".format(ref.toInstant.toEpochMilli / 1000)
val received = parseDate(refStr)
assertEquals(received, ref.minusHours(3))
}

test("parseDate, unix with add") {
val ref = ZonedDateTime.of(2012, 2, 1, 3, 0, 0, 0, ZoneOffset.UTC)
val refStr = "%d+3h".format(ref.toInstant.toEpochMilli / 1000)
val received = parseDate(refStr)
assertEquals(received, ref.plusHours(3))
}

test("parseDate, s=e-0h") {
val ref = ZonedDateTime.of(2012, 2, 1, 3, 0, 0, 0, ZoneOffset.UTC)
val expected = ZonedDateTime.of(2012, 2, 1, 3, 0, 0, 0, ZoneOffset.UTC)
Expand Down Expand Up @@ -712,6 +726,12 @@ class StringsSuite extends FunSuite {
}
}

test("range: unix time with op is not relative") {
val (s, e) = timeRange("e-5m", "1733292000+5m")
assertEquals(s, parseDate("2024-12-04T06:00:00Z").toInstant)
assertEquals(e, parseDate("2024-12-04T06:05:00Z").toInstant)
}

test("range: start relative to end") {
val (s, e) = timeRange("e-5m", "2018-07-24T00:05")
assertEquals(s, parseDate("2018-07-24").toInstant)
Expand Down

0 comments on commit 0fd4d09

Please sign in to comment.