/protocols/smpp/src/main/java/org/mobicents/protocols/smpp/util/SMPPDateFormat.java
Java | 319 lines | 182 code | 17 blank | 120 comment | 28 complexity | 52ae452794e0854a5e642ded793fb8bd MD5 | raw file
1/* 2 * JBoss, Home of Professional Open Source 3 * Copyright 2011, Red Hat, Inc. and individual contributors 4 * by the @authors tag. See the copyright.txt in the distribution for a 5 * full listing of individual contributors. 6 * 7 * This is free software; you can redistribute it and/or modify it 8 * under the terms of the GNU Lesser General Public License as 9 * published by the Free Software Foundation; either version 2.1 of 10 * the License, or (at your option) any later version. 11 * 12 * This software is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this software; if not, write to the Free 19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 21 */ 22 23package org.mobicents.protocols.smpp.util; 24 25import java.text.FieldPosition; 26import java.text.Format; 27import java.text.MessageFormat; 28import java.text.ParsePosition; 29import java.util.Calendar; 30import java.util.SimpleTimeZone; 31import java.util.TimeZone; 32import java.util.regex.Pattern; 33 34import org.mobicents.protocols.smpp.SMPPRuntimeException; 35 36/** 37 * Parse a String to an SMPPDate object. 38 * @version $Id: SMPPDateFormat.java 463 2009-06-16 12:07:19Z orank $ 39 */ 40public class SMPPDateFormat extends Format { 41 private static final long serialVersionUID = 2L; 42 43 /** 44 * "Local" time specification, no timezone info included. 45 */ 46 private static final String ABS_FORMAT_12 = 47 "{0,number,00}{1,number,00}{2,number,00}{3,number,00}{4,number,00}" 48 + "{5,number,00}"; 49 /** 50 * Absolute format, specifying timezone information. 51 */ 52 private static final String ABS_FORMAT_16 = 53 ABS_FORMAT_12 + "{6,number,0}{7,number,00}{8}"; 54 /** 55 * Relative format. 56 */ 57 private static final String REL_FORMAT_16 = ABS_FORMAT_12 + "000R"; 58 59 private static final Pattern ABS_PATTERN_16 = Pattern.compile("^\\d{15}[+-]"); 60 private static final Pattern REL_PATTERN_16 = Pattern.compile("^\\d{15}R"); 61 private static final Pattern ABS_PATTERN_12 = Pattern.compile("^\\d{12}"); 62 63 /** 64 * The default year modifier. The year modifier is the value used 65 * to convert 2-digit years into their full version. Year modifier is 66 * simply added to (or subtracted from) one form to get the other. 67 * <br /> 68 * <code> 69 * int twoDigitYear = fullYear - getYearModifier();<br /> 70 * int fullYear = twoDigitYear + getYearModifier();<br /> 71 * </code> 72 * The default value for this is <code>2000</code>, meaning the allowed 73 * values for a 2-digit year (0 to 99) represent the years 2000 to 2099. 74 */ 75 public static final int DEFAULT_YEAR_MODIFIER = 2000; 76 77 private int yearModifier = DEFAULT_YEAR_MODIFIER; 78 79 public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { 80 if (toAppendTo == null || pos == null) { 81 throw new NullPointerException(); 82 } 83 if (obj == null) { 84 return toAppendTo; 85 } 86 if (!(obj instanceof SMPPDate)) { 87 throw new IllegalArgumentException("Cannot format an object of type " 88 + obj.getClass().getName()); 89 } 90 SMPPDate date = (SMPPDate) obj; 91 int year = date.getYear(); 92 String format = ABS_FORMAT_16; 93 if (date.isAbsolute()) { 94 year -= yearModifier; 95 if (!date.hasTimezone()) { 96 format = ABS_FORMAT_12; 97 } 98 } else if (date.isRelative()) { 99 format = REL_FORMAT_16; 100 } 101 Object[] args = new Object[] { 102 Integer.valueOf(year), 103 Integer.valueOf(date.getMonth()), 104 Integer.valueOf(date.getDay()), 105 Integer.valueOf(date.getHour()), 106 Integer.valueOf(date.getMinute()), 107 Integer.valueOf(date.getSecond()), 108 Integer.valueOf(date.getTenth()), 109 Integer.valueOf(date.getUtcOffset()), 110 Character.valueOf(date.getSign()), 111 }; 112 toAppendTo.append(MessageFormat.format(format, args)); 113 return toAppendTo; 114 } 115 116 /** 117 * Parse a string into an {@link SMPPDate}. 118 * @param source The string to parse. 119 * @param pos The position to begin parsing from. 120 * @throws NullPointerException if <code>source</code> is <code>null</code>. 121 * @throws IllegalArgumentException if any of the parsed values exceed 122 * their allowed range. 123 */ 124 public Object parseObject(String source, ParsePosition pos) { 125 String s = source; 126 if (pos.getIndex() > 0) { 127 s = source.substring(pos.getIndex()); 128 } 129 int updatePos = 0; 130 boolean absolute = false; 131 boolean hasTz = true; 132 if (ABS_PATTERN_16.matcher(s).find()) { 133 absolute = true; 134 updatePos = 16; 135 } else if (REL_PATTERN_16.matcher(s).find()) { 136 absolute = false; 137 updatePos = 16; 138 } else if (ABS_PATTERN_12.matcher(s).find()) { 139 absolute = true; 140 hasTz = false; 141 updatePos = 12; 142 } else { 143 return null; 144 } 145 SMPPDate date = null; 146 try { 147 if (absolute) { 148 date = parseAbsoluteDate(s, pos, hasTz); 149 } else { 150 date = parseRelativeDate(s, pos); 151 } 152 pos.setIndex(pos.getIndex() + updatePos); 153 } catch (SMPPRuntimeException x) { 154 // Fall-through...ParsePosition will not get updated, and 155 // error index should have been set. 156 } 157 return date; 158 } 159 160 /** 161 * Get the value for the year modifier. 162 * @return The current value of the year modifier. 163 * @see #DEFAULT_YEAR_MODIFIER 164 */ 165 public int getYearModifier() { 166 return yearModifier; 167 } 168 169 /** 170 * Set the value for the year modifier. 171 * @param yearModifier The new value for year modifier. 172 * @see #DEFAULT_YEAR_MODIFIER 173 * @see #getYearModifier() 174 */ 175 public void setYearModifier(int yearModifier) { 176 this.yearModifier = yearModifier; 177 } 178 179 /** 180 * Parse an absolute date from the string. 181 * @param s The string to parse from. 182 * @param pos The parse position. 183 * @param hasTz Parse timezone information if <code>true</code>. 184 * @return The parsed SMPP date. 185 * @throws SMPPRuntimeException If the date cannot be parsed. 186 */ 187 private SMPPDate parseAbsoluteDate(String s, ParsePosition pos, boolean hasTz) { 188 int index = pos.getIndex(); 189 int year = parseAndCheck(s, pos, index, index + 2, 0, 99); 190 int month = parseAndCheck(s, pos, index + 2, index + 4, 1, 12); 191 int day = parseAndCheck(s, pos, index + 4, index + 6, 1, 31); 192 int hour = parseAndCheck(s, pos, index + 6, index + 8, 0, 23); 193 int minute = parseAndCheck(s, pos, index + 8, index + 10, 0, 59); 194 int second = parseAndCheck(s, pos, index + 10, index + 12, 0, 59); 195 Calendar cal; 196 if (hasTz) { 197 char sign = s.charAt(index + 15); 198 int tenth = parseAndCheck(s, pos, index + 12, index + 13, 0, 9); 199 int utcOffset = parseAndCheck( 200 s, pos, index + 13, index + 15, 0, 48); 201 cal = getCalendar(year, month, day, hour, minute, second, 202 tenth, utcOffset, sign); 203 } else { 204 cal = getCalendar( 205 year, month, day, hour, minute, second, 0, 0, (char) 0); 206 } 207 return SMPPDate.getAbsoluteInstance(cal, hasTz); 208 } 209 210 /** 211 * Parse a relative date from the string. 212 * @param s The string to parse the date from. 213 * @param pos The parse position. 214 * @return The parsed SMPP date. 215 * @throws SMPPRuntimeException If the date cannot be parsed. 216 */ 217 private SMPPDate parseRelativeDate(String s, ParsePosition pos) { 218 int index = pos.getIndex(); 219 int year = parseAndCheck(s, pos, index, index + 2, 0, 99); 220 int month = parseAndCheck(s, pos, index + 2, index + 4, 0, 99); 221 int day = parseAndCheck(s, pos, index + 4, index + 6, 0, 99); 222 int hour = parseAndCheck(s, pos, index + 6, index + 8, 0, 99); 223 int minute = parseAndCheck(s, pos, index + 8, index + 10, 0, 99); 224 int second = parseAndCheck(s, pos, 10, 12, 0, 99); 225 return SMPPDate.getRelativeInstance( 226 year, month, day, hour, minute, second); 227 } 228 229 /** 230 * Get a {@link java.util.Calendar} object, initialising its fields 231 * to the supplied values. The values should be specified in their 232 * SMPP form (that is, year is 0-99, month is 1-12) and this method will 233 * convert them appropriately before placing them in the calendar. 234 * @param year The year. 235 * @param month The month. 236 * @param day The day. 237 * @param hour The hour. 238 * @param minute The minute. 239 * @param second The second. 240 * @param tenths Tenths of a second. 241 * @param utcOffset The offset from UTC. 242 * @param sign The direction of the UTC offset ('+' or '-'). 243 * @return An initialised Calendar object. 244 */ 245 private Calendar getCalendar(int year, 246 int month, 247 int day, 248 int hour, 249 int minute, 250 int second, 251 int tenths, 252 int utcOffset, 253 char sign) { 254 Calendar calendar = Calendar.getInstance(); 255 if (sign != (char) 0) { 256 calendar.setTimeZone(getTimeZoneForOffset(utcOffset, sign)); 257 } 258 calendar.set(Calendar.YEAR, year + yearModifier); 259 calendar.set(Calendar.MONTH, month - 1); 260 calendar.set(Calendar.DAY_OF_MONTH, day); 261 calendar.set(Calendar.HOUR_OF_DAY, hour); 262 calendar.set(Calendar.MINUTE, minute); 263 calendar.set(Calendar.SECOND, second); 264 calendar.set(Calendar.MILLISECOND, tenths * 100); 265 return calendar; 266 } 267 268 /** 269 * Get a timezone for the specified UTC offset. 270 * A new {@link SimpleTimeZone} will be created and given the 271 * specified offset from UTC. 272 * @param utcOffset The offset, in quarter hours, from UTC. 273 * @param sign Whether the offset is ahead ('+') or behind ('-') UTC. 274 * @return A timezone object. 275 */ 276 private TimeZone getTimeZoneForOffset(int utcOffset, char sign) { 277 int rawOffset = utcOffset * 900000; 278 if (sign == '-') { 279 rawOffset = -rawOffset; 280 } 281 int hours = utcOffset / 4; 282 int minutes = (utcOffset - (hours * 4)) * 15; 283 String id = String.format("UTC%c%02d:%02d", sign, hours, minutes); 284 TimeZone tz = new SimpleTimeZone(rawOffset, id); 285 return tz; 286 } 287 288 /** 289 * Parse an integer from a string and verify the value lies within 290 * a maximum and minimum range. If it does 291 * not, update the <code>ParsePosition's</code> error index to point 292 * to <code>start</code> and throw an <code>SMPPRuntimeException</code>. 293 * <code>SMPPRuntimeException</code> is also thrown in the case of a 294 * <code>NumberFormatException</code>. 295 * @param s The string to parse the integer from. 296 * @param pos The parse position to update in case of error. 297 * @param start The start of the substring containing the integer. 298 * @param end The end of the substring containing the integer. 299 * @param min The minimum allowed value. 300 * @param max The maximum allowed value. 301 */ 302 private int parseAndCheck(String s, 303 ParsePosition pos, 304 int start, 305 int end, 306 int min, 307 int max) { 308 try { 309 int n = Integer.parseInt(s.substring(start, end)); 310 if (n < min || n > max) { 311 throw new NumberFormatException(); 312 } 313 return n; 314 } catch (NumberFormatException x) { 315 pos.setErrorIndex(start); 316 throw new SMPPRuntimeException(); 317 } 318 } 319}