1 package org.bouncycastle.asn1; 2 3 import java.io.IOException; 4 import java.text.ParseException; 5 import java.text.SimpleDateFormat; 6 import java.util.Date; 7 import java.util.SimpleTimeZone; 8 import java.util.TimeZone; 9 10 /** 11 * Generalized time object. 12 */ 13 public class DERGeneralizedTime 14 extends ASN1Object 15 { 16 String time; 17 18 /** 19 * return a generalized time from the passed in object 20 * 21 * @exception IllegalArgumentException if the object cannot be converted. 22 */ 23 public static DERGeneralizedTime getInstance( 24 Object obj) 25 { 26 if (obj == null || obj instanceof DERGeneralizedTime) 27 { 28 return (DERGeneralizedTime)obj; 29 } 30 31 throw new IllegalArgumentException("illegal object in getInstance: " + obj.getClass().getName()); 32 } 33 34 /** 35 * return a Generalized Time object from a tagged object. 36 * 37 * @param obj the tagged object holding the object we want 38 * @param explicit true if the object is meant to be explicitly 39 * tagged false otherwise. 40 * @exception IllegalArgumentException if the tagged object cannot 41 * be converted. 42 */ 43 public static DERGeneralizedTime getInstance( 44 ASN1TaggedObject obj, 45 boolean explicit) 46 { 47 DERObject o = obj.getObject(); 48 49 if (explicit || o instanceof DERGeneralizedTime) 50 { 51 return getInstance(o); 52 } 53 else 54 { 55 return new DERGeneralizedTime(((ASN1OctetString)o).getOctets()); 56 } 57 } 58 59 /** 60 * The correct format for this is YYYYMMDDHHMMSS[.f]Z, or without the Z 61 * for local time, or Z+-HHMM on the end, for difference between local 62 * time and UTC time. The fractional second amount f must consist of at 63 * least one number with trailing zeroes removed. 64 * 65 * @param time the time string. 66 * @exception IllegalArgumentException if String is an illegal format. 67 */ 68 public DERGeneralizedTime( 69 String time) 70 { 71 this.time = time; 72 try 73 { 74 this.getDate(); 75 } 76 catch (ParseException e) 77 { 78 throw new IllegalArgumentException("invalid date string: " + e.getMessage()); 79 } 80 } 81 82 /** 83 * base constructer from a java.util.date object 84 */ 85 public DERGeneralizedTime( 86 Date time) 87 { 88 SimpleDateFormat dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'"); 89 90 dateF.setTimeZone(new SimpleTimeZone(0,"Z")); 91 92 this.time = dateF.format(time); 93 } 94 95 DERGeneralizedTime( 96 byte[] bytes) 97 { 98 // 99 // explicitly convert to characters 100 // 101 char[] dateC = new char[bytes.length]; 102 103 for (int i = 0; i != dateC.length; i++) 104 { 105 dateC[i] = (char)(bytes[i] & 0xff); 106 } 107 108 this.time = new String(dateC); 109 } 110 111 /** 112 * Return the time. 113 * @return The time string as it appeared in the encoded object. 114 */ 115 public String getTimeString() 116 { 117 return time; 118 } 119 120 /** 121 * return the time - always in the form of 122 * YYYYMMDDhhmmssGMT(+hh:mm|-hh:mm). 123 * <p> 124 * Normally in a certificate we would expect "Z" rather than "GMT", 125 * however adding the "GMT" means we can just use: 126 * <pre> 127 * dateF = new SimpleDateFormat("yyyyMMddHHmmssz"); 128 * </pre> 129 * To read in the time and get a date which is compatible with our local 130 * time zone. 131 */ 132 public String getTime() 133 { 134 // 135 // standardise the format. 136 // 137 if (time.charAt(time.length() - 1) == 'Z') 138 { 139 return time.substring(0, time.length() - 1) + "GMT+00:00"; 140 } 141 else 142 { 143 int signPos = time.length() - 5; 144 char sign = time.charAt(signPos); 145 if (sign == '-' || sign == '+') 146 { 147 return time.substring(0, signPos) 148 + "GMT" 149 + time.substring(signPos, signPos + 3) 150 + ":" 151 + time.substring(signPos + 3); 152 } 153 else 154 { 155 signPos = time.length() - 3; 156 sign = time.charAt(signPos); 157 if (sign == '-' || sign == '+') 158 { 159 return time.substring(0, signPos) 160 + "GMT" 161 + time.substring(signPos) 162 + ":00"; 163 } 164 } 165 } 166 return time + calculateGMTOffset(); 167 } 168 169 private String calculateGMTOffset() 170 { 171 String sign = "+"; 172 TimeZone timeZone = TimeZone.getDefault(); 173 int offset = timeZone.getRawOffset(); 174 if (offset < 0) 175 { 176 sign = "-"; 177 offset = -offset; 178 } 179 int hours = offset / (60 * 60 * 1000); 180 int minutes = (offset - (hours * 60 * 60 * 1000)) / (60 * 1000); 181 182 try 183 { 184 if (timeZone.useDaylightTime() && timeZone.inDaylightTime(this.getDate())) 185 { 186 hours += sign.equals("+") ? 1 : -1; 187 } 188 } 189 catch (ParseException e) 190 { 191 // we'll do our best and ignore daylight savings 192 } 193 194 return "GMT" + sign + convert(hours) + ":" + convert(minutes); 195 } 196 197 private String convert(int time) 198 { 199 if (time < 10) 200 { 201 return "0" + time; 202 } 203 204 return Integer.toString(time); 205 } 206 207 public Date getDate() 208 throws ParseException 209 { 210 SimpleDateFormat dateF; 211 String d = time; 212 213 if (time.endsWith("Z")) 214 { 215 if (hasFractionalSeconds()) 216 { 217 dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSS'Z'"); 218 } 219 else 220 { 221 dateF = new SimpleDateFormat("yyyyMMddHHmmss'Z'"); 222 } 223 224 dateF.setTimeZone(new SimpleTimeZone(0, "Z")); 225 } 226 else if (time.indexOf('-') > 0 || time.indexOf('+') > 0) 227 { 228 d = this.getTime(); 229 if (hasFractionalSeconds()) 230 { 231 dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSSz"); 232 } 233 else 234 { 235 dateF = new SimpleDateFormat("yyyyMMddHHmmssz"); 236 } 237 238 dateF.setTimeZone(new SimpleTimeZone(0, "Z")); 239 } 240 else 241 { 242 if (hasFractionalSeconds()) 243 { 244 dateF = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); 245 } 246 else 247 { 248 dateF = new SimpleDateFormat("yyyyMMddHHmmss"); 249 } 250 251 dateF.setTimeZone(new SimpleTimeZone(0, TimeZone.getDefault().getID())); 252 } 253 254 if (hasFractionalSeconds()) 255 { 256 // java misinterprets extra digits as being milliseconds... 257 String frac = d.substring(14); 258 int index; 259 for (index = 1; index < frac.length(); index++) 260 { 261 char ch = frac.charAt(index); 262 if (!('0' <= ch && ch <= '9')) 263 { 264 break; 265 } 266 } 267 268 if (index - 1 > 3) 269 { 270 frac = frac.substring(0, 4) + frac.substring(index); 271 d = d.substring(0, 14) + frac; 272 } 273 else if (index - 1 == 1) 274 { 275 frac = frac.substring(0, index) + "00" + frac.substring(index); 276 d = d.substring(0, 14) + frac; 277 } 278 else if (index - 1 == 2) 279 { 280 frac = frac.substring(0, index) + "0" + frac.substring(index); 281 d = d.substring(0, 14) + frac; 282 } 283 } 284 285 return dateF.parse(d); 286 } 287 288 private boolean hasFractionalSeconds() 289 { 290 return time.indexOf('.') == 14; 291 } 292 293 private byte[] getOctets() 294 { 295 char[] cs = time.toCharArray(); 296 byte[] bs = new byte[cs.length]; 297 298 for (int i = 0; i != cs.length; i++) 299 { 300 bs[i] = (byte)cs[i]; 301 } 302 303 return bs; 304 } 305 306 307 void encode( 308 DEROutputStream out) 309 throws IOException 310 { 311 out.writeEncoded(GENERALIZED_TIME, this.getOctets()); 312 } 313 314 boolean asn1Equals( 315 DERObject o) 316 { 317 if (!(o instanceof DERGeneralizedTime)) 318 { 319 return false; 320 } 321 322 return time.equals(((DERGeneralizedTime)o).time); 323 } 324 325 public int hashCode() 326 { 327 return time.hashCode(); 328 } 329 } 330