Skip to content

Commit 4f1f650

Browse files
committed
Add an ability to write timestamps in a numeric format
1 parent 1531ffb commit 4f1f650

File tree

5 files changed

+173
-1
lines changed

5 files changed

+173
-1
lines changed

jsoniter-scala-core/js/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/core/JsonWriter.scala

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,27 @@ final class JsonWriter private[jsoniter_scala](
754754
writeDouble(x)
755755
}
756756

757+
/**
758+
* Writes a timestamp value as a JSON value.
759+
*
760+
* @param epochSecond the epoch second of the timestamp to write
761+
* @param nano the nanoseconds of the timestamp to write
762+
* @throws JsonWriterException if the nanoseconds value is less than 0 or greater than 999999999
763+
*/
764+
def writeTimestampVal(epochSecond: Long, nano: Int): Unit = {
765+
if (nano < 0 || nano > 999999999) encodeError("illegal nanoseconds value: " + nano)
766+
writeOptionalCommaAndIndentionBeforeValue()
767+
var pos = ensureBufCapacity(30)
768+
val buf = this.buf
769+
pos = writeLong(epochSecond, pos, buf)
770+
if (nano != 0) {
771+
val dotPos = pos
772+
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
773+
buf(dotPos) = '.'
774+
}
775+
this.count = pos
776+
}
777+
757778
/**
758779
* Writes a `BigDecimal` value as a JSON string value.
759780
*
@@ -871,6 +892,31 @@ final class JsonWriter private[jsoniter_scala](
871892
writeBytes('"')
872893
}
873894

895+
/**
896+
* Writes a timestamp value as a JSON string value.
897+
*
898+
* @param epochSecond the epoch second of the timestamp to write
899+
* @param nano the nanoseconds of the timestamp to write
900+
* @throws JsonWriterException if the nanoseconds value is less than 0 or greater than 999999999
901+
*/
902+
def writeTimestampValAsString(epochSecond: Long, nano: Int): Unit = {
903+
if (nano < 0 || nano > 999999999) encodeError("illegal nanoseconds value: " + nano)
904+
writeOptionalCommaAndIndentionBeforeValue()
905+
var pos = ensureBufCapacity(32)
906+
val buf = this.buf
907+
buf(pos) = '"'
908+
pos += 1
909+
pos = writeLong(epochSecond, pos, buf)
910+
if (nano != 0) {
911+
val dotPos = pos
912+
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
913+
buf(dotPos) = '.'
914+
}
915+
buf(pos) = '"'
916+
pos += 1
917+
this.count = pos
918+
}
919+
874920
/**
875921
* Writes a byte array as a JSON hexadecimal string value.
876922
*

jsoniter-scala-core/jvm/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/core/JsonWriter.scala

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,27 @@ final class JsonWriter private[jsoniter_scala](
697697
writeDouble(x)
698698
}
699699

700+
/**
701+
* Writes a timestamp value as a JSON value.
702+
*
703+
* @param epochSecond the epoch second of the timestamp to write
704+
* @param nano the nanoseconds of the timestamp to write
705+
* @throws JsonWriterException if the nanoseconds value is less than 0 or greater than 999999999
706+
*/
707+
def writeTimestampVal(epochSecond: Long, nano: Int): Unit = {
708+
if (nano < 0 || nano > 999999999) encodeError("illegal nanoseconds value: " + nano)
709+
writeOptionalCommaAndIndentionBeforeValue()
710+
var pos = ensureBufCapacity(30)
711+
val buf = this.buf
712+
pos = writeLong(epochSecond, pos, buf)
713+
if (nano != 0) {
714+
val dotPos = pos
715+
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
716+
buf(dotPos) = '.'
717+
}
718+
this.count = pos
719+
}
720+
700721
/**
701722
* Writes a `BigDecimal` value as a JSON string value.
702723
*
@@ -808,6 +829,31 @@ final class JsonWriter private[jsoniter_scala](
808829
writeBytes('"')
809830
}
810831

832+
/**
833+
* Writes a timestamp value as a JSON string value.
834+
*
835+
* @param epochSecond the epoch second of the timestamp to write
836+
* @param nano the nanoseconds of the timestamp to write
837+
* @throws JsonWriterException if the nanoseconds value is less than 0 or greater than 999999999
838+
*/
839+
def writeTimestampValAsString(epochSecond: Long, nano: Int): Unit = {
840+
if (nano < 0 || nano > 999999999) encodeError("illegal nanoseconds value: " + nano)
841+
writeOptionalCommaAndIndentionBeforeValue()
842+
var pos = ensureBufCapacity(32)
843+
val buf = this.buf
844+
buf(pos) = '"'
845+
pos += 1
846+
pos = writeLong(epochSecond, pos, buf)
847+
if (nano != 0) {
848+
val dotPos = pos
849+
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
850+
buf(dotPos) = '.'
851+
}
852+
buf(pos) = '"'
853+
pos += 1
854+
this.count = pos
855+
}
856+
811857
/**
812858
* Writes a byte array as a JSON hexadecimal string value.
813859
*

jsoniter-scala-core/native/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/core/JsonWriter.scala

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,27 @@ final class JsonWriter private[jsoniter_scala](
697697
writeDouble(x)
698698
}
699699

700+
/**
701+
* Writes a timestamp value as a JSON value.
702+
*
703+
* @param epochSecond the epoch second of the timestamp to write
704+
* @param nano the nanoseconds of the timestamp to write
705+
* @throws JsonWriterException if the nanoseconds value is less than 0 or greater than 999999999
706+
*/
707+
def writeTimestampVal(epochSecond: Long, nano: Int): Unit = {
708+
if (nano < 0 || nano > 999999999) encodeError("illegal nanoseconds value: " + nano)
709+
writeOptionalCommaAndIndentionBeforeValue()
710+
var pos = ensureBufCapacity(30)
711+
val buf = this.buf
712+
pos = writeLong(epochSecond, pos, buf)
713+
if (nano != 0) {
714+
val dotPos = pos
715+
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
716+
buf(dotPos) = '.'
717+
}
718+
this.count = pos
719+
}
720+
700721
/**
701722
* Writes a `BigDecimal` value as a JSON string value.
702723
*
@@ -808,6 +829,31 @@ final class JsonWriter private[jsoniter_scala](
808829
writeBytes('"')
809830
}
810831

832+
/**
833+
* Writes a timestamp value as a JSON string value.
834+
*
835+
* @param epochSecond the epoch second of the timestamp to write
836+
* @param nano the nanoseconds of the timestamp to write
837+
* @throws JsonWriterException if the nanoseconds value is less than 0 or greater than 999999999
838+
*/
839+
def writeTimestampValAsString(epochSecond: Long, nano: Int): Unit = {
840+
if (nano < 0 || nano > 999999999) encodeError("illegal nanoseconds value: " + nano)
841+
writeOptionalCommaAndIndentionBeforeValue()
842+
var pos = ensureBufCapacity(32)
843+
val buf = this.buf
844+
buf(pos) = '"'
845+
pos += 1
846+
pos = writeLong(epochSecond, pos, buf)
847+
if (nano != 0) {
848+
val dotPos = pos
849+
pos = writeSignificantFractionDigits(nano, pos + 9, pos, buf, digits)
850+
buf(dotPos) = '.'
851+
}
852+
buf(pos) = '"'
853+
pos += 1
854+
this.count = pos
855+
}
856+
811857
/**
812858
* Writes a byte array as a JSON hexadecimal string value.
813859
*

jsoniter-scala-core/shared/src/test/scala/com/github/plokhotnyuk/jsoniter_scala/core/JsonWriterSpec.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,40 @@ class JsonWriterSpec extends AnyWordSpec with Matchers with ScalaCheckPropertyCh
734734
forAll(genBigInt, minSuccessful(10000))(check)
735735
}
736736
}
737+
"JsonWriter.writeVal for a timestamp" should {
738+
"write timestamp values" in {
739+
def check(epochSecond: Long, nano: Int): Unit = {
740+
val s = BigDecimal({
741+
val es = java.math.BigDecimal.valueOf(epochSecond)
742+
if (nano == 0) es
743+
else es.add(java.math.BigDecimal.valueOf({
744+
if (epochSecond < 0) -nano
745+
else nano
746+
}.toLong, 9).stripTrailingZeros)
747+
}).toString
748+
withWriter(_.writeTimestampVal(epochSecond, nano)) shouldBe s
749+
withWriter(_.writeTimestampValAsString(epochSecond, nano)) shouldBe s""""$s""""
750+
}
751+
752+
check(1L, 0)
753+
check(1L, 900000000)
754+
check(1L, 990000000)
755+
check(1L, 999000000)
756+
check(1L, 999900000)
757+
check(1L, 999990000)
758+
check(1L, 999999000)
759+
check(1L, 999999900)
760+
check(1L, 999999990)
761+
check(1L, 999999999)
762+
forAll(arbitrary[Long], Gen.choose(0, 999999999), minSuccessful(10000))((es, n) => check(es, n))
763+
}
764+
"throw i/o exception for illegal nanos" in {
765+
forAll(arbitrary[Long], Gen.oneOf(Gen.choose(Int.MinValue, -1), Gen.choose(1000000000, Int.MaxValue))) { (es, n) =>
766+
assert(intercept[JsonWriterException](withWriter(_.writeTimestampVal(es, n))).getMessage.startsWith("illegal nanoseconds"))
767+
assert(intercept[JsonWriterException](withWriter(_.writeTimestampValAsString(es, n))).getMessage.startsWith("illegal nanoseconds"))
768+
}
769+
}
770+
}
737771
"JsonWriter.writeVal and JsonWriter.writeValAsString and JsonWriter.writeKey for BigDecimal" should {
738772
"don't write null value" in {
739773
intercept[NullPointerException](withWriter(_.writeVal(null.asInstanceOf[BigDecimal])))

version.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ThisBuild / version := "2.31.4-SNAPSHOT"
1+
ThisBuild / version := "2.32.0-SNAPSHOT"

0 commit comments

Comments
 (0)