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}