Skip to content

Commit c4c38b0

Browse files
committed
Merge pull request scala-js#12 from alonsodomin/add-instant-class
Implementation for `java.time.Instant` class
2 parents af1da97 + fc5a224 commit c4c38b0

File tree

2 files changed

+764
-0
lines changed

2 files changed

+764
-0
lines changed
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
package java.time
2+
3+
import scala.scalajs.js
4+
5+
import java.time.temporal._
6+
7+
/** Created by alonsodomin on 26/12/2015. */
8+
final class Instant private (private val seconds: Long, private val nanos: Int)
9+
extends TemporalAccessor with Temporal with TemporalAdjuster
10+
with Comparable[Instant] with java.io.Serializable {
11+
12+
import Preconditions._
13+
import Constants._
14+
import Instant._
15+
import ChronoField._
16+
import ChronoUnit._
17+
18+
requireDateTime(seconds >= MinSecond && seconds <= MaxSecond,
19+
s"Invalid seconds: $seconds")
20+
requireDateTime(nanos >= 0 && nanos <= MaxNanosInSecond,
21+
s"Invalid nanos: $nanos")
22+
23+
def isSupported(field: TemporalField): Boolean = field match {
24+
case _: ChronoField =>
25+
field == INSTANT_SECONDS || field == NANO_OF_SECOND || field == MICRO_OF_SECOND ||
26+
field == MILLI_OF_SECOND
27+
28+
case null => false
29+
case _ => field.isSupportedBy(this)
30+
}
31+
32+
def isSupported(unit: TemporalUnit): Boolean = unit match {
33+
case _: ChronoUnit => unit.isTimeBased || unit == DAYS
34+
case null => false
35+
case _ => unit.isSupportedBy(this)
36+
}
37+
38+
// Implemented by TemporalAccessor
39+
// def range(field: TemporalField): ValueRange
40+
41+
// Implemented by TemporalAccessor
42+
// def get(field: TemporalField): Int
43+
44+
def getLong(field: TemporalField): Long = field match {
45+
case INSTANT_SECONDS => seconds
46+
case NANO_OF_SECOND => nanos
47+
case MICRO_OF_SECOND => nanos / NANOS_IN_MICRO
48+
case MILLI_OF_SECOND => nanos / NANOS_IN_MILLI
49+
50+
case _: ChronoField =>
51+
throw new UnsupportedTemporalTypeException(s"Field not supported: $field")
52+
53+
case _ => field.getFrom(this)
54+
}
55+
56+
def getEpochSecond(): Long = seconds
57+
58+
def getNano(): Int = nanos
59+
60+
override def `with`(adjuster: TemporalAdjuster): Instant =
61+
adjuster.adjustInto(this).asInstanceOf[Instant]
62+
63+
def `with`(field: TemporalField, value: Long): Instant = {
64+
val msg = s"Invalid value for field $field: $value"
65+
field match {
66+
case INSTANT_SECONDS =>
67+
requireDateTime(value >= MinSecond && value <= MaxSecond, msg)
68+
if (value == seconds) this
69+
else ofEpochSecond(value, nanos)
70+
71+
case NANO_OF_SECOND =>
72+
requireDateTime(value >= 0 && value <= MaxNanosInSecond, msg)
73+
if (value == nanos) this
74+
else ofEpochSecond(seconds, value)
75+
76+
case MICRO_OF_SECOND =>
77+
requireDateTime(value >= 0 && value <= MaxNanosInSecond / NANOS_IN_MICRO, msg)
78+
val newNanos = value * NANOS_IN_MICRO
79+
if (newNanos == nanos) this
80+
else ofEpochSecond(seconds, newNanos)
81+
82+
case MILLI_OF_SECOND =>
83+
requireDateTime(value >= 0 && value <= MaxNanosInSecond / NANOS_IN_MILLI, msg)
84+
val newNanos = value * NANOS_IN_MILLI
85+
if (newNanos == nanos) this
86+
else ofEpochSecond(seconds, newNanos)
87+
88+
case _: ChronoField =>
89+
throw new UnsupportedTemporalTypeException(s"Field not supported: $field")
90+
91+
case _ => field.adjustInto(this, value)
92+
}
93+
}
94+
95+
def truncatedTo(unit: TemporalUnit): Instant = {
96+
if (unit == NANOS) {
97+
this
98+
} else {
99+
val duration = unit.getDuration
100+
if (duration.getSeconds > SECONDS_IN_DAY)
101+
throw new UnsupportedTemporalTypeException("Unit too large")
102+
103+
val unitNanos = duration.toNanos
104+
if ((NANOS_IN_DAY % unitNanos) != 0)
105+
throw new UnsupportedTemporalTypeException("Unit must be a multiple of a standard day")
106+
107+
val extraNanos = (seconds % SECONDS_IN_DAY) * NANOS_IN_SECOND + nanos
108+
val extraNanosPerUnit = (extraNanos / unitNanos) * unitNanos
109+
plusNanos(extraNanosPerUnit - extraNanos)
110+
}
111+
}
112+
113+
def plus(amount: Long, unit: TemporalUnit): Instant = unit match {
114+
case NANOS => plusNanos(amount)
115+
case MICROS => plusNanos(Math.multiplyExact(amount, NANOS_IN_MICRO))
116+
case MILLIS => plusMillis(amount)
117+
case SECONDS => plusSeconds(amount)
118+
case MINUTES => plusSeconds(Math.multiplyExact(amount, SECONDS_IN_MINUTE))
119+
case HOURS => plusSeconds(Math.multiplyExact(amount, SECONDS_IN_HOUR))
120+
case HALF_DAYS => plusSeconds(Math.multiplyExact(amount, SECONDS_IN_DAY) / 2)
121+
case DAYS => plusSeconds(Math.multiplyExact(amount, SECONDS_IN_DAY))
122+
123+
case _: ChronoUnit =>
124+
throw new UnsupportedTemporalTypeException(s"Unit not supported: $unit")
125+
126+
case _ => unit.addTo(this, amount)
127+
}
128+
129+
def plusSeconds(secs: Long): Instant = plus(secs, 0)
130+
131+
def plusMillis(millis: Long): Instant =
132+
plusNanos(Math.multiplyExact(millis, NANOS_IN_MILLI))
133+
134+
def plusNanos(nans: Long): Instant = plus(0, nans)
135+
136+
private def plus(secs: Long, nans: Long): Instant = {
137+
if (secs == 0 && nans == 0) {
138+
this
139+
} else {
140+
val secondsFromNanos = Math.floorDiv(nans, NANOS_IN_SECOND)
141+
val remainingNanos = Math.floorMod(nans, NANOS_IN_SECOND)
142+
val additionalSecs = Math.addExact(secs, secondsFromNanos)
143+
ofEpochSecond(
144+
Math.addExact(seconds, additionalSecs),
145+
Math.addExact(nanos, remainingNanos)
146+
)
147+
}
148+
}
149+
150+
override def minus(amount: TemporalAmount): Instant =
151+
amount.subtractFrom(this).asInstanceOf[Instant]
152+
153+
override def minus(amount: Long, unit: TemporalUnit): Instant = {
154+
if (amount == Long.MinValue) plus(Long.MaxValue, unit).plus(1, unit)
155+
else plus(-amount, unit)
156+
}
157+
158+
def minusSeconds(secs: Long): Instant = minus(secs, SECONDS)
159+
160+
def minusMillis(millis: Long): Instant = minus(millis, MILLIS)
161+
162+
def minusNanos(nans: Long): Instant = minus(nans, NANOS)
163+
164+
// Not implemented
165+
// def query[R](query: TemporalQuery[R]): R
166+
167+
def adjustInto(temporal: Temporal): Temporal =
168+
temporal.`with`(INSTANT_SECONDS, seconds).`with`(NANO_OF_SECOND, nanos)
169+
170+
def until(end: Temporal, unit: TemporalUnit): Long = {
171+
val endInstant = from(end)
172+
173+
def nanosUntil: Long = {
174+
val secsDiff: Long = Math.subtractExact(endInstant.seconds, seconds)
175+
val nanosBase: Long = Math.multiplyExact(secsDiff, NANOS_IN_SECOND)
176+
Math.addExact(nanosBase, endInstant.nanos - nanos)
177+
}
178+
179+
def secondsUntil: Long = {
180+
val secsDiff: Long = Math.subtractExact(endInstant.seconds, seconds)
181+
val nanosDiff: Int = endInstant.nanos - nanos
182+
183+
// correct "off by one" in the seconds difference
184+
if (secsDiff > 0 && nanosDiff < 0) {
185+
secsDiff - 1
186+
} else if (secsDiff < 0 && nanosDiff > 0) {
187+
secsDiff + 1
188+
} else {
189+
secsDiff
190+
}
191+
}
192+
193+
unit match {
194+
case NANOS => nanosUntil
195+
case MICROS => nanosUntil / NANOS_IN_MICRO
196+
case MILLIS => Math.subtractExact(endInstant.toEpochMilli(), toEpochMilli())
197+
case SECONDS => secondsUntil
198+
case MINUTES => secondsUntil / SECONDS_IN_MINUTE
199+
case HOURS => secondsUntil / SECONDS_IN_HOUR
200+
case HALF_DAYS => secondsUntil / (SECONDS_IN_HOUR * 12)
201+
case DAYS => secondsUntil / SECONDS_IN_DAY
202+
203+
case _: ChronoUnit =>
204+
throw new UnsupportedTemporalTypeException(s"Unit not supported: $unit")
205+
206+
case _ => unit.between(this, end)
207+
}
208+
}
209+
210+
// Not implemented
211+
// def atOffset(offset: ZoneOffset): OffsetDateTime
212+
213+
// Not implemented
214+
// def atZone(zone: ZoneId): ZonedDateTime
215+
216+
def toEpochMilli(): Long = {
217+
val millis: Long = Math.multiplyExact(seconds, MILLIS_IN_SECOND.toLong)
218+
millis + nanos / NANOS_IN_MILLI
219+
}
220+
221+
def compareTo(that: Instant): Int = {
222+
val cmp = seconds compareTo that.seconds
223+
if (cmp != 0) {
224+
cmp
225+
} else {
226+
nanos compareTo that.nanos
227+
}
228+
}
229+
230+
def isAfter(that: Instant): Boolean = compareTo(that) > 0
231+
232+
def isBefore(that: Instant): Boolean = compareTo(that) < 0
233+
234+
override def equals(other: Any): Boolean = other match {
235+
case that: Instant => seconds == that.seconds && nanos == that.nanos
236+
case _ => false
237+
}
238+
239+
override def hashCode(): Int = (seconds + 51 * nanos).hashCode
240+
241+
override def toString: String = {
242+
def tenThousandPartsBasedOnZero: (Long, Long) = {
243+
if (seconds < -secondsFromZeroToEpoch) {
244+
val zeroSecs = seconds + secondsFromZeroToEpoch
245+
val quot = zeroSecs / secondsInTenThousandYears
246+
val rem = zeroSecs % secondsInTenThousandYears
247+
(quot, rem)
248+
} else {
249+
val zeroSecs = seconds - secondsInTenThousandYears + secondsFromZeroToEpoch
250+
val quot = Math.floorDiv(zeroSecs, secondsInTenThousandYears) + 1
251+
val rem = Math.floorMod(zeroSecs, secondsInTenThousandYears)
252+
(quot, rem)
253+
}
254+
}
255+
256+
def dateTime(epochSecond: Long): (LocalDate, LocalTime) = {
257+
val epochDay = Math.floorDiv(epochSecond, SECONDS_IN_DAY)
258+
val secondsOfDay = Math.floorMod(epochSecond, SECONDS_IN_DAY).toInt
259+
(LocalDate.ofEpochDay(epochDay), LocalTime.ofSecondOfDay(secondsOfDay).withNano(nanos))
260+
}
261+
262+
val (hi, lo) = tenThousandPartsBasedOnZero
263+
val epochSecond = lo - secondsFromZeroToEpoch
264+
val (date, time) = dateTime(epochSecond)
265+
266+
val hiPart = {
267+
if (hi > 0) s"+$hi"
268+
else if (hi < 0) hi.toString
269+
else ""
270+
}
271+
272+
val timePart = {
273+
val timeStr = time.toString
274+
if (time.getSecond == 0) timeStr + ":00"
275+
else timeStr
276+
}
277+
278+
s"${hiPart}${date}T${timePart}Z"
279+
}
280+
281+
// Not implemented
282+
// def format(format: DateTimeFormatter): String
283+
284+
}
285+
286+
object Instant {
287+
import Constants._
288+
import ChronoField._
289+
290+
final val EPOCH = new Instant(0, 0)
291+
292+
private val MinSecond = -31557014167219200L
293+
private val MaxSecond = 31556889864403199L
294+
private val MaxNanosInSecond = 999999999
295+
296+
/*
297+
* 146097 days in 400 years
298+
* 86400 seconds in a day
299+
* 25 cycles of 400 years
300+
*/
301+
private val secondsInTenThousandYears = 146097L * SECONDS_IN_DAY * 25L
302+
private val secondsFromZeroToEpoch = ((146097L * 5L) - (30L * 365L + 7L)) * SECONDS_IN_DAY
303+
304+
final val MIN = ofEpochSecond(MinSecond)
305+
final val MAX = ofEpochSecond(MaxSecond, MaxNanosInSecond)
306+
307+
def now(): Instant = {
308+
val date = new js.Date()
309+
ofEpochMilli(date.getTime.toLong)
310+
}
311+
312+
// Not implemented
313+
// def now(zoneId: ZoneId): Instant
314+
// def now(clock: Clock): Instant
315+
316+
def ofEpochSecond(epochSecond: Long): Instant =
317+
ofEpochSecond(epochSecond, 0)
318+
319+
def ofEpochSecond(epochSecond: Long, nanos: Long): Instant = {
320+
val adjustedSeconds = Math.addExact(epochSecond,
321+
Math.floorDiv(nanos, NANOS_IN_SECOND))
322+
val adjustedNanos = Math.floorMod(nanos, NANOS_IN_SECOND).toInt
323+
new Instant(adjustedSeconds, adjustedNanos)
324+
}
325+
326+
def ofEpochMilli(epochMilli: Long): Instant = {
327+
val seconds = Math.floorDiv(epochMilli, MILLIS_IN_SECOND)
328+
val nanos = Math.floorMod(epochMilli, MILLIS_IN_SECOND)
329+
new Instant(seconds, nanos.toInt * NANOS_IN_MILLI)
330+
}
331+
332+
def from(temporal: TemporalAccessor): Instant = temporal match {
333+
case temporal: Instant => temporal
334+
case _ =>
335+
ofEpochSecond(temporal.getLong(INSTANT_SECONDS), temporal.getLong(NANO_OF_SECOND))
336+
}
337+
338+
// Not implemented
339+
// def parse(text: CharSequence): Instant
340+
341+
}

0 commit comments

Comments
 (0)