Skip to content

Commit

Permalink
Merge pull request #471 from disneystreaming/fix-concise-timestamps
Browse files Browse the repository at this point in the history
Fix AWS timestamp formats
  • Loading branch information
Baccata authored Sep 26, 2022
2 parents c5d2501 + 85ba93d commit e0626b4
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 22 deletions.
30 changes: 19 additions & 11 deletions modules/core/src-js/smithy4s/Timestamp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,25 +81,25 @@ case class Timestamp private (epochSecond: Long, nano: Int) {
append2Digits(day, s.append(' '))
s.append(' ').append(months(month - 1))
append4Digits(year, s.append(' '))
appendTime(secsOfDay, s.append(' '))
appendTime(secsOfDay, s.append(' '), addSeparator = true)
appendNano(nano, s)
s.append(" GMT").toString
case 2 =>
append4Digits(year, s)
append2Digits(month, s.append('-'))
append2Digits(day, s.append('-'))
append2Digits(month, s)
append2Digits(day, s)
s.toString
case 3 =>
append4Digits(year, s)
append2Digits(month, s.append('-'))
append2Digits(day, s.append('-'))
appendTime(secsOfDay, s.append('T'))
append2Digits(month, s)
append2Digits(day, s)
appendTime(secsOfDay, s.append('T'), addSeparator = false)
s.append('Z').toString
case _ =>
append4Digits(year, s)
append2Digits(month, s.append('-'))
append2Digits(day, s.append('-'))
appendTime(secsOfDay, s.append('T'))
appendTime(secsOfDay, s.append('T'), addSeparator = true)
appendNano(nano, s)
s.append('Z').toString
}
Expand All @@ -114,15 +114,23 @@ case class Timestamp private (epochSecond: Long, nano: Int) {

private[this] def appendTime(
secsOfDay: Int,
s: java.lang.StringBuilder
s: java.lang.StringBuilder,
addSeparator: Boolean
): Unit = {
val minutesOfDay = secsOfDay / 60
val hour = minutesOfDay / 60
val minute = minutesOfDay - hour * 60
val second = secsOfDay - minutesOfDay * 60
append2Digits(hour, s)
append2Digits(minute, s.append(':'))
append2Digits(second, s.append(':'))

if (addSeparator) {
append2Digits(hour, s)
append2Digits(minute, s.append(':'))
append2Digits(second, s.append(':'))
} else {
append2Digits(hour, s)
append2Digits(minute, s)
append2Digits(second, s)
}
}

private[this] def appendNano(nano: Int, s: java.lang.StringBuilder): Unit =
Expand Down
30 changes: 19 additions & 11 deletions modules/core/src-jvm-native/smithy4s/Timestamp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,25 @@ case class Timestamp private (epochSecond: Long, nano: Int)
append2Digits(day, s.append(' '))
s.append(' ').append(Timestamp.months(month - 1))
append4Digits(year, s.append(' '))
appendTime(secsOfDay, s.append(' '))
appendTime(secsOfDay, s.append(' '), addSeparator = true)
appendNano(nano, s)
s.append(" GMT").toString
case 2 =>
append4Digits(year, s)
append2Digits(month, s.append('-'))
append2Digits(day, s.append('-'))
append2Digits(month, s)
append2Digits(day, s)
s.toString
case 3 =>
append4Digits(year, s)
append2Digits(month, s.append('-'))
append2Digits(day, s.append('-'))
appendTime(secsOfDay, s.append('T'))
append2Digits(month, s)
append2Digits(day, s)
appendTime(secsOfDay, s.append('T'), addSeparator = false)
s.append('Z').toString
case _ =>
append4Digits(year, s)
append2Digits(month, s.append('-'))
append2Digits(day, s.append('-'))
appendTime(secsOfDay, s.append('T'))
appendTime(secsOfDay, s.append('T'), addSeparator = true)
appendNano(nano, s)
s.append('Z').toString
}
Expand All @@ -106,15 +106,23 @@ case class Timestamp private (epochSecond: Long, nano: Int)

private[this] def appendTime(
secsOfDay: Int,
s: java.lang.StringBuilder
s: java.lang.StringBuilder,
addSeparator: Boolean
): Unit = {
val y1 =
secsOfDay * 1193047L // Based on James Anhalt's algorithm: https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/
val y2 = (y1 & 0xffffffffL) * 60
val y3 = (y2 & 0xffffffffL) * 60
append2Digits((y1 >> 32).toInt, s)
append2Digits((y2 >> 32).toInt, s.append(':'))
append2Digits((y3 >> 32).toInt, s.append(':'))

if (addSeparator) {
append2Digits((y1 >> 32).toInt, s)
append2Digits((y2 >> 32).toInt, s.append(':'))
append2Digits((y3 >> 32).toInt, s.append(':'))
} else {
append2Digits((y1 >> 32).toInt, s)
append2Digits((y2 >> 32).toInt, s)
append2Digits((y3 >> 32).toInt, s)
}
}

private[this] def appendNano(nano: Int, s: java.lang.StringBuilder): Unit =
Expand Down
32 changes: 32 additions & 0 deletions modules/core/test/src-js/smithy4s/TimestampSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,36 @@ class TimestampSpec() extends munit.FunSuite with munit.ScalaCheckSuite {
expect.same(parsed, None)
}
}

property("Converts to concise date format") {
forAll { (i: Date) =>
val epochSecond = (i.valueOf() / 1000).toLong
val nano = (i.valueOf() % 1000).toInt * 1000000
val ts = Timestamp(epochSecond, nano)
val formatted = ts.conciseDate
val year = i.getUTCFullYear().toInt
val month = i.getUTCMonth().toInt + 1 // in js month is 0-11
val date = i.getUTCDate().toInt
val expected = f"$year%04d$month%02d$date%02d"
expect.same(formatted, expected)
}
}

property("Converts to concise date time format") {
forAll { (i: Date) =>
val epochSecond = (i.valueOf() / 1000).toLong
val nano = (i.valueOf() % 1000).toInt * 1000000
val ts = Timestamp(epochSecond = epochSecond, nano = nano)
val formatted = ts.conciseDateTime
val year = i.getUTCFullYear().toInt
val month = i.getUTCMonth().toInt + 1 // in js month is 0-11
val date = i.getUTCDate().toInt
val hours = i.getUTCHours().toInt
val minutes = i.getUTCMinutes().toInt
val seconds = i.getUTCSeconds().toInt
val expected =
f"$year%04d$month%02d$date%02dT$hours%02d$minutes%02d$seconds%02dZ"
expect.same(formatted, expected)
}
}
}
26 changes: 26 additions & 0 deletions modules/core/test/src-jvm/smithy4s/TimestampSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,30 @@ class TimestampSpec() extends munit.FunSuite with munit.ScalaCheckSuite {
expect.same(parsed, None)
}
}

private val conciseDateFormatter = DateTimeFormatter
.ofPattern("yyyyMMdd", Locale.ENGLISH)
.withZone(ZoneOffset.UTC)

private val conciseDateTimeFormatter = DateTimeFormatter
.ofPattern("yyyyMMdd'T'HHmmssX", Locale.ENGLISH)
.withZone(ZoneOffset.UTC)

property("Converts to concise date format") {
forAll { (i: Instant) =>
val ts = Timestamp(i.getEpochSecond, i.getNano)
val formatted = ts.conciseDate
val expected = conciseDateFormatter.format(i)
expect.same(formatted, expected)
}
}

property("Converts to concise date time format") {
forAll { (i: Instant) =>
val ts = Timestamp(i.getEpochSecond, i.getNano)
val formatted = ts.conciseDateTime
val expected = conciseDateTimeFormatter.format(i)
expect.same(formatted, expected)
}
}
}

0 comments on commit e0626b4

Please sign in to comment.