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.DateFormatSymbols;
035import java.util.Calendar;
036import java.util.Locale;
037import java.util.regex.Pattern;
038
039/**
040 * Defines components of {@link ISODateIO}.
041 * 
042 */
043public abstract class ISODateComponent {
044
045        /**
046         * Directives shortcut of the component. This property can not be changed.
047         */
048        protected final String directive;
049        /**
050         * Replacement of the directive. Could be a regular expression. The value
051         * catch will be sent to the component with
052         * <i>set(catched_value,Calendar)</i>. This property can not be changed.
053         */
054        protected final String replace;
055
056        /**
057         * Build a new component composed of a directive name ("%.") and a
058         * replacement value.
059         * 
060         * @param directive
061         *            directive name, should start with a leading '%'.
062         * @param replace
063         *            replace the directive with the value given here.
064         */
065        public ISODateComponent(String directive, String replace) {
066                this.directive = directive;
067                this.replace = replace;
068        }
069
070        /**
071         * Access to the directive name of the component.
072         * 
073         * @return directive of the component.
074         */
075        public String getDirective() {
076                return directive;
077        }
078
079        /**
080         * Return true if this component is an alias. An alias can contain other
081         * directive name and its replacement should be parse again.
082         * 
083         * @return true if component is an alias.
084         */
085        public boolean isAlias() {
086                return false;
087        }
088
089        /**
090         * Get the replacement value of this component.
091         * 
092         * @return replacement value
093         */
094        public String getReplacement() {
095                return replace;
096        }
097
098        /**
099         * Handle the value catched with the replacement value.
100         * 
101         * @param value
102         *            value matching the replacement string
103         * @param calendar
104         *            calendar we are working on
105         */
106        public abstract void set(String value, Calendar calendar);
107
108        /**
109         * Get a string representation of this component for a given calendar.
110         * 
111         * @param calendar
112         *            the calendar
113         * @return string representation of this component.
114         */
115        public abstract String get(Calendar calendar);
116
117        /**
118         * Defines an alias component. Such component does nothing else that replace
119         * them directive by another string.
120         */
121        public static class AliasComponent extends ISODateComponent {
122
123                public AliasComponent(String shortcut, String replace) {
124                        super(shortcut, replace);
125                }
126
127                public boolean isAlias() {
128                        return true;
129                }
130
131                public void set(String value, Calendar calendar) {
132                        // Nothing to do
133                }
134
135                public String get(Calendar calendar) {
136                        return "";
137                }
138        }
139
140        /**
141         * Defines a text component. Such component does nothing else that append
142         * text to the resulting regular expression.
143         */
144        public static class TextComponent extends ISODateComponent {
145                String unquoted;
146
147                public TextComponent(String value) {
148                        super(null, Pattern.quote(value));
149                        unquoted = value;
150                }
151
152                public void set(String value, Calendar calendar) {
153                        // Nothing to do
154                }
155
156                public String get(Calendar calendar) {
157                        return unquoted;
158                }
159        }
160
161        /**
162         * Defines a component associated with a field of a calendar. When a value
163         * is handled, component will try to set the associated field of the
164         * calendar.
165         */
166        public static class FieldComponent extends ISODateComponent {
167                protected final int field;
168                protected final int offset;
169                protected final String format;
170
171                public FieldComponent(String shortcut, String replace, int field,
172                                String format) {
173                        this(shortcut, replace, field, 0, format);
174                }
175
176                public FieldComponent(String shortcut, String replace, int field,
177                                int offset, String format) {
178                        super(shortcut, replace);
179                        this.field = field;
180                        this.offset = offset;
181                        this.format = format;
182                }
183
184                public void set(String value, Calendar calendar) {
185                        while (value.charAt(0) == '0' && value.length() > 1)
186                                value = value.substring(1);
187                        int val = Integer.parseInt(value);
188                        calendar.set(field, val + offset);
189                }
190
191                public String get(Calendar calendar) {
192                        return String.format(format, calendar.get(field));
193                }
194        }
195
196        /**
197         * Base for locale-dependent component.
198         */
199        protected static abstract class LocaleDependentComponent extends
200                        ISODateComponent {
201                protected Locale locale;
202                protected DateFormatSymbols symbols;
203
204                public LocaleDependentComponent(String shortcut, String replace) {
205                        this(shortcut, replace, Locale.getDefault());
206                }
207
208                public LocaleDependentComponent(String shortcut, String replace,
209                                Locale locale) {
210                        super(shortcut, replace);
211                        this.locale = locale;
212                        this.symbols = DateFormatSymbols.getInstance(locale);
213                }
214        }
215
216        /**
217         * Component handling AM/PM.
218         */
219        public static class AMPMComponent extends LocaleDependentComponent {
220                public AMPMComponent() {
221                        super("%p", "AM|PM|am|pm");
222                }
223
224                public void set(String value, Calendar calendar) {
225                        if (value.equalsIgnoreCase(symbols.getAmPmStrings()[Calendar.AM]))
226                                calendar.set(Calendar.AM_PM, Calendar.AM);
227                        else if (value
228                                        .equalsIgnoreCase(symbols.getAmPmStrings()[Calendar.PM]))
229                                calendar.set(Calendar.AM_PM, Calendar.PM);
230                }
231
232                public String get(Calendar calendar) {
233                        return symbols.getAmPmStrings()[calendar.get(Calendar.AM_PM)];
234                }
235        }
236
237        /**
238         * Component handling utc offset (+/- 0000).
239         */
240        public static class UTCOffsetComponent extends ISODateComponent {
241                public UTCOffsetComponent() {
242                        super("%z", "(?:[-+]\\d{4}|Z)");
243                }
244
245                public void set(String value, Calendar calendar) {
246                        if (value.equals("Z")) {
247                                calendar.getTimeZone().setRawOffset(0);
248                        } else {
249                                String hs = value.substring(1, 3);
250                                String ms = value.substring(3, 5);
251                                if (hs.charAt(0) == '0')
252                                        hs = hs.substring(1);
253                                if (ms.charAt(0) == '0')
254                                        ms = ms.substring(1);
255
256                                int i = value.charAt(0) == '+' ? 1 : -1;
257                                int h = Integer.parseInt(hs);
258                                int m = Integer.parseInt(ms);
259
260                                calendar.getTimeZone().setRawOffset(i * (h * 60 + m) * 60000);
261                        }
262                }
263
264                public String get(Calendar calendar) {
265                        int offset = calendar.getTimeZone().getRawOffset();
266                        String sign = "+";
267
268                        if (offset < 0) {
269                                sign = "-";
270                                offset = -offset;
271                        }
272
273                        offset /= 60000;
274
275                        int h = offset / 60;
276                        int m = offset % 60;
277
278                        return String.format("%s%02d%02d", sign, h, m);
279                }
280        }
281
282        /**
283         * Component handling a number of milliseconds since the epoch (january, 1st
284         * 1970).
285         */
286        public static class EpochComponent extends ISODateComponent {
287                public EpochComponent() {
288                        super("%K", "\\d+");
289                }
290
291                public void set(String value, Calendar calendar) {
292                        long e = Long.parseLong(value);
293                        calendar.setTimeInMillis(e);
294                }
295
296                public String get(Calendar calendar) {
297                        return String.format("%d", calendar.getTimeInMillis());
298                }
299        }
300
301        /**
302         * Defines a not implemented component. Such components throw an Error if
303         * used.
304         */
305        public static class NotImplementedComponent extends ISODateComponent {
306                public NotImplementedComponent(String shortcut, String replace) {
307                        super(shortcut, replace);
308                }
309
310                public void set(String value, Calendar cal) {
311                        throw new Error("not implemented component");
312                }
313
314                public String get(Calendar calendar) {
315                        throw new Error("not implemented component");
316                }
317        }
318
319        public static final ISODateComponent ABBREVIATED_WEEKDAY_NAME = new NotImplementedComponent(
320                        "%a", "\\w+[.]");
321        public static final ISODateComponent FULL_WEEKDAY_NAME = new NotImplementedComponent(
322                        "%A", "\\w+");
323        public static final ISODateComponent ABBREVIATED_MONTH_NAME = new NotImplementedComponent(
324                        "%b", "\\w+[.]");
325        public static final ISODateComponent FULL_MONTH_NAME = new NotImplementedComponent(
326                        "%B", "\\w+");
327        public static final ISODateComponent LOCALE_DATE_AND_TIME = new NotImplementedComponent(
328                        "%c", null);
329        public static final ISODateComponent CENTURY = new NotImplementedComponent(
330                        "%C", "\\d\\d");
331        public static final ISODateComponent DAY_OF_MONTH_2_DIGITS = new FieldComponent(
332                        "%d", "[012]\\d|3[01]", Calendar.DAY_OF_MONTH, "%02d");
333        public static final ISODateComponent DATE = new AliasComponent("%D",
334                        "%m/%d/%y");
335        public static final ISODateComponent DAY_OF_MONTH = new FieldComponent(
336                        "%e", "\\d|[12]\\d|3[01]", Calendar.DAY_OF_MONTH, "%2d");
337        public static final ISODateComponent DATE_ISO8601 = new AliasComponent(
338                        "%F", "%Y-%m-%d");
339        public static final ISODateComponent WEEK_BASED_YEAR_2_DIGITS = new FieldComponent(
340                        "%g", "\\d\\d", Calendar.YEAR, "%02d");
341        public static final ISODateComponent WEEK_BASED_YEAR_4_DIGITS = new FieldComponent(
342                        "%G", "\\d{4}", Calendar.YEAR, "%04d");
343        public static final ISODateComponent ABBREVIATED_MONTH_NAME_ALIAS = new AliasComponent(
344                        "%h", "%b");
345        public static final ISODateComponent HOUR_OF_DAY = new FieldComponent("%H",
346                        "[01]\\d|2[0123]", Calendar.HOUR_OF_DAY, "%02d");
347        public static final ISODateComponent HOUR = new FieldComponent("%I",
348                        "0\\d|1[012]", Calendar.HOUR, "%02d");
349        public static final ISODateComponent DAY_OF_YEAR = new FieldComponent("%j",
350                        "[012]\\d\\d|3[0-5]\\d|36[0-6]", Calendar.DAY_OF_YEAR, "%03d");
351        public static final ISODateComponent MILLISECOND = new FieldComponent("%k",
352                        "\\d{3}", Calendar.MILLISECOND, "%03d");
353        public static final ISODateComponent EPOCH = new EpochComponent();
354        public static final ISODateComponent MONTH = new FieldComponent("%m",
355                        "0[1-9]|1[012]", Calendar.MONTH, -1, "%02d");
356        public static final ISODateComponent MINUTE = new FieldComponent("%M",
357                        "[0-5]\\d", Calendar.MINUTE, "%02d");
358        public static final ISODateComponent NEW_LINE = new AliasComponent("%n",
359                        "\n");
360        public static final ISODateComponent AM_PM = new AMPMComponent();
361        public static final ISODateComponent LOCALE_CLOCK_TIME_12_HOUR = new NotImplementedComponent(
362                        "%r", "");
363        public static final ISODateComponent HOUR_AND_MINUTE = new AliasComponent(
364                        "%R", "%H:%M");
365        public static final ISODateComponent SECOND = new FieldComponent("%S",
366                        "[0-5]\\d|60", Calendar.SECOND, "%02d");
367        public static final ISODateComponent TABULATION = new AliasComponent("%t",
368                        "\t");
369        public static final ISODateComponent TIME_ISO8601 = new AliasComponent(
370                        "%T", "%H:%M:%S");
371        public static final ISODateComponent DAY_OF_WEEK_1_7 = new FieldComponent(
372                        "%u", "[1-7]", Calendar.DAY_OF_WEEK, -1, "%1d");
373        public static final ISODateComponent WEEK_OF_YEAR_FROM_SUNDAY = new FieldComponent(
374                        "%U", "[0-4]\\d|5[0123]", Calendar.WEEK_OF_YEAR, 1, "%2d");
375        public static final ISODateComponent WEEK_NUMBER_ISO8601 = new NotImplementedComponent(
376                        "%V", "0[1-9]|[2-4]\\d|5[0123]");
377        public static final ISODateComponent DAY_OF_WEEK_0_6 = new FieldComponent(
378                        "%w", "[0-6]", Calendar.DAY_OF_WEEK, "%01d");
379        public static final ISODateComponent WEEK_OF_YEAR_FROM_MONDAY = new FieldComponent(
380                        "%W", "[0-4]\\d|5[0123]", Calendar.WEEK_OF_YEAR, "%02d");
381        public static final ISODateComponent LOCALE_DATE_REPRESENTATION = new NotImplementedComponent(
382                        "%x", "");
383        public static final ISODateComponent LOCALE_TIME_REPRESENTATION = new NotImplementedComponent(
384                        "%X", "");
385        public static final ISODateComponent YEAR_2_DIGITS = new FieldComponent(
386                        "%y", "\\d\\d", Calendar.YEAR, "%02d");
387        public static final ISODateComponent YEAR_4_DIGITS = new FieldComponent(
388                        "%Y", "\\d{4}", Calendar.YEAR, "%04d");
389        public static final ISODateComponent UTC_OFFSET = new UTCOffsetComponent();
390        public static final ISODateComponent LOCALE_TIME_ZONE_NAME = new NotImplementedComponent(
391                        "%Z", "\\w*");
392        public static final ISODateComponent PERCENT = new AliasComponent("%%", "%");
393}