    001package org.apache.commons.ssl.asn1;
    002
    003import java.io.IOException;
    004import java.text.ParseException;
    005import java.text.SimpleDateFormat;
    006import java.util.Date;
    007import java.util.SimpleTimeZone;
    008
    009/** UTC time object. */
    010public class DERUTCTime
    011    extends ASN1Object {
    012    String time;
    013
    014    /**
    015     * return an UTC Time from the passed in object.
    016     *
    017     * @throws IllegalArgumentException if the object cannot be converted.
    018     */
    019    public static DERUTCTime getInstance(
    020        Object obj) {
    021        if (obj == null || obj instanceof DERUTCTime) {
    022            return (DERUTCTime) obj;
    023        }
    024
    025        if (obj instanceof ASN1OctetString) {
    026            return new DERUTCTime(((ASN1OctetString) obj).getOctets());
    027        }
    028
    029        throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
    030    }
    031
    032    /**
    033     * return an UTC Time from a tagged object.
    034     *
    035     * @param obj      the tagged object holding the object we want
    036     * @param explicit true if the object is meant to be explicitly
    037     *                 tagged false otherwise.
    038     * @throws IllegalArgumentException if the tagged object cannot
    039     *                                  be converted.
    040     */
    041    public static DERUTCTime getInstance(
    042        ASN1TaggedObject obj,
    043        boolean explicit) {
    044        return getInstance(obj.getObject());
    045    }
    046
    047    /**
    048     * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were
    049     * never encoded. When you're creating one of these objects from scratch, that's
    050     * what you want to use, otherwise we'll try to deal with whatever gets read from
    051     * the input stream... (this is why the input format is different from the getTime()
    052     * method output).
    053     * <p/>
    054     *
    055     * @param time the time string.
    056     */
    057    public DERUTCTime(
    058        String time) {
    059        this.time = time;
    060        try {
    061            this.getDate();
    062        }
    063        catch (ParseException e) {
    064            throw new IllegalArgumentException("invalid date string: " + e.getMessage());
    065        }
    066    }
    067
    068    /** base constructer from a java.util.date object */
    069    public DERUTCTime(
    070        Date time) {
    071        SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmss'Z'");
    072
    073        dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
    074
    075        this.time = dateF.format(time);
    076    }
    077
    078    DERUTCTime(
    079        byte[] bytes) {
    080        //
    081        // explicitly convert to characters
    082        //
    083        char[] dateC = new char[bytes.length];
    084
    085        for (int i = 0; i != dateC.length; i++) {
    086            dateC[i] = (char) (bytes[i] & 0xff);
    087        }
    088
    089        this.time = new String(dateC);
    090    }
    091
    092    /**
    093     * return the time as a date based on whatever a 2 digit year will return. For
    094     * standardised processing use getAdjustedDate().
    095     *
    096     * @return the resulting date
    097     * @throws ParseException if the date string cannot be parsed.
    098     */
    099    public Date getDate()
    100        throws ParseException {
    101        SimpleDateFormat dateF = new SimpleDateFormat("yyMMddHHmmssz");
    102
    103        return dateF.parse(getTime());
    104    }
    105
    106    /**
    107     * return the time as an adjusted date
    108     * in the range of 1950 - 2049.
    109     *
    110     * @return a date in the range of 1950 to 2049.
    111     * @throws ParseException if the date string cannot be parsed.
    112     */
    113    public Date getAdjustedDate()
    114        throws ParseException {
    115        SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
    116
    117        dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
    118
    119        return dateF.parse(getAdjustedTime());
    120    }
    121
    122    /**
    123     * return the time - always in the form of
    124     * YYMMDDhhmmssGMT(+hh:mm|-hh:mm).
    125     * <p/>
    126     * Normally in a certificate we would expect "Z" rather than "GMT",
    127     * however adding the "GMT" means we can just use:
    128     * <pre>
    129     *     dateF = new SimpleDateFormat("yyMMddHHmmssz");
    130     * </pre>
    131     * To read in the time and get a date which is compatible with our local
    132     * time zone.
    133     * <p/>
    134     * <b>Note:</b> In some cases, due to the local date processing, this
    135     * may lead to unexpected results. If you want to stick the normal
    136     * convention of 1950 to 2049 use the getAdjustedTime() method.
    137     */
    138    public String getTime() {
    139        //
    140        // standardise the format.
    141        //
    142        if (time.indexOf('-') < 0 && time.indexOf('+') < 0) {
    143            if (time.length() == 11) {
    144                return time.substring(0, 10) + "00GMT+00:00";
    145            } else {
    146                return time.substring(0, 12) + "GMT+00:00";
    147            }
    148        } else {
    149            int index = time.indexOf('-');
    150            if (index < 0) {
    151                index = time.indexOf('+');
    152            }
    153            String d = time;
    154
    155            if (index == time.length() - 3) {
    156                d += "00";
    157            }
    158
    159            if (index == 10) {
    160                return d.substring(0, 10) + "00GMT" + d.substring(10, 13) + ":" + d.substring(13, 15);
    161            } else {
    162                return d.substring(0, 12) + "GMT" + d.substring(12, 15) + ":" + d.substring(15, 17);
    163            }
    164        }
    165    }
    166
    167    /**
    168     * return a time string as an adjusted date with a 4 digit year. This goes
    169     * in the range of 1950 - 2049.
    170     */
    171    public String getAdjustedTime() {
    172        String d = this.getTime();
    173
    174        if (d.charAt(0) < '5') {
    175            return "20" + d;
    176        } else {
    177            return "19" + d;
    178        }
    179    }
    180
    181    private byte[] getOctets() {
    182        char[] cs = time.toCharArray();
    183        byte[] bs = new byte[cs.length];
    184
    185        for (int i = 0; i != cs.length; i++) {
    186            bs[i] = (byte) cs[i];
    187        }
    188
    189        return bs;
    190    }
    191
    192    void encode(
    193        DEROutputStream out)
    194        throws IOException {
    195        out.writeEncoded(UTC_TIME, this.getOctets());
    196    }
    197
    198    boolean asn1Equals(
    199        DERObject o) {
    200        if (!(o instanceof DERUTCTime)) {
    201            return false;
    202        }
    203
    204        return time.equals(((DERUTCTime) o).time);
    205    }
    206
    207    public int hashCode() {
    208        return time.hashCode();
    209    }
    210
    211    public String toString() {
    212        return time;
    213    }
    214}