All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.internal.xmp.impl.ISO8601Converter Maven / Gradle / Ivy

Go to download

The XMP Library for Java is based on the C++ XMPCore library and the API is similar.

There is a newer version: 6.1.11
Show newest version
// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2006 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================

package com.adobe.internal.xmp.impl;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.SimpleTimeZone;

import com.adobe.internal.xmp.XMPDateTime;
import com.adobe.internal.xmp.XMPError;
import com.adobe.internal.xmp.XMPException;


/**
 * Converts between ISO 8601 Strings and Calendar with millisecond resolution.
 *
 * @author  Stefan Makswit
 * @version $Revision$
 * @since   16.02.2006
 */
public final class ISO8601Converter
{
	/** Hides public constructor */
	private ISO8601Converter()
	{
		// EMPTY
	}


	/**
	 * Converts an ISO 8601 string to an XMPDateTime.
	 *
	 * Parse a date according to ISO 8601 and
	 * http://www.w3.org/TR/NOTE-datetime:
	 * 
    *
  • YYYY *
  • YYYY-MM *
  • YYYY-MM-DD *
  • YYYY-MM-DDThh:mmTZD *
  • YYYY-MM-DDThh:mm:ssTZD *
  • YYYY-MM-DDThh:mm:ss.sTZD *
* * Data fields: *
    *
  • YYYY = four-digit year *
  • MM = two-digit month (01=January, etc.) *
  • DD = two-digit day of month (01 through 31) *
  • hh = two digits of hour (00 through 23) *
  • mm = two digits of minute (00 through 59) *
  • ss = two digits of second (00 through 59) *
  • s = one or more digits representing a decimal fraction of a second *
  • TZD = time zone designator (Z or +hh:mm or -hh:mm) *
* * Note that ISO 8601 does not seem to allow years less than 1000 or greater * than 9999. We allow any year, even negative ones. The year is formatted * as "%.4d". *

* Note: Tolerate missing TZD, assume is UTC. Photoshop 8 writes * dates like this for exif:GPSTimeStamp.
* Note: DOES NOT APPLY ANYMORE. * Tolerate missing date portion, in case someone foolishly * writes a time-only value that way. * * @param iso8601String a date string that is ISO 8601 conform. * @return Returns a Calendar. * @throws XMPException Is thrown when the string is non-conform. */ public static XMPDateTime parse(String iso8601String) throws XMPException { return parse(iso8601String, new XMPDateTimeImpl()); } /** * @param iso8601String a date string that is ISO 8601 conform. * @param binValue an existing XMPDateTime to set with the parsed date * @return Returns an XMPDateTime-object containing the ISO8601-date. * @throws XMPException Is thrown when the string is non-conform. */ public static XMPDateTime parse(String iso8601String, XMPDateTime binValue) throws XMPException { if (iso8601String == null) { throw new XMPException("Parameter must not be null", XMPError.BADPARAM); } else if (iso8601String.length() == 0) { return binValue; } ParseState input = new ParseState(iso8601String); int value; if (input.ch(0) == '-') { input.skip(); } // Extract the year. value = input.gatherInt("Invalid year in date string", 9999); if (input.hasNext() && input.ch() != '-') { throw new XMPException("Invalid date string, after year", XMPError.BADVALUE); } if (input.ch(0) == '-') { value = -value; } binValue.setYear(value); if (!input.hasNext()) { return binValue; } input.skip(); // Extract the month. value = input.gatherInt("Invalid month in date string", 12); if (input.hasNext() && input.ch() != '-') { throw new XMPException("Invalid date string, after month", XMPError.BADVALUE); } binValue.setMonth(value); if (!input.hasNext()) { return binValue; } input.skip(); // Extract the day. value = input.gatherInt("Invalid day in date string", 31); if (input.hasNext() && input.ch() != 'T') { throw new XMPException("Invalid date string, after day", XMPError.BADVALUE); } binValue.setDay(value); if (!input.hasNext()) { return binValue; } input.skip(); // Extract the hour. value = input.gatherInt("Invalid hour in date string", 23); binValue.setHour(value); if (!input.hasNext()) { return binValue; } // Extract the minute. if (input.ch() == ':') { input.skip(); value = input.gatherInt("Invalid minute in date string", 59); if (input.hasNext() && input.ch() != ':' && input.ch() != 'Z' && input.ch() != '+' && input.ch() != '-') { throw new XMPException("Invalid date string, after minute", XMPError.BADVALUE); } binValue.setMinute(value); } if (!input.hasNext()) { return binValue; } else if (input.hasNext() && input.ch() == ':') { input.skip(); value = input.gatherInt("Invalid whole seconds in date string", 59); if (input.hasNext() && input.ch() != '.' && input.ch() != 'Z' && input.ch() != '+' && input.ch() != '-') { throw new XMPException("Invalid date string, after whole seconds", XMPError.BADVALUE); } binValue.setSecond(value); if (input.ch() == '.') { input.skip(); int digits = input.pos(); value = input.gatherInt("Invalid fractional seconds in date string", 999999999); if (input.hasNext() && (input.ch() != 'Z' && input.ch() != '+' && input.ch() != '-')) { throw new XMPException("Invalid date string, after fractional second", XMPError.BADVALUE); } digits = input.pos() - digits; for (; digits > 9; --digits) { value = value / 10; } for (; digits < 9; ++digits) { value = value * 10; } binValue.setNanoSecond(value); } } else if (input.ch() != 'Z' && input.ch() != '+' && input.ch() != '-') { throw new XMPException("Invalid date string, after time", XMPError.BADVALUE); } int tzSign = 0; int tzHour = 0; int tzMinute = 0; if (!input.hasNext()) { // no Timezone at all return binValue; } else if (input.ch() == 'Z') { input.skip(); } else if (input.hasNext()) { if (input.ch() == '+') { tzSign = 1; } else if (input.ch() == '-') { tzSign = -1; } else { throw new XMPException("Time zone must begin with 'Z', '+', or '-'", XMPError.BADVALUE); } input.skip(); // Extract the time zone hour. tzHour = input.gatherInt("Invalid time zone hour in date string", 23); if (input.hasNext()) { if (input.ch() == ':') { input.skip(); // Extract the time zone minute. tzMinute = input.gatherInt("Invalid time zone minute in date string", 59); } else { throw new XMPException("Invalid date string, after time zone hour", XMPError.BADVALUE); } } } // create a corresponding TZ and set it time zone // FfF: would it be better to map into existing time zones? int offset = (tzHour * 3600 * 1000 + tzMinute * 60 * 1000) * tzSign; binValue.setTimeZone(new SimpleTimeZone(offset, "")); if (input.hasNext()) { throw new XMPException( "Invalid date string, extra chars at end", XMPError.BADVALUE); } return binValue; } /** * Converts a Calendar into an ISO 8601 string. * Format a date according to ISO 8601 and http://www.w3.org/TR/NOTE-datetime: *

    *
  • YYYY *
  • YYYY-MM *
  • YYYY-MM-DD *
  • YYYY-MM-DDThh:mmTZD *
  • YYYY-MM-DDThh:mm:ssTZD *
  • YYYY-MM-DDThh:mm:ss.sTZD *
* * Data fields: *
    *
  • YYYY = four-digit year *
  • MM = two-digit month (01=January, etc.) *
  • DD = two-digit day of month (01 through 31) *
  • hh = two digits of hour (00 through 23) *
  • mm = two digits of minute (00 through 59) *
  • ss = two digits of second (00 through 59) *
  • s = one or more digits representing a decimal fraction of a second *
  • TZD = time zone designator (Z or +hh:mm or -hh:mm) *
*

* Note: ISO 8601 does not seem to allow years less than 1000 or greater than 9999. * We allow any year, even negative ones. The year is formatted as "%.4d".

* Note: Fix for bug 1269463 (silently fix out of range values) included in parsing. * The quasi-bogus "time only" values from Photoshop CS are not supported. * * @param dateTime an XMPDateTime-object. * @return Returns an ISO 8601 string. */ public static String render(XMPDateTime dateTime) { StringBuffer buffer = new StringBuffer(); if (dateTime.hasDate()) { // year is rendered in any case, even 0000 DecimalFormat df = new DecimalFormat("0000", new DecimalFormatSymbols(Locale.ENGLISH)); buffer.append(df.format(dateTime.getYear())); if (dateTime.getMonth() == 0) { return buffer.toString(); } // month df.applyPattern("'-'00"); buffer.append(df.format(dateTime.getMonth())); if (dateTime.getDay() == 0) { return buffer.toString(); } // day buffer.append(df.format(dateTime.getDay())); // time, rendered if any time field is not zero if (dateTime.hasTime()) { // hours and minutes buffer.append('T'); df.applyPattern("00"); buffer.append(df.format(dateTime.getHour())); buffer.append(':'); buffer.append(df.format(dateTime.getMinute())); // seconds and nanoseconds if (dateTime.getSecond() != 0 || dateTime.getNanoSecond() != 0) { double seconds = dateTime.getSecond() + dateTime.getNanoSecond() / 1e9d; df.applyPattern(":00.#########"); buffer.append(df.format(seconds)); } // time zone if (dateTime.hasTimeZone()) { // used to calculate the time zone offset incl. Daylight Savings long timeInMillis = dateTime.getCalendar().getTimeInMillis(); int offset = dateTime.getTimeZone().getOffset(timeInMillis); if (offset == 0) { // UTC buffer.append('Z'); } else { int thours = offset / 3600000; int tminutes = Math.abs(offset % 3600000 / 60000); df.applyPattern("+00;-00"); buffer.append(df.format(thours)); df.applyPattern(":00"); buffer.append(df.format(tminutes)); } } } } return buffer.toString(); } } /** * @author Stefan Makswit * @version $Revision$ * @since 22.08.2006 */ class ParseState { /** */ private String str; /** */ private int pos = 0; /** * @param str initializes the parser container */ public ParseState(String str) { this.str = str; } /** * @return Returns the length of the input. */ public int length() { return str.length(); } /** * @return Returns whether there are more chars to come. */ public boolean hasNext() { return pos < str.length(); } /** * @param index index of char * @return Returns char at a certain index. */ public char ch(int index) { return index < str.length() ? str.charAt(index) : 0x0000; } /** * @return Returns the current char or 0x0000 if there are no more chars. */ public char ch() { return pos < str.length() ? str.charAt(pos) : 0x0000; } /** * Skips the next char. */ public void skip() { pos++; } /** * @return Returns the current position. */ public int pos() { return pos; } /** * Parses a integer from the source and sets the pointer after it. * @param errorMsg Error message to put in the exception if no number can be found * @param maxValue the max value of the number to return * @return Returns the parsed integer. * @throws XMPException Thrown if no integer can be found. */ public int gatherInt(String errorMsg, int maxValue) throws XMPException { int value = 0; boolean success = false; char ch = ch(pos); while ('0' <= ch && ch <= '9') { value = (value * 10) + (ch - '0'); success = true; pos++; ch = ch(pos); } if (success) { if (value > maxValue) { return maxValue; } else if (value < 0) { return 0; } else { return value; } } else { throw new XMPException(errorMsg, XMPError.BADVALUE); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy