/**
* Copyright (C) 2012 52°North Initiative for Geospatial Open Source Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.n52.oxf.valueDomains.time;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Represents a single timePosition. It is leaned on the WMS profile of ISO8601 spec. Any suggestions about
* that profile are made in the decisions.html. <br>
* Valid example time strings: <li>2005-11-01</li> <li>2005-11-01T12:30</li> <li>2005-11-01T12:30:20Z</li>
*
* @author <a href="mailto:broering@52north.org">Arne Broering</a>
*/
public class TimePosition implements ITimePosition {
public static final String YEAR_PATTERN = "-?\\d+";
public static final String MONTH_PATTERN = "0*\\d?\\d";
public static final String DAY_PATTERN = "0*\\d?\\d";
public static final String HOUR_PATTERN = "0*\\d?\\d";
public static final String MINUTE_PATTERN = "0*\\d?\\d";
public static final String SECOND_PATTERN = "0*\\d?\\d(.\\d)?\\d?\\d?";
public static final String UTC_PATTERN = "\\d+(Z|[+-]\\d\\d([:]?(\\d\\d))?)";
private long year;
private int month = NOT_SET;
private int day = NOT_SET;
private int hour = NOT_SET;
private int minute = NOT_SET;
private float second = NOT_SET;
private String timeZone = "Z";
private boolean isDateComplete = false;
private String timePos = "";
private ITimeResolution timeRes;
/**
* constructs a timePosition out of a ISO 8601 String. The string has at least to indicate the year.
*
* @param timePos
* @throws IllegalArgumentException
* <ul>
* <li>if string ends with "T"</li>
* <li>if string is empty, <code>null</code> or -</li>
* <li>or does not match any additional pattern</li>
* </ul>
*/
public TimePosition(String timePos) throws IllegalArgumentException {
this.timePos = timePos;
if (timePos == null) {
throw new NullPointerException();
}
if (timePos.equals("")) {
throw new IllegalArgumentException("empty String not allowed!");
}
if (timePos.endsWith("T")) {
throw new IllegalArgumentException("timePos ends with T and does not contain any time information");
}
if (timePos.equalsIgnoreCase("now")) {
// TODO set most recent available data
throw new UnsupportedOperationException("for parameter 'now'");
}
String[] timePosArray = timePos.split("T");
if (timePosArray.length <= 2) {
initDate(timePosArray[0]);
if (timePosArray.length == 2) {
initTime(timePosArray[1]);
}
}
else {
throw new IllegalArgumentException("invalid timePosition!: " + timePos);
}
}
private void initDate(String date) throws IllegalArgumentException {
int negativeOffset = 0;
if (date.equals("-")) {
throw new IllegalArgumentException("date contains only \"-\"");
}
String[] dateArray = date.split("-");
if ( ( !dateArray[0].equalsIgnoreCase("") && dateArray.length > 3) || dateArray[0].equalsIgnoreCase("")
&& dateArray.length > 4) {
throw new IllegalArgumentException("date contains more than 3 parts");
}
if (date.startsWith("-")) {
dateArray[1] = "-" + dateArray[1];
negativeOffset = 1;
}
setYear(dateArray[0 + negativeOffset]);
if (dateArray.length >= (2 + negativeOffset)) {
setMonth(dateArray[1 + negativeOffset]);
}
if (dateArray.length == (3 + negativeOffset)) {
setDay(dateArray[2 + negativeOffset]);
isDateComplete = true;
}
}
/**
* Initializes the time (hours, minutes, seconds) after checking whether the date actually allows a time
* meaning that a following time is only possible after a date that matches the scheme year-month-day.
* Therefore initDate(String date) has to be called first otherwise this method will return an
* IllegalArgumentException.
*/
private void initTime(String time) throws IllegalArgumentException {
if ( !isDateComplete) {
throw new IllegalArgumentException("Date in: " + timePos
+ " does not correspond to the year-month-day scheme" + "that is why a time is not allowed.");
}
String[] timeArray = time.split(":");
Pattern utcPattern = Pattern.compile(UTC_PATTERN);
Matcher utcMatcher = utcPattern.matcher(timeArray[timeArray.length - 1]);
if ( !utcMatcher.matches()) {
// throw new IllegalArgumentException("timeZone is not valid");
}
else {
timeZone = utcMatcher.group(1);
}
if (timeArray.length > 4) {
throw new IllegalArgumentException("time contains more than 3 parts");
}
String lastToken = timeArray[timeArray.length - 1];
if (lastToken.endsWith("Z")) {
timeArray[timeArray.length - 1] = lastToken.substring(0, lastToken.length() - 1);
}
if (timeArray.length >= 1) {
setHour(timeArray[0]);
}
if (timeArray.length >= 2) {
setMinute(timeArray[1]);
}
if (timeArray.length == 3 || timeArray.length == 4) {
// changed to avoid parsing error with timeZone
String seconds = timeArray[2];
String[] secondAndTimeZonePlus = seconds.split("\\+");
String[] secondAndTimeZoneMinus = seconds.split("\\-");
if (secondAndTimeZonePlus.length == 1 && secondAndTimeZoneMinus.length == 1) {
setSecond(seconds);
}
else {
if (secondAndTimeZonePlus.length == 2) {
setSecond(secondAndTimeZonePlus[0]);
timeZone = "+" + secondAndTimeZonePlus[1];
// set time zone minutes:
if(timeArray.length == 4) {
timeZone += ":" + timeArray[3];
}
}
else if (secondAndTimeZoneMinus.length == 2) {
setSecond(secondAndTimeZoneMinus[0]);
timeZone = "-" + secondAndTimeZoneMinus[1];
// set time zone minutes:
if(timeArray.length == 4) {
timeZone += ":" + timeArray[3];
}
}
}
}
}
private void setYear(String year) throws IllegalArgumentException {
if (Pattern.matches(YEAR_PATTERN, year)) {
this.setYear(Long.parseLong(year));
}
else {
throw new IllegalArgumentException("year does not match pattern: applied Pattern: " + YEAR_PATTERN);
}
}
private void setYear(long year) throws IllegalArgumentException {
this.year = year;
}
private void setMonth(String month) throws IllegalArgumentException {
if (Pattern.matches(MONTH_PATTERN, month)) {
this.setMonth(Integer.parseInt(month));
}
else {
throw new IllegalArgumentException("month does not match pattern: applied Pattern: " + MONTH_PATTERN);
}
}
private void setMonth(int month) throws IllegalArgumentException {
if (month < 13 && month > 0) {
this.month = month;
}
else {
throw new IllegalArgumentException("month is not an allowed value");
}
}
private void setDay(String day) throws IllegalArgumentException {
if (Pattern.matches(DAY_PATTERN, day)) {
this.day = Integer.parseInt(day);
}
else {
throw new IllegalArgumentException("day does not match pattern: applied Pattern: " + DAY_PATTERN);
}
}
@SuppressWarnings("unused")
private void setDay(int day) throws IllegalArgumentException {
if (day < 32 && day > 0) {
this.day = day;
}
else {
throw new IllegalArgumentException("day is not an allowed value");
}
}
private void setHour(String hour) throws IllegalArgumentException {
if (Pattern.matches(HOUR_PATTERN, hour)) {
this.setHour(Integer.parseInt(hour));
}
else {
throw new IllegalArgumentException("hour does not match pattern: applied Pattern: " + HOUR_PATTERN);
}
}
private void setHour(int hour) throws IllegalArgumentException {
if (hour >= 0 && hour < 25) {
this.hour = hour;
}
else {
throw new IllegalArgumentException("hour is not an allowed value");
}
}
private void setMinute(String minute) throws IllegalArgumentException {
if (Pattern.matches(MINUTE_PATTERN, minute)) {
this.setMinute(Integer.parseInt(minute));
}
else {
throw new IllegalArgumentException("minute does not match pattern: applied Pattern: " + MINUTE_PATTERN);
}
}
private void setMinute(int minute) throws IllegalArgumentException {
if (minute >= 0 && minute < 60) {
this.minute = minute;
}
else {
throw new IllegalArgumentException("minute is not an allowed value");
}
}
private void setSecond(String second) throws IllegalArgumentException {
// TODO: hier kommt nen Hack (Arne):
/*
* { int plusIndex = second.indexOf("+"); if (plusIndex != -1) { second =
* second.substring(0,plusIndex); } }
*/
if (Pattern.matches(SECOND_PATTERN, second)) {
this.setSecond(Float.parseFloat(second));
}
else {
throw new IllegalArgumentException("second does not match pattern: applied Pattern: " + SECOND_PATTERN);
}
}
private void setSecond(float second) throws IllegalArgumentException {
if (second >= 0 && second < 60) {
this.second = second;
}
else {
throw new IllegalArgumentException("second is not an allowed value");
}
}
public long getYear() {
return year;
}
public int getMonth() {
return month;
}
public int getDay() {
return day;
}
public int getHour() {
return hour;
}
public int getMinute() {
return minute;
}
public float getSecond() {
return second;
}
/**
* @return the timeZone
*/
private String getTimeZone() {
return timeZone;
}
public ITimeResolution getTimeResolution() {
return timeRes;
}
public String toISO8601Format() {
StringBuilder isoDate = new StringBuilder();
isoDate.append(Long.toString(getYear()));
if (this.getMonth() != NOT_SET) {
isoDate.append("-");
if (this.getMonth() < 10) {
isoDate.append("0");
}
isoDate.append(this.getMonth());
}
else {
return isoDate.toString();
}
if (this.getDay() != NOT_SET) {
isoDate.append("-");
if (this.getDay() < 10) {
isoDate.append("0");
}
isoDate.append(this.getDay());
}
else {
return isoDate.toString();
}
if (this.getHour() != NOT_SET) {
isoDate.append("T");
if (this.getHour() < 10) {
isoDate.append("0");
}
isoDate.append(this.getHour());
}
else {
return isoDate.toString();
}
if (this.getMinute() != NOT_SET) {
isoDate.append(":");
if (this.getMinute() < 10) {
isoDate.append("0");
}
isoDate.append(this.getMinute());
}
else {
isoDate.toString();
}
if (this.getSecond() != NOT_SET) {
isoDate.append(":");
if (this.getSecond() < 10) {
isoDate.append("0");
}
// isoDate.append(new Double(this.getSecond()).intValue());
String fullSec = new Double(this.getSecond()).toString();
//3 nachkomma stellen erlauben
int end = fullSec.indexOf(".")+4;
if(end > fullSec.length()){
end = fullSec.length();
}
//Nachkommastellen entfernen falls nicht ben�tigt
if(fullSec.endsWith(".0")){
// replaceAll with .0 leads to problems
// just build substring
fullSec = fullSec.substring(0, fullSec.length()-2);
// fullSec = fullSec.replaceAll(".0", "");
end -=2;
}
isoDate.append(fullSec.substring(0,end));
}
else {
return isoDate.toString();
}
if ( !this.timeZone.equals("Z")) {
isoDate.append(timeZone);
}
// Should not be reached.
return isoDate.toString();
}
/**
* @return a String representation of this TimePosition object of the form: e.g.: 16.6.2006 14:53:12
*/
@Override
public String toString() {
StringBuilder ordinaryDate = new StringBuilder();
if (this.getDay() != NOT_SET) {
ordinaryDate.append(this.getDay());
ordinaryDate.append(".");
}
else {
ordinaryDate.append("?");
}
if (this.getMonth() != NOT_SET) {
ordinaryDate.append(this.getMonth());
ordinaryDate.append(".");
}
else {
ordinaryDate.append("?");
}
if (this.getYear() != NOT_SET) {
ordinaryDate.append(this.getYear());
ordinaryDate.append(" ");
}
else {
ordinaryDate.append("?");
}
if (this.getHour() != NOT_SET) {
ordinaryDate.append(this.getHour());
ordinaryDate.append(":");
}
else {
ordinaryDate.append("?");
}
if (this.getMinute() != NOT_SET) {
ordinaryDate.append(this.getMinute());
ordinaryDate.append(":");
}
else {
ordinaryDate.append("?");
}
if (this.getSecond() != NOT_SET) {
ordinaryDate.append(this.getSecond());
}
else {
ordinaryDate.append("?");
}
if ( !this.getTimeZone().equals("Z")) {
ordinaryDate.append(this.getTimeZone());
}
return ordinaryDate.toString();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ITimePosition) {
if (compareTo((ITimePosition) obj) == 0) {
return true;
}
}
return false;
}
/**
* a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than
* the specified object.
*
* @param timePosP
* @return -1 if this is before timePos; 1 if timePos is before this; 0 if this and timePos are equal.
*/
public int compareTo(ITimePosition timePosP) {
if (this.getYear() != timePosP.getYear()) {
return (this.getYear() < timePosP.getYear()) ? -1 : 1;
}
if (this.getMonth() != timePosP.getMonth()) {
return (this.getMonth() < timePosP.getMonth()) ? -1 : 1;
}
if (this.getDay() != timePosP.getDay()) {
return (this.getDay() < timePosP.getDay()) ? -1 : 1;
}
if (this.getHour() != timePosP.getHour()) {
return (this.getHour() < timePosP.getHour()) ? -1 : 1;
}
if (this.getMinute() != timePosP.getMinute()) {
return (this.getMinute() < timePosP.getMinute()) ? -1 : 1;
}
if (this.getSecond() != timePosP.getSecond()) {
return (this.getSecond() < timePosP.getSecond()) ? -1 : 1;
}
return 0;
}
/**
* a negative integer, zero, or a positive integer as the first argument is less than, equal to, or
* greater than the second.
*
* @param o1
* @param o2
* @return
*/
public int compare(ITimePosition o1, ITimePosition o2) {
return o1.compareTo(o2);
}
/**
* @return whether this TimePosition represents a time before the time represented by the specified
* TimePosition object. This method is equivalent to: compareTo(when) < 0
*/
public boolean before(ITimePosition timePosP) {
return compareTo(timePosP) < 0;
}
/**
* @return whether this TimePosition represents a time after the time represented by the specified
* TimePosition object. This method is equivalent to: compareTo(when) > 0
*/
public boolean after(ITimePosition timePosP) {
return compareTo(timePosP) > 0;
}
public Calendar getCalendar() {
return new GregorianCalendar((int) year, month - 1, day, hour, minute, (int) second);
}
public String getTimezone(){
return timeZone;
}
public void setTimezone(String timezone){
timeZone = timezone;
}
/**
* testing
* @param args
*/
public static void main(String[] args)
{
String t1 = "2011-02-13T13:00:23+02:00";
System.out.println(new TimePosition(t1).toISO8601Format());
}
}