/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010, Open Source Geospatial Foundation (OSGeo)
* (C) 2010, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.temporal.util;
import org.geotoolkit.temporal.object.ISODateParser;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParseException;
import java.util.Date;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeSet;
import static org.geotoolkit.temporal.object.TemporalConstants.*;
/**
* Not thread safe.
*
* @author Guilhem Legal
* @author Mehdi Sidhoum
*/
public class PeriodUtilities {
/**
* The format of the dates.
*/
private DateFormat dateFormat;
/**
* The date parser.
*/
private ISODateParser dateParser = new ISODateParser();
/**
* Build a new period worker with the specified DateFormat
*/
public PeriodUtilities(final DateFormat dateFormat) {
this.dateFormat = dateFormat;
}
/**
* Evaluate the periodical gap between the different available time.
* Return a String concatening periods and isolated date.
*
* @param dates a sorted set of date (ordered by time).
*/
public String getDatesRespresentation(final SortedSet<Date> dates) {
if (dates.comparator() != null) {
throw new IllegalArgumentException("Dates should be sorted naturaly without any custom comparator.");
}
if (dates.isEmpty()) {
return "";
}
final StringBuffer response = new StringBuffer();
Date first = dates.first();
Date previousDate = first;
long previousGap = 0;
long gap = 0;
int nbDataInGap = 0;
for (Date d : dates) {
previousGap = gap;
gap = d.getTime() - previousDate.getTime();
if (previousGap != gap) {
if (nbDataInGap >= 2) {
final String firstDate = dateFormat.format(first);
if (response.indexOf(firstDate + ',') != -1) {
final int pos = response.indexOf(firstDate);
response.delete(pos, pos + firstDate.length() + 1);
}
response.append(getPeriodDescription(dates.subSet(first, d), previousGap)).append(',');
nbDataInGap = 1;
} else {
if (nbDataInGap > 0) {
dateFormat.format(previousDate, response, new FieldPosition(0)).append(',');
nbDataInGap = 1;
}
}
first = previousDate;
} else {
nbDataInGap++;
}
previousDate = d;
}
if (nbDataInGap > 0) {
if (nbDataInGap >= 2) {
final String firstDate = dateFormat.format(first);
if (response.indexOf(firstDate + ',') != -1) {
final int pos = response.indexOf(firstDate);
response.delete(pos, pos + firstDate.length() + 1);
}
response.append(getPeriodDescription(dates.tailSet(first), gap));
nbDataInGap = 1;
} else {
if (nbDataInGap > 0) {
dateFormat.format(previousDate, response, new FieldPosition(0));
nbDataInGap = 1;
}
}
}
return response.toString();
}
/**
* Return a String for a range of date (or just one)
*/
public String getPeriodDescription(final SortedSet<Date> dates, long gap) {
final StringBuffer response = new StringBuffer();
dateFormat.format(dates.first(), response, new FieldPosition(0));
response.append('/');
dateFormat.format(dates.last(), response, new FieldPosition(0));
response.append("/P");
//we look if the gap is more than one year (31536000000 ms)
long temp = gap / YEAR_MS;
if (temp > 1) {
response.append(temp).append('Y');
gap -= temp * YEAR_MS;
}
//we look if the gap is more than one month (2628000000 ms)
temp = gap / MONTH_MS;
if (temp >= 1) {
response.append(temp).append('M');
gap -= temp * MONTH_MS;
}
//we look if the gap is more than one week (604800000 ms)
temp = gap / WEEK_MS;
if (temp >= 1) {
response.append(temp).append('W');
gap -= temp * WEEK_MS;
}
//we look if the gap is more than one day (86400000 ms)
temp = gap / DAY_MS;
if (temp >= 1) {
response.append(temp).append('D');
gap -= temp * DAY_MS;
}
//if the gap is not over we pass to the hours by adding 'T'
if (gap != 0) {
response.append('T');
}
//we look if the gap is more than one hour (3600000 ms)
temp = gap / HOUR_MS;
if (temp >= 1) {
response.append(temp).append('H');
gap -= temp * HOUR_MS;
}
//we look if the gap is more than one min (60000 ms)
temp = gap / MINUTE_MS;
if (temp >= 1) {
response.append(temp).append('M');
gap -= temp * MINUTE_MS;
}
//we look if the gap is more than one week (1000 ms)
temp = gap / SECOND_MS;
if (temp >= 1) {
gap -= temp * SECOND_MS;
response.append(temp);
if (gap != 0) {
response.append(".").append(gap);
}
response.append('S');
}
return response.toString();
}
/**
* Return a sorted set from a string description.
*/
public SortedSet<Date> getDatesFromPeriodDescription(final String periods) throws ParseException {
final SortedSet<Date> response = new TreeSet<Date>();
final StringTokenizer tokens = new StringTokenizer(periods, ",");
while (tokens.hasMoreTokens()) {
String dates = tokens.nextToken().trim();
int slash = dates.indexOf('/');
if (slash == -1) {
response.add((dateFormat == null) ? dateParser.parseToDate(dates) : dateFormat.parse(dates));
} else {
//we get the begin position
final String begin = dates.substring(0, slash);
final Date first = (dateFormat == null) ? dateParser.parseToDate(begin) : dateFormat.parse(begin);
dates = dates.substring(slash+1);
//we get the end position
slash = dates.indexOf('/');
final String end = dates.substring(0, slash);
final Date last = (dateFormat == null) ? dateParser.parseToDate(end) : dateFormat.parse(end);
dates = dates.substring(slash+1);
//then we get the period Description
final long gap = getTimeFromPeriodDescription(dates);
Date currentDate = first;
while (currentDate.getTime() <= last.getTime()) {
response.add(currentDate);
currentDate = new Date(currentDate.getTime() + gap);
}
response.add(last);
}
}
return response;
}
/**
* Return a Date (long time) from a String description
*/
public long getTimeFromPeriodDescription(String periodDescription) {
long time = 0;
//we remove the 'P'
periodDescription = periodDescription.substring(1);
//we look if the period contains years (31536000000 ms)
if (periodDescription.indexOf('Y') != -1) {
final int nbYear = Integer.parseInt(periodDescription.substring(0, periodDescription.indexOf('Y')));
time += nbYear * YEAR_MS;
periodDescription = periodDescription.substring(periodDescription.indexOf('Y') + 1);
}
//we look if the period contains months (2628000000 ms)
if ( periodDescription.indexOf('M') != -1 &&
(periodDescription.indexOf('T') == -1 || periodDescription.indexOf('T') > periodDescription.indexOf('M')) ) {
final int nbMonth = Integer.parseInt(periodDescription.substring(0, periodDescription.indexOf('M')));
time += nbMonth * MONTH_MS;
periodDescription = periodDescription.substring(periodDescription.indexOf('M') + 1);
}
//we look if the period contains weeks (604800000 ms)
if (periodDescription.indexOf('W') != -1) {
final int nbWeek = Integer.parseInt(periodDescription.substring(0, periodDescription.indexOf('W')));
time += nbWeek * WEEK_MS;
periodDescription = periodDescription.substring(periodDescription.indexOf('W') + 1);
}
//we look if the period contains days (86400000 ms)
if (periodDescription.indexOf('D') != -1) {
final int nbDay = Integer.parseInt(periodDescription.substring(0, periodDescription.indexOf('D')));
time += nbDay * DAY_MS;
periodDescription = periodDescription.substring(periodDescription.indexOf('D') + 1);
}
//if the periodDescription is not over we pass to the hours by removing 'T'
if (periodDescription.indexOf('T') != -1) {
periodDescription = periodDescription.substring(1);
}
//we look if the period contains hours (3600000 ms)
if (periodDescription.indexOf('H') != -1) {
final int nbHour = Integer.parseInt(periodDescription.substring(0, periodDescription.indexOf('H')));
time += nbHour * HOUR_MS;
periodDescription = periodDescription.substring(periodDescription.indexOf('H') + 1);
}
//we look if the period contains minutes (60000 ms)
if (periodDescription.indexOf('M') != -1) {
final int nbMin = Integer.parseInt(periodDescription.substring(0, periodDescription.indexOf('M')));
time += nbMin * MINUTE_MS;
periodDescription = periodDescription.substring(periodDescription.indexOf('M') + 1);
}
//we look if the period contains seconds (1000 ms)
if (periodDescription.indexOf('S') != -1) {
if (periodDescription.indexOf('.') != -1) {
final int nbSec = Integer.parseInt(periodDescription.substring(0, periodDescription.indexOf('.')));
time += nbSec * SECOND_MS;
periodDescription = periodDescription.substring(periodDescription.indexOf('.') + 1);
// millsecond
final int nbMSec = Integer.parseInt(periodDescription.substring(0, periodDescription.indexOf('S')));
time += nbMSec;
periodDescription = periodDescription.substring(periodDescription.indexOf('S') + 1);
} else {
final int nbSec = Integer.parseInt(periodDescription.substring(0, periodDescription.indexOf('S')));
time += nbSec * SECOND_MS;
periodDescription = periodDescription.substring(periodDescription.indexOf('S') + 1);
}
}
if (periodDescription.length() != 0) {
throw new IllegalArgumentException("The period descritpion is malformed");
}
return time;
}
}