/* Copyright 2008, 2009, 2010 by the Oxford University Computing Laboratory
This file is part of HermiT.
HermiT 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, either version 3 of the License, or
(at your option) any later version.
HermiT 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.
You should have received a copy of the GNU Lesser General Public License
along with HermiT. If not, see <http://www.gnu.org/licenses/>.
*/
package org.semanticweb.HermiT.datatypes.datetime;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DateTime {
public static final int NO_TIMEZONE=Integer.MAX_VALUE;
public static final long MAX_TIME_ZONE_CORRECTION=14L*60L*60L*1000L;
protected static final Pattern s_dateTimePattern=Pattern.compile(
"(-?[0-9]{4,})"+
"-"+
"([0-9]{2})"+
"-"+
"([0-9]{2})"+
"T"+
"([0-9]{2})"+
":"+
"([0-9]{2})"+
":"+
"([0-9]{2})([.]([0-9]{1,3}))?"+
"((Z)|(([+]|-)([0-9]{2}):([0-9]{2})))?"
);
// according to XML Schema 1.1 spec (http://www.w3.org/TR/xmlschema11-2/#dateTime) the reg exp is as follows:
// -?([1-9][0-9]{3,}|0[0-9]{3})
// -(0[1-9]|1[0-2])
// -(0[1-9]|[12][0-9]|3[01])
// T(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?|(24:00:00(\.0+)?))
// (Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?
protected static final int YEAR_GROUP=1;
protected static final int MONTH_GROUP=2;
protected static final int DAY_GROUP=3;
protected static final int HOUR_GROUP=4;
protected static final int MINUTE_GROUP=5;
protected static final int SECOND_WHOLE_GROUP=6;
protected static final int SECOND_FRACTION_GROUP=8;
protected static final int TZ_OFFSET_GROUP=9;
protected static final int TZ_OFFSET_Z_GROUP=10;
protected static final int TZ_OFFSET_SIGN_GROUP=12;
protected static final int TZ_OFFSET_HOUR_GROUP=13;
protected static final int TZ_OFFSET_MINUTE_GROUP=14;
protected final long m_timeOnTimeline;
protected final boolean m_lastDayInstant;
protected final int m_timeZoneOffset;
public DateTime(int year,int month,int day,int hour,int minute,int second,int millisecond,int timeZoneOffset) {
m_timeOnTimeline=getTimeOnTimelineRaw(year,month,day,hour,minute,second,millisecond)-(timeZoneOffset==NO_TIMEZONE ? 0 : timeZoneOffset*60L*1000L);
m_lastDayInstant=(hour==24 && minute==0 && second==0 && millisecond==0);
m_timeZoneOffset=timeZoneOffset;
}
public DateTime(long timeOnTimeline,boolean lastDayInstant,int timeZoneOffset) {
m_timeOnTimeline=timeOnTimeline;
m_lastDayInstant=lastDayInstant;
m_timeZoneOffset=timeZoneOffset;
}
public String toString() {
long timeOnTimeline=m_timeOnTimeline;
if (m_timeZoneOffset!=NO_TIMEZONE)
timeOnTimeline+=m_timeZoneOffset*60L*1000L;
int timePart=(int)(timeOnTimeline % (1000*60*60*24));
long days=timeOnTimeline/(1000L*60L*60L*24L);
if (timePart<0) {
timePart+=1000*60*60*24;
days--;
assert timePart>=0;
}
int millisecond=timePart % 1000;
timePart=timePart/1000;
int second=timePart % 60;
timePart=timePart/60;
int minute=(int)(timePart % 60L);
timePart=timePart/60;
int hour=(int)(timePart % 24L);
int year=(int)(days/367L);
if (year>=0) {
while (days>=daysToYearStart(year+1))
year++;
days-=daysToYearStart(year);
}
else {
while (days<daysToYearStart(year-1))
year--;
year--;
days-=daysToYearStart(year);
}
int month=1;
int daysInMonth=daysInMonth(year,month);
while (days>daysInMonth) {
days-=daysInMonth;
month++;
daysInMonth=daysInMonth(year,month);
}
int day=((int)days)+1;
if (day==0) {
month--;
if (month==0) {
month=12;
year--;
}
day=daysInMonth(year,month);
}
if (m_lastDayInstant) {
assert hour==0 && minute==0 && second==0 && millisecond==0;
hour=24;
day--;
if (day<=0) {
month--;
if (month<=0) {
month=12;
year--;
}
day=daysInMonth(year,month);
}
}
StringBuffer buffer=new StringBuffer();
appendPadded(buffer,year,4);
buffer.append('-');
appendPadded(buffer,month,2);
buffer.append('-');
appendPadded(buffer,day,2);
buffer.append('T');
appendPadded(buffer,hour,2);
buffer.append(':');
appendPadded(buffer,minute,2);
buffer.append(':');
appendPadded(buffer,second,2);
if (millisecond>0) {
buffer.append('.');
appendPadded(buffer,millisecond,3);
}
if (m_timeZoneOffset!=NO_TIMEZONE)
if (m_timeZoneOffset==0)
buffer.append('Z');
else {
int absTimeZoneOffset;
if (m_timeZoneOffset>0) {
buffer.append('+');
absTimeZoneOffset=m_timeZoneOffset;
}
else {
buffer.append('-');
absTimeZoneOffset=-m_timeZoneOffset;
}
int timeZoneHour=absTimeZoneOffset/60;
int timeZoneMinute=absTimeZoneOffset % 60;
appendPadded(buffer,timeZoneHour,2);
buffer.append(':');
appendPadded(buffer,timeZoneMinute,2);
}
return buffer.toString();
}
public long getTimeOnTimeline() {
return m_timeOnTimeline;
}
public boolean hasTimeZoneOffset() {
return m_timeZoneOffset!=NO_TIMEZONE;
}
public int getTimeZoneOffset() {
return m_timeZoneOffset;
}
protected void appendPadded(StringBuffer buffer,int value,int digits) {
if (value<0)
buffer.append('-');
String stringAbsValue=String.valueOf(Math.abs(value));
for (int i=digits-stringAbsValue.length();i>0;--i)
buffer.append('0');
buffer.append(stringAbsValue);
}
public static DateTime parse(String lexicalForm) {
Matcher matcher=s_dateTimePattern.matcher(lexicalForm.trim());
if (!matcher.matches())
return null;
try {
int year=Integer.parseInt(matcher.group(YEAR_GROUP));
int month=Integer.parseInt(matcher.group(MONTH_GROUP));
int day=Integer.parseInt(matcher.group(DAY_GROUP));
int hour=Integer.parseInt(matcher.group(HOUR_GROUP));
int minute=Integer.parseInt(matcher.group(MINUTE_GROUP));
int second=Integer.parseInt(matcher.group(SECOND_WHOLE_GROUP));
// Milliseconds must be padded to exactly three characters so
// that they can be parsed correctly!
String millisecondString=matcher.group(SECOND_FRACTION_GROUP);
int millisecond;
if (millisecondString!=null) {
while (millisecondString.length()<3)
millisecondString+='0';
millisecond=Integer.parseInt(millisecondString);
}
else
millisecond=0;
if (year<-9999 || year>9999 ||
month<=0 || month>12 ||
day<=0 || day>daysInMonth(year,month) ||
hour<0 || hour>24 || (hour==24 && (minute!=0 || second!=0 || millisecond!=0)) ||
minute<0 || minute>=60 ||
second<0 || second>=60 ||
millisecond<0 || millisecond>=1000)
return null;
int timeZoneOffset;
if (matcher.group(TZ_OFFSET_GROUP)==null)
timeZoneOffset=NO_TIMEZONE;
else {
if (matcher.group(TZ_OFFSET_Z_GROUP)!=null)
timeZoneOffset=0;
else {
int sign=("-".equals(matcher.group(TZ_OFFSET_SIGN_GROUP)) ? -1 : 1);
int timeZoneOffsetHour=Integer.parseInt(matcher.group(TZ_OFFSET_HOUR_GROUP));
int timeZoneOffsetMinute=Integer.parseInt(matcher.group(TZ_OFFSET_MINUTE_GROUP));
if (timeZoneOffsetHour<0 || timeZoneOffsetHour>14 || (timeZoneOffsetHour==14 && timeZoneOffsetMinute!=0) ||
timeZoneOffsetMinute<0 || timeZoneOffsetMinute>=60)
return null;
else
timeZoneOffset=sign*(timeZoneOffsetHour*60+timeZoneOffsetMinute);
}
}
return new DateTime(year,month,day,hour,minute,second,millisecond,timeZoneOffset);
}
catch (NumberFormatException nfe) {
return null;
}
}
public boolean equals(Object that) {
if (this==that)
return true;
if (!(that instanceof DateTime) || that==null)
return false;
DateTime thatObject=(DateTime)that;
return m_timeOnTimeline==thatObject.m_timeOnTimeline && m_lastDayInstant==thatObject.m_lastDayInstant && m_timeZoneOffset==thatObject.m_timeZoneOffset;
}
public int hashCode() {
return (int)(m_timeOnTimeline*3L+m_timeZoneOffset+(m_lastDayInstant ? 117L : 0L));
}
protected long getTimeOnTimelineRaw(int year,int month,int day,int hour,int minute,int second,int millisecond) {
long yearMinusOne=year-1;
long timeOnTimeline=31536000L*yearMinusOne;
timeOnTimeline+=86400L*(yearMinusOne/400-yearMinusOne/100+yearMinusOne/4);
for (int monthIndex=1;monthIndex<month;monthIndex++)
timeOnTimeline+=86400L*daysInMonth(year,monthIndex);
timeOnTimeline+=86400L*(day-1);
timeOnTimeline+=3600L*hour+60L*minute+second;
timeOnTimeline=timeOnTimeline*1000L+millisecond;
return timeOnTimeline;
}
protected static long daysToYearStart(int year) {
long yearMinusOne=year-1;
return 365*yearMinusOne+(yearMinusOne/400)-(yearMinusOne/100)+(yearMinusOne/4);
}
protected static int daysInMonth(int year,int month) {
if (month==2) {
if ((year % 4)!=0 || ((year % 100)==0 && (year % 400)!=0))
return 28;
else
return 29;
}
else if (month==4 || month==6 || month==9 || month==11)
return 30;
else
return 31;
}
public static boolean isLastDayInstant(long timeOnTimeline) {
return (timeOnTimeline % (1000L*60L*60L*24L))==0;
}
public static boolean secondsAreZero(long timeOnTimeline) {
return (timeOnTimeline % (1000L*60L))==0;
}
public static int getMinutesInDay(long timeOnTimeline) {
return (int)((timeOnTimeline/(1000L*60L)) % (24L*60L));
}
}