|
| 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