Skip to content

Commit

Permalink
Fix writing of numeric timestamps with negative epochSecond values …
Browse files Browse the repository at this point in the history
…+ add support for writing numeric timestamps as JSON keys
  • Loading branch information
plokhotnyuk committed Jan 2, 2025
1 parent 375a831 commit b5439e0
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,39 @@ final class JsonWriter private[jsoniter_scala](
writeParenthesesWithColon()
}

/**
* Writes a timestamp value as a JSON key.
*
* @param epochSecond the epoch second of the timestamp to write
* @param nano the nanoseconds of the timestamp to write
* @throws JsonWriterException if the nanoseconds value is less than 0 or greater than 999999999
*/
def writeTimestampKey(epochSecond: Long, nano: Int): Unit = {
if (nano < 0 || nano > 999999999) encodeError("illegal nanoseconds value: " + nano)
writeOptionalCommaAndIndentionBeforeKey()
writeBytes('"')
var pos = ensureBufCapacity(30)
val buf = this.buf
var es = epochSecond
var ns = nano
if (es < 0 & ns > 0) {
es += 1
ns = 1000000000 - ns
if (es == 0) {
buf(pos) = '-'
pos += 1
}
}
pos = writeLong(es, pos, buf)
if (ns != 0) {
val dotPos = pos
pos = writeSignificantFractionDigits(ns, pos + 9, pos, buf, digits)
buf(dotPos) = '.'
}
this.count = pos
writeParenthesesWithColon()
}

/**
* Writes a `BigInt` value as a JSON key.
*
Expand Down Expand Up @@ -766,10 +799,20 @@ final class JsonWriter private[jsoniter_scala](
writeOptionalCommaAndIndentionBeforeValue()
var pos = ensureBufCapacity(30)
val buf = this.buf
pos = writeLong(epochSecond, pos, buf)
if (nano != 0) {
var es = epochSecond
var ns = nano
if (es < 0 & ns > 0) {
es += 1
ns = 1000000000 - ns
if (es == 0) {
buf(pos) = '-'
pos += 1
}
}
pos = writeLong(es, pos, buf)
if (ns != 0) {
val dotPos = pos
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
pos = writeSignificantFractionDigits(ns, pos + 9, pos, buf, digits)
buf(dotPos) = '.'
}
this.count = pos
Expand Down Expand Up @@ -906,10 +949,20 @@ final class JsonWriter private[jsoniter_scala](
val buf = this.buf
buf(pos) = '"'
pos += 1
pos = writeLong(epochSecond, pos, buf)
if (nano != 0) {
var es = epochSecond
var ns = nano
if (es < 0 & ns > 0) {
es += 1
ns = 1000000000 - ns
if (es == 0) {
buf(pos) = '-'
pos += 1
}
}
pos = writeLong(es, pos, buf)
if (ns != 0) {
val dotPos = pos
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
pos = writeSignificantFractionDigits(ns, pos + 9, pos, buf, digits)
buf(dotPos) = '.'
}
buf(pos) = '"'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,39 @@ final class JsonWriter private[jsoniter_scala](
writeParenthesesWithColon()
}

/**
* Writes a timestamp value as a JSON key.
*
* @param epochSecond the epoch second of the timestamp to write
* @param nano the nanoseconds of the timestamp to write
* @throws JsonWriterException if the nanoseconds value is less than 0 or greater than 999999999
*/
def writeTimestampKey(epochSecond: Long, nano: Int): Unit = {
if (nano < 0 || nano > 999999999) encodeError("illegal nanoseconds value: " + nano)
writeOptionalCommaAndIndentionBeforeKey()
writeBytes('"')
var pos = ensureBufCapacity(30)
val buf = this.buf
var es = epochSecond
var ns = nano
if (es < 0 & ns > 0) {
es += 1
ns = 1000000000 - ns
if (es == 0) {
buf(pos) = '-'
pos += 1
}
}
pos = writeLong(es, pos, buf)
if (ns != 0) {
val dotPos = pos
pos = writeSignificantFractionDigits(ns, pos + 9, pos, buf, digits)
buf(dotPos) = '.'
}
this.count = pos
writeParenthesesWithColon()
}

/**
* Writes a `BigInt` value as a JSON key.
*
Expand Down Expand Up @@ -709,10 +742,20 @@ final class JsonWriter private[jsoniter_scala](
writeOptionalCommaAndIndentionBeforeValue()
var pos = ensureBufCapacity(30)
val buf = this.buf
pos = writeLong(epochSecond, pos, buf)
if (nano != 0) {
var es = epochSecond
var ns = nano
if (es < 0 & ns > 0) {
es += 1
ns = 1000000000 - ns
if (es == 0) {
buf(pos) = '-'
pos += 1
}
}
pos = writeLong(es, pos, buf)
if (ns != 0) {
val dotPos = pos
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
pos = writeSignificantFractionDigits(ns, pos + 9, pos, buf, digits)
buf(dotPos) = '.'
}
this.count = pos
Expand Down Expand Up @@ -843,10 +886,20 @@ final class JsonWriter private[jsoniter_scala](
val buf = this.buf
buf(pos) = '"'
pos += 1
pos = writeLong(epochSecond, pos, buf)
if (nano != 0) {
var es = epochSecond
var ns = nano
if (es < 0 & ns > 0) {
es += 1
ns = 1000000000 - ns
if (es == 0) {
buf(pos) = '-'
pos += 1
}
}
pos = writeLong(es, pos, buf)
if (ns != 0) {
val dotPos = pos
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
pos = writeSignificantFractionDigits(ns, pos + 9, pos, buf, digits)
buf(dotPos) = '.'
}
buf(pos) = '"'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,39 @@ final class JsonWriter private[jsoniter_scala](
writeParenthesesWithColon()
}

/**
* Writes a timestamp value as a JSON key.
*
* @param epochSecond the epoch second of the timestamp to write
* @param nano the nanoseconds of the timestamp to write
* @throws JsonWriterException if the nanoseconds value is less than 0 or greater than 999999999
*/
def writeTimestampKey(epochSecond: Long, nano: Int): Unit = {
if (nano < 0 || nano > 999999999) encodeError("illegal nanoseconds value: " + nano)
writeOptionalCommaAndIndentionBeforeKey()
writeBytes('"')
var pos = ensureBufCapacity(30)
val buf = this.buf
var es = epochSecond
var ns = nano
if (es < 0 & ns > 0) {
es += 1
ns = 1000000000 - ns
if (es == 0) {
buf(pos) = '-'
pos += 1
}
}
pos = writeLong(es, pos, buf)
if (ns != 0) {
val dotPos = pos
pos = writeSignificantFractionDigits(ns, pos + 9, pos, buf, digits)
buf(dotPos) = '.'
}
this.count = pos
writeParenthesesWithColon()
}

/**
* Writes a `BigInt` value as a JSON key.
*
Expand Down Expand Up @@ -709,10 +742,20 @@ final class JsonWriter private[jsoniter_scala](
writeOptionalCommaAndIndentionBeforeValue()
var pos = ensureBufCapacity(30)
val buf = this.buf
pos = writeLong(epochSecond, pos, buf)
if (nano != 0) {
var es = epochSecond
var ns = nano
if (es < 0 & ns > 0) {
es += 1
ns = 1000000000 - ns
if (es == 0) {
buf(pos) = '-'
pos += 1
}
}
pos = writeLong(es, pos, buf)
if (ns != 0) {
val dotPos = pos
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
pos = writeSignificantFractionDigits(ns, pos + 9, pos, buf, digits)
buf(dotPos) = '.'
}
this.count = pos
Expand Down Expand Up @@ -843,10 +886,20 @@ final class JsonWriter private[jsoniter_scala](
val buf = this.buf
buf(pos) = '"'
pos += 1
pos = writeLong(epochSecond, pos, buf)
if (nano != 0) {
var es = epochSecond
var ns = nano
if (es < 0 & ns > 0) {
es += 1
ns = 1000000000 - ns
if (es == 0) {
buf(pos) = '-'
pos += 1
}
}
pos = writeLong(es, pos, buf)
if (ns != 0) {
val dotPos = pos
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
pos = writeSignificantFractionDigits(ns, pos + 9, pos, buf, digits)
buf(dotPos) = '.'
}
buf(pos) = '"'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -737,18 +737,21 @@ class JsonWriterSpec extends AnyWordSpec with Matchers with ScalaCheckPropertyCh
"JsonWriter.writeVal for a timestamp" should {
"write timestamp values" in {
def check(epochSecond: Long, nano: Int): Unit = {
val s = BigDecimal({
val es = java.math.BigDecimal.valueOf(epochSecond)
if (nano == 0) es
else es.add(java.math.BigDecimal.valueOf({
if (epochSecond < 0) -nano
else nano
}.toLong, 9).stripTrailingZeros)
}).toString
withWriter(_.writeTimestampVal(epochSecond, nano)) shouldBe s
withWriter(_.writeTimestampValAsString(epochSecond, nano)) shouldBe s""""$s""""
val s =
if (nano == 0) epochSecond.toString
else BigDecimal({
java.math.BigDecimal.valueOf(epochSecond)
.add(java.math.BigDecimal.valueOf(nano.toLong, 9).stripTrailingZeros)
}).toString
if (!s.contains("E")) {
withWriter(_.writeTimestampVal(epochSecond, nano)) shouldBe s
withWriter(_.writeTimestampValAsString(epochSecond, nano)) shouldBe s""""$s""""
withWriter(_.writeTimestampKey(epochSecond, nano)) shouldBe s""""$s":"""
}
}

check(-1L, 123456789)
check(-1L, 0)
check(1L, 0)
check(1L, 900000000)
check(1L, 990000000)
Expand Down
2 changes: 1 addition & 1 deletion version.sbt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ThisBuild / version := "2.32.1-SNAPSHOT"
ThisBuild / version := "2.33.0-SNAPSHOT"

0 comments on commit b5439e0

Please sign in to comment.