Skip to content

Commit 2fed13f

Browse files
author
Antonio Alonso Dominguez
committed
Implementation of java.time.YearMonth
1 parent b57fc34 commit 2fed13f

File tree

6 files changed

+746
-17
lines changed

6 files changed

+746
-17
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ project/plugins/project/
1515
# Scala-IDE specific
1616
.scala_dependencies
1717
.worksheet
18+

src/main/scala/java/time/Year.scala

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,6 @@ final class Year private (year: Int)
3232
case _ => unit.isSupportedBy(this)
3333
}
3434

35-
override def range(field: TemporalField): ValueRange = field match {
36-
case YEAR_OF_ERA =>
37-
if (year <= 0) ValueRange.of(1, MAX_VALUE + 1)
38-
else ValueRange.of(1, MAX_VALUE)
39-
40-
case _ => super.range(field)
41-
}
42-
4335
override def get(field: TemporalField): Int = field match {
4436
case YEAR_OF_ERA => if (year < 1) 1 - year else year
4537
case YEAR => year
@@ -117,10 +109,11 @@ final class Year private (year: Int)
117109
}
118110
}
119111

120-
override def minus(amount: Long, unit: TemporalUnit): Year = {
121-
if (amount != Long.MinValue) plus(-amount, unit)
122-
else plus(Long.MaxValue, unit).plus(1, unit)
123-
}
112+
override def minus(amount: TemporalAmount): Year =
113+
super.minus(amount).asInstanceOf[Year]
114+
115+
override def minus(amount: Long, unit: TemporalUnit): Year =
116+
super.minus(amount, unit).asInstanceOf[Year]
124117

125118
def minusYears(amount: Long): Year = minus(amount, YEARS)
126119

@@ -154,11 +147,9 @@ final class Year private (year: Int)
154147
def atDay(dayOfYear: Int): LocalDate =
155148
LocalDate.ofYearDay(year, dayOfYear)
156149

157-
// TODO
158-
// def atMonth(month: Month): YearMonth = YearMonth.of(year, month)
150+
def atMonth(month: Month): YearMonth = YearMonth.of(year, month)
159151

160-
// TODO
161-
// def atMonth(month: Int): YearMonth = YearMonth.of(year, month)
152+
def atMonth(month: Int): YearMonth = YearMonth.of(year, month)
162153

163154
def atMonthDay(monthDay: MonthDay): LocalDate = monthDay.atYear(year)
164155

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
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+
}

src/main/scala/java/time/temporal/TemporalAccessor.scala

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
package java.time.temporal
22

3+
import java.time.Year
4+
35
trait TemporalAccessor {
46
def isSupported(field: TemporalField): Boolean
57

68
def range(field: TemporalField): ValueRange = field match {
7-
case _: ChronoField if isSupported(field) => field.range
9+
case _: ChronoField if isSupported(field) =>
10+
if (field == ChronoField.YEAR_OF_ERA) {
11+
val era = get(ChronoField.ERA)
12+
if (era < 1) ValueRange.of(1, Year.MAX_VALUE + 1)
13+
else ValueRange.of(1, Year.MAX_VALUE)
14+
} else {
15+
field.range
16+
}
817

918
case _: ChronoField =>
1019
throw new UnsupportedTemporalTypeException(s"Unsupported field: $field")

0 commit comments

Comments
 (0)