losizm / little-time

The Scala library that provides extension methods to java.time.

GitHub

little-time

The Scala library that provides extension methods to java.time.

Maven Central

Getting Started

To use little-time, add it as a dependency to your project:

libraryDependencies += "com.github.losizm" %% "little-time" % "0.4.0"

A Taste of little-time

Here's a taste of what little-time offers.

Iterating over Dates

You can create an iterator from one LocalDate to another, stepping thru each date between the two.

import java.time._
import little.time.Implicits._

// Convert String to LocalDate
val start = "2018-04-01".toLocalDate
val end   = "2018-07-01".toLocalDate

// Iterate over dates, one day at a time (end inclusive)
start iterateTo end foreach { date =>
  println(s"$date is on a ${date.getDayOfWeek}")
}

// Iterate over dates, one day at a time (end exclusive)
start iterateUntil end foreach { date =>
  println(s"$date is on a ${date.getDayOfWeek}")
}

Or, if you prefer, use the ~> and ~>| shorthands.

// Iterate over dates, one day at a time (end inclusive)
start ~> end foreach { date =>
  println(s"$date is on a ${date.getDayOfWeek}")
}

// Iterate over dates, one day at a time (end exclusive)
start ~>| end foreach { date =>
  println(s"$date is on a ${date.getDayOfWeek}")
}

Specifying Step Unit

When creating the iterator, you can specify the Period by which to step.

// Iterate over dates, two weeks at a time
start iterateTo (end, Period.ofWeeks(2)) foreach { date =>
  println(s"$date is on a ${date.getDayOfWeek}")
}

You can iterate forward or backward, based on the supplied period. If the period is negative, you step backward to the end date.

import java.time.temporal.ChronoUnit

val _1day = Period.ofDays(1)
val startDate = LocalDate.now()
val endDate   = startDate - 7

// Iterator backward over dates, 1 day at a time
startDate iterateUntil (endDate, -_1day) foreach { date =>
  val daysAgo = date.until(startDate, ChronoUnit.DAYS)
  println(s"$date is $daysAgo days(s) ago")
}

Iterating over Other Types

You can create iterators of other types, like YearMonth and LocalTime. Similar to LocalDate, you specify a Period by which to step when creating an iterator of YearMonth; whereas, for LocalTime, you must specify a Duration.

// Convert String to YearMonth
val startMonth = "2018-04".toYearMonth
val endMonth   = "2020-03".toYearMonth

// Iterate over months, 3 months at a time
startMonth ~> (endMonth, Period.ofMonths(3)) foreach { month =>
  val diff = startMonth.until(month, ChronoUnit.MONTHS)
  println(s"$month is $diff month(s) after $startMonth")
}

// Convert String to LocalTime
val startTime = "09:00".toLocalTime
val endTime   = "17:00".toLocalTime

// Iterate over times, 15 minutes at a time
startTime ~>| (endTime, Duration.ofMinutes(15)) foreach {
  case time if time.getMinute == 0  => println(s"It's $time, back to work")
  case time if time.getMinute == 45 => println(s"It's $time, take a break")
  case time                         => println(s"It's $time")
}

For LocalDateTime, you can specify either a Period or Duration by which to step.

val _2yrs = Period.ofYears(2)
val startDateTime = "2019-06-15T12:30:45".toLocalDateTime
val endDateTime   = startDateTime + _2yrs * 3

// Iterate over 6 years, 2 years at a time
startDateTime iterateTo (endDateTime, _2yrs) foreach { dateTime =>
  println(s"Date-time is $dateTime")
}

val _45secs = Duration.ofSeconds(45)
val otherDateTime = startDateTime + Duration.ofMinutes(3)

// Iterate over 3 minutes, 45 seconds at a time
startDateTime.iterateTo(otherDateTime, _45secs).foreach { dateTime =>
  println(s"Date-time is $dateTime")
}

Lastly, you can iterate between one Duration and another.

val _17mins = Duration.ofMinutes(17)

// Iterate from 17 minutes to 34 minutes, 45 seconds at a time
_17mins iterateTo (_17mins * 2, _45secs) foreach { duration =>
  println(s"${duration.toMillis / 1000} secs from now is ${LocalTime.now() + duration}")
}

Setting Dates to Common Boundaries

When working with dates, you can set them to common boundaries.

import DayOfWeek._

val now = LocalDate.now()

// Get first and last date of current month
val startOfMonth = now.atStartOfMonth
val endOfMonth = now.atEndOfMonth

// Get first and last date of current week
val startOfWeek = now.atStartOfWeek
val endOfWeek = now.atEndOfWeek

// Print first and last date of current week, with Monday as first day of week
println { now.atStartOfWeek(MONDAY) }
println { now.atEndOfWeek(SUNDAY) }

Setting Times to Common Boundaries

When working with times, you can set them to common boundaries, too. However, to set an end boundary, you must specify the TimePrecision. You may do this explicitly by passing the precision in the method call, or keep an implicit precision in scope.

import little.time.TimePrecision

// Implicitly set time precision to milliseconds
implicit val precision = TimePrecision.Milliseconds

val now = LocalTime.now()

// Get first and last millisecond of current hour
val startOfHour = now.atStartOfHour
val endOfHour = now.atEndOfHour

// Get first and last millisecond of current minute
val startOfMinute = now.atStartOfMinute
val endOfMinute = now.atEndOfMinute

// Get first and last millisecond of current second
val startOfSecond = now.atStartOfSecond
val endOfSecond = now.atEndOfSecond

The same applies to LocalDateTime, which is both a date and a time. You must ensure an implicit TimePrecision is in scope when setting to an end boundary – or supply one explicitly in the method call.

implicit val precision = TimePrecision.Seconds

val dateTime = "2017-05-12T15:23:17.123456789".toLocalDateTime

// Get last second of year
val endOfYear = dateTime.atEndOfYear

// Get last microsecond of day
val endOfDay = dateTime.atEndOfDay(TimePrecision.Microseconds)

API Documentation

See scaladoc for additional details.

License

little-time is licensed under the Apache License, Version 2. See LICENSE file for more information.