/*
* Copyright 2003-2005 The Apache Software Foundation.
*
* 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.neo4j.meta.model;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.TimeZone;
/**
* An implementation of the standard xml schema date format:
* http://www.w3.org/2001/XMLSchema#dateTime as a {@link DateFormat}.
*/
public class XmlSchemaDateFormat extends DateFormat
{
private static final DateFormat DATEFORMAT_XSD_ZULU = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" );
static
{
DATEFORMAT_XSD_ZULU.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
}
@Override
public Date parse( String src, ParsePosition position )
{
Date date = null;
// validate fixed portion of format
int index = 0;
try
{
if ( src != null )
{
if ( ( src.charAt( 0 ) == '+' ) || ( src.charAt( 0 ) == '-' ) )
{
src = src.substring( 1 );
}
if ( src.length() < 19 )
{
position.setIndex( src.length() - 1 );
handleParseError( position, "TOO_FEW_CHARS" );
}
validateChar( src, position, index = 4, '-', "EXPECTED_DASH" );
validateChar( src, position, index = 7, '-', "EXPECTED_DASH" );
validateChar( src, position, index = 10, 'T',
"EXPECTED_CAPITAL_T" );
validateChar( src, position, index = 13, ':',
"EXPECTED_COLON_IN_TIME" );
validateChar( src, position, index = 16, ':',
"EXPECTED_COLON_IN_TIME" );
}
// convert what we have validated so far
try
{
date = DATEFORMAT_XSD_ZULU.parse( ( src == null ) ?
null : ( src.substring( 0, 19 ) + ".000Z" ) );
}
catch ( Exception e )
{
throw new NumberFormatException( e.toString() );
}
index = 19;
// parse optional milliseconds
if ( src != null )
{
if ( ( index < src.length() ) &&
( src.charAt( index ) == '.' ) )
{
int milliseconds = 0;
int start = ++index;
while ( ( index < src.length() )
&& Character.isDigit( src.charAt( index ) ) )
{
index++;
}
String decimal = src.substring( start, index );
if ( decimal.length() == 3 )
{
milliseconds = Integer.parseInt( decimal );
}
else if ( decimal.length() < 3 )
{
milliseconds = Integer.parseInt( ( decimal + "000" )
.substring( 0, 3 ) );
}
else
{
milliseconds = Integer.parseInt( decimal.substring( 0,
3 ) );
if ( decimal.charAt( 3 ) >= '5' )
{
++milliseconds;
}
}
// add milliseconds to the current date
date.setTime( date.getTime() + milliseconds );
}
// parse optional timezone
if ( ( ( index + 5 ) < src.length() )
&& ( ( src.charAt( index ) == '+' ) ||
( src.charAt( index ) == '-' ) ) )
{
validateCharIsDigit( src, position, index + 1,
"EXPECTED_NUMERAL" );
validateCharIsDigit( src, position, index + 2,
"EXPECTED_NUMERAL" );
validateChar( src, position, index + 3, ':',
"EXPECTED_COLON_IN_TIMEZONE" );
validateCharIsDigit( src, position, index + 4,
"EXPECTED_NUMERAL" );
validateCharIsDigit( src, position, index + 5,
"EXPECTED_NUMERAL" );
final int hours = ( ( ( src.charAt( index + 1 ) - '0' ) *
10 ) + src.charAt( index + 2 ) ) - '0';
final int mins = ( ( ( src.charAt( index + 4 ) - '0' ) *
10 ) + src.charAt( index + 5 ) ) - '0';
int millisecs = ( ( hours * 60 ) + mins ) * 60 * 1000;
// subtract millisecs from current date to obtain GMT
if ( src.charAt( index ) == '+' )
{
millisecs = -millisecs;
}
date.setTime( date.getTime() + millisecs );
index += 6;
}
if ( ( index < src.length() ) &&
( src.charAt( index ) == 'Z' ) )
{
index++;
}
if ( index < src.length() )
{
handleParseError( position, "TOO_MANY_CHARS" );
}
}
}
catch ( ParseException pe )
{
// This tells DateFormat.parse() to throw a ParseException
index = 0;
position.setErrorIndex( index );
date = null;
}
position.setIndex( index );
return ( date );
}
@Override
public StringBuffer format( Date date, StringBuffer buffer,
FieldPosition position )
{
String str = DATEFORMAT_XSD_ZULU.format( date );
if ( buffer == null )
{
buffer = new StringBuffer();
}
buffer.append( str );
return ( buffer );
}
private void validateChar( String str, ParsePosition parse_pos, int index,
char expected, String erorrReason ) throws ParseException
{
if ( str.charAt( index ) != expected )
{
handleParseError( parse_pos, erorrReason );
}
}
private void validateCharIsDigit( String str, ParsePosition position,
int index, String errorReason ) throws ParseException
{
if ( !Character.isDigit( str.charAt( index ) ) )
{
handleParseError( position, errorReason );
}
}
private void handleParseError( ParsePosition position, String errorReason )
throws ParseException
{
throw new ParseException( "INVALID_XSD_DATETIME", position
.getErrorIndex() );
}
}