com.datastax.driver.core.LocalDate Maven / Gradle / Ivy
Show all versions of dse-java-driver-core Show documentation
/*
* Copyright DataStax, Inc.
*
* This software can be used solely with DataStax Enterprise. Please consult the license at
* http://www.datastax.com/terms/datastax-dse-driver-license-terms
*/
package com.datastax.driver.core;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
/**
* A date with no time components, no time zone, in the ISO 8601 calendar.
*
* Note that ISO 8601 has a number of differences with the default gregorian calendar used in
* Java:
*
*
* - it uses a proleptic gregorian calendar, meaning that it's gregorian indefinitely back in
* the past (there is no gregorian change);
*
- there is a year 0.
*
*
* This class implements these differences, so that year/month/day fields match exactly the ones
* in CQL string literals.
*
* @since 2.2
*/
public final class LocalDate {
private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
private final long millisSinceEpoch;
private final int daysSinceEpoch;
// This gets initialized lazily if we ever need it. Once set, it is effectively immutable.
private volatile GregorianCalendar calendar;
private LocalDate(int daysSinceEpoch) {
this.daysSinceEpoch = daysSinceEpoch;
this.millisSinceEpoch = TimeUnit.DAYS.toMillis(daysSinceEpoch);
}
/**
* Builds a new instance from a number of days since January 1st, 1970 GMT.
*
* @param daysSinceEpoch the number of days.
* @return the new instance.
*/
public static LocalDate fromDaysSinceEpoch(int daysSinceEpoch) {
return new LocalDate(daysSinceEpoch);
}
/**
* Builds a new instance from a number of milliseconds since January 1st, 1970 GMT. Note that if
* the given number does not correspond to a whole number of days, it will be rounded towards 0.
*
* @param millisSinceEpoch the number of milliseconds since January 1st, 1970 GMT.
* @return the new instance.
* @throws IllegalArgumentException if the date is not in the range [-5877641-06-23;
* 5881580-07-11].
*/
public static LocalDate fromMillisSinceEpoch(long millisSinceEpoch)
throws IllegalArgumentException {
long daysSinceEpoch = TimeUnit.MILLISECONDS.toDays(millisSinceEpoch);
checkArgument(
daysSinceEpoch >= Integer.MIN_VALUE && daysSinceEpoch <= Integer.MAX_VALUE,
"Date should be in the range [-5877641-06-23; 5881580-07-11]");
return new LocalDate((int) daysSinceEpoch);
}
/**
* Builds a new instance from a year/month/day specification.
*
*
This method is not lenient, i.e. '2014-12-32' will not be treated as '2015-01-01', but
* instead throw an {@code IllegalArgumentException}.
*
* @param year the year in ISO format (see {@link LocalDate this class's Javadoc}).
* @param month the month. It is 1-based (e.g. 1 for January).
* @param dayOfMonth the day of the month.
* @return the new instance.
* @throws IllegalArgumentException if the corresponding date does not exist in the ISO8601
* calendar.
*/
public static LocalDate fromYearMonthDay(int year, int month, int dayOfMonth) {
int calendarYear = (year <= 0) ? -year + 1 : year;
int calendarEra = (year <= 0) ? GregorianCalendar.BC : GregorianCalendar.AD;
GregorianCalendar calendar = isoCalendar();
// We can't allow leniency because that could mess with our year shift above (for example if the
// arguments were 0, 12, 32)
calendar.setLenient(false);
calendar.clear();
calendar.set(calendarYear, month - 1, dayOfMonth, 0, 0, 0);
calendar.set(Calendar.ERA, calendarEra);
LocalDate date = fromMillisSinceEpoch(calendar.getTimeInMillis());
date.calendar = calendar;
return date;
}
/**
* Returns the number of days since January 1st, 1970 GMT.
*
* @return the number of days.
*/
public int getDaysSinceEpoch() {
return daysSinceEpoch;
}
/**
* Returns the number of milliseconds since January 1st, 1970 GMT.
*
* @return the number of milliseconds.
*/
public long getMillisSinceEpoch() {
return millisSinceEpoch;
}
/**
* Returns the year.
*
* @return the year.
*/
public int getYear() {
GregorianCalendar c = getCalendar();
int year = c.get(Calendar.YEAR);
if (c.get(Calendar.ERA) == GregorianCalendar.BC) year = -year + 1;
return year;
}
/**
* Returns the month.
*
* @return the month. It is 1-based, e.g. 1 for January.
*/
public int getMonth() {
return getCalendar().get(Calendar.MONTH) + 1;
}
/**
* Returns the day in the month.
*
* @return the day in the month.
*/
public int getDay() {
return getCalendar().get(Calendar.DAY_OF_MONTH);
}
/**
* Return a new {@link LocalDate} with the specified (signed) amount of time added to (or
* subtracted from) the given {@link Calendar} field, based on the calendar's rules.
*
*
Note that adding any amount to a field smaller than {@link Calendar#DAY_OF_MONTH} will
* remain without effect, as this class does not keep time components.
*
*
See {@link Calendar} javadocs for more information.
*
* @param field a {@link Calendar} field to modify.
* @param amount the amount of date or time to be added to the field.
* @return a new {@link LocalDate} with the specified (signed) amount of time added to (or
* subtracted from) the given {@link Calendar} field.
* @throws IllegalArgumentException if the new date is not in the range [-5877641-06-23;
* 5881580-07-11].
*/
public LocalDate add(int field, int amount) {
GregorianCalendar newCalendar = isoCalendar();
newCalendar.setTimeInMillis(millisSinceEpoch);
newCalendar.add(field, amount);
LocalDate newDate = fromMillisSinceEpoch(newCalendar.getTimeInMillis());
newDate.calendar = newCalendar;
return newDate;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof LocalDate) {
LocalDate that = (LocalDate) o;
return this.daysSinceEpoch == that.daysSinceEpoch;
}
return false;
}
@Override
public int hashCode() {
return daysSinceEpoch;
}
@Override
public String toString() {
return String.format("%d-%s-%s", getYear(), pad2(getMonth()), pad2(getDay()));
}
private static String pad2(int i) {
String s = Integer.toString(i);
return s.length() == 2 ? s : "0" + s;
}
private GregorianCalendar getCalendar() {
// Two threads can race and both create a calendar. This is not a problem.
if (calendar == null) {
// Use a local variable to only expose after we're done mutating it.
GregorianCalendar tmp = isoCalendar();
tmp.setTimeInMillis(millisSinceEpoch);
calendar = tmp;
}
return calendar;
}
// This matches what Cassandra uses server side (from Joda Time's LocalDate)
private static GregorianCalendar isoCalendar() {
GregorianCalendar calendar = new GregorianCalendar(UTC);
calendar.setGregorianChange(new Date(Long.MIN_VALUE));
return calendar;
}
}