blob: 3f6bef682b6890b24e15755cf5ae628a49e4ebcc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.jena.sparql.util;
/** XSD date/time 7-component model.
* Includes parsing xsd:dateTime, xsd:date and xsd:g*
*/
public class DateTimeStruct
{
// public boolean isXsdDateTime ;
// public boolean isXsdDate ;
// public boolean isXsdTime ;
public String neg = null ; // Null if none.
public String year = null ;
public String month = null ;
public String day = null ;
public String hour = null ;
public String minute = null ;
public String second = null ; // Inc. fractional parts
public String timezone = null ; // Null if none.
public DateTimeStruct() {}
public static class DateTimeParseException extends RuntimeException
{
public DateTimeParseException(String msg) { super(msg) ; }
}
@Override
public String toString()
{
String ySep = "-" ;
String tSep = ":" ;
String x = "";
if ( hasDate() ) {
x = year+ySep+month+ySep+day ;
if ( hasTime() )
x = x+"T";
}
if ( hasTime() )
x = x + hour+tSep+minute+tSep+second ;
if ( neg != null )
x = neg+x ;
if ( timezone != null )
x = x+timezone ;
return x ;
}
public boolean hasDate() { return year != null && month != null && day != null; }
public boolean hasTime() { return hour != null && minute != null && second != null; }
public boolean hasTimezone() { return timezone != null; }
public boolean isDate() { return hasDate() && ! hasTime(); };
public boolean isDateTime() { return hasDate() && hasTime(); };
public static DateTimeStruct parseDateTime(String str)
{ return _parseYMD(str, true, true, true) ; }
public static DateTimeStruct parseTime(String str)
{ return _parseTime(str) ; }
public static DateTimeStruct parseDate(String str)
{ return _parseYMD(str, true, true, false) ; }
public static DateTimeStruct parseGYear(String str)
{ return _parseYMD(str, false, false, false) ; }
public static DateTimeStruct parseGYearMonth(String str)
{ return _parseYMD(str, true, false, false) ; }
public static DateTimeStruct parseGMonth(String str)
{ return _parseMD(str, true, false) ; }
public static DateTimeStruct parseGMonthDay(String str)
{ return _parseMD(str, true, true) ; }
public static DateTimeStruct parseGDay(String str)
{ return _parseMD(str, false, true) ; }
// Date with year: date, dateTime, gYear, gYearMonth but not gMonth, gMonthDay,
private static DateTimeStruct _parseYMD(String str, boolean month, boolean day, boolean includeTime)
{
DateTimeStruct struct = new DateTimeStruct() ;
int idx = 0 ; // if whitespace fact processing -- skipWhitespace(str, 0) ;
boolean negYear = false ;
if ( str.charAt(idx) == '-' )
{
struct.neg = "-" ;
idx ++ ;
}
struct.year = getDigits(str, idx) ;
if ( struct.year.length() < 4 )
throw new DateTimeParseException("Year too short (must be 4 or more digits)") ;
idx += struct.year.length() ;
if ( month )
{
check(str, idx, '-') ;
idx += 1 ;
struct.month = getDigits(2, str, idx) ;
idx += 2 ;
}
if ( day )
{
check(str, idx, '-') ;
idx += 1 ;
struct.day = getDigits(2, str, idx) ;
idx += 2 ;
}
if ( includeTime )
{
check(str, idx, 'T') ;
idx += 1 ;
idx = _parseTime(struct, idx, str) ;
}
// Timezone
idx = _parseTimezone(struct, str, idx) ;
idx = skipWhitespace(str, idx) ;
if ( idx != str.length() )
throw new DateTimeParseException("Trailing characters after date/time") ;
return struct ;
}
// No year: gMonth, gMonthDay, gDay
private static DateTimeStruct _parseMD(String str, boolean month, boolean day)
{
DateTimeStruct struct = new DateTimeStruct() ;
int idx = 0 ;
check(str, idx, '-') ;
idx += 1 ;
check(str, idx, '-') ;
idx += 1 ;
if ( month )
{
struct.month = getDigits(2, str, idx) ;
idx += 2 ;
}
if ( day )
{
check(str, idx, '-') ;
idx += 1 ;
struct.day = getDigits(2, str, idx) ;
idx += 2 ;
}
// Timezone
idx = _parseTimezone(struct, str, idx) ;
if ( idx != str.length() )
throw new DateTimeParseException("Unexpected trailing characters in string") ;
return struct ;
}
private static DateTimeStruct _parseTime(String str)
{
DateTimeStruct struct = new DateTimeStruct() ;
int idx = 0 ;
idx = _parseTime(struct, 0, str) ;
idx = _parseTimezone(struct, str, idx) ;
idx = skipWhitespace(str, idx) ;
if ( idx != str.length() )
throw new DateTimeParseException("Trailing characters after date/time") ;
return struct ;
}
private static int _parseTime(DateTimeStruct struct, int idx, String str)
{
// Hour-minute-seconds
struct.hour = getDigits(2, str, idx) ;
idx += 2 ;
check(str, idx, ':') ;
idx += 1 ;
struct.minute = getDigits(2, str, idx) ;
idx += 2 ;
check(str, idx, ':') ;
idx += 1 ;
// seconds
struct.second = getDigits(2, str, idx) ;
idx += 2 ;
if ( idx < str.length() && str.charAt(idx) == '.' )
{
idx += 1 ;
int idx2 = idx ;
for ( ; idx2 < str.length() ; idx2++ )
{
char ch = str.charAt(idx2) ;
if ( ! Character.isDigit(ch) )
break ;
}
if ( idx == idx2 )
throw new DateTimeParseException("Bad time part") ;
struct.second = struct.second+'.'+str.substring(idx, idx2) ;
idx = idx2 ;
}
return idx ;
}
private static int _parseTimezone(DateTimeStruct struct, String str, int idx)
{
if ( idx >= str.length() )
{
struct.timezone = null ;
return idx ;
}
if ( str.charAt(idx) == 'Z' )
{
struct.timezone = "Z" ;
idx += 1 ;
}
else
{
StringBuilder sb = new StringBuilder() ;
if ( str.charAt(idx) == '+' )
sb.append('+') ;
else if ( str.charAt(idx) == '-' )
sb.append('-') ;
else
throw new DateTimeParseException("Bad timezone") ;
idx += 1 ;
sb.append(getDigits(2, str, idx)) ;
idx += 2 ;
check(str, idx, ':') ;
sb.append(':') ;
idx += 1 ;
sb.append(getDigits(2, str, idx)) ;
idx += 2 ;
struct.timezone = sb.toString() ;
}
return idx ;
}
// // DateTime or Date - not gregorian
// // Replace with generic code.
// private static DateTimeStruct _parse(String str, boolean includeTime)
// {
// // -? YYYY-MM-DD T hh:mm:ss.ss TZ
// DateTimeStruct struct = new DateTimeStruct() ;
// int idx = 0 ;
//
// if ( str.startsWith("-") )
// {
// struct.neg = "-" ;
// idx = 1 ;
// }
//
// // ---- Year-Month-Day
// struct.year = getDigits(4, str, idx) ;
// idx += 4 ;
// check(str, idx, '-') ;
// idx += 1 ;
//
// struct.month = getDigits(2, str, idx) ;
// idx += 2 ;
// check(str, idx, '-') ;
// idx += 1 ;
//
// struct.day = getDigits(2, str, idx) ;
// idx += 2 ;
//
// struct.xsdDateTime = false ;
//
// if ( includeTime )
// {
// struct.xsdDateTime = true ;
// // ----
// check(str, idx, 'T') ;
// idx += 1 ;
//
// // ----
// // Hour-minute-seconds
// struct.hour = getDigits(2, str, idx) ;
// idx += 2 ;
// check(str, idx, ':') ;
// idx += 1 ;
//
// struct.minute = getDigits(2, str, idx) ;
// idx += 2 ;
// check(str, idx, ':') ;
// idx += 1 ;
//
// // seconds
// struct.second = getDigits(2, str, idx) ;
// idx += 2 ;
// if ( idx < str.length() && str.charAt(idx) == '.' )
// {
// idx += 1 ;
// int idx2 = idx ;
// for ( ; idx2 < str.length() ; idx2++ )
// {
// char ch = str.charAt(idx2) ;
// if ( ! Character.isDigit(ch) )
// break ;
// }
// if ( idx == idx2 )
// throw new DateTimeParseException() ;
// struct.second = struct.second+'.'+str.substring(idx, idx2) ;
// idx = idx2 ;
// }
// }
// else
// {
// struct.hour = null ;
// struct.minute = null ;
// struct.second = null ;
//
// }
// // timezone. Z or +/- 00:00
//
// if ( idx < str.length() )
// {
// if ( str.charAt(idx) == 'Z' )
// {
// struct.timezone = "Z" ;
// idx += 1 ;
// }
// else
// {
// StringBuilder sb = new StringBuilder() ;
//
// if ( str.charAt(idx) == '+' )
// sb.append('+') ;
// else if ( str.charAt(idx) == '-' )
// sb.append('-') ;
// else
// throw new DateTimeParseException() ;
// idx += 1 ;
//
// sb.append(getDigits(2, str, idx)) ;
// idx += 2 ;
//
// check(str, idx, ':') ;
// sb.append(':') ;
// idx += 1 ;
//
//
// sb.append(getDigits(2, str, idx)) ;
// idx += 2 ;
//
// struct.timezone = sb.toString() ;
// }
// }
//
// if ( idx != str.length() )
// throw new DateTimeParseException() ;
// return struct ;
// }
private static String getDigits(int num, String string, int start)
{
for ( int i = start ; i < (start+num) ; i++ )
{
char ch = string.charAt(i) ;
// Only ASCII digits
if ( ch < '0' || ch > '9' )
throw new DateTimeParseException("Bad number (expected "+num+" digits)") ;
continue ;
}
return string.substring(start, start+num) ;
}
private static String getDigits(String string, int start)
{
int i = start ;
for ( ;; i++ )
{
if ( i >= string.length() )
break ;
char ch = string.charAt(i) ;
// Only ASCII digits
if ( ch < '0' || ch > '9' )
break ;
continue ;
}
return string.substring(start, i) ;
}
private static int skipWhitespace(String string, int idx)
{
while ( idx < string.length() )
{
char ch = string.charAt(idx) ;
if ( ! Character.isWhitespace(ch) )
return idx ;
idx++ ;
}
return idx ;
}
private static void check(String string, int idx, char x)
{
if ( string.length() <= idx || string.charAt(idx) != x )
throw new DateTimeParseException("Expected: "+x+" at index "+idx) ;
}
}