001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.util.time; 033 034import java.text.ParseException; 035import java.util.Calendar; 036import java.util.LinkedList; 037import java.util.regex.Matcher; 038import java.util.regex.Pattern; 039 040import org.graphstream.util.time.ISODateComponent.TextComponent; 041 042/** 043 * Scanner for date in ISO/IEC 9899:1999 format. The scanner takes a format and 044 * then is able to parse timestamp in the given format. 045 * 046 * The <i>parse()</i> return a {@link java.util.Calendar} for convenience. 047 * 048 * Format of the scanner can be composed of %? directive which define components 049 * of the time. These directives are listed below. For example, the format 050 * "%F %T", which is equivalent to "%Y-%m-%d %H:%M:%S" can parse the following 051 * timestamp: "2010-12-09 03:45:39"; 052 * 053 * <dl> 054 * <dt>%a</dt> 055 * <dd>locale's abbreviated weekday name</dd> 056 * <dt>%A</dt> 057 * <dd>locale's weekday name</dd> 058 * <dt>%b</dt> 059 * <dd>locale's abbreviated month name</dd> 060 * <dt>%B</dt> 061 * <dd>locale's month name</dd> 062 * <dt>%c</dt> 063 * <dd>locale's date and time representation</dd> 064 * <dt>%C</dt> 065 * <dd>two first digits of full year as an integer (00-99)</dd> 066 * <dt>%d</dt> 067 * <dd>day of the month (01-31)</dd> 068 * <dt>%D</dt> 069 * <dd>%m/%d/%y</dd> 070 * <dt>%e</dt> 071 * <dd>day of the month (1-31)</dd> 072 * <dt>%F</dt> 073 * <dd>%Y-%m-%d</dd> 074 * <dt>%g</dt> 075 * <dd>last 2 digits of the week-based year (00-99)</dd> 076 * <dt>%G</dt> 077 * <dd>"week-based year as a decimal number</dd> 078 * <dt>%h</dt> 079 * <dd>%b</dd> 080 * <dt>%H</dt> 081 * <dd>hour (24-hour clock) as a decimal number (00-23)</dd> 082 * <dt>%I</dt> 083 * <dd>hour (12-hour clock) as a decimal number (01-12)</dd> 084 * <dt>%j</dt> 085 * <dd>day of the year as a decimal number (001-366)</dd> 086 * <dt>%k</dt> 087 * <dd>milliseconds as a decimal number (001-999)</dd> 088 * <dt>%K</dt> 089 * <dd>milliseconds since the epoch</dd> 090 * <dt>%m</dt> 091 * <dd>month as a decimal number (01-12)</dd> 092 * <dt>%M</dt> 093 * <dd>minute as a decimal number (00-59)</dd> 094 * <dt>%n</dt> 095 * <dd>\n</dd> 096 * <dt>%p</dt> 097 * <dd>locale-s equivalent of the AM/PM</dd> 098 * <dt>%r</dt> 099 * <dd>locale's 12-hour clock time</dd> 100 * <dt>%R</dt> 101 * <dd>%H:%M</dd> 102 * <dt>%S</dt> 103 * <dd>second as a decimal number (00-60)</dd> 104 * <dt>%t</dt> 105 * <dd>\t</dd> 106 * <dt>%T</dt> 107 * <dd>%H:%M:%S</dd> 108 * <dt>%u</dt> 109 * <dd>ISO 8601 weekday as a decimal number (1-7)</dd> 110 * <dt>%U</dt> 111 * <dd>week number of the year as a decimal number (00-53)</dd> 112 * <dt>%V</dt> 113 * <dd>ISO 8601 week number as a decimal number (01-53)</dd> 114 * <dt>%w</dt> 115 * <dd>weekday as a decimal number (0-6)</dd> 116 * <dt>%W</dt> 117 * <dd>week number of the year as a decimal number (00-53)</dd> 118 * <dt>%x</dt> 119 * <dd>locale's date representation</dd> 120 * <dt>%X</dt> 121 * <dd>locale's time representation</dd> 122 * <dt>%y</dt> 123 * <dd>last 2 digits of the year as a decimal number (00-99)</dd> 124 * <dt>%Y</dt> 125 * <dd>year as a decimal number</dd> 126 * <dt>%z</dt> 127 * <dd>offset from UTC in the ISO 8601 format</dd> 128 * <dt>%Z</dt> 129 * <dd>locale's time zone name of abbreviation or empty</dd> 130 * </dl> 131 * 132 * @author Guilhelm Savin 133 */ 134public class ISODateIO { 135 136 private static final ISODateComponent[] KNOWN_COMPONENTS = { 137 ISODateComponent.ABBREVIATED_WEEKDAY_NAME, 138 ISODateComponent.FULL_WEEKDAY_NAME, 139 ISODateComponent.ABBREVIATED_MONTH_NAME, 140 ISODateComponent.FULL_MONTH_NAME, 141 ISODateComponent.LOCALE_DATE_AND_TIME, ISODateComponent.CENTURY, 142 ISODateComponent.DAY_OF_MONTH_2_DIGITS, ISODateComponent.DATE, 143 ISODateComponent.DAY_OF_MONTH, ISODateComponent.DATE_ISO8601, 144 ISODateComponent.WEEK_BASED_YEAR_2_DIGITS, 145 ISODateComponent.WEEK_BASED_YEAR_4_DIGITS, 146 ISODateComponent.ABBREVIATED_MONTH_NAME_ALIAS, 147 ISODateComponent.HOUR_OF_DAY, ISODateComponent.HOUR, 148 ISODateComponent.DAY_OF_YEAR, ISODateComponent.MILLISECOND, 149 ISODateComponent.EPOCH, ISODateComponent.MONTH, 150 ISODateComponent.MINUTE, ISODateComponent.NEW_LINE, 151 ISODateComponent.AM_PM, ISODateComponent.LOCALE_CLOCK_TIME_12_HOUR, 152 ISODateComponent.HOUR_AND_MINUTE, ISODateComponent.SECOND, 153 ISODateComponent.TABULATION, ISODateComponent.TIME_ISO8601, 154 ISODateComponent.DAY_OF_WEEK_1_7, 155 ISODateComponent.WEEK_OF_YEAR_FROM_SUNDAY, 156 ISODateComponent.WEEK_NUMBER_ISO8601, 157 ISODateComponent.DAY_OF_WEEK_0_6, 158 ISODateComponent.WEEK_OF_YEAR_FROM_MONDAY, 159 ISODateComponent.LOCALE_DATE_REPRESENTATION, 160 ISODateComponent.LOCALE_TIME_REPRESENTATION, 161 ISODateComponent.YEAR_2_DIGITS, ISODateComponent.YEAR_4_DIGITS, 162 ISODateComponent.UTC_OFFSET, 163 ISODateComponent.LOCALE_TIME_ZONE_NAME, ISODateComponent.PERCENT }; 164 165 /** 166 * List of components, build from a string format. Some of these components 167 * can just be text. 168 */ 169 protected LinkedList<ISODateComponent> components; 170 /** 171 * The regular expression builds from the components. 172 */ 173 protected Pattern pattern; 174 175 /** 176 * Create a scanner with default format "%K". 177 * 178 * @throws ParseException 179 */ 180 public ISODateIO() throws ParseException { 181 this("%K"); 182 } 183 184 /** 185 * Create a new scanner with a given format. 186 * 187 * @param format 188 * format of the scanner. 189 * @throws ParseException 190 * if bad directives found 191 */ 192 public ISODateIO(String format) throws ParseException { 193 setFormat(format); 194 } 195 196 /** 197 * Get the current pattern used to parse timestamp. 198 * 199 * @return a regular expression as a string 200 */ 201 public Pattern getPattern() { 202 return pattern; 203 } 204 205 /** 206 * Build a list of component from a string. 207 * 208 * @param format 209 * format of the scanner 210 * @return a list of components found in the string format 211 * @throws ParseException 212 * if invalid component found 213 */ 214 protected LinkedList<ISODateComponent> findComponents(String format) 215 throws ParseException { 216 LinkedList<ISODateComponent> components = new LinkedList<ISODateComponent>(); 217 int offset = 0; 218 219 while (offset < format.length()) { 220 if (format.charAt(offset) == '%') { 221 boolean found = false; 222 for (int i = 0; !found && i < KNOWN_COMPONENTS.length; i++) { 223 if (format.startsWith(KNOWN_COMPONENTS[i].getDirective(), 224 offset)) { 225 found = true; 226 if (KNOWN_COMPONENTS[i].isAlias()) { 227 LinkedList<ISODateComponent> sub = findComponents(KNOWN_COMPONENTS[i] 228 .getReplacement()); 229 components.addAll(sub); 230 } else 231 components.addLast(KNOWN_COMPONENTS[i]); 232 233 offset += KNOWN_COMPONENTS[i].getDirective().length(); 234 } 235 } 236 if (!found) 237 throw new ParseException("unknown identifier", offset); 238 } else { 239 int from = offset; 240 while (offset < format.length() && format.charAt(offset) != '%') 241 offset++; 242 components.addLast(new TextComponent(format.substring(from, 243 offset))); 244 } 245 } 246 247 return components; 248 } 249 250 /** 251 * Build a regular expression from the components of the scanner. 252 */ 253 protected void buildRegularExpression() { 254 String pattern = ""; 255 256 for (int i = 0; i < components.size(); i++) { 257 Object c = components.get(i); 258 String regexValue; 259 if (c instanceof ISODateComponent) 260 regexValue = ((ISODateComponent) c).getReplacement(); 261 else 262 regexValue = c.toString(); 263 264 pattern += "(" + regexValue + ")"; 265 } 266 267 this.pattern = Pattern.compile(pattern); 268 } 269 270 /** 271 * Set the format of this scanner. 272 * 273 * @param format 274 * new format of the scanner 275 * @throws ParseException 276 * if an error is found in the new format 277 */ 278 public void setFormat(String format) throws ParseException { 279 components = findComponents(format); 280 buildRegularExpression(); 281 } 282 283 /** 284 * Parse a string which should be in the scanner format. If not, null is 285 * returned. 286 * 287 * @param time 288 * timestamp in the scanner format 289 * @return a calendar modeling the time value or null if invalid format 290 */ 291 public Calendar parse(String time) { 292 Calendar cal = Calendar.getInstance(); 293 Matcher match = pattern.matcher(time); 294 295 if (match.matches()) { 296 for (int i = 0; i < components.size(); i++) 297 components.get(i).set(match.group(i + 1), cal); 298 } else 299 return null; 300 301 return cal; 302 } 303 304 /** 305 * Convert a calendar into a string according to the format of this object. 306 * 307 * @param calendar 308 * the calendar to convert 309 * @return a string modeling the calendar. 310 */ 311 public String toString(Calendar calendar) { 312 StringBuffer buffer = new StringBuffer(); 313 314 for (int i = 0; i < components.size(); i++) 315 buffer.append(components.get(i).get(calendar)); 316 317 return buffer.toString(); 318 } 319}