📄 iso8601converter.java
字号:
// =================================================================================================// ADOBE SYSTEMS INCORPORATED// Copyright 2006-2007 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.xmp.impl;import java.text.DecimalFormat;import java.text.DecimalFormatSymbols;import java.util.Locale;import java.util.SimpleTimeZone;import com.adobe.xmp.XMPDateTime;import com.adobe.xmp.XMPError;import com.adobe.xmp.XMPException;/** * Converts between ISO 8601 Strings and <code>Calendar</code> with millisecond resolution. * * @since 16.02.2006 */public final class ISO8601Converter{ /** Hides public constructor */ private ISO8601Converter() { // EMPTY } /** * Converts an ISO 8601 string to an <code>XMPDateTime</code>. * * Parse a date according to ISO 8601 and * http://www.w3.org/TR/NOTE-datetime: * <ul> * <li>YYYY * <li>YYYY-MM * <li>YYYY-MM-DD * <li>YYYY-MM-DDThh:mmTZD * <li>YYYY-MM-DDThh:mm:ssTZD * <li>YYYY-MM-DDThh:mm:ss.sTZD * </ul> * * Data fields: * <ul> * <li>YYYY = four-digit year * <li>MM = two-digit month (01=January, etc.) * <li>DD = two-digit day of month (01 through 31) * <li>hh = two digits of hour (00 through 23) * <li>mm = two digits of minute (00 through 59) * <li>ss = two digits of second (00 through 59) * <li>s = one or more digits representing a decimal fraction of a second * <li>TZD = time zone designator (Z or +hh:mm or -hh:mm) * </ul> * * 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". * <p> * <em>Note:</em> Tolerate missing TZD, assume is UTC. Photoshop 8 writes * dates like this for exif:GPSTimeStamp.<br> * <em>Note:</em> 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 <code>Calendar</code>. * @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 { ParameterAsserts.assertNotNull(iso8601String); ParseState input = new ParseState(iso8601String); int value; boolean timeOnly = input.ch(0) == 'T' || (input.length() >= 2 && input.ch(1) == ':' || (input.length() >= 3 && input.ch(2) == ':')); if (!timeOnly) { 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; } } else { // set default day and month in the year 0000 binValue.setMonth(1); binValue.setDay(1); } if (input.ch() == 'T') { input.skip(); } else if (!timeOnly) { throw new XMPException("Invalid date string, missing 'T' after date", XMPError.BADVALUE); } // Extract the hour. value = input.gatherInt("Invalid hour in date string", 23); if (input.ch() != ':') { throw new XMPException("Invalid date string, after hour", XMPError.BADVALUE); } binValue.setHour(value); // Don't check for done, we have to work up to the time zone. input.skip(); // Extract the minute. 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.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.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); } } int tzSign = 0; int tzHour = 0; int tzMinute = 0; 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.ch() != ':') { throw new XMPException("Invalid date string, after time zone hour", XMPError.BADVALUE); } input.skip(); // Extract the time zone minute. tzMinute = input.gatherInt("Invalid time zone minute in date string", 59); } // create a corresponding TZ and set it time zone 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 <code>Calendar</code> into an ISO 8601 string. * Format a date according to ISO 8601 and http://www.w3.org/TR/NOTE-datetime: * <ul> * <li>YYYY * <li>YYYY-MM * <li>YYYY-MM-DD * <li>YYYY-MM-DDThh:mmTZD * <li>YYYY-MM-DDThh:mm:ssTZD * <li>YYYY-MM-DDThh:mm:ss.sTZD * </ul> * * Data fields: * <ul> * <li>YYYY = four-digit year * <li>MM = two-digit month (01=January, etc.) * <li>DD = two-digit day of month (01 through 31) * <li>hh = two digits of hour (00 through 23) * <li>mm = two digits of minute (00 through 59) * <li>ss = two digits of second (00 through 59) * <li>s = one or more digits representing a decimal fraction of a second * <li>TZD = time zone designator (Z or +hh:mm or -hh:mm) * </ul> * <p> * <em>Note:</em> 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".<p> * <em>Note:</em> 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(); // 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.getHour() != 0 || dateTime.getMinute() != 0 || dateTime.getSecond() != 0 || dateTime.getNanoSecond() != 0 || (dateTime.getTimeZone() != null && dateTime.getTimeZone().getRawOffset() != 0)) { // 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.getTimeZone() != null) { if (dateTime.getTimeZone().getRawOffset() == 0) { // UTC buffer.append('Z'); } else { int offset = dateTime.getTimeZone().getRawOffset(); 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(); } }/** * @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); } }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -