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

com.unboundid.asn1.ASN1GeneralizedTime Maven / Gradle / Ivy

/*
 * Copyright 2017-2019 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright (C) 2017-2019 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.asn1;



import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.StaticUtils;

import static com.unboundid.asn1.ASN1Messages.*;



/**
 * This class provides an ASN.1 generalized time element, which represents a
 * timestamp in the generalized time format.  The value is encoded as a string,
 * although the ASN.1 specification imposes a number of restrictions on that
 * string representation, including:
 * 
    *
  • * The generic generalized time specification allows you to specify the time * zone either by ending the value with "Z" to indicate that the value is in * the UTC time zone, or by ending it with a positive or negative offset * (expressed in hours and minutes) from UTC time. The ASN.1 specification * only allows the "Z" option. *
  • *
  • * The generic generalized time specification only requires generalized time * values to include the year, month, day, and hour components of the * timestamp, while the minute, second, and sub-second components are * optional. The ASN.1 specification requires that generalized time values * always include the minute and second components. Sub-second components * are permitted, but with the restriction noted below. *
  • *
  • * The ASN.1 specification for generalized time values does not allow the * sub-second component to include any trailing zeroes. If the sub-second * component is all zeroes, then it will be omitted, along with the decimal * point that would have separated the second and sub-second components. *
  • *
* Note that this implementation only supports up to millisecond-level * precision. It will never generate a value with a sub-second component that * contains more than three digits, and any value decoded from a string * representation that contains a sub-second component with more than three * digits will return a timestamp rounded to the nearest millisecond from the * {@link #getDate()} and {@link #getTime()} methods, although the original * string representation will be retained and will be used in the encoded * representation. */ @NotMutable() @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class ASN1GeneralizedTime extends ASN1Element { /** * The thread-local date formatters used to encode generalized time values * that do not include milliseconds. */ private static final ThreadLocal DATE_FORMATTERS_WITHOUT_MILLIS = new ThreadLocal<>(); /** * The serial version UID for this serializable class. */ private static final long serialVersionUID = -7215431927354583052L; // The timestamp represented by this generalized time value. private final long time; // The string representation of the generalized time value. private final String stringRepresentation; /** * Creates a new generalized time element with the default BER type that * represents the current time. */ public ASN1GeneralizedTime() { this(ASN1Constants.UNIVERSAL_GENERALIZED_TIME_TYPE); } /** * Creates a new generalized time element with the specified BER type that * represents the current time. * * @param type The BER type to use for this element. */ public ASN1GeneralizedTime(final byte type) { this(type, System.currentTimeMillis()); } /** * Creates a new generalized time element with the default BER type that * represents the indicated time. * * @param date The date value that specifies the time to represent. This * must not be {@code null}. */ public ASN1GeneralizedTime(final Date date) { this(ASN1Constants.UNIVERSAL_GENERALIZED_TIME_TYPE, date); } /** * Creates a new generalized time element with the specified BER type that * represents the indicated time. * * @param type The BER type to use for this element. * @param date The date value that specifies the time to represent. This * must not be {@code null}. */ public ASN1GeneralizedTime(final byte type, final Date date) { this(type, date.getTime()); } /** * Creates a new generalized time element with the default BER type that * represents the indicated time. * * @param time The time to represent. This must be expressed in * milliseconds since the epoch (the same format used by * {@code System.currentTimeMillis()} and * {@code Date.getTime()}). */ public ASN1GeneralizedTime(final long time) { this(ASN1Constants.UNIVERSAL_GENERALIZED_TIME_TYPE, time); } /** * Creates a new generalized time element with the specified BER type that * represents the indicated time. * * @param type The BER type to use for this element. * @param time The time to represent. This must be expressed in * milliseconds since the epoch (the same format used by * {@code System.currentTimeMillis()} and * {@code Date.getTime()}). */ public ASN1GeneralizedTime(final byte type, final long time) { this(type, time, encodeTimestamp(time, true)); } /** * Creates a new generalized time element with the default BER type and a * time decoded from the provided string representation. * * @param timestamp The string representation of the timestamp to represent. * This must not be {@code null}. * * @throws ASN1Exception If the provided timestamp does not represent a * valid ASN.1 generalized time string representation. */ public ASN1GeneralizedTime(final String timestamp) throws ASN1Exception { this(ASN1Constants.UNIVERSAL_GENERALIZED_TIME_TYPE, timestamp); } /** * Creates a new generalized time element with the specified BER type and a * time decoded from the provided string representation. * * @param type The BER type to use for this element. * @param timestamp The string representation of the timestamp to represent. * This must not be {@code null}. * * @throws ASN1Exception If the provided timestamp does not represent a * valid ASN.1 generalized time string representation. */ public ASN1GeneralizedTime(final byte type, final String timestamp) throws ASN1Exception { this(type, decodeTimestamp(timestamp), timestamp); } /** * Creates a new generalized time element with the provided information. * * @param type The BER type to use for this element. * @param time The time to represent. This must be * expressed in milliseconds since the epoch * (the same format used by * {@code System.currentTimeMillis()} and * {@code Date.getTime()}). * @param stringRepresentation The string representation of the timestamp to * represent. This must not be {@code null}. */ private ASN1GeneralizedTime(final byte type, final long time, final String stringRepresentation) { super(type, StaticUtils.getBytes(stringRepresentation)); this.time = time; this.stringRepresentation = stringRepresentation; } /** * Encodes the time represented by the provided date into the appropriate * ASN.1 generalized time format. * * @param date The date value that specifies the time to * represent. This must not be {@code null}. * @param includeMilliseconds Indicate whether the timestamp should include * a sub-second component representing a * precision of up to milliseconds. Note that * even if this is {@code true}, the sub-second * component will only be included if it is not * all zeroes. If this is {@code false}, then * the resulting timestamp will only use a * precision indicated in seconds, and the * sub-second portion will be truncated rather * than rounded to the nearest second (which is * the behavior that {@code SimpleDateFormat} * exhibits for formatting timestamps without a * sub-second component). * * @return The encoded timestamp. */ public static String encodeTimestamp(final Date date, final boolean includeMilliseconds) { if (includeMilliseconds) { final String timestamp = StaticUtils.encodeGeneralizedTime(date); if (! timestamp.endsWith("0Z")) { return timestamp; } final StringBuilder buffer = new StringBuilder(timestamp); while (true) { final char c = buffer.charAt(buffer.length() - 2); if ((c == '0') || (c == '.')) { buffer.deleteCharAt(buffer.length() - 2); } if (c != '0') { break; } } return buffer.toString(); } else { SimpleDateFormat dateFormat = DATE_FORMATTERS_WITHOUT_MILLIS.get(); if (dateFormat == null) { dateFormat = new SimpleDateFormat("yyyyMMddHHmmss'Z'"); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); DATE_FORMATTERS_WITHOUT_MILLIS.set(dateFormat); } return dateFormat.format(date); } } /** * Encodes the specified time into the appropriate ASN.1 generalized time * format. * * @param time The time to represent. This must be expressed * in milliseconds since the epoch (the same * format used by * {@code System.currentTimeMillis()} and * {@code Date.getTime()}). * @param includeMilliseconds Indicate whether the timestamp should include * a sub-second component representing a * precision of up to milliseconds. Note that * even if this is {@code true}, the sub-second * component will only be included if it is not * all zeroes. * * @return The encoded timestamp. */ public static String encodeTimestamp(final long time, final boolean includeMilliseconds) { return encodeTimestamp(new Date(time), includeMilliseconds); } /** * Decodes the provided string as a timestamp in the generalized time format. * * @param timestamp The string representation of a generalized time to be * parsed as a timestamp. It must not be {@code null}. * * @return The decoded time, expressed in milliseconds since the epoch (the * same format used by {@code System.currentTimeMillis()} and * {@code Date.getTime()}). * * @throws ASN1Exception If the provided timestamp cannot be parsed as a * valid string representation of an ASN.1 generalized * time value. */ public static long decodeTimestamp(final String timestamp) throws ASN1Exception { if (timestamp.length() < 15) { throw new ASN1Exception(ERR_GENERALIZED_TIME_STRING_TOO_SHORT.get()); } if (! (timestamp.endsWith("Z") || timestamp.endsWith("z"))) { throw new ASN1Exception( ERR_GENERALIZED_TIME_STRING_DOES_NOT_END_WITH_Z.get()); } boolean hasSubSecond = false; for (int i=0; i < (timestamp.length() - 1); i++) { final char c = timestamp.charAt(i); if (i == 14) { if (c != '.') { throw new ASN1Exception( ERR_GENERALIZED_TIME_STRING_CHAR_NOT_PERIOD.get(i + 1)); } else { hasSubSecond = true; } } else { if ((c < '0') || (c > '9')) { throw new ASN1Exception( ERR_GENERALIZED_TIME_STRING_CHAR_NOT_DIGIT.get(i + 1)); } } } final GregorianCalendar calendar = new GregorianCalendar(StaticUtils.getUTCTimeZone()); final int year = Integer.parseInt(timestamp.substring(0, 4)); calendar.set(Calendar.YEAR, year); final int month = Integer.parseInt(timestamp.substring(4, 6)); if ((month < 1) || (month > 12)) { throw new ASN1Exception(ERR_GENERALIZED_TIME_STRING_INVALID_MONTH.get()); } else { calendar.set(Calendar.MONTH, (month - 1)); } final int day = Integer.parseInt(timestamp.substring(6, 8)); if ((day < 1) || (day > 31)) { throw new ASN1Exception(ERR_GENERALIZED_TIME_STRING_INVALID_DAY.get()); } else { calendar.set(Calendar.DAY_OF_MONTH, day); } final int hour = Integer.parseInt(timestamp.substring(8, 10)); if (hour > 23) { throw new ASN1Exception(ERR_GENERALIZED_TIME_STRING_INVALID_HOUR.get()); } else { calendar.set(Calendar.HOUR_OF_DAY, hour); } final int minute = Integer.parseInt(timestamp.substring(10, 12)); if (minute > 59) { throw new ASN1Exception(ERR_GENERALIZED_TIME_STRING_INVALID_MINUTE.get()); } else { calendar.set(Calendar.MINUTE, minute); } final int second = Integer.parseInt(timestamp.substring(12, 14)); if (second > 60) { // In the case of a leap second, there can be 61 seconds in a minute. throw new ASN1Exception(ERR_GENERALIZED_TIME_STRING_INVALID_SECOND.get()); } else { calendar.set(Calendar.SECOND, second); } if (hasSubSecond) { final StringBuilder subSecondString = new StringBuilder(timestamp.substring(15, timestamp.length() - 1)); while (subSecondString.length() < 3) { subSecondString.append('0'); } final boolean addOne; if (subSecondString.length() > 3) { final char charFour = subSecondString.charAt(3); addOne = ((charFour >= '5') && (charFour <= '9')); subSecondString.setLength(3); } else { addOne = false; } while (subSecondString.charAt(0) == '0') { subSecondString.deleteCharAt(0); } final int millisecond = Integer.parseInt(subSecondString.toString()); if (addOne) { calendar.set(Calendar.MILLISECOND, (millisecond + 1)); } else { calendar.set(Calendar.MILLISECOND, millisecond); } } else { calendar.set(Calendar.MILLISECOND, 0); } return calendar.getTimeInMillis(); } /** * Retrieves the time represented by this generalized time element, expressed * as the number of milliseconds since the epoch (the same format used by * {@code System.currentTimeMillis()} and {@code Date.getTime()}). * @return The time represented by this generalized time element. */ public long getTime() { return time; } /** * Retrieves a {@code Date} object that is set to the time represented by this * generalized time element. * * @return A {@code Date} object that is set ot the time represented by this * generalized time element. */ public Date getDate() { return new Date(time); } /** * Retrieves the string representation of the generalized time value contained * in this element. * * @return The string representation of the generalized time value contained * in this element. */ public String getStringRepresentation() { return stringRepresentation; } /** * Decodes the contents of the provided byte array as a generalized time * element. * * @param elementBytes The byte array to decode as an ASN.1 generalized time * element. * * @return The decoded ASN.1 generalized time element. * * @throws ASN1Exception If the provided array cannot be decoded as a * generalized time element. */ public static ASN1GeneralizedTime decodeAsGeneralizedTime( final byte[] elementBytes) throws ASN1Exception { try { int valueStartPos = 2; int length = (elementBytes[1] & 0x7F); if (length != elementBytes[1]) { final int numLengthBytes = length; length = 0; for (int i=0; i < numLengthBytes; i++) { length <<= 8; length |= (elementBytes[valueStartPos++] & 0xFF); } } if ((elementBytes.length - valueStartPos) != length) { throw new ASN1Exception(ERR_ELEMENT_LENGTH_MISMATCH.get(length, (elementBytes.length - valueStartPos))); } final byte[] elementValue = new byte[length]; System.arraycopy(elementBytes, valueStartPos, elementValue, 0, length); return new ASN1GeneralizedTime(elementBytes[0], StaticUtils.toUTF8String(elementValue)); } catch (final ASN1Exception ae) { Debug.debugException(ae); throw ae; } catch (final Exception e) { Debug.debugException(e); throw new ASN1Exception(ERR_ELEMENT_DECODE_EXCEPTION.get(e), e); } } /** * Decodes the provided ASN.1 element as a generalized time element. * * @param element The ASN.1 element to be decoded. * * @return The decoded ASN.1 generalized time element. * * @throws ASN1Exception If the provided element cannot be decoded as a * generalized time element. */ public static ASN1GeneralizedTime decodeAsGeneralizedTime( final ASN1Element element) throws ASN1Exception { return new ASN1GeneralizedTime(element.getType(), StaticUtils.toUTF8String(element.getValue())); } /** * {@inheritDoc} */ @Override() public void toString(final StringBuilder buffer) { buffer.append(stringRepresentation); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy