1. Introduction

One of the tried and tested libraries for handling date and time computations is a Java library called Joda-Time. This Java library still also works in Scala, but for a more natural experience, the nScala-Time library provides a wrapper for Joda-Time with a more pleasant syntax for Scala programmers. In this tutorial, we’ll learn about nScala-Time and what it offers to manage date and time computations in Scala.

2. Setting Up

To add the nScala-Time library to our project, we must add the following code to the build.sbt file:

libraryDependencies += "com.github.nscala-time" %% "nscala-time" % "2.32.0"

The nScala-Time library at the time of this writing is on version 2.32.0, however, it’s always advised to crosscheck and install the latest version.

3. Working with DateTime

DateTime is the primary data type used in nScala-Time and it’s the base from which multiple operations are done. To work with it, we’ll need first to import the nScala-Time library:

scala> import com.github.nscala_time.time.Imports.*

Creating a DateTime instance can be done in multiple ways:

scala> val st = DateTime.now()
val st: org.joda.time.DateTime = 2024-08-09T21:09:21.438+03:00

Here we call the now() method which returns a DateTime instance with the current timestamp:

scala> val parsedDate = DateTime.parse("2024-08-07")
val parsedDate: org.joda.time.DateTime = 2024-08-07T00:00:00.000+03:00

Another way to create a DateTime instance is by calling the parse() method which creates a timestamp with the date that was passed as an argument:

scala> val parsedDate2 = DateTime.parse("2024")
val parsedDate2: org.joda.time.DateTime = 2024-01-01T00:00:00.000+03:00

The parse() method is very flexible, in the above example we only pass the year as an argument and still get a DateTime instance:

4. JDK Interoperability

We can convert to and from JDK types using nScala-Time. Below, we’ll look at a simple date conversion:

scala> import java.util.Date

scala> val jdkDate = new Date()
val jdkDate: java.util.Date = Fri Aug 09 21:57:13 EAT 2024

scala> val convertTo = new DateTime(jdkDate)
val convertTo: org.joda.time.DateTime = 2024-08-09T21:57:13.866+03:00

First, we import the Date class for java.util, assign it to jdkDate. We can then convert this to DateTime by passing it as an argument to the DateTime class as shown above. It’s also possible to make a conversion from the JDK Calendar to DateTime:

scala> import java.util.Calendar

scala> val jdkCal  = Calendar.getInstance()
val jdkCal: java.util.Calendar = java.util.GregorianCalendar[time=1723231095608,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Africa/Nairobi",offset=10800000,dstSavings=0,useDaylight=false,transitions=7,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2024,MONTH=7,WEEK_OF_YEAR=32,WEEK_OF_MONTH=2,DAY_OF_MONTH=9,DAY_OF_YEAR=222,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=10,HOUR_OF_DAY=22,MINUTE=18,SECOND=15,MILLISECOND=608,ZONE_OFFSET=10800000,DST_OFFSET=0]

scala> val st2 = new DateTime(jdkCal)
val st2: org.joda.time.DateTime = 2024-08-09T22:07:51.584+03:00

We’ll need first to import the Calendar object from java.util. Secondly, we call the getInstance() method, which sets multiple fields in the Calendar object with current values. Similarly to the previous example, we pass the jdkCal as a parameter to DateTime, creating a timestamp. There’s also a way to convert back from DateTime to JDK Calendar:

scala> val st3 = new DateTime
val st3: org.joda.time.DateTime = 2024-08-10T14:01:00.752+03:00

scala> val cal = st3.gregorianCalendar
val cal: java.util.Calendar = java.util.GregorianCalendar[time=1723287660752,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Africa/Nairobi",offset=10800000,dstSavings=0,useDaylight=false,transitions=7,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2024,MONTH=7,WEEK_OF_YEAR=32,WEEK_OF_MONTH=2,DAY_OF_MONTH=10,DAY_OF_YEAR=223,DAY_OF_WEEK=7,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=2,HOUR_OF_DAY=14,MINUTE=1,SECOND=0,MILLISECOND=752,ZONE_OFFSET=10800000,DST_OFFSET=0]

In the above example, we use the gregorianCalendar method that returns a JDK Calendar object.

5. Querying DateTimes

The DateTime instance can be queried in multiple ways to provide a more drilled-down date or time value:

scala> val dayOfWeek = new DateTime().dayOfWeek()
val dayOfWeek: org.joda.time.DateTime#Property = Property[dayOfWeek]

scala> val shortText = dayOfWeek.asShortText
val shortText: String = Sat

scala> val asText = dayOfWeek.asText
val asText: String = Saturday

In this section, we create a DateTime instance and then call the dayOfWeek() function. This creates a DateTime#Property object with several useful methods. The asShortText method returns an abbreviated version of the day, while asText returns the full name. More customized results can be achieved through chaining methods together:

scala> val monthOfYearString = DateTime.now().monthOfYear().getAsText()
val monthOfYearString: String = August

scala> import java.util.Locale

scala> val monthOfYearStringFrench = DateTime.now().monthOfYear().getAsShortText(Locale.FRENCH)
val monthOfYearStringFrench: String = août

scala> val isLeapYear = DateTime.now().year().isLeap()
val isLeapYear: Boolean = true

scala> val nowToTomorrow = DateTime.now().to(DateTime.tomorrow())
val nowToTomorrow: org.joda.time.Interval = 2024-08-10T14:15:51.166+03:00/2024-08-11T14:15:51.174+03:00

In the first example, we return the current month. Additionally, in the next example, we return the shortText version of the month in French by passing Locale.French from java.util as a parameter. Thirdly, we check if the current year is a leap year using the isLeap() method, and finally, we create an Interval object from the current DateTime to that of tomorrow*.*

6. Accessing Fields

The nScala-Time library comes with several fields for showing date and time values; we already looked at dayOfWeek(), however here are a couple more we might find useful:

scala> val st4 = new DateTime()
val st4: org.joda.time.DateTime = 2024-08-10T14:22:56.029+03:00

scala> val year2 = st4.year.asText
val year2: String = 2024

scala> val monthOfYr = st4.monthOfYear.asText
val monthOfYr: String = August

scala> val dayOfWeek = st4.dayOfWeek.asText
val dayOfWeek: String = Saturday

There are also multiple time fields we can access:

scala> val hrOfDay = st4.hourOfDay.asText
val hrOfDay: String = 14

scala> val minOfHr = st4.minuteOfHour.asText
val minOfHr: String = 22

The names come from Joda-Time and are designed to be readable and intuitive.

7. Manipulating DateTime

DateTime manipulation is where the nScala-Time wrapper shines. It allows us to do this manipulation in a more Scala-centric way:

scala> val compare = DateTime.now() < DateTime.lastMonth()
val compare: Boolean = false

Here we compare two DateTime instances using the <* operator, this returns a boolean showing whether the comparison is *true* or *false*. **As you might have guessed, all the other comparison operators also work, such as *>, >=, and <=, to mention but a few.**

There is also a concept of duration builder in nScala-Time.  We can easily create a duration by composing together multiple duration objects to create a Period:

scala> val addVarious = 2.month + 5.hours + 6.millis
val addVarious: org.joda.time.Period = P2MT5H0.006S

In this case, we create a 2 months, 5 hours, and 6 milliseconds Period object using the + operator in a similar fashion to Scala’s Duration utility. We can also add or subtract a Period or duration from a DateTime instance, as seen below:

scala> val less5 = DateTime.now() - 5.days
val less5: org.joda.time.DateTime = 2024-08-05T15:00:51.107+03:00

In this example, we get the current timestamp and then subtract 5 days:

scala> val sampleDuration = 4.hours + 30.minutes + 10.seconds
val sampleDuration: com.github.nscala_time.time.DurationBuilder = com.github.nscala_time.time.DurationBuilder@62eb7f75

scala> val datetimebeforeDuration = sampleDuration.before(DateTime.now())
val datetimebeforeDuration: org.joda.time.DateTime = 2024-08-10T10:31:56.883+03:00

Above, we create sampleDuration of type DurationBuilder. Next, use the before() method to get a DateTime instance 4 hours, 30 minutes, and 10 seconds before the current timestamp.

We can also easily change a timezone using the withZone() method:

scala> val changeTZ = less5.withZone(DateTimeZone.forID("Africa/Kampala"))
val changeTZ: org.joda.time.DateTime = 2024-08-05T15:00:51.107+03:00

We apply this on the less5 DateTime instance created previously converting it to the Kampala timezone. This is done by providing the timezone ID to the DateTimeZone.forID() function. To take this a step further, we can also change the chronology of the DateTime instance using the withChronology() function.

scala> import org.joda.time.chrono.EthiopicChronology

scala> val chrono = less5.withChronology(EthiopicChronology.getInstance())
val chrono: org.joda.time.DateTime = 2016-11-29T15:05:17.589+03:00

scala> val ethioYear = chrono.getYear()
val ethioYear: Int = 2016

To do this, we provide the EthiopicChronology.getInstance() as an argument to the withChronology() method. The only catch is that we’ll have to use the following import from Joda-Time, org.joda.time.chrono.EthiopicChronology import.

When we check the year using this chronology, we see that it’s currently 2016.

8. Working With Formatters

We can format DateTime instances in the following way:

scala> val fmt = DateTimeFormat.forPattern("dd-MM-yyyy")
val fmt: org.joda.time.format.DateTimeFormatter = org.joda.time.format.DateTimeFormatter@50936a8b

scala> val st5 = fmt.parseDateTime("31-07-2024")
val st5: org.joda.time.DateTime = 2024-07-31T00:00:00.000+03:00

First, we use the forPattern() method from the DateTimeFormat object and provide a pattern as an argument. This creates a DateTimeFormatter object on which we can call the parseDateTime() method, passing it a date value of the type String.

9. Conclusion

In this article, we learned about the nScala-Time library which is a wrapper around the Joda-Time library from Java. Here, we covered how to use DateTime, JDK interoperability, querying instances of DateTime, accessing date and time fields, manipulating DateTime values, and how to use Formatters.

The Joda-Time library provides so many useful tools to handle Date and Time values. The nScala-Time library provides an improved interface on top of it for Scala developers. Joda-Time is well documented, and the reader is encouraged to read through it as a next step to get a more in-depth understanding of the library.

As always, the code for this article will be available over on GitHub.


« 上一篇: ElasticMQ 简介