001    package org.apache.commons.ssl.asn1;
002    
003    import java.io.IOException;
004    import java.text.ParseException;
005    import java.text.SimpleDateFormat;
006    import java.util.Date;
007    import java.util.SimpleTimeZone;
008    
009    /** UTC time object. */
010    public 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    }