Skip to content

Commit

Permalink
util-core: add convenience functions for java.time Instant,
Browse files Browse the repository at this point in the history
ZonedDateTime, and OffsetDateTime

Problem:
When working with both com.twitter.util.Time and java.time,
switching to the latter is verbose.

Solution:
Add convenience functions to convert to java.time objects.

Differential Revision: https://phabricator.twitter.biz/D757636
  • Loading branch information
Kostas Pagratis authored and jenkins committed Oct 9, 2021
1 parent bf493f9 commit c275076
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 8 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -7,6 +7,14 @@ Note that ``PHAB_ID=#`` and ``RB_ID=#`` correspond to associated messages in com
Unreleased
----------

New Features
~~~~~~~~~~~~

* util-core: Add convenience methods to convert to java.time.ZonedDateTime and
java.time.OffsetDateTime. `toInstant`, `toZonedDateTime`, and `toOffsetDateTime` also preserve
nanosecond resolution. ``PHAB_ID=D757636``


21.9.0
------

Expand Down
29 changes: 27 additions & 2 deletions util-core/src/main/scala/com/twitter/util/Time.scala
Expand Up @@ -17,9 +17,15 @@
package com.twitter.util

import java.io.Serializable
import java.time.{Clock, Instant}
import java.time.ZonedDateTime
import java.time.Clock
import java.time.Instant
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.concurrent.TimeUnit
import java.util.{Date, Locale, TimeZone}
import java.util.Date
import java.util.Locale
import java.util.TimeZone

/**
* @define now
Expand Down Expand Up @@ -688,6 +694,25 @@ sealed class Time private[util] (protected val nanos: Long)
*/
def toDate: Date = new Date(inMillis)

/**
* Converts this Time object to a java.time.Instant
*/
def toInstant: Instant = {
val millis = inMilliseconds
val nanos = inNanoseconds - (millis * Duration.NanosPerMillisecond)
Instant.ofEpochMilli(millis).plusNanos(nanos)
}

/**
* Converts this Time object to a java.time.ZonedDateTime with ZoneId of UTC
*/
def toZonedDateTime: ZonedDateTime = ZonedDateTime.ofInstant(toInstant, ZoneOffset.UTC)

/**
* Converts this Time object to a java.time.OffsetDateTime with ZoneId of UTC
*/
def toOffsetDateTime: OffsetDateTime = OffsetDateTime.ofInstant(toInstant, ZoneOffset.UTC)

private def writeReplace(): Object = TimeBox.Finite(inNanoseconds)

/**
Expand Down
50 changes: 44 additions & 6 deletions util-core/src/test/scala/com/twitter/util/TimeTest.scala
@@ -1,13 +1,20 @@
package com.twitter.util

import java.io.{ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream}
import java.util.{Locale, TimeZone}
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit

import org.scalatest.concurrent.{Eventually, IntegrationPatience}
import org.scalatest.concurrent.Eventually
import org.scalatest.concurrent.IntegrationPatience
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks

import com.twitter.conversions.DurationOps._
import java.time.Instant
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.ZonedDateTime
import org.scalatest.wordspec.AnyWordSpec

trait TimeLikeSpec[T <: TimeLike[T]] extends AnyWordSpec with ScalaCheckDrivenPropertyChecks {
Expand Down Expand Up @@ -509,7 +516,9 @@ class TimeTest
val t = new Thread(r)
t.start()
assert(t.isAlive == true)
eventually { assert(t.getState == Thread.State.TIMED_WAITING) }
eventually {
assert(t.getState == Thread.State.TIMED_WAITING)
}
ctl.advance(5.seconds)
t.join()
assert(t.isAlive == false)
Expand Down Expand Up @@ -712,5 +721,34 @@ class TimeTest
assert(t0.untilNow == 100.hours)
}
}

"toInstant" in {
Time.withCurrentTimeFrozen { _ =>
val instant = Instant.ofEpochMilli(Time.now.inMilliseconds)
assert(instant.toEpochMilli == Time.now.inMillis)
// java.time.Instant:getNano returns the nanoseconds of the second
assert(instant.getNano == Time.now.inNanoseconds % Duration.NanosPerSecond)
}
}

"toZonedDateTime" in {
Time.withCurrentTimeFrozen { _ =>
val zonedDateTime =
ZonedDateTime.ofInstant(Instant.ofEpochMilli(Time.now.inMilliseconds), ZoneOffset.UTC)
assert(Time.now.toZonedDateTime == zonedDateTime)
// java.time.Instant:getNano returns the nanoseconds of the second
assert(zonedDateTime.getNano == Time.now.inNanoseconds % Duration.NanosPerSecond)
}
}

"toOffsetDateTime" in {
Time.withCurrentTimeFrozen { _ =>
val offsetDateTime =
OffsetDateTime.ofInstant(Instant.ofEpochMilli(Time.now.inMilliseconds), ZoneOffset.UTC)
assert(Time.now.toOffsetDateTime == offsetDateTime)
// java.time.Instant:getNano returns the nanoseconds of the second
assert(offsetDateTime.getNano == Time.now.inNanoseconds % Duration.NanosPerSecond)
}
}
}
}

0 comments on commit c275076

Please sign in to comment.