It's about time
People like us, who believe in physics, know that the distinction between past, present, and future is only a stubbornly persistent illusion.
Time representation is a necessity for nearly all applications. The ability to display, reference, track, and manipulate moments of time plays an important role in the development of applications. But when developing a Kotlin multi-platform application, this becomes a difficult task, as there is no notion of a moment of time in the standard library. Waiting for one to be added would be a waste of time, so I decided to create one. This article takes an early look at this new library that is still in development.
TL;DR
Introducing a new Kotlin multi-platform time library.
The library
Kotlin does provide a new experimental duration class which serves as a period of time, but no notion of a moment, or instant, of time. There is already a promising Kotlin multi-platform library called Klock, but this doesn't make use of the new experimental Duration class. So I created a library that expands on the Duration class by provided a Moment
interface. These Moments
are obtained by a TimeProvider
. Getting this correct was pretty difficult and took some time, but, while the library is still in the early stages of development, I am pretty satisfied with the API and it's ease of use. Let's have a look.
TimeProvider
The TimeProvider
interface is the entry point to doing anything time related, such as, obtaining a specific Moment
in time.
val now = timeProvider.now()
val later = now + 20.minutes
val tomorrow = timeProvider.tomorrow()
val diffDuration = later to tomorrow
Any Moment
in time can be obtained by using the TimeProvider.moment()
function and providing the Duration
since the epoch as a parameter. Moments in time for a specific Time Zone can be retrieved by passing in a TimeZoneRegionId
. Without passing in a TimeZoneRegionId
, the default is used, by accessing the TimeZoneOffsetProvider.defaultTimeZoneRegionId
property that is available to the TimeProvider
.
val moment = timeProvider.moment(
durationSinceEpoch = 20_000.hours,
regionId = TimeZoneRegionId("America/New_York"))
Each platform target is responsible for providing their own implementation of the TimeProvider
interface. For instance, the jvmMain
source set provides a JavaTimeProvider
implementation. This implementation delegates to the java.time
classes. Then each platform can provide their own platform specific implementations and the Kotlin common code can just reference the interfaces, such as, TimeProvider
.
@Provides
fun provideTimeProvider(): TimeProvider = JavaTimeProvider(locale = Locale.getDefault())
Moment
The Moment
interface represents an instant of time, as a duration since the epoch, with a TimeOffset
from UTC for a specified TimeZoneRegionId
and it's corresponding rules (daylight savings time, etc). A Moment
can also represent UTC/GMT time which has an offset equal to zero, and which can be retrieved for any Moment
using the Moment.toUtc()
function.
Kotlin Durations
can be added or subtracted from a Moment
, and a Duration
between two Moments
can be retrieved using the to
infix function. Also, CalendarDate
and TimeOfDay
objects can be retrieved from the Moment
instance.
val yesterday = timeProvider.yesterday()
val later = timeProvider.now() + 3.hours
val duration = yesterday to later
val date = later.calendarDate
val time = later.timeOfDay
CalendarDate
The CalendarDate
class represents the date of a Moment
in a calendar year with no information about the time within the day.
val date = moment.calendarDate
date.year
date.month
date.dayInYear
date.dayInMonth
date.dayOfWeek
TimeOfDay
The TimeOfDay
class is similar to CalendarDate
but instead of providing information about the date within a calendar year, it provides information about the time within a calendar date.
val time = moment.timeOfDay
time.elapsedDurationInDay
time.minuteInHour
time.secondInMinute
time.millisecondInSecond
time.hourInDay(ClockConvention.TWENTY_FOUR_HOUR_CLOCK)
Parsing and Formatting
The library comes with two interfaces, MomentFormatter
and MomentParser
, which provide a way to transform a Moment
into a String
for a particular pattern and vice-versa. These can be obtained from the TimeProvider
interface.
val formatter = timeProvider.formatter(MomentFormatPattern("MM/dd/yyyy"))
val string = formatter.format(timeProvider.now())
val parser = timeProvider.parser(MomentFormatPattern("MM/dd/yyyy"))
val moment = parser.parse("2/12/2020")
JSON
One idea of the library is to have JSON serializers, for popular JSON parsing libaries, that handle common time formats. This way it is simple to pass Moments
between components. These aren't implemented yet, as of writing this article, but I imagine that they would reside in their own modules per library supported.
Why not mimic java.time?
There are some similarities between the two libraries and java.time
has provided some inspiration in the development of this library. But there seems to be a common source of confusion for the components within the java.time
library, such as, OffsetDateTime
, ZonedDateTime
, LocalDateTime
, and Instant
. I wanted to avoid this confusion for a casual user of the library and wanted to provide a more Kotlin-esque API. For this reason, the Moment
interface was created, which acts similar to a combination of the OffsetDateTime
, ZonedDateTime
, and Instant
classes. The CalendarDateAndTime
class corresponds to Java Time's LocalDateTime
class. And a new interface was created, TimeProvider
, that acts as a single source for everything time related.
Recapitulate
This new library, still in the early stages of development, provides a promising vision of working with time within Kotlin multi-platform code. Still, implementations need to be finalized, tests need to be written, and other platforms need to be targeted. And since the library uses the experimental Duration
class, the library is experimental as well. The implementation of time related code isn't as trivial as one might expect, but, overall, the future of this Kotlin time library looks bright, however, only time will tell.