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    import java.util.TimeZone;
009    
010    /** Generalized time object. */
011    public class DERGeneralizedTime
012        extends ASN1Object {
013        String time;
014    
015        /**
016         * return a generalized time from the passed in object
017         *
018         * @throws IllegalArgumentException if the object cannot be converted.
019         */
020        public static DERGeneralizedTime getInstance(
021            Object obj) {
022            if (obj == null || obj instanceof DERGeneralizedTime) {
023                return (DERGeneralizedTime) obj;
024            }
025    
026            if (obj instanceof ASN1OctetString) {
027                return new DERGeneralizedTime(((ASN1OctetString) obj).getOctets());
028            }
029    
030            throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName());
031        }
032    
033        /**
034         * return a Generalized Time object from a tagged object.
035         *
036         * @param obj      the tagged object holding the object we want
037         * @param explicit true if the object is meant to be explicitly
038         *                 tagged false otherwise.
039         * @throws IllegalArgumentException if the tagged object cannot
040         *                                  be converted.
041         */
042        public static DERGeneralizedTime getInstance(
043            ASN1TaggedObject obj,
044            boolean explicit) {
045            return getInstance(obj.getObject());
046        }
047    
048        /**
049         * The correct format for this is YYYYMMDDHHMMSS[.f]Z, or without the Z
050         * for local time, or Z+-HHMM on the end, for difference between local
051         * time and UTC time. The fractional second amount f must consist of at
052         * least one number with trailing zeroes removed.
053         *
054         * @param time the time string.
055         * @throws IllegalArgumentException if String is an illegal format.
056         */
057        public DERGeneralizedTime(
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 DERGeneralizedTime(
070            Date time) {
071            SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
072    
073            dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
074    
075            this.time = dateF.format(time);
076        }
077    
078        DERGeneralizedTime(
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.
094         *
095         * @return The time string as it appeared in the encoded object.
096         */
097        public String getTimeString() {
098            return time;
099        }
100    
101        /**
102         * return the time - always in the form of
103         * YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm).
104         * <p/>
105         * Normally in a certificate we would expect "Z" rather than "GMT",
106         * however adding the "GMT" means we can just use:
107         * <pre>
108         *     dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
109         * </pre>
110         * To read in the time and get a date which is compatible with our local
111         * time zone.
112         */
113        public String getTime() {
114            //
115            // standardise the format.
116            //             
117            if (time.charAt(time.length() - 1) == 'Z') {
118                return time.substring(0, time.length() - 1) + "GMT+00:00";
119            } else {
120                int signPos = time.length() - 5;
121                char sign = time.charAt(signPos);
122                if (sign == '-' || sign == '+') {
123                    return time.substring(0, signPos)
124                           + "GMT"
125                           + time.substring(signPos, signPos + 3)
126                           + ":"
127                           + time.substring(signPos + 3);
128                } else {
129                    signPos = time.length() - 3;
130                    sign = time.charAt(signPos);
131                    if (sign == '-' || sign == '+') {
132                        return time.substring(0, signPos)
133                               + "GMT"
134                               + time.substring(signPos)
135                               + ":00";
136                    }
137                }
138            }
139            return time + calculateGMTOffset();
140        }
141    
142        private String calculateGMTOffset() {
143            String sign = "+";
144            TimeZone timeZone = TimeZone.getDefault();
145            int offset = timeZone.getRawOffset();
146            if (offset < 0) {
147                sign = "-";
148                offset = -offset;
149            }
150            int hours = offset / (60 * 60 * 1000);
151            int minutes = (offset - (hours * 60 * 60 * 1000)) / (60 * 1000);
152    
153            try {
154                if (timeZone.useDaylightTime() && timeZone.inDaylightTime(this.getDate())) {
155                    hours += sign.equals("+") ? 1 : -1;
156                }
157            }
158            catch (ParseException e) {
159                // we'll do our best and ignore daylight savings
160            }
161    
162            return "GMT" + sign + convert(hours) + ":" + convert(minutes);
163        }
164    
165        private String convert(int time) {
166            if (time < 10) {
167                return "0" + time;
168            }
169    
170            return Integer.toString(time);
171        }
172    
173        public Date getDate()
174            throws ParseException {
175            SimpleDateFormat dateF;
176            String d = time;
177    
178            if (time.endsWith("Z")) {
179                if (hasFractionalSeconds()) {
180                    dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSSS'Z'");
181                } else {
182                    dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
183                }
184    
185                dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
186            } else if (time.indexOf('-') > 0 || time.indexOf('+') > 0) {
187                d = this.getTime();
188                if (hasFractionalSeconds()) {
189                    dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSSSz");
190                } else {
191                    dateF = new SimpleDateFormat("yyyyMMddHHmmssz");
192                }
193    
194                dateF.setTimeZone(new SimpleTimeZone(0, "Z"));
195            } else {
196                if (hasFractionalSeconds()) {
197                    dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSSS");
198                } else {
199                    dateF = new SimpleDateFormat("yyyyMMddHHmmss");
200                }
201    
202                dateF.setTimeZone(new SimpleTimeZone(0, TimeZone.getDefault().getID()));
203            }
204    
205            return dateF.parse(d);
206        }
207    
208        private boolean hasFractionalSeconds() {
209            return time.indexOf('.') == 14;
210        }
211    
212        private byte[] getOctets() {
213            char[] cs = time.toCharArray();
214            byte[] bs = new byte[cs.length];
215    
216            for (int i = 0; i != cs.length; i++) {
217                bs[i] = (byte) cs[i];
218            }
219    
220            return bs;
221        }
222    
223    
224        void encode(
225            DEROutputStream out)
226            throws IOException {
227            out.writeEncoded(GENERALIZED_TIME, this.getOctets());
228        }
229    
230        boolean asn1Equals(
231            DERObject o) {
232            if (!(o instanceof DERGeneralizedTime)) {
233                return false;
234            }
235    
236            return time.equals(((DERGeneralizedTime) o).time);
237        }
238    
239        public int hashCode() {
240            return time.hashCode();
241        }
242    }