package edu.stanford.nlp.time;
import org.joda.time.*;
import org.joda.time.chrono.ISOChronology;
import org.joda.time.field.DividedDateTimeField;
import org.joda.time.field.OffsetDateTimeField;
import org.joda.time.field.RemainderDateTimeField;
import org.joda.time.field.ScaledDurationField;
import java.util.Set;
import static org.joda.time.DateTimeFieldType.*;
import static org.joda.time.DurationFieldType.*;
import edu.stanford.nlp.util.Generics;
/**
* Extensions to Joda time.
*
* @author Angel Chang
* @author Gabor Angeli
*/
public class JodaTimeUtils {
private JodaTimeUtils() {} // static methods only
// Standard ISO fields
private static final Chronology isoUTCChronology = ISOChronology.getInstanceUTC();
private static final DateTimeFieldType[] standardISOFields = {
DateTimeFieldType.year(),
DateTimeFieldType.monthOfYear(),
DateTimeFieldType.dayOfMonth(),
DateTimeFieldType.hourOfDay(),
DateTimeFieldType.minuteOfHour(),
DateTimeFieldType.secondOfMinute(),
DateTimeFieldType.millisOfSecond()
};
private static final DateTimeFieldType[] standardISOWeekFields = {
DateTimeFieldType.year(),
DateTimeFieldType.weekOfWeekyear(),
DateTimeFieldType.dayOfWeek(),
DateTimeFieldType.hourOfDay(),
DateTimeFieldType.minuteOfHour(),
DateTimeFieldType.secondOfMinute(),
DateTimeFieldType.millisOfSecond()
};
private static final DateTimeFieldType[] standardISODateFields = {
DateTimeFieldType.year(),
DateTimeFieldType.monthOfYear(),
DateTimeFieldType.dayOfMonth(),
};
private static final DateTimeFieldType[] standardISOTimeFields = {
DateTimeFieldType.hourOfDay(),
DateTimeFieldType.minuteOfHour(),
DateTimeFieldType.secondOfMinute(),
DateTimeFieldType.millisOfSecond()
};
public static final Partial EMPTY_ISO_PARTIAL = new Partial(standardISOFields, new int[]{0,1,1,0,0,0,0});
public static final Partial EMPTY_ISO_WEEK_PARTIAL = new Partial(standardISOWeekFields, new int[]{0,1,1,0,0,0,0});
public static final Partial EMPTY_ISO_DATE_PARTIAL = new Partial(standardISODateFields, new int[]{0,1,1});
public static final Partial EMPTY_ISO_TIME_PARTIAL = new Partial(standardISOTimeFields, new int[]{0,0,0,0});
public static final Instant INSTANT_ZERO = new Instant(0);
// Extensions to Joda time fields
// Duration Fields
public static final DurationFieldType Quarters = new DurationFieldType("quarters") {
private static final long serialVersionUID = -8167713675442491871L;
public DurationField getField(Chronology chronology) {
return new ScaledDurationField(chronology.months(), Quarters, 3);
}
};
public static final DurationFieldType HalfYears = new DurationFieldType("halfyear") {
private static final long serialVersionUID = -8167713675442491872L;
public DurationField getField(Chronology chronology) {
return new ScaledDurationField(chronology.months(), HalfYears, 6);
}
};
public static final DurationFieldType Decades = new DurationFieldType("decades") {
private static final long serialVersionUID = -4594189766036833410L;
public DurationField getField(Chronology chronology) {
return new ScaledDurationField(chronology.years(), Decades, 10);
}
};
public static final DurationFieldType Centuries = new DurationFieldType("centuries") {
private static final long serialVersionUID = -7268694266711862790L;
public DurationField getField(Chronology chronology) {
return new ScaledDurationField(chronology.years(), Centuries, 100);
}
};
// DateTimeFields
public static final DateTimeFieldType QuarterOfYear = new DateTimeFieldType("quarterOfYear") {
private static final long serialVersionUID = -5677872459807379123L;
public DurationFieldType getDurationType() {
return Quarters;
}
public DurationFieldType getRangeDurationType() {
return DurationFieldType.years();
}
public DateTimeField getField(Chronology chronology) {
return new OffsetDateTimeField(new DividedDateTimeField(new OffsetDateTimeField(chronology.monthOfYear(), -1), QuarterOfYear, 3), 1);
}
};
public static final DateTimeFieldType HalfYearOfYear = new DateTimeFieldType("halfYearOfYear") {
private static final long serialVersionUID = -5677872459807379123L;
public DurationFieldType getDurationType() {
return HalfYears;
}
public DurationFieldType getRangeDurationType() {
return DurationFieldType.years();
}
public DateTimeField getField(Chronology chronology) {
return new OffsetDateTimeField(new DividedDateTimeField(new OffsetDateTimeField(chronology.monthOfYear(), -1), HalfYearOfYear, 6), 1);
}
};
public static final DateTimeFieldType MonthOfQuarter = new DateTimeFieldType("monthOfQuarter") {
private static final long serialVersionUID = -5677872459807379123L;
public DurationFieldType getDurationType() {
return DurationFieldType.months();
}
public DurationFieldType getRangeDurationType() {
return Quarters;
}
public DateTimeField getField(Chronology chronology) {
return new OffsetDateTimeField(new RemainderDateTimeField(new OffsetDateTimeField(chronology.monthOfYear(), -1), MonthOfQuarter, 3), 1);
}
};
public static final DateTimeFieldType MonthOfHalfYear = new DateTimeFieldType("monthOfHalfYear") {
private static final long serialVersionUID = -5677872459807379123L;
public DurationFieldType getDurationType() {
return DurationFieldType.months();
}
public DurationFieldType getRangeDurationType() {
return HalfYears;
}
public DateTimeField getField(Chronology chronology) {
return new OffsetDateTimeField(new RemainderDateTimeField(new OffsetDateTimeField(chronology.monthOfYear(), -1), MonthOfHalfYear, 6), 1);
}
};
public static final DateTimeFieldType WeekOfMonth = new DateTimeFieldType("weekOfMonth") {
private static final long serialVersionUID = 8676056306203579438L;
public DurationFieldType getDurationType() {
return DurationFieldType.weeks();
}
public DurationFieldType getRangeDurationType() {
return DurationFieldType.months();
}
public DateTimeField getField(Chronology chronology) {
return new OffsetDateTimeField(new RemainderDateTimeField(new OffsetDateTimeField(chronology.weekOfWeekyear(), -1), WeekOfMonth, 4), 1);
}
};
public static final DateTimeFieldType DecadeOfCentury = new DateTimeFieldType("decadeOfCentury") {
private static final long serialVersionUID = 4301444712229535664L;
public DurationFieldType getDurationType() {
return Decades;
}
public DurationFieldType getRangeDurationType() {
return DurationFieldType.centuries();
}
public DateTimeField getField(Chronology chronology) {
return new DividedDateTimeField(chronology.yearOfCentury(), DecadeOfCentury, 10);
}
};
public static final DateTimeFieldType YearOfDecade = new DateTimeFieldType("yearOfDecade") {
private static final long serialVersionUID = 4301444712229535664L;
public DurationFieldType getDurationType() {
return DurationFieldType.years();
}
public DurationFieldType getRangeDurationType() {
return Decades;
}
public DateTimeField getField(Chronology chronology) {
return new DividedDateTimeField(chronology.yearOfCentury(), YearOfDecade, 10);
}
};
// Helper functions for working with joda time type
protected static boolean hasField(ReadablePartial base, DateTimeFieldType field)
{
if (base == null) {
return false;
} else {
return base.isSupported(field);
}
}
protected static boolean hasYYYYMMDD(ReadablePartial base)
{
if (base == null) {
return false;
} else {
return base.isSupported(DateTimeFieldType.year()) &&
base.isSupported(DateTimeFieldType.monthOfYear()) &&
base.isSupported(DateTimeFieldType.dayOfMonth());
}
}
protected static boolean hasYYMMDD(ReadablePartial base)
{
if (base == null) {
return false;
} else {
return base.isSupported(DateTimeFieldType.yearOfCentury()) &&
base.isSupported(DateTimeFieldType.monthOfYear()) &&
base.isSupported(DateTimeFieldType.dayOfMonth());
}
}
protected static boolean hasField(ReadablePeriod base, DurationFieldType field)
{
if (base == null) {
return false;
} else {
return base.isSupported(field);
}
}
protected static Partial setField(Partial base, DateTimeFieldType field, int value) {
if (base == null) {
return new Partial(field, value);
} else {
return base.with(field, value);
}
}
public static Set<DurationFieldType> getSupportedDurationFields(Partial p)
{
Set<DurationFieldType> supportedDurations = Generics.newHashSet();
for (int i = 0; i < p.size(); i++) {
supportedDurations.add(p.getFieldType(i).getDurationType());
}
return supportedDurations;
}
public static Period getUnsupportedDurationPeriod(Partial p, Period offset)
{
if (offset == null) { return null; }
Set<DurationFieldType> supported = getSupportedDurationFields(p);
Period res = null;
for (int i = 0; i < offset.size(); i++) {
if (!supported.contains(offset.getFieldType(i))) {
if (offset.getValue(i) != 0) {
if (res == null) { res = new Period(); }
res = res.withField(offset.getFieldType(i), offset.getValue(i));
}
}
}
return res;
}
public static Partial combine(Partial p1, Partial p2) {
if (p1 == null) return p2;
if (p2 == null) return p1;
Partial p = p1;
for (int i = 0; i < p2.size(); i++) {
DateTimeFieldType fieldType = p2.getFieldType(i);
if (fieldType == DateTimeFieldType.year()) {
if (p.isSupported(DateTimeFieldType.yearOfCentury())) {
if (!p.isSupported(DateTimeFieldType.centuryOfEra())) {
int yoc = p.get(DateTimeFieldType.yearOfCentury());
int refYear = p2.getValue(i);
int century = refYear / 100;
int y2 = yoc + century*100;
// TODO: Figure out which way to go
if (refYear < y2) {
y2 -= 100;
}
p = p.without(DateTimeFieldType.yearOfCentury());
p = p.with(DateTimeFieldType.year(), y2);
}
continue;
} else if (p.isSupported(DateTimeFieldType.centuryOfEra())) {
continue;
}
} else if (fieldType == DateTimeFieldType.yearOfCentury()) {
if (p.isSupported(DateTimeFieldType.year())) {
continue;
}
} else if (fieldType == DateTimeFieldType.centuryOfEra()) {
if (p.isSupported(DateTimeFieldType.year())) {
continue;
}
}
if (!p.isSupported(fieldType)) {
p = p.with(fieldType, p2.getValue(i));
}
}
if (!p.isSupported(DateTimeFieldType.year())) {
if (p.isSupported(DateTimeFieldType.yearOfCentury()) && p.isSupported(DateTimeFieldType.centuryOfEra())) {
int year = p.get(DateTimeFieldType.yearOfCentury()) + p.get(DateTimeFieldType.centuryOfEra())*100;
p = p.with(DateTimeFieldType.year(), year);
p = p.without(DateTimeFieldType.yearOfCentury());
p = p.without(DateTimeFieldType.centuryOfEra());
}
}
if (p.isSupported(DateTimeFieldType.halfdayOfDay())) {
int hour = -1;
if (p.isSupported(DateTimeFieldType.hourOfHalfday())) {
hour = p.get(DateTimeFieldType.hourOfHalfday());
p = p.without(DateTimeFieldType.hourOfHalfday());
} else if (p.isSupported(DateTimeFieldType.clockhourOfHalfday())) {
hour = p.get(DateTimeFieldType.clockhourOfHalfday())-1;
p = p.without(DateTimeFieldType.clockhourOfHalfday());
} else if (p.isSupported(DateTimeFieldType.clockhourOfDay())) {
hour = p.get(DateTimeFieldType.clockhourOfDay())-1;
p = p.without(DateTimeFieldType.clockhourOfDay());
} else if (p.isSupported(DateTimeFieldType.hourOfDay())) {
hour = p.get(DateTimeFieldType.hourOfDay());
p = p.without(DateTimeFieldType.hourOfDay());
}
if (hour >= 0) {
if (p.get(DateTimeFieldType.halfdayOfDay()) == SUTime.HALFDAY_PM) {
if (hour < 12) {
hour = hour+12;
}
} else if (hour == 12) {
hour = 0;
}
if (hour < 24) {
p = p.with(DateTimeFieldType.hourOfDay(), hour);
} else {
p = p.with(DateTimeFieldType.clockhourOfDay(), hour);
}
}
}
return p;
}
protected static DateTimeFieldType getMostGeneral(Partial p)
{
if (p.size() > 0) { return p.getFieldType(0); }
return null;
}
protected static DateTimeFieldType getMostSpecific(Partial p)
{
if (p.size() > 0) { return p.getFieldType(p.size()-1); }
return null;
}
protected static DurationFieldType getMostGeneral(Period p)
{
for (int i = 0; i < p.size(); i++) {
if (p.getValue(i) != 0) {
return p.getFieldType(i);
}
}
return null;
}
protected static DurationFieldType getMostSpecific(Period p)
{
for (int i = p.size()-1; i >= 0; i--) {
if (p.getValue(i) != 0) {
return p.getFieldType(i);
}
}
return null;
}
protected static Period getJodaTimePeriod(Partial p)
{
if (p.size() > 0) {
DateTimeFieldType dtType = p.getFieldType(p.size()-1);
DurationFieldType dType = dtType.getDurationType();
Period period = new Period();
if (period.isSupported(dType)) {
return period.withField(dType, 1);
} else {
DurationField df = dType.getField(p.getChronology());
if (df instanceof ScaledDurationField) {
ScaledDurationField sdf = (ScaledDurationField) df;
return period.withField(sdf.getWrappedField().getType(), sdf.getScalar());
}
// PeriodType.forFields(new DurationFieldType[]{dType});
// return new Period(df.getUnitMillis(), PeriodType.forFields(new DurationFieldType[]{dType}));
}
}
return null;
}
public static Partial combineMoreGeneralFields(Partial p1, Partial p2) {
return combineMoreGeneralFields(p1, p2, null);
}
// Combines more general fields from p2 to p1
public static Partial combineMoreGeneralFields(Partial p1, Partial p2, DateTimeFieldType mgf) {
Partial p = p1;
Chronology c1 = p1.getChronology();
Chronology c2 = p2.getChronology();
if (!c1.equals(c2)) {
throw new RuntimeException("Different chronology: c1=" + c1 + ", c2=" + c2);
}
DateTimeFieldType p1MostGeneralField = null;
if (p1.size() > 0) {
p1MostGeneralField = p1.getFieldType(0); // Assume fields ordered from most general to least....
}
if (mgf == null || (p1MostGeneralField != null && isMoreGeneral(p1MostGeneralField, mgf, c1))) {
mgf = p1MostGeneralField;
}
for (int i = 0; i < p2.size(); i++) {
DateTimeFieldType fieldType = p2.getFieldType(i);
if (fieldType == DateTimeFieldType.year()) {
if (p.isSupported(DateTimeFieldType.yearOfCentury())) {
if (!p.isSupported(DateTimeFieldType.centuryOfEra())) {
int yoc = p.get(DateTimeFieldType.yearOfCentury());
int refYear = p2.getValue(i);
int century = refYear / 100;
int y2 = yoc + century*100;
// TODO: Figure out which way to go
if (refYear < y2) {
y2 -= 100;
}
p = p.without(DateTimeFieldType.yearOfCentury());
p = p.with(DateTimeFieldType.year(), y2);
}
continue;
} else if (p.isSupported(JodaTimeUtils.DecadeOfCentury)) {
if (!p.isSupported(DateTimeFieldType.centuryOfEra())) {
int decade = p.get(JodaTimeUtils.DecadeOfCentury);
int refYear = p2.getValue(i);
int century = refYear / 100;
int y2 = decade*10 + century*100;
// TODO: Figure out which way to go
if (refYear < y2) {
century--;
}
p = p.with(DateTimeFieldType.centuryOfEra(), century);
}
continue;
}
}
if (mgf == null || isMoreGeneral(fieldType, mgf, c1)) {
if (!p.isSupported(fieldType)) {
p = p.with(fieldType, p2.getValue(i));
}
} else {
break;
}
}
if (!p.isSupported(DateTimeFieldType.year())) {
if (p.isSupported(DateTimeFieldType.yearOfCentury()) && p.isSupported(DateTimeFieldType.centuryOfEra())) {
int year = p.get(DateTimeFieldType.yearOfCentury()) + p.get(DateTimeFieldType.centuryOfEra())*100;
p = p.with(DateTimeFieldType.year(), year);
p = p.without(DateTimeFieldType.yearOfCentury());
p = p.without(DateTimeFieldType.centuryOfEra());
}
}
return p;
}
public static Partial discardMoreSpecificFields(Partial p, DateTimeFieldType d)
{
Partial res = new Partial();
for (int i = 0; i < p.size(); i++) {
DateTimeFieldType fieldType = p.getFieldType(i);
if (fieldType.equals(d) || isMoreGeneral(fieldType, d, p.getChronology())) {
res = res.with(fieldType, p.getValue(i));
}
}
if (res.isSupported(JodaTimeUtils.DecadeOfCentury) && !res.isSupported(DateTimeFieldType.centuryOfEra())) {
if (p.isSupported(DateTimeFieldType.year())) {
res = res.with(DateTimeFieldType.centuryOfEra(), p.get(DateTimeFieldType.year()) / 100);
}
}
return res;
}
public static Partial discardMoreSpecificFields(Partial p, DurationFieldType dft)
{
DurationField df = dft.getField(p.getChronology());
Partial res = new Partial();
for (int i = 0; i < p.size(); i++) {
DateTimeFieldType fieldType = p.getFieldType(i);
DurationField f = fieldType.getDurationType().getField(p.getChronology());
int cmp = df.compareTo(f);
if (cmp <= 0) {
res = res.with(fieldType, p.getValue(i));
}
}
return res;
}
public static Period discardMoreSpecificFields(Period p, DurationFieldType dft, Chronology chronology)
{
DurationField df = dft.getField(chronology);
Period res = new Period();
for (int i = 0; i < p.size(); i++) {
DurationFieldType fieldType = p.getFieldType(i);
DurationField f = fieldType.getField(chronology);
int cmp = df.compareTo(f);
if (cmp <= 0) {
res = res.withField(fieldType, p.getValue(i));
}
}
return res;
}
public static Partial padMoreSpecificFields(Partial p, Period granularity)
{
DateTimeFieldType msf = getMostSpecific(p);
if (isMoreGeneral(msf, DateTimeFieldType.year(), p.getChronology()) ||
isMoreGeneral(msf, DateTimeFieldType.yearOfCentury(), p.getChronology())) {
if (p.isSupported(DateTimeFieldType.yearOfCentury())) {
// OKAY
} else {
if (p.isSupported(JodaTimeUtils.DecadeOfCentury)) {
if (p.isSupported(DateTimeFieldType.centuryOfEra())) {
int year = p.get(DateTimeFieldType.centuryOfEra()) * 100 + p.get(JodaTimeUtils.DecadeOfCentury)*10;
p = p.without(JodaTimeUtils.DecadeOfCentury);
p = p.without(DateTimeFieldType.centuryOfEra());
p = p.with(DateTimeFieldType.year(), year);
} else {
int year = p.get(JodaTimeUtils.DecadeOfCentury)*10;
p = p.without(JodaTimeUtils.DecadeOfCentury);
p = p.with(DateTimeFieldType.yearOfCentury(), year);
}
} else {
if (p.isSupported(DateTimeFieldType.centuryOfEra())) {
int year = p.get(DateTimeFieldType.centuryOfEra()) * 100;
p = p.without(DateTimeFieldType.centuryOfEra());
p = p.with(DateTimeFieldType.year(), year);
}
}
}
}
boolean useWeek = false;
if (p.isSupported(DateTimeFieldType.weekOfWeekyear())) {
if (!p.isSupported(DateTimeFieldType.dayOfMonth()) && !p.isSupported(DateTimeFieldType.dayOfWeek())) {
p = p.with(DateTimeFieldType.dayOfWeek(), 1);
if (p.isSupported(DateTimeFieldType.monthOfYear())) {
p = p.without(DateTimeFieldType.monthOfYear());
}
}
useWeek = true;
}
Partial p2 = useWeek? EMPTY_ISO_WEEK_PARTIAL:EMPTY_ISO_PARTIAL;
for (int i = 0; i < p2.size(); i++) {
DateTimeFieldType fieldType = p2.getFieldType(i);
if (msf == null || isMoreSpecific(fieldType, msf, p.getChronology())) {
if (!p.isSupported(fieldType)) {
p = p.with(fieldType, p2.getValue(i));
}
}
}
if (granularity != null) {
DurationFieldType mostSpecific = getMostSpecific(granularity);
p = discardMoreSpecificFields(p, mostSpecific);
}
return p;
}
public static boolean isCompatible(Partial p1, Partial p2) {
if (p1 == null) return true;
if (p2 == null) return true;
for (int i = 0; i < p1.size(); i++) {
DateTimeFieldType type = p1.getFieldType(i);
int v = p1.getValue(i);
if (JodaTimeUtils.hasField(p2,type)) {
if (v != p2.get(type)) {
return false;
}
}
}
return true;
}
// Uses p2 to resolve dow for p1
public static Partial resolveDowToDay(Partial p1, Partial p2)
{
// Discard anything that's more specific than dayOfMonth for p2
p2 = JodaTimeUtils.discardMoreSpecificFields(p2, DateTimeFieldType.dayOfMonth());
if (isCompatible(p1,p2)) {
if (p1.isSupported(DateTimeFieldType.dayOfWeek())) {
if (!p1.isSupported(DateTimeFieldType.dayOfMonth())) {
if (p2.isSupported(DateTimeFieldType.dayOfMonth()) && p2.isSupported(DateTimeFieldType.monthOfYear()) && p2.isSupported(DateTimeFieldType.year())) {
Instant t2 = getInstant(p2);
DateTime t1 = p1.toDateTime(t2);
return getPartial(t1.toInstant(), p1.with(DateTimeFieldType.dayOfMonth(), 1)/*.with(DateTimeFieldType.weekOfWeekyear(), 1) */);
}
}
}
}
return p1;
}
// Resolve dow for p1
public static Partial resolveDowToDay(Partial p)
{
if (p.isSupported(DateTimeFieldType.dayOfWeek())) {
if (!p.isSupported(DateTimeFieldType.dayOfMonth())) {
if (p.isSupported(DateTimeFieldType.weekOfWeekyear()) && p.isSupported(DateTimeFieldType.year())) {
Instant t2 = getInstant(p);
DateTime t1 = p.toDateTime(t2);
Partial res = getPartial(t1.toInstant(), EMPTY_ISO_PARTIAL);
DateTimeFieldType mostSpecific = getMostSpecific(p);
res = discardMoreSpecificFields(res, mostSpecific.getDurationType());
return res;
}
}
}
return p;
}
// Uses p2 to resolve week for p1
public static Partial resolveWeek(Partial p1, Partial p2)
{
if (isCompatible(p1,p2)) {
if (!p1.isSupported(DateTimeFieldType.dayOfMonth())) {
if (p2.isSupported(DateTimeFieldType.dayOfMonth()) && p2.isSupported(DateTimeFieldType.monthOfYear()) && p2.isSupported(DateTimeFieldType.year())) {
Instant t2 = getInstant(p2);
DateTime t1 = p1.toDateTime(t2);
return getPartial(t1.toInstant(), p1.without(DateTimeFieldType.dayOfMonth()).without(DateTimeFieldType.monthOfYear()).with(DateTimeFieldType.weekOfWeekyear(), 1));
}
}
}
return p1;
}
public static Partial resolveWeek(Partial p)
{
// Figure out week
if (p.isSupported(DateTimeFieldType.dayOfMonth()) && p.isSupported(DateTimeFieldType.monthOfYear()) && p.isSupported(DateTimeFieldType.year())) {
Instant t = getInstant(p);
// return getPartial(t.toInstant(), p.without(DateTimeFieldType.dayOfMonth()).without(DateTimeFieldType.monthOfYear()).with(DateTimeFieldType.weekOfWeekyear(), 1));
return getPartial(t.toInstant(), p.with(DateTimeFieldType.weekOfWeekyear(), 1));
} else return p;
}
public static Instant getInstant(Partial p)
{
if (p == null) return null;
int year = p.isSupported(DateTimeFieldType.year())? p.get(DateTimeFieldType.year()):0;
if (!p.isSupported(DateTimeFieldType.year())) {
if (p.isSupported(DateTimeFieldType.centuryOfEra())) {
year += 100*p.get(DateTimeFieldType.centuryOfEra());
}
if (p.isSupported(DateTimeFieldType.yearOfCentury())) {
year += p.get(DateTimeFieldType.yearOfCentury());
} else if (p.isSupported(DecadeOfCentury)) {
year += 10*p.get(DecadeOfCentury);
}
}
int moy = p.isSupported(DateTimeFieldType.monthOfYear())? p.get(DateTimeFieldType.monthOfYear()):1;
if (!p.isSupported(DateTimeFieldType.monthOfYear())) {
if (p.isSupported(QuarterOfYear)) {
moy += 3*(p.get(QuarterOfYear)-1);
}
}
int dom = p.isSupported(DateTimeFieldType.dayOfMonth())? p.get(DateTimeFieldType.dayOfMonth()):1;
int hod = p.isSupported(DateTimeFieldType.hourOfDay())? p.get(DateTimeFieldType.hourOfDay()):0;
int moh = p.isSupported(DateTimeFieldType.minuteOfHour())? p.get(DateTimeFieldType.minuteOfHour()):0;
int som = p.isSupported(DateTimeFieldType.secondOfMinute())? p.get(DateTimeFieldType.secondOfMinute()):0;
int msos = p.isSupported(DateTimeFieldType.millisOfSecond())? p.get(DateTimeFieldType.millisOfSecond()):0;
return new DateTime(year, moy, dom, hod, moh, som, msos, isoUTCChronology).toInstant();
}
public static Partial getPartial(Instant t, Partial p)
{
Partial res = new Partial(p);
for (int i = 0; i < p.size(); i++) {
res = res.withField(p.getFieldType(i), t.get(p.getFieldType(i)));
}
return res;
}
// Add duration to partial
public static Partial addForce(Partial p, Period d, int scalar)
{
Instant t = getInstant(p);
t = t.withDurationAdded(d.toDurationFrom(INSTANT_ZERO), scalar);
return getPartial(t, p);
}
// Returns if df1 is more general than df2
public static boolean isMoreGeneral(DateTimeFieldType df1, DateTimeFieldType df2, Chronology chronology)
{
DurationFieldType df1DurationFieldType = df1.getDurationType();
DurationFieldType df2DurationFieldType = df2.getDurationType();
if (!df2DurationFieldType.equals(df1DurationFieldType)) {
DurationField df1Unit = df1DurationFieldType.getField(chronology);
DurationFieldType p = df2.getRangeDurationType();
if (p != null) {
DurationField df2Unit = df2DurationFieldType.getField(chronology);
int cmp = df1Unit.compareTo(df2Unit);
if (cmp > 0) {
return true;
}
}
}
return false;
}
// Returns if df1 is more specific than df2
public static boolean isMoreSpecific(DateTimeFieldType df1, DateTimeFieldType df2, Chronology chronology)
{
DurationFieldType df1DurationFieldType = df1.getDurationType();
DurationFieldType df2DurationFieldType = df2.getDurationType();
if (!df2DurationFieldType.equals(df1DurationFieldType)) {
DurationField df2Unit = df2DurationFieldType.getField(chronology);
DurationFieldType p = df1.getRangeDurationType();
if (p != null) {
DurationField df1Unit = df1DurationFieldType.getField(chronology);
int cmp = df1Unit.compareTo(df2Unit);
if (cmp < 0) {
return true;
}
}
}
return false;
}
private static String zeroPad(int value, int padding){
StringBuilder b = new StringBuilder();
b.append(value);
while(b.length() < padding){
b.insert(0,"0");
}
return b.toString();
}
private static boolean noFurtherFields(DateTimeFieldType smallestFieldSet, ReadableDateTime begin, ReadableDateTime end){
//--Get Indices
//(standard fields)
int indexInStandard = -1;
for(int i=0; i<standardISOFields.length; i++){
if(standardISOFields[i] == smallestFieldSet){
indexInStandard = i+1;
}
}
//(week-based fields)
int indexInWeek = -1;
for(int i=0; i<standardISOWeekFields.length; i++){
if(standardISOWeekFields[i] == smallestFieldSet){
indexInWeek = i+1;
}
}
//(special fields)
if(smallestFieldSet == QuarterOfYear){
for(int i=0; i<standardISOFields.length; i++){
if(standardISOFields[i] == monthOfYear()){
indexInStandard = i;
}
}
}
//(get data)
int index = -1;
DateTimeFieldType[] toCheck = null;
if(indexInStandard >= 0){
index = indexInStandard;
toCheck = standardISOFields;
} else if(indexInWeek >= 0){
index = indexInWeek;
toCheck = standardISOWeekFields;
} else {
throw new IllegalArgumentException("Field is not in my list of fields: " + smallestFieldSet);
}
//--Perform Check
for(int i=index; i<toCheck.length; i++){
int minValue = minimumValue(toCheck[i], begin) ;
if(begin.get(toCheck[i]) != minValue || end.get(toCheck[i]) != minValue){
return false;
}
}
return true;
}
/**
* Return the minimum value of a field, closest to the reference time
*/
public static int minimumValue(DateTimeFieldType type, ReadableDateTime reference){
return reference.toDateTime().property(type).getMinimumValue();
}
/**
* Return the maximum value of a field, closest to the reference time
*/
public static int maximumValue(DateTimeFieldType type, ReadableDateTime reference){
return reference.toDateTime().property(type).getMaximumValue();
}
/**
* Return the TIMEX string for the time given
*/
public static String timexTimeValue(ReadableDateTime time){
return String.valueOf(time.getYear()) + '-' + zeroPad(time.getMonthOfYear(), 2) + '-' + zeroPad(time.getDayOfMonth(), 2) + 'T' + zeroPad(time.getHourOfDay(), 2) + ':' + zeroPad(time.getMinuteOfHour(), 2);
}
public static class ConversionOptions{
/**
* If true, give a "best guess" of the right date; if false, backoff to giving a duration
* for malformed dates.
*/
public boolean forceDate = false;
/**
* Force particular units -- e.g., force 20Y to be 20Y (20 years) rather than 2E (2 decades)
*/
public String[] forceUnits = new String[0];
/**
* Treat durations as approximate
*/
public boolean approximate = false;
}
public static String timexDateValue(ReadableDateTime begin, ReadableDateTime end){
return timexDateValue(begin,end,new ConversionOptions());
}
/**
* Return the TIMEX string for the range of dates given.
* For example, (2011-12-3:00:00,000 to 2011-12-4:00:00.000) would give 2011-12-3.
* @param begin The begin time for the timex
* @param end The end time for the timex
* @param opts Tweaks in the heuristic conversion
* @return The string representation of a DATE type Timex3 expression
*/
public static String timexDateValue(ReadableDateTime begin, ReadableDateTime end, ConversionOptions opts){
//--Special Cases
if( begin.getYear() < -100000){
return "PAST_REF";
} else if(end.getYear() > 100000){
return "FUTURE_REF";
} else if(begin.equals(end)){
return timexTimeValue(begin);
}
StringBuilder value = new StringBuilder();
boolean shouldBeDone = false;
//--Differences
int monthDiff = (end.getMonthOfYear() - begin.getMonthOfYear()) + (end.getYear()-begin.getYear())*12;
int weekDiff = end.getWeekOfWeekyear()-begin.getWeekOfWeekyear() + (end.getYear()-begin.getYear())*maximumValue(weekOfWeekyear(),begin);
int dayDiff = end.getDayOfMonth()-begin.getDayOfMonth() + monthDiff*maximumValue(dayOfMonth(),begin);
int hrDiff = end.getHourOfDay()-begin.getHourOfDay() + dayDiff*24;
int minDiff = end.getMinuteOfHour()-begin.getMinuteOfHour() + hrDiff*60;
int secDiff = end.getSecondOfMinute()-begin.getSecondOfMinute() + minDiff*60;
//--Years
if(noFurtherFields(year(), begin, end)){
int diff = end.getYear()-begin.getYear();
if(diff == 100 && (opts.forceDate || begin.getYear() % 100 == 0)){
//(case: century)
value.append(( begin.getYear() / 100)).append("XX");
} else if(diff == 10 && (opts.forceDate || begin.getYear() % 10 == 0)){
//(case: decade)
value.append(( begin.getYear() / 10));
} else if(diff == 1 || opts.forceDate){
//(case: year)
value.append(begin.getYear());
} else {
//(case: duration)
return timexDurationValue(begin,end);
}
return value.toString();
} else if(monthDiff < 12 || opts.forceDate) {
//(case: year and more)
value.append(begin.getYear());
} else {
//(case: treat as duration)
return timexDurationValue(begin, end);
}
//--Week/Month/Quarters
value.append("-");
if(noFurtherFields(monthOfYear(), begin, end) || noFurtherFields(weekOfWeekyear(), begin, end)){
boolean monthTerminal = noFurtherFields(monthOfYear(), begin, end);
boolean weekTerminal = noFurtherFields(weekOfWeekyear(), begin, end);
//(Month/Quarter)
if(monthTerminal && monthDiff == 6 && (begin.getMonthOfYear()-1) % 6 == 0){
//(case: half of year)
value.append("H").append(( begin.getMonthOfYear()-1) / 6 + 1);
}else if(monthTerminal && monthDiff == 3 && (begin.getMonthOfYear()-1) % 3 == 0){
//(case: quarter of year)
value.append("Q").append(( begin.getMonthOfYear()-1) / 3 + 1);
} else if(monthTerminal && monthDiff == 3 && begin.getMonthOfYear() % 3 == 0){
//(case: season)
switch( begin.getMonthOfYear() ){
case 12:
value.append("WI");
break;
case 3:
value.append("SP");
break;
case 6:
value.append("SU");
break;
case 9:
value.append("FA");
break;
default:
throw new IllegalStateException("Season start month is unknown");
}
} else if(weekTerminal && weekDiff == 1) {
//(case: a week)
value.append("W").append(zeroPad(begin.getWeekOfWeekyear(), 2));
} else if(monthTerminal && monthDiff == 1 && weekDiff != 1 || opts.forceDate) {
//(case: a month)
value.append( zeroPad(begin.getMonthOfYear(),2) );
} else {
//(case: treat as duration)
return timexDurationValue(begin, end);
}
return value.toString();
} else if(noFurtherFields(dayOfWeek(), begin, end) && dayDiff == 2 && begin.getDayOfWeek() == 6){
//(case: a weekend)
value.append("W").append(zeroPad(begin.getWeekOfWeekyear(),2)).append("-WE");
return value.toString();
} else if(dayDiff < maximumValue(dayOfMonth(),begin) || opts.forceDate) {
//(case: month and more)
value.append(zeroPad(begin.getMonthOfYear(),2));
} else {
//(case: treat as duration)
return timexDurationValue(begin, end);
}
//--Weekday/Day
value.append("-");
if(noFurtherFields(dayOfMonth(), begin, end)){
if(dayDiff == 1 || opts.forceDate){
//(case: a day)
value.append(zeroPad(begin.getDayOfMonth(),2));
} else {
//(case: treat as duration)
return timexDurationValue(begin, end);
}
return value.toString();
} else if(hrDiff < 24 || opts.forceDate){
//(case: day and more)
value.append(zeroPad(begin.getDayOfMonth(),2));
} else {
//(case: treat as duration)
return timexDurationValue(begin, end);
}
//--Hour/TimeOfDay
value.append("T");
if(noFurtherFields(hourOfDay(),begin,end)){
//((case: half day)
if(hrDiff == 12 && begin.getHourOfDay() == 0){
value.append("H1");
} else if(hrDiff == 12 && begin.getHourOfDay() == 12){
value.append("H2");
//(case: time of day)
}else if(hrDiff == 4 && begin.getHourOfDay() == 8){
value.append("MO");
}else if(hrDiff == 4 && begin.getHourOfDay() == 12){
value.append("AF");
}else if(hrDiff == 4 && begin.getHourOfDay() == 16){
value.append("EV");
}else if(hrDiff == 4 && begin.getHourOfDay() == 20){
value.append("NI");
} else if(hrDiff == 1 || opts.forceDate){
//(case: an hour)
value.append(zeroPad(begin.getHourOfDay()+1,2));
} else {
//(case: treat as duration)
return timexDurationValue(begin,end);
}
return value.toString();
} else if(minDiff <= 60 || opts.forceDate){
//(case: hour and more)
value.append(zeroPad(begin.getHourOfDay(),2));
} else {
//(case: treat as duration)
return timexDurationValue(begin, end);
}
//--Minute/Second
value.append(":");
value.append(zeroPad(begin.getMinuteOfHour(),2));
return value.toString();
}
private static boolean consistentWithForced(String cand, String[] forcedList){
//--Check If Forced
for(String forced : forcedList){
if(forced.equals(cand)){ return true; }
}
//--Get Ordering
String[] ordering = {"L","C","E","Y","Q","M","W","D","H","m","S"};
int candIndex = -1;
for(int i=0; i<ordering.length; i++){
if(ordering[i].equals(cand)){
candIndex = i;
break;
}
}
assert candIndex >= 0;
//--Check If Lower Priority Forced
for(int candI=candIndex+1; candI < ordering.length; candI++){
for(String forced : forcedList){
if(ordering[candI].equals(forced)){
return false;
}
}
}
//--OK
return true;
}
/**
* Return the TIMEX string for the duration represented by the given period; approximately if
* approximate is set to true.
* @param duration The JodaTime period representing this duration
* @param opts Options for the conversion (e.g., mark duration as approximates)
* @return The string representation of a DURATION type Timex3 expression
*/
public static String timexDurationValue(ReadablePeriod duration, ConversionOptions opts){
StringBuilder b = new StringBuilder().append("P");
boolean seenTime = false;
int years = duration.get(years());
//(millenia)
if(years >= 1000 && consistentWithForced("L",opts.forceUnits)){
b.append(opts.approximate ? "X" : years / 1000).append("L");
years = years % 1000;
}
//(centuries)
if(years >= 100 && consistentWithForced("C", opts.forceUnits)){
b.append(opts.approximate ? "X" : years / 100).append("C");
years = years % 100;
}
//(decades)
if(years >= 10 && consistentWithForced("E", opts.forceUnits)){
b.append(opts.approximate ? "X" : years / 10).append("E");
years = years % 10;
}
//(years)
if(years != 0 && consistentWithForced("Y", opts.forceUnits)){
b.append(opts.approximate ? "X" : years).append("Y");
}
//(months)
int months = duration.get(months());
if(months != 0){
if(months % 3 == 0 && consistentWithForced("Q", opts.forceUnits)){
b.append(opts.approximate ? "X" : months / 3).append("Q");
months = months % 3;
} else {
b.append(opts.approximate ? "X" : months).append("M");
}
}
//(weeks)
if(duration.get(weeks()) != 0){
b.append(opts.approximate ? "X" : duration.get(weeks())).append("W");
}
//(days)
if(duration.get(days()) != 0){
b.append(opts.approximate ? "X" : duration.get(days())).append("D");
}
//(hours)
if(duration.get(hours()) != 0){
if(!seenTime){ b.append("T"); seenTime = true; }
b.append(opts.approximate ? "X" : duration.get(hours())).append("H");
}
//(minutes)
if(duration.get(minutes()) != 0){
if(!seenTime){ b.append("T"); seenTime = true; }
b.append(opts.approximate ? "X" : duration.get(minutes())).append("M");
}
//(seconds)
if(duration.get(seconds()) != 0){
if(!seenTime){ b.append("T"); seenTime = true; }
b.append(opts.approximate ? "X" : duration.get(seconds())).append("S");
}
return b.toString();
}
public static String timexDurationValue(ReadablePeriod duration){ return timexDurationValue(duration, new ConversionOptions()); }
/**
* Return the TIMEX string for the difference between two dates
* TODO not really sure if this works...
*/
public static String timexDurationValue(ReadableDateTime begin, ReadableDateTime end){
return timexDurationValue( new Period(end.getMillis()-begin.getMillis()) );
}
}