| | |
| | | |
| | | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Calendar; |
| | | import java.util.Date; |
| | | import java.util.GregorianCalendar; |
| | | import java.util.TimeZone; |
| | | import java.util.concurrent.locks.ReentrantLock; |
| | | |
| | |
| | | public class GeneralizedTimeSyntax |
| | | extends AttributeSyntax |
| | | { |
| | | |
| | | |
| | | |
| | | /** |
| | | * The lock that will be used to provide threadsafe access to the date |
| | | * formatter. |
| | |
| | | public boolean valueIsAcceptable(ByteString value, |
| | | StringBuilder invalidReason) |
| | | { |
| | | // Get the value as a string and verify that it is at least long enough for |
| | | // "YYYYMMDDhhZ", which is the shortest allowed value. |
| | | String valueString = value.stringValue().toUpperCase(); |
| | | int length = valueString.length(); |
| | | if (length < 11) |
| | | try |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT; |
| | | String message = getMessage(msgID, valueString); |
| | | invalidReason.append(message); |
| | | decodeGeneralizedTimeValue(value); |
| | | return true; |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | invalidReason.append(de.getErrorMessage()); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // The first four characters are the century and year, and they must be |
| | | // numeric digits between 0 and 9. |
| | | for (int i=0; i < 4; i++) |
| | | { |
| | | switch (valueString.charAt(i)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_YEAR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.charAt(i)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | |
| | | // The next two characters are the month, and they must form the string |
| | | // representation of an integer between 01 and 12. |
| | | char m1 = valueString.charAt(4); |
| | | char m2 = valueString.charAt(5); |
| | | switch (m1) |
| | | { |
| | | case '0': |
| | | // m2 must be a digit between 1 and 9. |
| | | switch (m2) |
| | | { |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(4, 6)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | break; |
| | | case '1': |
| | | // m2 must be a digit between 0 and 2. |
| | | switch (m2) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(4, 6)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(4, 6)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // The next two characters should be the day of the month, and they must |
| | | // form the string representation of an integer between 01 and 31. |
| | | // This doesn't do any validation against the year or month, so it will |
| | | // allow dates like April 31, or February 29 in a non-leap year, but we'll |
| | | // let those slide. |
| | | char d1 = valueString.charAt(6); |
| | | char d2 = valueString.charAt(7); |
| | | switch (d1) |
| | | { |
| | | case '0': |
| | | // d2 must be a digit between 1 and 9. |
| | | switch (d2) |
| | | { |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(6, 8)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | break; |
| | | case '1': |
| | | // Treated the same as '2'. |
| | | case '2': |
| | | // d2 must be a digit between 0 and 9. |
| | | switch (d2) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(6, 8)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | break; |
| | | case '3': |
| | | // d2 must be either 0 or 1. |
| | | switch (d2) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(6, 8)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(6, 8)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // The next two characters must be the hour, and they must form the string |
| | | // representation of an integer between 00 and 23. |
| | | char h1 = valueString.charAt(8); |
| | | char h2 = valueString.charAt(9); |
| | | switch (h1) |
| | | { |
| | | case '0': |
| | | // This is treated the same as '1'. |
| | | case '1': |
| | | switch (h2) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(8, 10)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | break; |
| | | case '2': |
| | | // This must be a digit between 0 and 3. |
| | | switch (h2) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(8, 10)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(8, 10)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // Next, there should be either two digits comprising an integer between 00 |
| | | // and 59 (for the minute), a letter 'Z' (for the UTC specifier), or a plus |
| | | // or minus sign followed by two or four digits (for the UTC offset). |
| | | m1 = valueString.charAt(10); |
| | | switch (m1) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | // There must be at least two more characters, and the next one must |
| | | // be a digit between 0 and 9. |
| | | if (length < 13) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, m1, 10); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | switch (valueString.charAt(11)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(10, 12)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | break; |
| | | case 'Z': |
| | | // This is fine only if we are at the end of the value. |
| | | if (length == 11) |
| | | { |
| | | return true; |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, m1, 10); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | case '+': |
| | | case '-': |
| | | // These are fine only if there are exactly two or four more digits that |
| | | // specify a valid offset. |
| | | if ((length == 13) || (length == 15)) |
| | | { |
| | | return hasValidOffset(valueString, 11, invalidReason); |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, m1, 10); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, m1, 10); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // Next, there should be either two digits comprising an integer between 00 |
| | | // and 60 (for the second, including a possible leap second), a letter 'Z' |
| | | // (for the UTC specifier), or a plus or minus sign followed by two or four |
| | | // digits (for the UTC offset). |
| | | char s1 = valueString.charAt(12); |
| | | switch (s1) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | // There must be at least two more characters, and the next one must |
| | | // be a digit between 0 and 9. |
| | | if (length < 15) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, s1, 12); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | switch (valueString.charAt(13)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SECOND; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(12, 14)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | break; |
| | | case '6': |
| | | // There must be at least two more characters and the next one must be |
| | | // a 0. |
| | | if (length < 15) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, s1, 12); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | if (valueString.charAt(13) != '0') |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SECOND; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(12, 14)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | break; |
| | | case 'Z': |
| | | // This is fine only if we are at the end of the value. |
| | | if (length == 13) |
| | | { |
| | | return true; |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, s1, 12); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | case '+': |
| | | case '-': |
| | | // These are fine only if there are exactly two or four more digits that |
| | | // specify a valid offset. |
| | | if ((length == 15) || (length == 17)) |
| | | { |
| | | return hasValidOffset(valueString, 13, invalidReason); |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, s1, 12); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, s1, 12); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // Next, there should be either a period or comma followed by between one |
| | | // and three digits (to specify the sub-second), a letter 'Z' (for the UTC |
| | | // specifier), or a plus or minus sign followed by two our four digits (for |
| | | // the UTC offset). |
| | | switch (valueString.charAt(14)) |
| | | { |
| | | case '.': |
| | | case ',': |
| | | // There will be a sub-second portion. Walk through the rest of the |
| | | // value until we find a Z, +, or -. |
| | | boolean endFound = false; |
| | | int pos = 15; |
| | | while (pos < length) |
| | | { |
| | | |
| | | switch (valueString.charAt(pos)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are fine as long as we don't have more than three. |
| | | if (pos > 17) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_LONG_SUBSECOND; |
| | | String message = getMessage(msgID, value); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | break; |
| | | case 'Z': |
| | | // This must be the end of the string and there must have been |
| | | // at least one sub-second digit. |
| | | if (pos == 15) |
| | | { |
| | | int msgID = |
| | | MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SUBSECOND; |
| | | String message = getMessage(msgID, value); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | if (pos != (length-1)) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.charAt(pos), pos); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | |
| | | case '+': |
| | | case '-': |
| | | // There must have been at least one sub-second digit, and there |
| | | // must be either two or four digits left. |
| | | if (pos == 15) |
| | | { |
| | | int msgID = |
| | | MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SUBSECOND; |
| | | String message = getMessage(msgID, value); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | else if ((length != 17) && (length != 19)) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.charAt(pos), pos); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | else |
| | | { |
| | | return hasValidOffset(valueString, pos+1, invalidReason); |
| | | } |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.charAt(pos), pos); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | pos++; |
| | | } |
| | | |
| | | |
| | | // There must be at least two more characters and the first must be a |
| | | // digit. |
| | | if (length < 16) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, s1, 12); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | break; |
| | | |
| | | case 'Z': |
| | | // This is fine only if we are at the end of the value. |
| | | if (length == 15) |
| | | { |
| | | return true; |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.charAt(14), 14); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | case '+': |
| | | case '-': |
| | | // These are fine only if there are exactly two or four more digits that |
| | | // specify a valid offset. |
| | | if ((length == 17) || (length == 19)) |
| | | { |
| | | return hasValidOffset(valueString, 15, invalidReason); |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.charAt(14), 14); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, valueString.charAt(14), |
| | | 14); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Indicates whether the provided string contains a valid set of two or four |
| | | * UTC offset digits. The provided string must have either two or four |
| | | * characters from the provided start position to the end of the value. |
| | | * |
| | | * @param value The whole value, including the offset. |
| | | * @param startPos The position of the first character that is |
| | | * contained in the offset. |
| | | * @param invalidReason The buffer to which the invalid reason may be |
| | | * appended if the string does not contain a valid set |
| | | * of UTC offset digits. |
| | | * |
| | | * @return <CODE>true</CODE> if the provided offset string is valid, or |
| | | * <CODE>false</CODE> if it is not. |
| | | */ |
| | | private boolean hasValidOffset(String value, int startPos, |
| | | StringBuilder invalidReason) |
| | | { |
| | | int offsetLength = value.length() - startPos; |
| | | if (offsetLength < 2) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT; |
| | | String message = getMessage(msgID, value); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | // The first two characters must be an integer between 00 and 23. |
| | | switch (value.charAt(startPos)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | switch (value.charAt(startPos+1)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = getMessage(msgID, value, |
| | | value.substring(startPos, |
| | | startPos+offsetLength)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | break; |
| | | case '2': |
| | | switch (value.charAt(startPos+1)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = getMessage(msgID, value, |
| | | value.substring(startPos, |
| | | startPos+offsetLength)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = getMessage(msgID, value, |
| | | value.substring(startPos, |
| | | startPos+offsetLength)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | |
| | | |
| | | // If there are two more characters, then they must be an integer between |
| | | // 00 and 59. |
| | | if (offsetLength == 4) |
| | | { |
| | | switch (value.charAt(startPos+2)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | switch (value.charAt(startPos+3)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are all fine. |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = |
| | | getMessage(msgID, value,value.substring(startPos, |
| | | startPos+offsetLength)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = getMessage(msgID, value, |
| | | value.substring(startPos, |
| | | startPos+offsetLength)); |
| | | invalidReason.append(message); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | |
| | |
| | | * Decodes the provided normalized value as a generalized time value and |
| | | * retrieves a timestamp containing its representation. |
| | | * |
| | | * @param normalizedValue The normalized generalized time value to decode to |
| | | * a Java <CODE>Date</CODE>. |
| | | * @param value The normalized value to decode using the generalized time |
| | | * syntax. |
| | | * |
| | | * @return The timestamp created from the provided generalized time value. |
| | | * |
| | | * @throws DirectoryException If the provided value cannot be parsed as a |
| | | * valid generalized time string. |
| | | */ |
| | | public static long decodeGeneralizedTimeValue(ByteString normalizedValue) |
| | | public static long decodeGeneralizedTimeValue(ByteString value) |
| | | throws DirectoryException |
| | | { |
| | | String valueString = normalizedValue.stringValue(); |
| | | int year = 0; |
| | | int month = 0; |
| | | int day = 0; |
| | | int hour = 0; |
| | | int minute = 0; |
| | | int second = 0; |
| | | |
| | | |
| | | // Get the value as a string and verify that it is at least long enough for |
| | | // "YYYYMMDDhhZ", which is the shortest allowed value. |
| | | String valueString = value.stringValue().toUpperCase(); |
| | | int length = valueString.length(); |
| | | if (length < 11) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT; |
| | | String message = getMessage(msgID, valueString); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, |
| | | msgID); |
| | | } |
| | | |
| | | |
| | | // The first four characters are the century and year, and they must be |
| | | // numeric digits between 0 and 9. |
| | | for (int i=0; i < 4; i++) |
| | | { |
| | | switch (valueString.charAt(i)) |
| | | { |
| | | case '0': |
| | | year = (year * 10); |
| | | break; |
| | | |
| | | case '1': |
| | | year = (year * 10) + 1; |
| | | break; |
| | | |
| | | case '2': |
| | | year = (year * 10) + 2; |
| | | break; |
| | | |
| | | case '3': |
| | | year = (year * 10) + 3; |
| | | break; |
| | | |
| | | case '4': |
| | | year = (year * 10) + 4; |
| | | break; |
| | | |
| | | case '5': |
| | | year = (year * 10) + 5; |
| | | break; |
| | | |
| | | case '6': |
| | | year = (year * 10) + 6; |
| | | break; |
| | | |
| | | case '7': |
| | | year = (year * 10) + 7; |
| | | break; |
| | | |
| | | case '8': |
| | | year = (year * 10) + 8; |
| | | break; |
| | | |
| | | case '9': |
| | | year = (year * 10) + 9; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_YEAR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.charAt(i)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | } |
| | | |
| | | |
| | | // The next two characters are the month, and they must form the string |
| | | // representation of an integer between 01 and 12. |
| | | char m1 = valueString.charAt(4); |
| | | char m2 = valueString.charAt(5); |
| | | switch (m1) |
| | | { |
| | | case '0': |
| | | // m2 must be a digit between 1 and 9. |
| | | switch (m2) |
| | | { |
| | | case '1': |
| | | month = Calendar.JANUARY; |
| | | break; |
| | | |
| | | case '2': |
| | | month = Calendar.FEBRUARY; |
| | | break; |
| | | |
| | | case '3': |
| | | month = Calendar.MARCH; |
| | | break; |
| | | |
| | | case '4': |
| | | month = Calendar.APRIL; |
| | | break; |
| | | |
| | | case '5': |
| | | month = Calendar.MAY; |
| | | break; |
| | | |
| | | case '6': |
| | | month = Calendar.JUNE; |
| | | break; |
| | | |
| | | case '7': |
| | | month = Calendar.JULY; |
| | | break; |
| | | |
| | | case '8': |
| | | month = Calendar.AUGUST; |
| | | break; |
| | | |
| | | case '9': |
| | | month = Calendar.SEPTEMBER; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(4, 6)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | case '1': |
| | | // m2 must be a digit between 0 and 2. |
| | | switch (m2) |
| | | { |
| | | case '0': |
| | | month = Calendar.OCTOBER; |
| | | break; |
| | | |
| | | case '1': |
| | | month = Calendar.NOVEMBER; |
| | | break; |
| | | |
| | | case '2': |
| | | month = Calendar.DECEMBER; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(4, 6)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(4, 6)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | |
| | | // The next two characters should be the day of the month, and they must |
| | | // form the string representation of an integer between 01 and 31. |
| | | // This doesn't do any validation against the year or month, so it will |
| | | // allow dates like April 31, or February 29 in a non-leap year, but we'll |
| | | // let those slide. |
| | | char d1 = valueString.charAt(6); |
| | | char d2 = valueString.charAt(7); |
| | | switch (d1) |
| | | { |
| | | case '0': |
| | | // d2 must be a digit between 1 and 9. |
| | | switch (d2) |
| | | { |
| | | case '1': |
| | | day = 1; |
| | | break; |
| | | |
| | | case '2': |
| | | day = 2; |
| | | break; |
| | | |
| | | case '3': |
| | | day = 3; |
| | | break; |
| | | |
| | | case '4': |
| | | day = 4; |
| | | break; |
| | | |
| | | case '5': |
| | | day = 5; |
| | | break; |
| | | |
| | | case '6': |
| | | day = 6; |
| | | break; |
| | | |
| | | case '7': |
| | | day = 7; |
| | | break; |
| | | |
| | | case '8': |
| | | day = 8; |
| | | break; |
| | | |
| | | case '9': |
| | | day = 9; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(6, 8)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | |
| | | case '1': |
| | | // d2 must be a digit between 0 and 9. |
| | | switch (d2) |
| | | { |
| | | case '0': |
| | | day = 10; |
| | | break; |
| | | |
| | | case '1': |
| | | day = 11; |
| | | break; |
| | | |
| | | case '2': |
| | | day = 12; |
| | | break; |
| | | |
| | | case '3': |
| | | day = 13; |
| | | break; |
| | | |
| | | case '4': |
| | | day = 14; |
| | | break; |
| | | |
| | | case '5': |
| | | day = 15; |
| | | break; |
| | | |
| | | case '6': |
| | | day = 16; |
| | | break; |
| | | |
| | | case '7': |
| | | day = 17; |
| | | break; |
| | | |
| | | case '8': |
| | | day = 18; |
| | | break; |
| | | |
| | | case '9': |
| | | day = 19; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(6, 8)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | |
| | | case '2': |
| | | // d2 must be a digit between 0 and 9. |
| | | switch (d2) |
| | | { |
| | | case '0': |
| | | day = 20; |
| | | break; |
| | | |
| | | case '1': |
| | | day = 21; |
| | | break; |
| | | |
| | | case '2': |
| | | day = 22; |
| | | break; |
| | | |
| | | case '3': |
| | | day = 23; |
| | | break; |
| | | |
| | | case '4': |
| | | day = 24; |
| | | break; |
| | | |
| | | case '5': |
| | | day = 25; |
| | | break; |
| | | |
| | | case '6': |
| | | day = 26; |
| | | break; |
| | | |
| | | case '7': |
| | | day = 27; |
| | | break; |
| | | |
| | | case '8': |
| | | day = 28; |
| | | break; |
| | | |
| | | case '9': |
| | | day = 29; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(6, 8)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | |
| | | case '3': |
| | | // d2 must be either 0 or 1. |
| | | switch (d2) |
| | | { |
| | | case '0': |
| | | day = 30; |
| | | break; |
| | | |
| | | case '1': |
| | | day = 31; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(6, 8)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(6, 8)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | |
| | | // The next two characters must be the hour, and they must form the string |
| | | // representation of an integer between 00 and 23. |
| | | char h1 = valueString.charAt(8); |
| | | char h2 = valueString.charAt(9); |
| | | switch (h1) |
| | | { |
| | | case '0': |
| | | switch (h2) |
| | | { |
| | | case '0': |
| | | hour = 0; |
| | | break; |
| | | |
| | | case '1': |
| | | hour = 1; |
| | | break; |
| | | |
| | | case '2': |
| | | hour = 2; |
| | | break; |
| | | |
| | | case '3': |
| | | hour = 3; |
| | | break; |
| | | |
| | | case '4': |
| | | hour = 4; |
| | | break; |
| | | |
| | | case '5': |
| | | hour = 5; |
| | | break; |
| | | |
| | | case '6': |
| | | hour = 6; |
| | | break; |
| | | |
| | | case '7': |
| | | hour = 7; |
| | | break; |
| | | |
| | | case '8': |
| | | hour = 8; |
| | | break; |
| | | |
| | | case '9': |
| | | hour = 9; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(8, 10)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | |
| | | case '1': |
| | | switch (h2) |
| | | { |
| | | case '0': |
| | | hour = 10; |
| | | break; |
| | | |
| | | case '1': |
| | | hour = 11; |
| | | break; |
| | | |
| | | case '2': |
| | | hour = 12; |
| | | break; |
| | | |
| | | case '3': |
| | | hour = 13; |
| | | break; |
| | | |
| | | case '4': |
| | | hour = 14; |
| | | break; |
| | | |
| | | case '5': |
| | | hour = 15; |
| | | break; |
| | | |
| | | case '6': |
| | | hour = 16; |
| | | break; |
| | | |
| | | case '7': |
| | | hour = 17; |
| | | break; |
| | | |
| | | case '8': |
| | | hour = 18; |
| | | break; |
| | | |
| | | case '9': |
| | | hour = 19; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(8, 10)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | |
| | | case '2': |
| | | switch (h2) |
| | | { |
| | | case '0': |
| | | hour = 20; |
| | | break; |
| | | |
| | | case '1': |
| | | hour = 21; |
| | | break; |
| | | |
| | | case '2': |
| | | hour = 22; |
| | | break; |
| | | |
| | | case '3': |
| | | hour = 23; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(8, 10)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(8, 10)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | |
| | | // Next, there should be either two digits comprising an integer between 00 |
| | | // and 59 (for the minute), a letter 'Z' (for the UTC specifier), a plus |
| | | // or minus sign followed by two or four digits (for the UTC offset), or a |
| | | // period or comma representing the fraction. |
| | | m1 = valueString.charAt(10); |
| | | switch (m1) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | // There must be at least two more characters, and the next one must |
| | | // be a digit between 0 and 9. |
| | | if (length < 13) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, m1, 10); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | |
| | | minute = 10 * (m1 - '0'); |
| | | |
| | | switch (valueString.charAt(11)) |
| | | { |
| | | case '0': |
| | | break; |
| | | |
| | | case '1': |
| | | minute += 1; |
| | | break; |
| | | |
| | | case '2': |
| | | minute += 2; |
| | | break; |
| | | |
| | | case '3': |
| | | minute += 3; |
| | | break; |
| | | |
| | | case '4': |
| | | minute += 4; |
| | | break; |
| | | |
| | | case '5': |
| | | minute += 5; |
| | | break; |
| | | |
| | | case '6': |
| | | minute += 6; |
| | | break; |
| | | |
| | | case '7': |
| | | minute += 7; |
| | | break; |
| | | |
| | | case '8': |
| | | minute += 8; |
| | | break; |
| | | |
| | | case '9': |
| | | minute += 9; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(10, 12)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | break; |
| | | |
| | | case 'Z': |
| | | // This is fine only if we are at the end of the value. |
| | | if (length == 11) |
| | | { |
| | | try |
| | | { |
| | | GregorianCalendar calendar = new GregorianCalendar(); |
| | | calendar.setLenient(false); |
| | | calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC)); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return calendar.getTimeInMillis(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | // This should only happen if the provided date wasn't legal |
| | | // (e.g., September 31). |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME; |
| | | String message = getMessage(msgID, valueString, String.valueOf(e)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID, e); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, m1, 10); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | case '+': |
| | | case '-': |
| | | // These are fine only if there are exactly two or four more digits that |
| | | // specify a valid offset. |
| | | if ((length == 13) || (length == 15)) |
| | | { |
| | | try |
| | | { |
| | | GregorianCalendar calendar = new GregorianCalendar(); |
| | | calendar.setLenient(false); |
| | | calendar.setTimeZone(getTimeZoneForOffset(valueString, 10)); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return calendar.getTimeInMillis(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | // This should only happen if the provided date wasn't legal |
| | | // (e.g., September 31). |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME; |
| | | String message = getMessage(msgID, valueString, String.valueOf(e)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID, e); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, m1, 10); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | case '.': |
| | | case ',': |
| | | return finishDecodingFraction(valueString, 11, year, month, day, hour, |
| | | minute, second, 3600000); |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, m1, 10); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | |
| | | // Next, there should be either two digits comprising an integer between 00 |
| | | // and 60 (for the second, including a possible leap second), a letter 'Z' |
| | | // (for the UTC specifier), a plus or minus sign followed by two or four |
| | | // digits (for the UTC offset), or a period or comma to start the fraction. |
| | | char s1 = valueString.charAt(12); |
| | | switch (s1) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | // There must be at least two more characters, and the next one must |
| | | // be a digit between 0 and 9. |
| | | if (length < 15) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, s1, 12); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | |
| | | second = 10 * (s1 - '0'); |
| | | |
| | | switch (valueString.charAt(13)) |
| | | { |
| | | case '0': |
| | | break; |
| | | |
| | | case '1': |
| | | second += 1; |
| | | break; |
| | | |
| | | case '2': |
| | | second += 2; |
| | | break; |
| | | |
| | | case '3': |
| | | second += 3; |
| | | break; |
| | | |
| | | case '4': |
| | | second += 4; |
| | | break; |
| | | |
| | | case '5': |
| | | second += 5; |
| | | break; |
| | | |
| | | case '6': |
| | | second += 6; |
| | | break; |
| | | |
| | | case '7': |
| | | second += 7; |
| | | break; |
| | | |
| | | case '8': |
| | | second += 8; |
| | | break; |
| | | |
| | | case '9': |
| | | second += 9; |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(12, 14)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | break; |
| | | |
| | | case '6': |
| | | // There must be at least two more characters and the next one must be |
| | | // a 0. |
| | | if (length < 15) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, s1, 12); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | if (valueString.charAt(13) != '0') |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SECOND; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.substring(12, 14)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | second = 60; |
| | | break; |
| | | |
| | | case 'Z': |
| | | // This is fine only if we are at the end of the value. |
| | | if (length == 13) |
| | | { |
| | | try |
| | | { |
| | | GregorianCalendar calendar = new GregorianCalendar(); |
| | | calendar.setLenient(false); |
| | | calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC)); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return calendar.getTimeInMillis(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | // This should only happen if the provided date wasn't legal |
| | | // (e.g., September 31). |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME; |
| | | String message = getMessage(msgID, valueString, String.valueOf(e)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID, e); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, s1, 12); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | case '+': |
| | | case '-': |
| | | // These are fine only if there are exactly two or four more digits that |
| | | // specify a valid offset. |
| | | if ((length == 15) || (length == 17)) |
| | | { |
| | | try |
| | | { |
| | | GregorianCalendar calendar = new GregorianCalendar(); |
| | | calendar.setLenient(false); |
| | | calendar.setTimeZone(getTimeZoneForOffset(valueString, 12)); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return calendar.getTimeInMillis(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | // This should only happen if the provided date wasn't legal |
| | | // (e.g., September 31). |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME; |
| | | String message = getMessage(msgID, valueString, String.valueOf(e)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID, e); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, s1, 12); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | case '.': |
| | | case ',': |
| | | return finishDecodingFraction(valueString, 13, year, month, day, hour, |
| | | minute, second, 60000); |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, s1, 12); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | |
| | | // Next, there should be either a period or comma followed by between one |
| | | // and three digits (to specify the sub-second), a letter 'Z' (for the UTC |
| | | // specifier), or a plus or minus sign followed by two our four digits (for |
| | | // the UTC offset). |
| | | switch (valueString.charAt(14)) |
| | | { |
| | | case '.': |
| | | case ',': |
| | | return finishDecodingFraction(valueString, 15, year, month, day, hour, |
| | | minute, second, 1000); |
| | | |
| | | case 'Z': |
| | | // This is fine only if we are at the end of the value. |
| | | if (length == 15) |
| | | { |
| | | try |
| | | { |
| | | GregorianCalendar calendar = new GregorianCalendar(); |
| | | calendar.setLenient(false); |
| | | calendar.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC)); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return calendar.getTimeInMillis(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | // This should only happen if the provided date wasn't legal |
| | | // (e.g., September 31). |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME; |
| | | String message = getMessage(msgID, valueString, String.valueOf(e)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID, e); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.charAt(14), 14); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | case '+': |
| | | case '-': |
| | | // These are fine only if there are exactly two or four more digits that |
| | | // specify a valid offset. |
| | | if ((length == 17) || (length == 19)) |
| | | { |
| | | try |
| | | { |
| | | GregorianCalendar calendar = new GregorianCalendar(); |
| | | calendar.setLenient(false); |
| | | calendar.setTimeZone(getTimeZoneForOffset(valueString, 14)); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return calendar.getTimeInMillis(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | if (debugEnabled()) |
| | | { |
| | | debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | // This should only happen if the provided date wasn't legal |
| | | // (e.g., September 31). |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME; |
| | | String message = getMessage(msgID, valueString, String.valueOf(e)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID, e); |
| | | } |
| | | } |
| | | else |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, |
| | | valueString.charAt(14), 14); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR; |
| | | String message = getMessage(msgID, valueString, valueString.charAt(14), |
| | | 14); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Completes decoding the generalized time value containing a fractional |
| | | * component. It will also decode the trailing 'Z' or offset. |
| | | * |
| | | * @param value The whole value, including the fractional component and |
| | | * time zone information. |
| | | * @param startPos The position of the first character after the period |
| | | * in the value string. |
| | | * @param year The year decoded from the provided value. |
| | | * @param month The month decoded from the provided value. |
| | | * @param day The day decoded from the provided value. |
| | | * @param hour The hour decoded from the provided value. |
| | | * @param minute The minute decoded from the provided value. |
| | | * @param second The second decoded from the provided value. |
| | | * @param multiplier The multiplier value that should be used to scale the |
| | | * fraction appropriately. If it's a fraction of an hour, |
| | | * then it should be 3600000 (60*60*1000). If it's a |
| | | * fraction of a minute, then it should be 60000. If it's |
| | | * a fraction of a second, then it should be 1000. |
| | | * |
| | | * @return The timestamp created from the provided generalized time value |
| | | * including the fractional element. |
| | | * |
| | | * @throws DirectoryException If the provided value cannot be parsed as a |
| | | * valid generalized time string. |
| | | */ |
| | | private static long finishDecodingFraction(String value, int startPos, |
| | | int year, int month, int day, |
| | | int hour, int minute, int second, |
| | | int multiplier) |
| | | throws DirectoryException |
| | | { |
| | | int length = value.length(); |
| | | StringBuilder fractionBuffer = new StringBuilder(2 + length - startPos); |
| | | fractionBuffer.append("0."); |
| | | |
| | | TimeZone timeZone = null; |
| | | |
| | | outerLoop: |
| | | for (int i=startPos; i < length; i++) |
| | | { |
| | | char c = value.charAt(i); |
| | | switch (c) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | fractionBuffer.append(c); |
| | | break; |
| | | |
| | | case 'Z': |
| | | // This is only acceptable if we're at the end of the value. |
| | | if (i != (value.length() - 1)) |
| | | { |
| | | int msgID = |
| | | MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR; |
| | | String message = getMessage(msgID, value, String.valueOf(c)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | timeZone = TimeZone.getTimeZone(TIME_ZONE_UTC); |
| | | break outerLoop; |
| | | |
| | | case '+': |
| | | case '-': |
| | | timeZone = getTimeZoneForOffset(value, i); |
| | | break outerLoop; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR; |
| | | String message = getMessage(msgID, value, String.valueOf(c)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | } |
| | | |
| | | if (fractionBuffer.length() == 2) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_EMPTY_FRACTION; |
| | | String message = getMessage(msgID, value); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, |
| | | msgID); |
| | | } |
| | | |
| | | if (timeZone == null) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_NO_TIME_ZONE_INFO; |
| | | String message = getMessage(msgID, value); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, |
| | | msgID); |
| | | } |
| | | |
| | | Double fractionValue = Double.parseDouble(fractionBuffer.toString()); |
| | | long additionalMilliseconds = Math.round(fractionValue * multiplier); |
| | | |
| | | try |
| | | { |
| | | dateFormatLock.lock(); |
| | | |
| | | try |
| | | { |
| | | return dateFormat.parse(valueString).getTime(); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | // We'll let this one be handled by the outer try/catch block. |
| | | throw e; |
| | | } |
| | | finally |
| | | { |
| | | dateFormatLock.unlock(); |
| | | } |
| | | GregorianCalendar calendar = new GregorianCalendar(); |
| | | calendar.setLenient(false); |
| | | calendar.setTimeZone(timeZone); |
| | | calendar.set(year, month, day, hour, minute, second); |
| | | calendar.set(Calendar.MILLISECOND, 0); |
| | | return calendar.getTimeInMillis() + additionalMilliseconds; |
| | | } |
| | | catch (Exception e) |
| | | { |
| | |
| | | debugCaught(DebugLogLevel.ERROR, e); |
| | | } |
| | | |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_CANNOT_PARSE; |
| | | String message = getMessage(msgID, valueString, String.valueOf(e)); |
| | | |
| | | // This should only happen if the provided date wasn't legal |
| | | // (e.g., September 31). |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME; |
| | | String message = getMessage(msgID, value, String.valueOf(e)); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID, e); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Decodes a time zone offset from the provided value. |
| | | * |
| | | * @param value The whole value, including the offset. |
| | | * @param startPos The position of the first character that is |
| | | * contained in the offset. This should be the |
| | | * position of the plus or minus character. |
| | | * |
| | | * @return The {@code TimeZone} object representing the decoded time zone. |
| | | * |
| | | * @throws DirectoryException If the provided value does not contain a valid |
| | | * offset. |
| | | */ |
| | | private static TimeZone getTimeZoneForOffset(String value, int startPos) |
| | | throws DirectoryException |
| | | { |
| | | String offSetStr = value.substring(startPos); |
| | | if ((offSetStr.length() != 3) && (offSetStr.length() != 5)) |
| | | { |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = getMessage(msgID, value, offSetStr); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message, |
| | | msgID); |
| | | } |
| | | |
| | | |
| | | // The first character must be either a plus or minus. |
| | | switch (offSetStr.charAt(0)) |
| | | { |
| | | case '+': |
| | | case '-': |
| | | // These are OK. |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = getMessage(msgID, value, offSetStr); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | |
| | | // The first two characters must be an integer between 00 and 23. |
| | | switch (offSetStr.charAt(1)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | switch (offSetStr.charAt(2)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are all fine. |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = getMessage(msgID, value, offSetStr); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | |
| | | case '2': |
| | | switch (offSetStr.charAt(2)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | // These are all fine. |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = getMessage(msgID, value, offSetStr); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = getMessage(msgID, value, offSetStr); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | |
| | | |
| | | // If there are two more characters, then they must be an integer between |
| | | // 00 and 59. |
| | | if (offSetStr.length() == 5) |
| | | { |
| | | switch (offSetStr.charAt(3)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | switch (offSetStr.charAt(4)) |
| | | { |
| | | case '0': |
| | | case '1': |
| | | case '2': |
| | | case '3': |
| | | case '4': |
| | | case '5': |
| | | case '6': |
| | | case '7': |
| | | case '8': |
| | | case '9': |
| | | // These are all fine. |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = getMessage(msgID, value, offSetStr); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | break; |
| | | |
| | | default: |
| | | int msgID = MSGID_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET; |
| | | String message = getMessage(msgID, value, offSetStr); |
| | | throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, |
| | | message, msgID); |
| | | } |
| | | } |
| | | |
| | | |
| | | // If we've gotten here, then it looks like a valid offset. We can create a |
| | | // time zone by using "GMT" followed by the offset. |
| | | return TimeZone.getTimeZone("GMT" + offSetStr); |
| | | } |
| | | } |
| | | |