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
As shown in Table 2-1, the numeric types have the same data ranges as their Java primitive equivalents.
Data type | Description | Range |
---|---|---|
|
16-bit unsigned Unicode character |
0 to 65,535 |
|
8-bit signed value |
-128 to 127 |
|
16-bit signed value |
32,768 to 32,767 |
|
32-bit signed value |
2,147,483,648 to 2,147,483,647 |
|
64-bit signed value |
-263 to 263-1, inclusive |
|
32-bit IEEE 754 single precision float |
See below |
|
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.
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.
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
.
You want to convert a String
to one of Scala’s numeric types.
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
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
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
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
.
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")
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.
TODO:LINK:OptionSomeNoneRecipe (link to the Option/Some/None recipe)
The StringOps class
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
.
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.
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
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 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.
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.
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.
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
=
1
l
// Long = 1
val
a
=
1L
// Long = 1
val
a
=
1
d
// Double = 1.0
val
a
=
1
D
// Double = 1.0
val
a
=
1
f
// Float = 1.0
val
a
=
1
F
// 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
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.
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.”
++
and −−
You want to increment or decrement numbers using operators like ++
and −−
that are available in other languages, but Scala doesn’t have these operators.
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 ^
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.
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
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.
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
You’re writing an application and need to use very large integer or decimal values.
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)
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 currencyBigDecimal
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.”
The BigDecimal and BigInteger in Java page on baeldung.com has a lot of details on the Java classes that are wrapped by Scala’s BigDecimal
and BigInt
class]
If you need to save these data types into a database, these pages may be helpful:
A Stack Overflow page, How to insert BigInteger in a Java PreparedStatement
A Stack Overflow page, How to store BigInteger into MySql
You need to create random numbers, such as when testing an application, performing a simulation, and many other situations.
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
.
This section shows several other useful things you can do with the Random
class.
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)
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
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)
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
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.
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.
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
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)
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, ?)
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)
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)
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.
You want to format numbers or currency to control decimal places and separators (commas and decimals), typically for printed output.
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.
f
string interpolatorThe 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
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
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
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 €
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
).
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
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.
You need to create new date and time instances using the Date and Time API that was introduced with Java 8.
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.
Class | Description |
---|---|
|
A date without a time-zone, such as 2007-12-03. |
|
A time without a time-zone, such as 10:15:30. |
|
A date-time without a time-zone, such as 2007-12-03T10:15:30. |
|
A date-time with a time-zone, such as 2007-12-03T10:15:30+01:00 Europe/Paris. |
|
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
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.
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
You need to determine the difference between two dates.
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
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.
You need to print dates in a desired format.
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
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
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
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.
You need to parse a string into one of the date/time types introduced in Java 8.
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.
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
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
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
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
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
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.
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.
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.
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
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.