Chapter 2. Numbers and Dates

Introduction

This chapter covers recipes for working with Scala’s numeric types, and also includes recipes for working with the Date and Time API that was introduced with Java 8.

In Scala, the types Byte, Short, Int, Long, and Char are known as integral types because they are represented by integers, or whole numbers. The integral types along with Double and Float comprise Scala’s numeric types. These numeric types extend the AnyVal trait, as do the Boolean and Unit types. As discussed on the page, Tour of Scala: Unified Types, these nine types are called the predefined value types, and they are non-nullable.

The relationship of the predefined value types to AnyVal and Any (as well as Nothing) is shown in Figure 2-1.

As shown in that image:

  • All of the numeric types extend AnyVal

  • All other types in the Scala class hierarchy extend AnyRef

NOT USED
Figure 2-1. All of the numeric types extend AnyVal

As shown in Table 2-1, the numeric types have the same data ranges as their Java primitive equivalents.

Table 2-1. The data ranges of Scala’s built-in numeric types
Data type Description Range

Char

16-bit unsigned Unicode character

0 to 65,535

Byte

8-bit signed value

-128 to 127

Short

16-bit signed value

32,768 to 32,767

Int

32-bit signed value

2,147,483,648 to 2,147,483,647

Long

64-bit signed value

-263 to 263-1, inclusive

Float

32-bit IEEE 754 single precision float

See below

Double

64-bit IEEE 754 single precision float

See below

In addition to those types, Boolean can have the values true or false.

If you ever need to know the exact values of the data ranges and don’t have this book handy, you can find them in the Scala REPL:

Char.MinValue.toInt   // 0
Char.MaxValue.toInt   // 65535
Byte.MinValue         // -128
Byte.MaxValue         // +127
Short.MinValue        // −32768
Short.MaxValue        // +32767
Int.MinValue          // −2147483648
Int.MaxValue          // +2147483647
Long.MinValue         // -9,223,372,036,854,775,808
Long.MaxValue         // +9,223,372,036,854,775,807
Float.MinValue        // −3.4028235e38
Float.MaxValue        // +3.4028235e38
Double.MinValue       // -1.7976931348623157e308
Double.MaxValue       // +1.7976931348623157e308

In addition to these basic numeric types, the BigInt and BigDecimal classes are also covered in this chapter.

Complex numbers

If you need more powerful math classes than those that are included with the standard Scala distribution, check out the Spire project, which includes classes like Rational, Complex, Real, and more.

Dates and times

The last several recipes in this chapter cover the Date and Time API that was introduced with Java 8, and they show how to work with new classes like LocalDate, LocalTime, LocalDateTime, Instant, and ZonedDateTime.

2.1 Parsing a Number from a String

Problem

You want to convert a String to one of Scala’s numeric types.

Solution

Use the to* methods that are available on a String, thanks to the StringLike trait and StringOps class:

"1".toByte     // Byte = 1
"1".toShort    // Short = 1
"1".toInt      // Int = 1
"1".toLong     // Long = 1
"1".toFloat    // Float = 1.0
"1".toDouble   // Double = 1.0

Be careful, because these methods can throw a NumberFormatException:

"hello!".toInt   // java.lang.NumberFormatException

BigInt and BigDecimal instances can also be created directly from strings:

val b = BigInt("1")           // BigInt = 1
val b = BigDecimal("1.234")   // BigDecimal = 1.234

And they can also throw a NumberFormatException:

val b = BigInt("yo")          // NumberFormatException
val b = BigDecimal("dude!")   // NumberFormatException

Handling a base and radix

If you need to perform calculations using bases other than 10, use the parseInt method of the java.lang.Integer class, as shown in these examples:

Integer.parseInt("1", 2)     // Int = 1
Integer.parseInt("10", 2)    // Int = 2
Integer.parseInt("100", 2)   // Int = 4
Integer.parseInt("1", 8)     // Int = 1
Integer.parseInt("10", 8)    // Int = 8

Discussion

If you’ve used Java to convert a String to a numeric data type, then the NumberFormatException is familiar. However, Scala doesn’t have checked exceptions, so you’ll probably want to handle this situation differently.

A first thing to know is that you don’t have to declare that Scala methods can throw an exception, so it’s perfectly legal to write a method like this:

// not required to declare "throws NumberFormatException"
def makeInt(s: String) = s.toInt

Writing a pure function

However, in functional programming (FP) you’d never do this. As written, this method can short-circuit a caller’s code, and that’s something you never do in FP. (You might think of it as something you’d never do to another developer, or want another developer to do to you.) Instead, a pure function always returns the type that its signature shows. Therefore, in FP you’d write this function like this instead:

def makeInt(s: String): Option[Int] = {
    try {
        Some(s.toInt)
    } catch {
        case e: NumberFormatException => None
    }
}

This function is declared to return Option[Int], meaning that if you give it a "10", it will return a Some(10), and if you give it "Yo", it returns a None.

Shorter makeInt functions

While that code shows a perfectly legitimate way to write a makeInt function that returns Option[Int], you can write it shorter like this:

import scala.util.Try
def makeInt(s: String): Option[Int] = Try(s.toInt).toOption

Both this function and the previous makeInt function always return either Some[Int] or None:

makeInt("a")            // None
makeInt("1")            // Some(1)
makeInt("2147483647")   // Some(2147483647)
makeInt("2147483648")   // None

If you prefer to return Try from your function instead of Option you can write it like this:

import scala.util.{Try, Success, Failure}
def makeInt(s: String): Try[Int] = Try(s.toInt)

The advantage of using Try is that when things go wrong, it returns the reason for the failure inside a Failure object:

makeInt("1")   // Success(1)
makeInt("a")   // Failure(java.lang.NumberFormatException: For input string: "a")

Document methods that throw exceptions

These days I don’t like methods that throw exceptions, but if for some reason they do, I prefer that the behavior is documented. Therefore, if you’re going to allow an exception to be thrown, consider adding an @throws Scaladoc comment to your method:

@throws(classOf[NumberFormatException])
def makeInt(s: String) = s.toInt

This approach is required if the method will be called from Java code, as described in TODO:LINK:AddExceptionAnnotationsToScalaMethodsRecipe.

See Also

  • TODO:LINK:OptionSomeNoneRecipe (link to the Option/Some/None recipe)

  • The StringOps class

2.2 Converting Between Numeric Types (Casting)

Problem

You want to convert from one numeric type to another, such as from an Int to a Double, Double to Int, or possibly a conversion involving BigInt or BigDecimal.

Solution

Numeric values are typically converted from one type to another with a collection of to* methods, including toByte, toChar, toDouble, toFloat, toInt, toLong, and toShort. These methods are added to the base numeric types by classes like RichDouble, RichInt, RichFloat, etc., which are automatically brought into scope by scala.Predef.

Numeric values are easily converted in the direction shown in Figure 2-2.

NOT USED
Figure 2-2. The direction in which numeric values are easily converted

A few examples show how casting in this direction is done:

val b: Byte = 1

b.toShort       // Short = 1
b.toInt         // Int = 1
b.toLong        // Long = 1
b.toFloat       // Float = 1.0
b.toDouble      // Double = 1.0

When you go with the flow like that, conversion is simple. It’s also possible to go in the opposite direction — against the flow — like this:

val d = 100.0   // Double = 100.0
d.toFloat       // Float = 100.0
d.toLong        // Long = 100
d.toInt         // Int = 100
d.toShort       // Short = 100
d.toByte        // Byte = 100

However, beware that going in this direction can cause serious problems:

val d = Double.MaxValue   // 1.7976931348623157E308

// don’t do this
d.toFloat   // Float = Infinity
d.toLong    // Long = 9223372036854775807
d.toInt     // Int = 2147483647
d.toShort   // Short = -1
d.toByte    // Byte = -1

Therefore, before attempting to ever use those methods you should always check to see if the conversion attempt is valid:

val d: Double = Double.MaxValue

d.isValidFloat   // not a member of Double (see the note)
d.isValidLong    // not a member of Double (see the note)
d.isValidInt     // true
d.isValidShort   // false
d.isValidByte    // false

Also note that when using this technique, the Int/Short/Byte tests will fail if the Double has a non-zero fractional part:

val d = 1.5      // Double = 1.5

d.isValidInt     // false
d.isValidShort   // false
d.isValidByte    // false

asInstanceOf

Depending on your needs you can also cast “with the flow” using asInstanceOf:

val b: Byte = 1          // Byte = 1

b.asInstanceOf[Short]    // Short = 1
b.asInstanceOf[Int]      // Int = 1
b.asInstanceOf[Long]     // Long = 1
b.asInstanceOf[Float]    // Float = 1.0
b.asInstanceOf[Double]   // Double = 1.0

For instance, if you have a Byte and a method wants a Double, you can use this technique:

// a method that wants a Double
def meWantDouble(d: Double): Unit = println(d.getClass)

// cast the Byte to Double when calling it
meWantDouble(b.asInstanceOf[Double])

It’s possible to pass a Byte where a Double is required

It’s possible to implicitly pass a Byte to a method that wants a Double if you import scala.language.implicitConversions or enable the -language:implicitConversions compiler option. I don’t use this feature — I come from the school of “Too much magic causes software rot” — but you can read the pros and cons of it on the scala.language Scaladoc page.

Discussion

Because all of these numeric types are classes (and not primitive values), the BigInt and BigDecimal also work similarly. The following examples show how they work with the numeric value types.

BigInt

The BigInt constructor is overloaded, giving you nine different ways to construct one, including giving it an Int, Long, or String:

val i: Int = 101
val l: Long = 102
val s = "103"

val b1 = BigInt(i)   // BigInt = 101
val b2 = BigInt(l)   // BigInt = 102
val b3 = BigInt(s)   // BigInt = 103

BigInt also has isValid* and to* methods to help you cast a BigInt value to the numeric types:

  • isValidByte, toByte

  • isValidChar, toChar

  • isValidDouble, toDouble

  • isValidFloat, toFloat

  • isValidInt, toInt

  • isValidLong, toLong

  • isValidShort, toShort

BigDecimal

Similarly, BigDecimal can be constructed in these ways:

BigDecimal(100.asInstanceOf[Int])     // BigDecimal = 100
BigDecimal(100.asInstanceOf[Long])    // BigDecimal = 100
BigDecimal(100.asInstanceOf[Float])   // BigDecimal = 100.0
BigDecimal(100.asInstanceOf[Double])  // BigDecimal = 100

BigDecimal has all the same isValid* and to* methods that the other types have. It also *Exact methods that work like this:

BigDecimal(100).toBigIntExact     // Some(100)
BigDecimal(100.5).toBigIntExact   // None

BigDecimal(100).toIntExact        // Int = 100
BigDecimal(100.5).toIntExact      // java.lang.ArithmeticException
BigDecimal(100.5).toLongExact     // java.lang.ArithmeticException
BigDecimal(100.5).toByteExact     // java.lang.ArithmeticException
BigDecimal(100.5).toShortExact    // java.lang.ArithmeticException

See the BigInt and BigDecimal Scaladoc for even more methods.

2.3 Overriding the Default Numeric Type

Problem

When using an implicit type declaration style, Scala automatically assigns types based on their numeric values, and you need to override the default type when you create a numeric field.

Solution

If you assign 1 to a variable without explicitly declaring its type, Scala assigns it the type Int:

scala> val a = 1
a: Int = 1

Therefore, when you need to control the type, explicitly declare it:

val a: Byte = 1     // Byte = 1
val a: Short = 1    // Short = 1
val a: Int = 1      // Int = 1
val a: Long = 1     // Long = 1
val a: Float = 1    // Float = 1.0
val a: Double = 1   // Double = 1.0

While I prefer that style, it’s also legal to specify the type at the end of the expression:

val a = 0: Byte
val a = 0: Int
val a = 0: Short
val a = 0: Double
val a = 0: Float

For longs, doubles, and floats you can also use this style:

val a = 1l   // Long = 1
val a = 1L   // Long = 1
val a = 1d   // Double = 1.0
val a = 1D   // Double = 1.0
val a = 1f   // Float = 1.0
val a = 1F   // Float = 1.0

You can create hex values by preceding the number with a leading 0x or 0X, and you can store them as an Int or Long:

val a = 0x20    // Int = 32
val a = 0x20L   // Long = 32

Discussion

It’s helpful to know about this approach when creating any object instance. The general syntax looks like this:

// general case
var [name]: [Type] = [initial value]

// example
var a: Short = 0

This form can be helpful when you need to initialize var fields in a class:

class Foo {
    var a: Short = 0     // specify a default value
    var b: Short = _     // defaults to 0
    var s: String = _    // defaults to null
}

As shown, you can use the underscore character as a placeholder when assigning an initial value. This works when creating class variables, but doesn’t work in other places, such as inside a method. For numeric types this isn’t an issue — you can just assign the type the value zero — but with most other types, if you really want a null value you can use this approach inside a method:

var name = null.asInstanceOf[String]

But the usual warning applies: Don’t use null values. It’s better to use the Option/Some/None pattern, which you’ll see in the best Scala libraries and frameworks, such as the Play Framework. See TODO:LINK:EliminateNullValuesFromYourCodeRecipe and TODO:LINK:UsingOptionSomeNoneRecipe for more discussion of this important topic.

Type ascription

In some rare instances, you may need to take advantage of a technique known as type ascription, whose syntax looks like these examples. Stack Overflow shows a case where it’s advantageous to upcast a String to an Object. The technique is shown here:

val s = "Hala"      // s: String = Hala
val o = s: Object   // o: Object = Hala

As shown, the syntax is similar to this recipe. This upcasting is known as “type ascription,” and the Scala style guide on types describes it as follows:

“Ascription is basically just an up-cast performed at compile time for the sake of the type checker. Its use is not common, but it does happen on occasion. The most often seen case of ascription is invoking a varargs method with a single Seq parameter. This is done by ascribing the _* type.”

2.4 Replacements for ++ and −−

Problem

You want to increment or decrement numbers using operators like ++ and −− that are available in other languages, but Scala doesn’t have these operators.

Solution

Because val fields are immutable, they can’t be incremented or decremented, but var Int fields can be mutated with the += and −= methods:

var a = 1   // a = 1
a += 1      // a = 2
a −= 1      // a = 1

As an added benefit, you use similar methods for multiplication and division:

var i = 1   // i = 1
i *= 4      // i = 4
i /= 2      // i = 2

Note that these symbols aren’t operators; they’re implemented as methods that are available on Int fields declared as a var. Attempting to use them on val fields results in a compile-time error:

scala> val x = 1
x: Int = 1

scala> x += 1
<console>:9: error: value += is not a member of Int
              x += 1
                ^

Methods, not operators

As mentioned, the symbols +=, −=, *=, and /= aren’t operators, they’re methods. This approach of building functionality with libraries instead of operators is a consistent pattern in Scala. Actors, for instance, aren’t built into the language, but are instead implemented as a library. See the Dr. Dobbs link in the See Also section for Martin Odersky’s discussion of this philosophy.

Discussion

Another benefit of this approach is that you can call methods of the same name on other numeric types besides Int. For instance, the Double and Float classes have methods of the same name:

var d = 1.2    // Double = 1.2
d += 1         // 2.2
d *= 2         // 4.4
d /= 2         // 2.2

var f = 1.2F   // Float = 1.2
f += 1         // 2.2
f *= 2         // 4.4
f /= 2         // 2.2

2.5 Comparing Floating-Point Numbers

Problem

You need to compare two floating-point numbers, but as in some other programming languages, two floating-point numbers that should be equivalent may not be.

Solution

When you begin working with floating-point numbers, you quickly learn that 0.1 plus 0.1 is 0.2:

scala> 0.1 + 0.1
res0: Double = 0.2

But 0.1 plus 0.2 isn’t exactly 0.3:

scala> 0.1 + 0.2
res1: Double = 0.30000000000000004

This inaccuracy makes comparing two floating-point numbers a significant problem:

val a = 0.3         // Double = 0.3
val b = 0.1 + 0.2   // Double = 0.30000000000000004
a == b              // false

The solution to this problem is to write your own functions to compare floating-point numbers with a precision (or tolerance). The following “approximately equals” method demonstrates the approach:

def ~=(x: Double, y: Double, precision: Double): Boolean =
    if ((x - y).abs < precision) true else false

You can use this method like this:

val a = 0.3         // 0.3
val b = 0.1 + 0.2   // 0.30000000000000004
~=(a, b, 0.0001)    // true
~=(b, a, 0.0001)    // true

2.6 Handling Large Numbers

Problem

You’re writing an application and need to use very large integer or decimal values.

Solution

If the Long and Double types aren’t large enough, use the Scala BigInt and BigDecimal classes:

val bi = BigInt(1234567890)       // BigInt = 1234567890
val bd = BigDecimal(123456.789)   // BigDecimal = 123456.789

BigInt and BigDecimal wrap the Java BigInteger and BigDecimal classes, and they support all the operators you’re used to using with numeric types:

bi + bi          // BigInt = 2469135780
bi * bi          // BigInt = 1524157875019052100
bi / BigInt(2)   // BigInt = 617283945

You can convert them to other numeric types:

// bad conversions
bi.toByte     // -46
bi.toChar     // ˒
bi.toShort    // 722

// correct conversions
bi.toInt      // 1234567891
bi.toLong     // 1234567891
bi.toFloat    // 1.23456794E9
bi.toDouble   // 1.234567891E9

To avoid conversion errors, test them first:

bi.isValidByte    // false
bi.isValidChar    // false
bi.isValidShort   // false
bi.toInt          // true
bi.toLong         // true

BigInt also converts to a byte array:

bi.toByteArray    // Array[Byte] = Array(73, -106, 2, -46)

Discussion

Before using BigInt or BigDecimal, you can check the minimum and maximum values that Long and Double can handle:

Long.MinValue     // -9,223,372,036,854,775,808
Long.MaxValue     // +9,223,372,036,854,775,807
Double.MinValue   // -1.7976931348623157e308
Double.MaxValue   // +1.7976931348623157e308

Depending on your needs, you may also be able to use the PositiveInfinity and NegativeInfinity of the standard numeric types:

scala> 1.7976931348623157E308 > Double.PositiveInfinity
res0: Boolean = false

BigDecimal is often used for currency

BigDecimal is often used to represent currency because it offers control over rounding behavior. As shown in previous recipes, adding $0.10 + $0.20 with a Double isn’t exactly $0.30:

0.10 + 0.20   // Double = 0.30000000000000004

But BigDecimal doesn’t suffer from that problem:

BigDecimal(0.10) + BigDecimal(0.20)   // BigDecimal = 0.3

As Joshua Block states in Effective Java, “use BigDecimal, int, or long for monetary calculations.”

See Also

2.7 Generating Random Numbers

Problem

You need to create random numbers, such as when testing an application, performing a simulation, and many other situations.

Solution

Create random numbers with the scala.util.Random class. The following examples show common random number use cases:

val r = scala.util.Random

// random integers
r.nextInt      // 455978773
r.nextInt      // -1837771511

// returns a value between 0.0 and 1.0
r.nextDouble   // 0.22095085955974536
r.nextDouble   // 0.3349793259700605

// returns a value between 0.0 and 1.0
r.nextFloat    // 0.34705013
r.nextFloat    // 0.79055405

// set a seed when creating a new Random
val r = new scala.util.Random(100)

// update the seed after you already have a Random instance
r.setSeed(1000L)

// limit the integers to a maximum value
r.nextInt(6)   // 0
r.nextInt(6)   // 5
r.nextInt(6)   // 1

When setting a maximum value on nextInt, the Int returned is between 0 (inclusive) and the value you specify (exclusive), so specifying 100 returns an Int from 0 to 99.

Discussion

This section shows several other useful things you can do with the Random class.

Random length ranges

Scala makes it easy to create a random-length range of numbers, which is especially useful for testing:

// random length ranges
0 to r.nextInt(10)   // Range 0 to 9
0 to r.nextInt(10)   // Range 0 to 3
0 to r.nextInt(10)   // Range 0 to 7

Remember that you can always convert a Range to a sequence if that’s what you need:

(0 to r.nextInt(10)).toList     // List(0, 1, 2, 3, 4)
(0 to r.nextInt(10)).toVector   // Vector(0, 1, 2)
(0 to r.nextInt(10)).toStream   // Stream(0, ?)

A for/yield loop gives you a nice way to modify the values in the sequence:

for (i <- 0 to r.nextInt(10)) yield i * 10

That approach yields sequences like these:

Vector(0, 10, 20, 30)
Vector(0, 10)
Vector(0, 10, 20, 30, 40, 50, 60, 70, 80)

Fixed-length ranges with random values

Another approach is to create a sequence of known length, filled with random numbers:

val seq = for (i <- 1 to 5) yield r.nextInt(100)

That approach yields sequences that contain five random integers, like these:

Vector(99, 6, 40, 77, 19)
Vector(1, 75, 87, 55, 39)
Vector(46, 40, 4, 82, 92)

You can do the same thing with nextFloat and nextDouble:

val seq = for (i <- 1 to 5) yield r.nextFloat
val seq = for (i <- 1 to 5) yield r.nextDouble

Shuffling an existing sequence

Another common need is to “randomize” an existing sequence. To do that, use the Random class shuffle method:

import scala.util.Random
val x = List(1, 2, 3)

Random.shuffle(x)   // List(3, 1, 2)
Random.shuffle(x)   // List(2, 3, 1)

Getting a random element from a sequence

If you have an existing sequence and want to get a single random element from it, you can use this function:

import scala.util.Random

def getRandomElement[A](list: Seq[A], random: Random): A =
    list(random.nextInt(list.length))

Here are a few examples of how to use this method:

val r = scala.util.Random

// integers
val ints = (1 to 100).toList
getRandomElement(ints, r)    // Int = 66
getRandomElement(ints, r)    // Int = 11

// strings
val names = List("Hala", "Helia", "Hannah", "Hope")
getRandomElement(names, r)   // Hala
getRandomElement(names, r)   // Hannah

2.8 Creating a Range or Sequence of Numbers

Problem

You need to create a range, such as in a for loop, or create a sequence of numbers from a range, typically for testing purposes.

Solution

Use the to or until methods of the Int class to create a Range with the desired elements. You can then convert that range to a sequence, if desired. To populate a sequence you can also use the range method of the desired sequence class (List, Vector, etc.). Depending on your needs you can also use the fill and tabulate methods that are available on sequences.

to

A simple way to create a range is with the to method:

scala> val r = 1 to 5
r: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)

You can set an optional step with the by method:

val r = 1 to 10 by 2   // Range(1, 3, 5, 7, 9)
val r = 1 to 10 by 3   // Range(1, 4, 7, 10)

Ranges are commonly used in for loops:

scala> for (i <- 1 to 3) println(i)
1
2
3

until

When creating a Range you can use until instead of to:

scala> for (i <- 1 until 3) println(i)
1
2

until doesn’t include the last element you specify, as shown in these examples:

(1 to 3).toVector            // Vector(1, 2, 3)
(1 until 3).toVector         // Vector(1, 2)

(1 to 10 by 3).toVector      // Vector(1, 4, 7, 10)
(1 until 10 by 3).toVector   // Vector(1, 4, 7)

Creating sequences

Once you have a Range you can convert it to other sequences:

(1 to 5).toList        // List[Int] = List(1, 2, 3, 4, 5)
(1 until 5).toVector   // Vector[Int] = Vector(1, 2, 3, 4)
(1 to 5).toBuffer      // mutable.Buffer[Int] = ArrayBuffer(1, 2, 3, 4, 5)

(1 to 5).toSeq        // immutable.Range = Range 1 to 5
(1 to 5).toSet        // Set[Int] = Set(5, 1, 2, 3, 4)
(1 to 5).toStream     // Stream[Int] = Stream(1, ?)

Discussion

Another approach to creating a sequence is to use the range method that’s available on sequence objects:

Vector.range(1, 3)      // Vector(1, 2)
Vector.range(1, 6, 2)   // Vector(1, 3, 5)
List.range(1, 6, 2)     // List(1, 3, 5)

As shown in those examples, the second parameter is not included in the result, so it works like the until method, and the third parameter is the step.

Finally, note that you can use the same approaches with characters:

('d' to 'h').toVector       // Vector(d, e, f, g, h)
('d' until 'h').toVector    // Vector(d, e, f, g)
('a' to 'f').by(2).toList   // List(a, c, e)

The details

Behind the scenes, the to and until methods are defined in the RichInt class. When you type the following portion of code, you’re invoking its to method:

1 to

You can clearly see that to, by, and until are methods in these examples:

(1 to 10 by 3).toVector      // Vector(1, 4, 7, 10)
(1 to 10).by(3).toVector     // Vector(1, 4, 7, 10)
1.to(10).by(3).toVector      // Vector(1, 4, 7, 10)
1.until(10).by(3).toVector   // Vector(1, 4, 7)

fill and tabulate

Depending on your needs, another way to initially populate a sequence is to use the fill and tabulate methods that are available on sequence objects:

Vector.fill(3)("yo")             // Vector(yo, yo, yo)
Vector.tabulate(3)(n => n * n)   // Vector(0, 1, 4)
Vector.tabulate(4)(n => n * n)   // Vector(0, 1, 4, 9)

A few more examples of these methods are included in the recipes on the collections classes.

2.9 Formatting Numbers and Currency

Problem

You want to format numbers or currency to control decimal places and separators (commas and decimals), typically for printed output.

Solution

For basic number formatting, use the f string interpolator. For other needs, such as adding commas and working with locales and currency, use instances of the java.text.NumberFormat class:

NumberFormat.getInstance           // general-purpose numbers (floating-point)
NumberFormat.getIntegerInstance    // integers
NumberFormat.getCurrencyInstance   // currency
NumberFormat.getPercentInstance    // percentages

The NumberFormat instances can also be customized for locales.

The f string interpolator

The f string interpolator, which is discussed in detail in Recipe 1.4, provides simple number formatting capabilities:

val pi = scala.math.Pi   // Double = 3.141592653589793
println(f"${pi}%1.5f")   // 3.14159

A few more examples demonstrate the technique:

// floating-point
f"${pi}%1.2f"    // String = 3.14
f"${pi}%1.3f"    // String = 3.142
f"${pi}%1.5f"    // String = 3.14159
f"${pi}%6.2f"    // String = "  3.14"
f"${pi}%06.2f"   // String = 003.14

// whole numbers
val x = 10000
f"${x}%d"        // 10000
f"${x}%2d"       // 10000
f"${x}%8d"       // "   10000"
f"${x}%-8d"      // "10000   "

If you prefer the explicit use of the format method that’s available to strings, write the code like this instead:

"%06.2f".format(pi)   // String = 003.14

Commas, locales, and integers

If you need to add commas to integer values, use NumberFormat’s getIntegerInstance method:

import java.text.NumberFormat
val formatter = NumberFormat.getIntegerInstance

formatter.format(10000)     // String = 10,000
formatter.format(1000000)   // String = 1,000,000

You can set a locale with getIntegerInstance and the Locale class:

import java.util.Locale

val formatter = NumberFormat.getIntegerInstance(Locale.GERMANY)

formatter.format(1000)      // 1.000
formatter.format(10000)     // 10.000
formatter.format(100000)    // 100.000
formatter.format(1000000)   // 1.000.000

Commas, locales, and floating-point values

You can handle floating-point values with a formatter returned by getInstance:

val formatter = NumberFormat.getInstance

formatter.format(12.34)        // 12.34
formatter.format(1234.56)      // 1,234.56
formatter.format(1234567.89)   // 1,234,567.89

You can also set a locale with getInstance:

val formatter = NumberFormat.getInstance(Locale.GERMANY)

formatter.format(12.34)        // 12,34
formatter.format(1234.56)      // 1.234,56
formatter.format(1234567.89)   // 1.234.567,89

Currency

For currency output, use the getCurrencyInstance formatter:

val formatter = NumberFormat.getCurrencyInstance

formatter.format(123.456789)   // $123.46
formatter.format(12345.6789)   // $12,345.68
formatter.format(1234567.89)   // $1,234,567.89

Use a Locale to handle international currency:

import java.util.{Currency, Locale}

val deCurrency = Currency.getInstance(Locale.GERMANY)
val formatter = java.text.NumberFormat.getCurrencyInstance
formatter.setCurrency(deCurrency)

formatter.format(123.456789)   // EUR123.46
formatter.format(12345.6789)   // EUR12,345.68
formatter.format(1234567.89)   // EUR1,234,567.89

If you don’t use a currency library you’ll probably want to use BigDecimal, which also works with getCurrencyInstance:

import java.text.NumberFormat
import scala.math.BigDecimal.RoundingMode

var b = BigDecimal("10000")
b = b.setScale(2, RoundingMode.DOWN)

val formatter = NumberFormat.getCurrencyInstance

formatter.format(b)   // String = $10,000.00

Here’s a BigDecimal example that uses a locale:

import java.util.Locale
val formatter = NumberFormat.getCurrencyInstance(Locale.GERMANY)
var b = BigDecimal("1234567.891").setScale(2, RoundingMode.DOWN)

formatter.format(b)   // String = 1.234.567,89 €

Custom formatting patterns

You can also create your own formatting patterns with the DecimalFormat class. Just create the pattern you want, then apply the pattern to a number using the format method, as shown in these examples:

import java.text.DecimalFormat

val df = new DecimalFormat("0.##")
df.format(123.45)        // 123.45 (type = String)
df.format(123.4567890)   // 123.46
df.format(.1234567890)   // 0.12
df.format(1234567890)    // 1234567890

val df = new DecimalFormat("0.####")
df.format(.1234567890)   // 0.1235
df.format(1234.567890)   // 1234.5679
df.format(1234567890)    // 1234567890

val df = new DecimalFormat("#,###,##0.00")
df.format(123)           // 123.00
df.format(123.4567890)   // 123.46
df.format(1234.567890)   // 1,234.57
df.format(1234567890)    // 1,234,567,890.00

See the Java DecimalFormat class for more formatting pattern characters (and a warning that, in general, you shouldn’t create a direct instance of DecimalFormat).

Locales

The java.util.Locale class has three constructors:

Locale(String language)
Locale(String language, String country)
Locale(String language, String country, String data)

It also includes more than a dozen static instances for locales like CANADA, CHINA, FRANCE, GERMAN, JAPAN, UK, US, and more. For countries and languages that don’t have Locale constants, you can still specify them using a language or a pair of language/country strings. For example, per Oracle’s JDK 10 and JRE 10 Supported Locales page, locales in India can be specified like this:

new Locale("hi-IN", "IN")
new Locale("en-IN", "IN")

Here are a few other examples:

new Locale("en-AU", "AU")   // Australia
new Locale("pt-BR", "BR")   // Brazil
new Locale("es-ES", "ES")   // Spain

These examples demonstrate how the first India local is used:

// India
import java.util.{Currency, Locale}

val indiaLocale = Currency.getInstance(new Locale("hi-IN", "IN"))
val formatter = java.text.NumberFormat.getCurrencyInstance
formatter.setCurrency(indiaLocale)

formatter.format(123.456789)   // INR123.46
formatter.format(1234.56789)   // INR1,234.57

With all of the get*Instance methods of NumberFormat you can also set a default locale:

val default = Locale.getDefault
val formatter = NumberFormat.getInstance(default)

formatter.format(12.34)        // 12.34
formatter.format(1234.56)      // 1,234.56
formatter.format(1234567.89)   // 1,234,567.89

Discussion

This recipe falls back to the Java approach for printing currency and other formatted numeric fields, though of course the currency solution depends on how you handle currency in your applications. In my work as a consultant, I’ve seen most companies handle currency using the Java BigDecimal class, and others create their own custom currency classes, which are typically wrappers around BigDecimal.

JSR-354, Money and Currency API defines a proposed API that hasn’t made it into the Java SDK at the time of this writing. You can also use libraries like Joda Money.

2.10 Creating New Date and Time Instances

Problem

You need to create new date and time instances using the Date and Time API that was introduced with Java 8.

Solution

Using the Java 8 API you can create new dates, times, and date/time values. Table 2-2 provides a description of some of the new classes you’ll use (from the java.time Javadoc), all of which work in the ISO-8601 calendar system.

Table 2-2. Descriptions of common Java 8 Date and Time classes
Class Description

LocalDate

A date without a time-zone, such as 2007-12-03.

LocalTime

A time without a time-zone, such as 10:15:30.

LocalDateTime

A date-time without a time-zone, such as 2007-12-03T10:15:30.

ZonedDateTime

A date-time with a time-zone, such as 2007-12-03T10:15:30+01:00 Europe/Paris.

Instant

Models a single instantaneous point on the time-line. This might be used to record event timestamps in the application.

To create new date/time instances:

  • Use now methods on those classes to create new instances that represent the current moment

  • Use of methods on those classes to create dates that represent past or future date/time values

“Now”

To create instances to represent the current date and time, use the now methods that are available on the new classes in the API:

import java.time._

LocalDate.now       // 2019-01-20
LocalTime.now       // 12:19:26.270
LocalDateTime.now   // 2019-01-20T12:19:26.270
Instant.now         // 2019-01-20T19:19:26.270Z
ZonedDateTime.now   // 2019-01-20T12:44:53.466-07:00[America/Denver]

The results of those methods demonstrate the data that’s stored in each type.

Past or future

To create dates and times in the past or future, use the of factory methods on each of the classes shown. For example, here are a few ways to create java.time.LocalDate instances with its of factory methods:

val squirrelDay = LocalDate.of(2020, 1, 21)
val squirrelDay = LocalDate.of(2020, Month.JANUARY, 21)
val squirrelDay = LocalDate.of(2020, 1, 1).plusDays(20)

Note that with LocalDate, January is represented by 1 (not 0).

java.time.LocalTime has five of* factory methods, including these:

LocalTime.of(hour: Int, minute: Int)
LocalTime.of(hour: Int, minute: Int, second: Int)

LocalTime.of(0, 0)    // 00:00
LocalTime.of(0, 1)    // 00:01
LocalTime.of(1, 1)    // 01:01
LocalTime.of(23, 59)  // 23:59

These intentional exceptions help demonstrate the valid values for minutes and hours:

LocalTime.of(23, 60)  // DateTimeException: Invalid value for MinuteOfHour,
                      // (valid values 0 - 59): 60

LocalTime.of(24, 1)   // DateTimeException: Invalid value for HourOfDay,
                      // (valid values 0 - 23): 24

java.time.LocalDateTime has nine of* factory method constructors, including these:

LocalDateTime.of(year: Int, month: Int, dayOfMonth: Int, hour: Int, minute: Int)
LocalDateTime.of(year: Int, month: Month, dayOfMonth: Int, hour: Int, minute: Int)
LocalDateTime.of(date: LocalDate, time: LocalTime)

java.time.ZonedDateTime has seven of* factory method constructors, including these:

of(int year, int month, int dayOfMonth, int hour, int minute, int second,
   int nanoOfSecond, ZoneId zone)
of(LocalDate date, LocalTime time, ZoneId zone)
of(LocalDateTime localDateTime, ZoneId zone)
ofInstant(Instant instant, ZoneId zone)

Here’s an example of the second method:

val zdt = ZonedDateTime.of(
    LocalDate.now,
    LocalTime.now,
    ZoneId.of("America/New_York")
)

// result: 2019-01-20T13:05:28.119-05:00[America/New_York]

While I’m in the neighborhood, a few other java.time.ZoneId values look like this:

ZoneId.of("Europe/Paris")
ZoneId.of("Asia/Tokyo")
ZoneId.of("America/New_York")
ZoneId zoneId = ZoneId.of("UTC+1")   // an offset from UTC (Greenwich) time

java.time.Instant has three of* factory methods:

Instant.ofEpochMilli(epochMilli: Long)
Instant.ofEpochSecond(epochSecond: Long)
Instant.ofEpochSecond(epochSecond: Long, nanoAdjustment: Long)

Instant.ofEpochMilli(100)   // Instant = 1970-01-01T00:00:00.100Z

The Instant class is nice for many reasons, including giving you the ability to calculation the time duration between two instants:

import java.time.{Instant, Duration}

val start = Instant.now                     // Instant = 2019-01-20T19:31:59.467Z
Thread.sleep(2000)
val stop = Instant.now                      // Instant = 2019-01-20T19:32:01.472Z
val delta = Duration.between(start, stop)   // Duration = PT2.005S
delta.toMillis                              // Long = 2194
delta.toNanos                               // Long = 2194000000

2.11 Calculating The Difference Between Two Dates

Problem

You need to determine the difference between two dates.

Solution

If you need to determine the number of days between two dates, the DAYS enum constant of the java.time.temporal.ChronoUnit class is the easiest solution:

import java.time.LocalDate
import java.time.temporal.ChronoUnit.DAYS

val now  = LocalDate.of(2019,  1, 20)   // 2019-01-20
val xmas = LocalDate.of(2019, 12, 25)   // 2019-12-25

DAYS.between(now, xmas)                 // Long = 339

If you need the number of years or months between two dates, you can also use the YEARS and MONTHS enum constants of ChronoUnit:

import java.time.LocalDate
import java.time.temporal.ChronoUnit._

val now = LocalDate.of(2019,  1, 20)              // 2019-01-20
val nextXmas = LocalDate.of(2020, 12, 25)         // 2020-12-25

val years: Long  = YEARS.between(now, nextXmas)   // 1
val months: Long = MONTHS.between(now, nextXmas)  // 23
val days: Long   = DAYS.between(now, nextXmas)    // 705

Using the same LocalDate values, you can also use the Period class, but notice the significant difference in the output between the ChronoUnit and Period approaches:

import java.time.Period

val diff = Period.between(now, nextXmas)   // P1Y11M5D
diff.getYears                              // 1
diff.getMonths                             // 11
diff.getDays                               // 5

Discussion

The between method of the ChronoUnit class takes two Temporal arguments:

between(temporal1Inclusive: Temporal, temporal2Exclusive: Temporal)

Therefore it works with all Temporal subclasses, including Instant, LocalDate, LocalDateTime, LocalTime, ZonedDateTime, and more. Here’s a LocalDateTime example:

import java.time.LocalDateTime
import java.time.temporal.ChronoUnit

// of(year, month, dayOfMonth, hour, minute)
val d1 = LocalDateTime.of(2020, 1, 1, 1, 1)
val d2 = LocalDateTime.of(2063, 4, 5, 1, 1)

ChronoUnit.DAYS.between(d1, d2)      // Long = 15800
ChronoUnit.YEARS.between(d1, d2)     // Long = 43
ChronoUnit.MINUTES.between(d1, d2)   // Long = 22752000

The ChronoUnit class has many other enum constants including CENTURIES, DECADES, HOURS, MICROS, MILLIS, SECONDS, WEEKS, YEARS, and more.

2.12 Formatting Dates

Problem

You need to print dates in a desired format.

Solution

Use the java.time.format.DateTimeFormatter class. It provides three types of formatters for printing date/time values:

  • Predefined formatters

  • Locale formatters

  • The ability to create your own custom formatters

Predefined formatters

DateTimeFormatter provides fifteen predefined formatters you can use. This example shows how to use a formatter with a LocalDate:

import java.time.format.DateTimeFormatter
val d = LocalDate.now   // 2019-01-19

val f = DateTimeFormatter.BASIC_ISO_DATE
f.format(d)             // 20190119

These examples show what the other date formatters look like:

ISO_LOCAL_DATE     // 2019-01-19
ISO_DATE           // 2019-01-19
BASIC_ISO_DATE     // 20190119
ISO_ORDINAL_DATE   // 2019-019
ISO_WEEK_DATE      // 2019-W03-6

Locale formatters

Create locale formatters using these static DateTimeFormatter methods:

  • ofLocalizedDate

  • ofLocalizedTime

  • ofLocalizedDateTime

  • ofLocalizedDate

You also apply one of four java.time.format.FormatStyle values when creating a localized date:

  • SHORT

  • MEDIUM

  • LONG

  • FULL

This example demonstrates how to use ofLocalizedDate with a LocalDate and FormatStyle.FULL:

import java.time.LocalDate
import java.time.format.{DateTimeFormatter, FormatStyle}

val d = LocalDate.of(2019, 12, 25)
val f = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)
f.format(d)   // 12/25/19

Using the same technique, this is what the four format styles look like:

SHORT    // 12/25/19
MEDIUM   // Dec 25, 2019
LONG     // December 25, 2019
FULL     // Wednesday, December 25, 2019

Custom patterns with ofPattern

You can also create custom patterns by specifying your own formatting strings. Here’s an example of the technique:

import java.time.LocalDate
import java.time.format.DateTimeFormatter

val d = LocalDate.now  //2019-01-20
val f = DateTimeFormatter.ofPattern("yyyy-MM-dd")
f.format(d)   // 2019-01-20

Here are a few other common patterns:

"MM/dd/yyyy"       // 01/20/2019
"MMM dd, yyyy"     // Jan 20, 2019
"E, MMM dd yyyy"   // Sun, Jan 20 2019

This example demonstrates how to format a LocalTime:

import java.time.LocalTime
import java.time.format.DateTimeFormatter

val t = LocalTime.now
val f = DateTimeFormatter.ofPattern("h:mm a")
f.format(t)   // 6:48 PM

These are two time patterns I commonly use:

"h:mm a"       // 6:48 PM
"HH:mm:ss a"   // 18:48:33 PM

With a LocalDateTime you can format both date and time output:

import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

val t = LocalDateTime.now
val f = DateTimeFormatter.ofPattern("MMM dd, yyyy h:mm a")
f.format(t)   // Jan 20, 2019 6:52 PM

See the DateTimeFormatter class for a complete list of predefined formats and formatting pattern characters that are available.

2.13 Parsing Strings Into Dates

Problem

You need to parse a string into one of the date/time types introduced in Java 8.

Solution

If your string is in the expected format, pass it to the parse method of the desired class. If the string is not in the expected (default) format, create a formatter to define the format you want to accept.

LocalDate

This example shows the default format for LocalDate:

val d = LocalDate.parse("2010-12-10")   // LocalDate = 2010-12-10

If you try to pass a string into parse with the wrong format, you’ll get an exception:

val d = LocalDate.parse("2010/12/10")   // java.time.format.DateTimeParseException

To accept a string in a different format, create a formatter for the desired pattern:

import java.time.format.DateTimeFormatter

val df = DateTimeFormatter.ofPattern("yyyy/MM/dd")
val d = LocalDate.parse("2010/12/10", df)   // LocalDate = 2010-12-10

LocalTime

These examples demonstrate the default format for java.time.LocalTime:

val t = LocalTime.parse("01:02")     //01:02
val t = LocalTime.parse("13:02:03")  //13:02:03

Notice that each field requires a leading 0:

val t = LocalTime.parse("1:02")      //java.time.format.DateTimeParseException
val t = LocalTime.parse("1:02:03")   //java.time.format.DateTimeParseException

These examples demonstrate several ways of using formatters:

import java.time.format.DateTimeFormatter

LocalTime.parse("00:00", DateTimeFormatter.ISO_TIME)
    // 00:00
LocalTime.parse("23:59", DateTimeFormatter.ISO_LOCAL_TIME)
    // 23:59
LocalTime.parse("23 59 59", DateTimeFormatter.ofPattern("HH mm ss"))
    // 23:59:59
LocalTime.parse("11 59 59 PM", DateTimeFormatter.ofPattern("hh mm ss a"))
    // 23:59:59

LocalDateTime

This example demonstrates the default format for java.time.LocalDateTime:

val s = "2019-07-22T12:13:14"
val ldt = LocalDateTime.parse(s)   // 2019-07-22T12:13:14

These examples demonstrate several ways of using formatters:

val s = "1999-12-31 23:59"
val f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
val ldt = LocalDateTime.parse(s, f)
    // 1999-12-31 23:59

val s = "1999-12-31 11:59:59 PM"
val f = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss a")
val ldt = LocalDateTime.parse(s, f)
    // 1999-12-31 11:59:59 PM

Instant

java.time.Instant only has one parse method that requires a date/time stamp in the proper format:

Instant.parse("1970-01-01T00:01:02.00Z")   // 1970-01-01T00:01:02Z
Instant.parse("2019-07-22T23:59:59.00Z")   // 2019-07-22T23:59:59Z

ZonedDateTime

These examples demonstrate the default formats for java.time.ZonedDateTime:

ZonedDateTime.parse("2019-12-31T23:59:59-06:00")
    // 2019-12-31T23:59:59-06:00

ZonedDateTime.parse("2019-12-31T23:59:59-00:00[US/Mountain]")
    // 2019-12-31T23:59:59-07:00[US/Mountain]

These examples demonstrate several ways of using formatters:

import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter._

val zdt = ZonedDateTime.parse("2020-01-01T01:02:03Z", ISO_ZONED_DATE_TIME)
    // 2020-01-01T01:02:03Z

ZonedDateTime.parse("2019-12-31T23:59:59+01:00", ISO_DATE_TIME)
    // 2019-12-31T23:59:59+01:00

ZonedDateTime.parse("2020-02-29T00:00:00-05:00", ISO_OFFSET_DATE_TIME)
    // 2020-02-29T00:00-05:00

ZonedDateTime.parse("Sat, 29 Feb 2020 00:01:02 GMT", RFC_1123_DATE_TIME)
    // 2020-02-29T00:01:02Z

2.14 Converting To/From Legacy Date/Time Classes

Problem

You want to convert between the java.time classes introduced in Java 8 and the legacy java.util and java.sql date and time classes.

Solution

When the java.time classes were introduced with Java 8, conversion methods were added to some of the legacy classes so you can convert between them. Conversion methods for the java.sql classes are shown in Table 2-3.

Table 2-3. Conversions to/from java.sql classes
New Class Legacy Class To Legacy Class From Legacy Class

LocalDate

java.sql.Date

Date.valueOf(localDate)

sqlDate.toLocalDate

LocalTime

java.sql.Time

Time.valueOf(localTime)

sqlTime.toLocalTime

LocalDateTime

java.sql.Timestamp

Timestamp.valueOf(localDateTime)

timestamp.toLocalDateTime

Instant

java.sql.Timestamp

N/A

timestamp.toInstant

Other conversion methods are shown in Table 2-4.

Table 2-4. Conversions to/from other legacy date/time classes
New Class Legacy Class To Legacy Class From Legacy Class

Instant

java.util.Date

Date.from(instant)

date.toInstant

ZonedDateTime

java.util.GregorianCalendar

GregorianCalendar.from(zonedDateTime)

cal.toZonedDateTime

DateTimeFormatter

java.text.Format

dtFormatter.toFormat

N/A

ZoneId

java.util.TimeZone

TimeZone.getTimeZone(zoneId)

timeZone.toZoneId

To demonstrate how to use these methods, these examples show how to convert between java.time.Instant and java.util.Date:

import java.time.Instant
import java.util.Date

// convert Instant to Date
val instant = Instant.now   // 2019-01-21T03:37:10.780Z
Date.from(instant)          // java.util.Date = Sun Jan 20 20:37:10 MST 2019

// convert Date to Instant
val d = new Date            // Sun Jan 20 20:39:00 MST 2019
d.toInstant                 // Instant = 2019-01-21T03:39:00.826Z

Discussion

In other cases where direct conversions aren’t available, you’ll need to handle intermediate steps yourself. For instance, these examples demonstrate how to go from a Date to LocalDateTime, and from a Calendar to a LocalDateTime:

import java.time.{Instant, LocalDateTime, ZoneId}
import java.util.{Calendar, Date}

// Date -> LocalDateTime
val legacyDate = new Date
val localDateTime = LocalDateTime.ofInstant(
    legacyDate.toInstant,
    ZoneId.systemDefault
)
// legacyDate:    Mon Jan 21 10:57:49 MST 2019
// localDateTime: 2019-01-21T10:57:49.920

// Calendar -> LocalDateTime
val legacyCalendar = Calendar.getInstance
val localDateTime = LocalDateTime.ofInstant(
    legacyCalendar.toInstant,
    ZoneId.systemDefault
)
// legacyCalendar: (too many details to show)
// localDateTime:  2019-01-21T10:59:38.719

This example shows one way to convert a GregorianCalendar to a LocalDate:

import java.util.{Calendar, GregorianCalendar}
import java.time.LocalDate

val cal = new GregorianCalendar(2019, 5, 30)
// result: YEAR=2019, MONTH=5, DAY_OF_MONTH=30, DAY_OF_YEAR=181

// Calendar to LocalDate
LocalDate.ofYearDay(2019, cal.get(Calendar.DAY_OF_YEAR))
// result: 2019-06-30

As shown in that example, remember that the legacy classes use zero-based values in several areas, including when setting the month of the year.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset