|
| 1 | +package java.time |
| 2 | + |
| 3 | +import java.time.temporal._ |
| 4 | + |
| 5 | +/** Created by alonsodomin on 25/12/2015. */ |
| 6 | +final class YearMonth private (year: Int, month: Int) |
| 7 | + extends TemporalAccessor with Temporal with TemporalAdjuster |
| 8 | + with Comparable[YearMonth] with java.io.Serializable { |
| 9 | + |
| 10 | + import Preconditions._ |
| 11 | + import ChronoField._ |
| 12 | + import ChronoUnit._ |
| 13 | + |
| 14 | + requireDateTime(year >= Year.MIN_VALUE && year <= Year.MAX_VALUE, |
| 15 | + s"Invalid year: $year") |
| 16 | + requireDateTime(month >= 1 && month <= 12, s"Invalid month: $month") |
| 17 | + |
| 18 | + def isSupported(field: TemporalField): Boolean = field match { |
| 19 | + case _: ChronoField => |
| 20 | + field == YEAR || field == YEAR_OF_ERA || field == MONTH_OF_YEAR || |
| 21 | + field == PROLEPTIC_MONTH || field == ERA |
| 22 | + |
| 23 | + case null => false |
| 24 | + case _ => field.isSupportedBy(this) |
| 25 | + } |
| 26 | + |
| 27 | + def isSupported(unit: TemporalUnit): Boolean = unit match { |
| 28 | + case _: ChronoUnit => |
| 29 | + unit == MONTHS || unit == YEARS || unit == DECADES || unit == CENTURIES || |
| 30 | + unit == MILLENNIA || unit == ERAS |
| 31 | + |
| 32 | + case null => false |
| 33 | + case _ => unit.isSupportedBy(this) |
| 34 | + } |
| 35 | + |
| 36 | + // Implemented by TemporalAccessor |
| 37 | + // def range(field: TemporalField): ValueRange |
| 38 | + |
| 39 | + def getLong(field: TemporalField): Long = field match { |
| 40 | + case YEAR => year |
| 41 | + case YEAR_OF_ERA => if (year < 1) 1 - year else year |
| 42 | + case MONTH_OF_YEAR => month |
| 43 | + case PROLEPTIC_MONTH => prolepticMonth |
| 44 | + case ERA => if (year < 1) 0 else 1 |
| 45 | + |
| 46 | + case _: ChronoField => |
| 47 | + throw new UnsupportedTemporalTypeException("Unsupported field: " + field) |
| 48 | + |
| 49 | + case _ => field.getFrom(this) |
| 50 | + } |
| 51 | + |
| 52 | + private def prolepticMonth: Long = (year * 12.0 + (month - 1)).toLong |
| 53 | + |
| 54 | + def getYear(): Int = year |
| 55 | + |
| 56 | + def getMonthValue(): Int = month |
| 57 | + |
| 58 | + def getMonth(): Month = Month.of(month) |
| 59 | + |
| 60 | + def isLeapYear(): Boolean = Year.isLeap(year) |
| 61 | + |
| 62 | + def isValidDay(dayOfMonth: Int): Boolean = |
| 63 | + dayOfMonth >= 1 && dayOfMonth <= lengthOfMonth() |
| 64 | + |
| 65 | + def lengthOfMonth(): Int = getMonth().length(isLeapYear()) |
| 66 | + |
| 67 | + def lengthOfYear(): Int = Year.of(year).length() |
| 68 | + |
| 69 | + def `with`(field: TemporalField, value: Long): YearMonth = { |
| 70 | + def withYearMonth(y: Int, m: Int): YearMonth = { |
| 71 | + if (y == year && m == month) this |
| 72 | + else YearMonth.of(y, m) |
| 73 | + } |
| 74 | + |
| 75 | + field match { |
| 76 | + case YEAR => |
| 77 | + withYearMonth(YEAR.checkValidIntValue(value), month) |
| 78 | + |
| 79 | + case YEAR_OF_ERA => |
| 80 | + val isoYear = YEAR.checkValidIntValue( |
| 81 | + if (year < 1) 1 - value |
| 82 | + else value |
| 83 | + ) |
| 84 | + withYearMonth(isoYear, month) |
| 85 | + |
| 86 | + case MONTH_OF_YEAR => |
| 87 | + withYearMonth(year, MONTH_OF_YEAR.checkValidIntValue(value)) |
| 88 | + |
| 89 | + case PROLEPTIC_MONTH => |
| 90 | + plusMonths(value - getLong(PROLEPTIC_MONTH)) |
| 91 | + |
| 92 | + case ERA => |
| 93 | + requireDateTime(value >= 0 && value <= 1, s"Invalid value for ERA: $value") |
| 94 | + val era = getLong(ERA) |
| 95 | + if (value == era) this |
| 96 | + else withYearMonth(YEAR.checkValidIntValue(1 - year), month) |
| 97 | + |
| 98 | + case _: ChronoField => |
| 99 | + throw new UnsupportedTemporalTypeException("Unsupported field: " + field) |
| 100 | + |
| 101 | + case _ => field.adjustInto(this, value) |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + def withYear(year: Int): YearMonth = `with`(YEAR, year) |
| 106 | + |
| 107 | + def withMonth(month: Int): YearMonth = `with`(MONTH_OF_YEAR, month) |
| 108 | + |
| 109 | + def plus(amount: Long, unit: TemporalUnit): YearMonth = unit match { |
| 110 | + case MONTHS => plusMonths(amount) |
| 111 | + case YEARS => plusYears(amount) |
| 112 | + case DECADES => plusYears(Math.multiplyExact(amount, 10)) |
| 113 | + case CENTURIES => plusYears(Math.multiplyExact(amount, 100)) |
| 114 | + case MILLENNIA => plusYears(Math.multiplyExact(amount, 1000)) |
| 115 | + |
| 116 | + case ERAS => |
| 117 | + val era = getLong(ERA) |
| 118 | + `with`(ERA, Math.addExact(era, amount)) |
| 119 | + |
| 120 | + case _: ChronoUnit => |
| 121 | + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit) |
| 122 | + |
| 123 | + case _ => unit.addTo(this, amount) |
| 124 | + } |
| 125 | + |
| 126 | + def plusYears(years: Long): YearMonth = { |
| 127 | + if (years == 0) { |
| 128 | + this |
| 129 | + } else { |
| 130 | + // Allowing the Long to overflow to align with JDK implementation |
| 131 | + val newYear = year + years |
| 132 | + new YearMonth(YEAR.checkValidIntValue(newYear), month) |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + def plusMonths(months: Long): YearMonth = { |
| 137 | + if (months == 0) { |
| 138 | + this |
| 139 | + } else { |
| 140 | + // Allowing the Long to overflow to align with JDK implementation |
| 141 | + val newProlepticMonth = prolepticMonth + months |
| 142 | + val newYear = Math.floorDiv(newProlepticMonth, 12) |
| 143 | + val newMonth = Math.floorMod(newProlepticMonth, 12) + 1 |
| 144 | + new YearMonth( |
| 145 | + YEAR.checkValidIntValue(newYear), |
| 146 | + MONTH_OF_YEAR.checkValidIntValue(newMonth) |
| 147 | + ) |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + override def minus(amount: TemporalAmount): YearMonth = |
| 152 | + super.minus(amount).asInstanceOf[YearMonth] |
| 153 | + |
| 154 | + override def minus(amount: Long, unit: TemporalUnit): YearMonth = |
| 155 | + super.minus(amount, unit).asInstanceOf[YearMonth] |
| 156 | + |
| 157 | + def minusYears(years: Long): YearMonth = minus(years, YEARS) |
| 158 | + |
| 159 | + def minusMonths(months: Long): YearMonth = minus(months, MONTHS) |
| 160 | + |
| 161 | + // Not implemented |
| 162 | + // def query[R](query: TemporalQuery[R]): R |
| 163 | + |
| 164 | + def adjustInto(temporal: Temporal): Temporal = |
| 165 | + temporal.`with`(PROLEPTIC_MONTH, prolepticMonth) |
| 166 | + |
| 167 | + def until(endExclusive: Temporal, unit: TemporalUnit): Long = { |
| 168 | + def other: YearMonth = YearMonth.from(endExclusive) |
| 169 | + def monthsDiff: Long = other.prolepticMonth - prolepticMonth |
| 170 | + unit match { |
| 171 | + case MONTHS => monthsDiff |
| 172 | + case YEARS => monthsDiff / 12 |
| 173 | + case DECADES => until(endExclusive, YEARS) / 10 |
| 174 | + case CENTURIES => until(endExclusive, YEARS) / 100 |
| 175 | + case MILLENNIA => until(endExclusive, YEARS) / 1000 |
| 176 | + case ERAS => other.getLong(ERA) - getLong(ERA) |
| 177 | + |
| 178 | + case _: ChronoUnit => |
| 179 | + throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit) |
| 180 | + |
| 181 | + case _ => unit.between(this, endExclusive) |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + def atDay(dayOfMonth: Int): LocalDate = |
| 186 | + LocalDate.of(year, month, dayOfMonth) |
| 187 | + |
| 188 | + def atEndOfMonth: LocalDate = |
| 189 | + atDay(getMonth().length(isLeapYear())) |
| 190 | + |
| 191 | + def compareTo(other: YearMonth): Int = |
| 192 | + if (year == other.getYear) month - other.getMonthValue |
| 193 | + else year - other.getYear |
| 194 | + |
| 195 | + def isAfter(other: YearMonth): Boolean = compareTo(other) > 0 |
| 196 | + |
| 197 | + def isBefore(other: YearMonth): Boolean = compareTo(other) < 0 |
| 198 | + |
| 199 | + override def equals(other: Any): Boolean = other match { |
| 200 | + case that: YearMonth => |
| 201 | + year == that.getYear && month == that.getMonthValue |
| 202 | + |
| 203 | + case _ => false |
| 204 | + } |
| 205 | + |
| 206 | + override def hashCode(): Int = year + (month << 27) |
| 207 | + |
| 208 | + override def toString: String = f"$year%04d-$month%02d" |
| 209 | + |
| 210 | + // Not implemented |
| 211 | + // def format(formatter: DateTimeFormatter): String |
| 212 | + |
| 213 | +} |
| 214 | + |
| 215 | +object YearMonth { |
| 216 | + import ChronoField._ |
| 217 | + |
| 218 | + def now(): YearMonth = from(LocalDate.now()) |
| 219 | + |
| 220 | + // Not implemented |
| 221 | + // def now(zoneId: ZoneId): YearMonth |
| 222 | + // def now(clock: Clock): YearMonth |
| 223 | + |
| 224 | + def of(year: Int, month: Month): YearMonth = { |
| 225 | + if (month == null) throw new NullPointerException("month") |
| 226 | + of(year, month.getValue) |
| 227 | + } |
| 228 | + |
| 229 | + def of(year: Int, month: Int): YearMonth = { |
| 230 | + YEAR.checkValidIntValue(year) |
| 231 | + MONTH_OF_YEAR.checkValidIntValue(month) |
| 232 | + new YearMonth(year, month) |
| 233 | + } |
| 234 | + |
| 235 | + def from(temporal: TemporalAccessor): YearMonth = temporal match { |
| 236 | + case temporal: YearMonth => temporal |
| 237 | + case _ => of(temporal.get(YEAR), temporal.get(MONTH_OF_YEAR)) |
| 238 | + } |
| 239 | + |
| 240 | + // Not implemented |
| 241 | + // def parse(text: CharSequence): YearMonth |
| 242 | + // def parse(text: CharSequence, formatter: DateTimeFormatter): YearMonth |
| 243 | + |
| 244 | +} |
0 commit comments