I am trying to represent time information that has precision at the scale of year or year-month. Eg. 'patient has a history of fracture of the right wrist in 1998'; 'history of headaches since October 2015'.
I looked at Joda-Time which has YearMonth org.joda.time.YearMonth but not just 'Year'. But even the YearMonth seems kinda a disconnected. E.g.
(clj-time.core/year-month 2015 02)
(clj-time.core/date-time 2015 11 15)
=> #object[org.joda.time.YearMonth 0x7a19d121 "2015-02"]
=> #object[org.joda.time.DateTime 0x5625de93 "2015-11-15T00:00:00.000Z"]
and you can do some operations such as:
(clj-time.core/month (clj-time.core/year-month 2015 02))
(clj-time.core/month (clj-time.core/date-time 2015 11 15))
=> 2
=> 11
but not this:
(clj-time.core/before? (clj-time.core/year-month 2015 02)
(clj-time.core/date-time 2015 11 15))
=> Exception thrown: java.lang.ClassCastException (org.joda.time.DateTime
cannot be cast to org.joda.time.ReadablePartial)
and similar results with java.time:
(.compareTo (java.time.Year/of 2007) (java.time.LocalDate/now))
=> Exception thrown: java.lang.ClassCastException (java.time.LocalDate
cannot be cast to java.time.Year)
What I want to be able to do is represent temporal entities such as Year and YearMonth at the precision they are - not more, not less, and also do date math with them. E.g.:
Has anyone found a good solution for this? Or should I look at building my own solution/library?
[Java syntax rather than Clojure.)
Java 8 and later comes with an industry-leading date-time framework, the java.time classes. These came from JSR 310, led by the same man who created Joda-Time, Stephen Colebourne.
java.time.YearThe class java.time.Year represents an entire year.
Year year = Year.of( 2015 ) ;
As for math, that class offers plus and minus methods.
Year followingYear = year.plusYears( 1 ) ;
java.time.LocalDateIf you want dates within the year, call various methods that return a java.time.LocalDate.
LocalDate firstOfYear = year.atDay( 1 ) ; // January 1st of that year.
LocalDate endOfYear = year.atDay( year.length() ) ; // December 31st of that year. Year may be either 365 or 366 days long.
If you want the pair of first & last days of year, just write your own little class.
import java.time.*;
public class YearBeginEnd
{
public final Year year;
public final LocalDate begin, end;
public final int length;
public YearBeginEnd ( final Year year )
{
this.year = year;
this.length = this.year.length();
this.begin = this.year.atDay( 1 );
this.end = this.year.atDay( this.year.length() );
}
}
java.time.MonthDayIf you want a day and month of that year, use MonthDay class.
MonthDay march17 = MonthDay.of( Month.MARCH , 17 ) ;
LocalDate march17Of2015 = year.atMonthDay( march17 ) ;
java.time.YearMonthFor year-month, use YearMonth class. You can get dates and do math similar to above.
YearMonth yearMonth = YearMonth.of( 2015 , Month.FEBRUARY )
YearMonth followingYearMonth = yearMonth.plusMonths( 1 ) ;
LocalDate start = yearMonth.atDay( 1 ) ;
LocalDate end = yearMonth.atEndOfMonth() ;
If you want other chunks of time, such as half-year or quarters, add the ThreeTen-Extra library to your project for more classes compatible with java.time.
That library also provides the LocalDateRange class which you could use as part of (or instead of) that custom class above.
import org.threeten.extra.LocalDateRange;
import java.time.LocalDate;
import java.time.Year;
public class YearBeginEnd
{
public final Year year;
public final LocalDateRange dateRangeClosed;
public final int length;
public YearBeginEnd ( final Year year )
{
this.year = year;
this.length = this.year.length();
this.dateRangeClosed = LocalDateRange.ofClosed( this.year.atDay( 1 ) , this.year.atDay( this.year.length() ) );
}
public LocalDate begin ( ) { return this.dateRange.getStart(); }
public LocalDate end ( ) { return this.dateRange.getEnd(); }
}
Or represent that entire month.
LocalDateRange monthRangeClosed =
LocalDateRange.ofClosed(
YearMonth.of( 2015 , Month.FEBRUARY ).atDay( 1 ) ,
YearMonth.of( 2015 , Month.FEBRUARY ).atEndOfMonth()
);
Note that in date-time handling, it is generally wiser to use Half-Open rather than Fully-Closed approach. In Half-Open, the beginning of a span of time is inclusive while the ending is exclusive. So a month starts on day one, and runs up to, but does not include, the first of the following month.
LocalDateRange monthRange =
LocalDateRange.of(
YearMonth.of( 2015 , Month.FEBRUARY ).atDay( 1 ) ,
YearMonth.of( 2015 , Month.FEBRUARY ).plusMonths( 1 ).atDay( 1 ) ;
);
Notice that the LocalDateRange class offers handy methods such as abuts, overlaps, intersection, etc.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With