001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2000, Institut de Recherche pour le Développement
006: * (C) 1999, Pêches et Océans Canada
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: */
018: package org.geotools.axis;
019:
020: // Date and time
021: import java.io.PrintWriter;
022: import java.io.StringWriter;
023: import java.text.DateFormat;
024: import java.util.Arrays;
025: import java.util.Calendar;
026: import java.util.Date;
027: import java.util.Locale;
028: import java.util.TimeZone;
029:
030: /**
031: * Itérateur balayant les barres et étiquettes de graduation d'un axe du temps. Cet itérateur
032: * retourne les positions des graduations à partir de la date la plus ancienne jusqu'à la date
033: * la plus récente. Il choisit les intervalles de graduation en supposant qu'on utilise un
034: * calendrier grégorien.
035: *
036: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/axis/DateIterator.java $
037: * @version $Id: DateIterator.java 20883 2006-08-07 13:48:09Z jgarnett $
038: * @author Martin Desruisseaux
039: */
040: final class DateIterator implements TickIterator {
041: /**
042: * Nombre de millisecondes dans certaines unités de temps.
043: */
044: private static final long SEC = 1000, MIN = 60 * SEC,
045: HRE = 60 * MIN, DAY = 24 * HRE, YEAR = 365 * DAY
046: + (DAY / 4) - (DAY / 100) + (DAY / 400),
047: MNT = YEAR / 12;
048:
049: /**
050: * Liste des intervales souhaités pour la graduation. Les éléments de
051: * cette table doivent obligatoirement apparaître en ordre croissant.
052: * Voici un exemple d'interprétation: la présence de {@@code 5*MIN}
053: * suivit de {@code 10*MIN} implique que si le pas estimé se trouve
054: * entre 5 et 10 minutes, ce sera le pas de 10 minutes qui sera sélectionné.
055: */
056: private static final long[] INTERVAL = { SEC, 2 * SEC, 5 * SEC,
057: 10 * SEC, 15 * SEC, 20 * SEC, 30 * SEC, MIN, 2 * MIN,
058: 5 * MIN, 10 * MIN, 15 * MIN, 20 * MIN, 30 * MIN, HRE,
059: 2 * HRE, 3 * HRE, 4 * HRE, 6 * HRE, 8 * HRE, 12 * HRE, DAY,
060: 2 * DAY, 3 * DAY, 7 * DAY, 14 * DAY, 21 * DAY, MNT,
061: 2 * MNT, 3 * MNT, 4 * MNT, 6 * MNT, YEAR, 2 * YEAR,
062: 3 * YEAR, 4 * YEAR, 5 * YEAR };
063:
064: /**
065: * Intervalles des graduations principales et des sous-graduations correspondants
066: * à chaque des intervalles du tableau {@link #INTERVAL}. Cette classe cherchera
067: * d'abord un intervalle en millisecondes dans le tableau {@link #INTERVAL}, puis
068: * traduira cet intervalle en champ du calendrier grégorien en lisant les éléments
069: * correspondants de ce tableau {@link #ROLL}.
070: */
071: private static final byte[] ROLL = {
072: 1,
073: (byte) Calendar.SECOND,
074: 25,
075: (byte) Calendar.MILLISECOND, // x10 millis
076: 2,
077: (byte) Calendar.SECOND,
078: 50,
079: (byte) Calendar.MILLISECOND, // x10 millis
080: 5, (byte) Calendar.SECOND, 1, (byte) Calendar.SECOND, 10,
081: (byte) Calendar.SECOND, 2, (byte) Calendar.SECOND, 15,
082: (byte) Calendar.SECOND, 5, (byte) Calendar.SECOND, 20,
083: (byte) Calendar.SECOND, 5, (byte) Calendar.SECOND, 30,
084: (byte) Calendar.SECOND, 5, (byte) Calendar.SECOND, 1,
085: (byte) Calendar.MINUTE, 10, (byte) Calendar.SECOND, 2,
086: (byte) Calendar.MINUTE, 30, (byte) Calendar.SECOND, 5,
087: (byte) Calendar.MINUTE, 1, (byte) Calendar.MINUTE, 10,
088: (byte) Calendar.MINUTE, 2, (byte) Calendar.MINUTE, 15,
089: (byte) Calendar.MINUTE, 5, (byte) Calendar.MINUTE, 20,
090: (byte) Calendar.MINUTE, 5, (byte) Calendar.MINUTE, 30,
091: (byte) Calendar.MINUTE, 5, (byte) Calendar.MINUTE, 1,
092: (byte) Calendar.HOUR_OF_DAY, 15, (byte) Calendar.MINUTE, 2,
093: (byte) Calendar.HOUR_OF_DAY, 30, (byte) Calendar.MINUTE, 3,
094: (byte) Calendar.HOUR_OF_DAY, 30, (byte) Calendar.MINUTE, 4,
095: (byte) Calendar.HOUR_OF_DAY, 1,
096: (byte) Calendar.HOUR_OF_DAY, 6,
097: (byte) Calendar.HOUR_OF_DAY, 1,
098: (byte) Calendar.HOUR_OF_DAY, 8,
099: (byte) Calendar.HOUR_OF_DAY, 2,
100: (byte) Calendar.HOUR_OF_DAY, 12,
101: (byte) Calendar.HOUR_OF_DAY, 2,
102: (byte) Calendar.HOUR_OF_DAY, 1,
103: (byte) Calendar.DAY_OF_MONTH, 4,
104: (byte) Calendar.HOUR_OF_DAY, 2,
105: (byte) Calendar.DAY_OF_MONTH, 6,
106: (byte) Calendar.HOUR_OF_DAY, 3,
107: (byte) Calendar.DAY_OF_MONTH, 12,
108: (byte) Calendar.HOUR_OF_DAY, 7,
109: (byte) Calendar.DAY_OF_MONTH, 1,
110: (byte) Calendar.DAY_OF_MONTH, 14,
111: (byte) Calendar.DAY_OF_MONTH, 2,
112: (byte) Calendar.DAY_OF_MONTH, 21,
113: (byte) Calendar.DAY_OF_MONTH, 7,
114: (byte) Calendar.DAY_OF_MONTH, 1, (byte) Calendar.MONTH, 7,
115: (byte) Calendar.DAY_OF_MONTH, 2, (byte) Calendar.MONTH, 14,
116: (byte) Calendar.DAY_OF_MONTH, 3, (byte) Calendar.MONTH, 14,
117: (byte) Calendar.DAY_OF_MONTH, 4, (byte) Calendar.MONTH, 1,
118: (byte) Calendar.MONTH, 6, (byte) Calendar.MONTH, 1,
119: (byte) Calendar.MONTH, 1, (byte) Calendar.YEAR, 2,
120: (byte) Calendar.MONTH, 2, (byte) Calendar.YEAR, 4,
121: (byte) Calendar.MONTH, 3, (byte) Calendar.YEAR, 6,
122: (byte) Calendar.MONTH, 4, (byte) Calendar.YEAR, 1,
123: (byte) Calendar.YEAR, 5, (byte) Calendar.YEAR, 1,
124: (byte) Calendar.YEAR };
125:
126: /**
127: * Nombre de colonne dans le tableau {@link ROLL}. Le tableau {@link ROLL} doit être
128: * interprété comme une matrice de 4 colonnes et d'un nombre indéterminé de lignes.
129: */
130: private static final int ROLL_WIDTH = 4;
131:
132: /**
133: * Liste des champs de dates qui apparaissent dans le tableau {@link ROLL}. Cette liste
134: * doit être du champ le plus grand (YEAR) vers le champ le plus petit (MILLISECOND).
135: */
136: private static final int[] FIELD = { Calendar.YEAR, Calendar.MONTH,
137: Calendar.DAY_OF_MONTH, Calendar.HOUR_OF_DAY,
138: Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND };
139:
140: /**
141: * Liste des noms des champs (à des fins de déboguage seulement).
142: * Cette liste doit être dans le même ordre que les éléments de {@link #FIELD}.
143: */
144: private static final String[] FIELD_NAME = { "YEAR", "MONTH",
145: "DAY", "HOUR", "MINUTE", "SECOND", "MILLISECOND" };
146:
147: /**
148: * Date de la première graduation principale. Cette valeur est fixée par {@link #init}.
149: */
150: private long minimum;
151:
152: /**
153: * Date limite des graduations. La dernière graduation ne sera pas nécessairement à
154: * cette date. Cette valeur est fixée par {@link #init}.
155: */
156: private long maximum;
157:
158: /**
159: * Estimation de l'intervalle entre deux graduations principales.
160: * Cette valeur est fixée par {@link #init}.
161: */
162: private long increment;
163:
164: /**
165: * Longueur de l'axe (en points). Cette information est conservée afin d'éviter de
166: * refaire toute la procédure {@link #init} si les paramètres n'ont pas changés.
167: */
168: private float visualLength;
169:
170: /**
171: * Espace à laisser (en points) entre les graduations principales.
172: * Cette information est conservée afin d'éviter de refaire toute
173: * la procédure {@link #init} si les paramètres n'ont pas changés.
174: */
175: private float visualTickSpacing;
176:
177: /**
178: * Nombre de fois qu'il faut incrémenter le champ {@link #tickField} du
179: * calendrier pour passer à la graduation suivante. Cette opération peut
180: * se faire avec {@code calendar.add(tickField, tickAdd)}.
181: */
182: private int tickAdd;
183:
184: /**
185: * Champ du calendrier qu'il faut incrémenter pour passer à la graduation suivante.
186: * Cette opération peut se faire avec {@code calendar.add(tickField, tickAdd)}.
187: */
188: private int tickField;
189:
190: /**
191: * Nombre de fois qu'il faut incrémenter le champ {@link #tickField} du calendrier pour
192: * passer à la sous-graduation suivante. Cette opération peut se faire avec
193: * {@code calendar.add(tickField, tickAdd)}.
194: */
195: private int subTickAdd;
196:
197: /**
198: * Champ du calendrier qu'il faut incrémenter pour passer à la sous-graduation suivante.
199: * Cette opération peut se faire avec {@code calendar.add(tickField, tickAdd)}.
200: */
201: private int subTickField;
202:
203: /**
204: * Date de la graduation principale ou secondaire actuelle.
205: * Cette valeur sera modifiée à chaque appel à {@link #next}.
206: */
207: private long value;
208:
209: /**
210: * Date de la prochaine graduation principale. Cette
211: * valeur sera modifiée à chaque appel à {@link #next}.
212: */
213: private long nextTick;
214:
215: /**
216: * Date de la prochaine graduation secondaire. Cette
217: * valeur sera modifiée à chaque appel à {@link #next}.
218: */
219: private long nextSubTick;
220:
221: /**
222: * Indique si {@link #value} représente une graduation principale.
223: */
224: private boolean isMajorTick;
225:
226: /**
227: * Valeurs de {@link #value}, {@link #nextTick} et {@link #nextSubTick0}
228: * immédiatement après l'appel de {@link #rewind}.
229: */
230: private long value0, nextTick0, nextSubTick0;
231:
232: /**
233: * Valeur de {@link #isMajorTick} immédiatement après l'appel de {@link #rewind}.
234: */
235: private boolean isMajorTick0;
236:
237: /**
238: * Calendrier servant à avancer d'une certaine période de temps (jour, semaine, mois...).
239: * <strong>Note: Par convention et pour des raisons de performances (pour éviter d'imposer
240: * au calendrier de recalculer ses champs trop souvent), ce calendrier devrait toujours
241: * contenir la date {@link #nextSubTick}.
242: */
243: private Calendar calendar;
244:
245: /**
246: * Objet temporaire à utiliser pour passer des dates
247: * en argument à {@link #calendar} et {@link #format}.
248: */
249: private final Date date = new Date();
250:
251: /**
252: * Format à utiliser pour écrire les étiquettes de graduation. Ce format ne
253: * sera construit que la première fois où {@link #currentLabel} sera appelée.
254: */
255: private transient DateFormat format;
256:
257: /**
258: * Code du format utilisé pour construire le champ de date de {@link #format}. Les codes
259: * valides sont notamment {@link DateFormat#SHORT}, {@link DateFormat#MEDIUM} ou {@link
260: * DateFormat#LONG}. La valeur -1 indique que le format ne contient pas de champ de date,
261: * seulement un champ des heures.
262: */
263: private transient int dateFormat = -1;
264:
265: /**
266: * Code du format utilisé pour construire le champ des heures de {@link #format}. Les codes
267: * valides sont notamment {@link DateFormat#SHORT}, {@link DateFormat#MEDIUM} ou {@link
268: * DateFormat#LONG}. La valeur -1 indique que le format ne contient pas de champ des heures,
269: * seulement un champ de date.
270: */
271: private transient int timeFormat = -1;
272:
273: /**
274: * Indique si {@link #format} est valide. Le format peut devenir invalide si {@link #init} a
275: * été appelée. Dans ce cas, il peut falloir changer le nombre de chiffres après la virgule
276: * qu'il écrit.
277: */
278: private transient boolean formatValid;
279:
280: /**
281: * Conventions à utiliser pour le formatage des nombres.
282: */
283: private Locale locale;
284:
285: /**
286: * Construit un itérateur pour la graduation d'un axe du temps. La méthode {@link #init}
287: * <u>doit</u> être appelée avant que l'itérateur ne soit utilisable.
288: *
289: * @param timezone Fuseau horaire des dates.
290: * @param locale Conventions à utiliser pour le formatage des dates.
291: */
292: protected DateIterator(final TimeZone timezone, final Locale locale) {
293: assert INTERVAL.length * ROLL_WIDTH == ROLL.length;
294: calendar = Calendar.getInstance(timezone, locale);
295: this .locale = locale;
296: }
297:
298: /**
299: * Initialise l'itérateur.
300: *
301: * @param minimum Date minimale de la première graduation.
302: * @param maximum Date limite des graduations. La dernière graduation
303: * ne sera pas nécessairement à cette date.
304: * @param visualLength Longueur visuelle de l'axe sur laquelle tracer la graduation.
305: * Cette longueur doit être exprimée en pixels ou en points.
306: * @param visualTickSpace Espace à laisser visuellement entre deux marques de graduation.
307: * Cet espace doit être exprimé en pixels ou en points (1/72 de pouce).
308: */
309: protected void init(final long minimum, final long maximum,
310: final float visualLength, final float visualTickSpacing) {
311: if (minimum == this .minimum && maximum == this .maximum
312: && visualLength == this .visualLength
313: && visualTickSpacing == this .visualTickSpacing) {
314: rewind();
315: return;
316: }
317: AbstractGraduation.ensureNonNull("visualLength", visualLength);
318: AbstractGraduation.ensureNonNull("visualTickSpacing",
319: visualTickSpacing);
320: this .visualLength = visualLength;
321: this .visualTickSpacing = visualTickSpacing;
322: this .formatValid = false;
323: this .minimum = minimum;
324: this .maximum = maximum;
325: this .increment = Math.round((maximum - minimum)
326: * ((double) visualTickSpacing / (double) visualLength));
327: /*
328: * Après avoir fait une estimation de l'intervalle d'échantillonage,
329: * vérifie si on trouve cette estimation dans le tableau 'INTERVAL'.
330: * Si on trouve la valeur exacte, tant mieux! Sinon, on cherchera
331: * l'intervalle le plus proche.
332: */
333: int index = Arrays.binarySearch(INTERVAL, increment);
334: if (index < 0) {
335: index = ~index;
336: if (index == 0) {
337: // L'intervalle est plus petit que le
338: // plus petit élément de 'INTERVAL'.
339: round(Calendar.MILLISECOND);
340: findFirstTick();
341: return;
342: } else if (index >= INTERVAL.length) {
343: // L'intervalle est plus grand que le
344: // plus grand élément de 'INTERVAL'.
345: increment /= YEAR;
346: round(Calendar.YEAR);
347: increment *= YEAR;
348: findFirstTick();
349: return;
350: } else {
351: // L'index pointe vers un intervalle plus grand que
352: // l'intervalle demandé. Vérifie si l'intervalle
353: // inférieur ne serait pas plus proche.
354: if (increment - INTERVAL[index - 1] < INTERVAL[index]
355: - increment) {
356: index--;
357: }
358: }
359: }
360: this .increment = INTERVAL[index];
361: index *= ROLL_WIDTH;
362: this .tickAdd = ROLL[index + 0];
363: this .tickField = ROLL[index + 1];
364: this .subTickAdd = ROLL[index + 2];
365: this .subTickField = ROLL[index + 3];
366: if (subTickField == Calendar.MILLISECOND) {
367: subTickAdd *= 10;
368: }
369: findFirstTick();
370: }
371:
372: /**
373: * Arrondi {@link #increment} à un nombre qui se lit bien. Le nombre
374: * choisit sera un de ceux de la suite 1, 2, 5, 10, 20, 50, 100, 200, 500, etc.
375: */
376: private void round(final int field) {
377: int factor = 1;
378: while (factor <= increment) {
379: factor *= 10;
380: }
381: if (factor >= 10) {
382: factor /= 10;
383: }
384: increment /= factor;
385: if (increment <= 0)
386: increment = 1;
387: else if (increment >= 3 && increment <= 4)
388: increment = 5;
389: else if (increment >= 6)
390: increment = 10;
391: increment = Math.max(increment * factor, 5);
392: tickAdd = (int) increment;
393: subTickAdd = (int) (increment / (increment == 2 ? 4 : 5));
394: tickField = field;
395: subTickField = field;
396: }
397:
398: /**
399: * Replace l'itérateur sur la première graduation. La position de la première graduation
400: * sera calculée et retenue pour un positionnement plus rapide à l'avenir.
401: */
402: private void findFirstTick() {
403: calendar.clear();
404: value = minimum;
405: date.setTime(value);
406: calendar.setTime(date);
407: if (true) {
408: // Arrondie la date de départ. Note: ce calcul exige que
409: // tous les champs commencent à 0 plutôt que 1, y compris
410: // les mois et le jour du mois.
411: final int offset = calendar.getActualMinimum(tickField);
412: int toRound = calendar.get(tickField) - offset;
413: toRound = (toRound / tickAdd) * tickAdd;
414: calendar.set(tickField, toRound + offset);
415: }
416: truncate(calendar, tickField);
417: nextTick = calendar.getTime().getTime();
418: nextSubTick = nextTick;
419: while (nextTick < minimum) {
420: calendar.add(tickField, tickAdd);
421: nextTick = calendar.getTime().getTime();
422: }
423: date.setTime(nextSubTick);
424: calendar.setTime(date);
425: while (nextSubTick < minimum) {
426: calendar.add(subTickField, subTickAdd);
427: nextSubTick = calendar.getTime().getTime();
428: }
429: /* 'calendar' a maintenant la valeur 'nextSubTick', comme le veut la spécification de
430: * ce champ. On appelle maintenant 'next' pour transférer cette valeur 'nextSubTick'
431: * vers 'value'. Notez que 'next' peut être appelée même si value>maximum.
432: */
433: next();
434:
435: // Retient les positions trouvées.
436: this .value0 = this .value;
437: this .nextTick0 = this .nextTick;
438: this .nextSubTick0 = this .nextSubTick;
439: this .isMajorTick0 = this .isMajorTick;
440:
441: assert calendar.getTime().getTime() == nextSubTick;
442: }
443:
444: /**
445: * Met à 0 tous les champs du calendrier inférieur au champ {@code field}
446: * spécifié. Note: si le calendrier spécifié est {@link #calendar}, il est de
447: * la responsabilité de l'appelant de restituer {@link #calendar} dans son état
448: * correct après l'appel de cette méthode.
449: */
450: private static void truncate(final Calendar calendar, int field) {
451: for (int i = 0; i < FIELD.length; i++) {
452: if (FIELD[i] == field) {
453: calendar.get(field); // Force la mise à jour des champs.
454: while (++i < FIELD.length) {
455: field = FIELD[i];
456: calendar.set(field, calendar
457: .getActualMinimum(field));
458: }
459: break;
460: }
461: }
462: }
463:
464: /**
465: * Indique s'il reste des graduations à retourner. Cette méthode retourne {@code true}
466: * tant que {@link #currentValue} ou {@link #currentLabel} peuvent être appelées.
467: */
468: public boolean hasNext() {
469: return value <= maximum;
470: }
471:
472: /**
473: * Indique si la graduation courante est une graduation majeure.
474: *
475: * @return {@code true} si la graduation courante est une graduation majeure, ou
476: * {@code false} si elle est une graduation mineure.
477: */
478: public boolean isMajorTick() {
479: return isMajorTick;
480: }
481:
482: /**
483: * Returns the position where to draw the current tick. The position is scaled
484: * from the graduation's minimum to maximum. This is usually the same number
485: * than {@link #currentValue}.
486: */
487: public double currentPosition() {
488: return value;
489: }
490:
491: /**
492: * Retourne la valeur de la graduation courante. Cette méthode
493: * peut être appelée pour une graduation majeure ou mineure.
494: */
495: public double currentValue() {
496: return value;
497: }
498:
499: /**
500: * Retourne l'étiquette de la graduation courante. On n'appele généralement
501: * cette méthode que pour les graduations majeures, mais elle peut aussi
502: * être appelée pour les graduations mineures. Cette méthode retourne
503: * {@code null} s'il n'y a pas d'étiquette pour la graduation courante.
504: */
505: public String currentLabel() {
506: if (!formatValid) {
507: date.setTime(minimum);
508: calendar.setTime(date);
509: final int firstDay = calendar.get(Calendar.DAY_OF_YEAR);
510: final int firstYear = calendar.get(Calendar.YEAR);
511:
512: date.setTime(maximum);
513: calendar.setTime(date);
514: final int lastDay = calendar.get(Calendar.DAY_OF_YEAR);
515: final int lastYear = calendar.get(Calendar.YEAR);
516:
517: final int dateFormat = (firstYear == lastYear && firstDay == lastDay) ? -1
518: : DateFormat.MEDIUM;
519: final int timeFormat;
520:
521: if (increment >= DAY)
522: timeFormat = -1;
523: else if (increment >= MIN)
524: timeFormat = DateFormat.SHORT;
525: else if (increment >= SEC)
526: timeFormat = DateFormat.MEDIUM;
527: else
528: timeFormat = DateFormat.LONG;
529:
530: if (dateFormat != this .dateFormat
531: || timeFormat != this .timeFormat || format == null) {
532: this .dateFormat = dateFormat;
533: this .timeFormat = timeFormat;
534: if (dateFormat == -1) {
535: if (timeFormat == -1) {
536: format = DateFormat.getDateInstance(
537: DateFormat.DEFAULT, locale);
538: } else {
539: format = DateFormat.getTimeInstance(timeFormat,
540: locale);
541: }
542: } else if (timeFormat == -1) {
543: format = DateFormat.getDateInstance(dateFormat,
544: locale);
545: } else {
546: format = DateFormat.getDateTimeInstance(dateFormat,
547: timeFormat, locale);
548: }
549: format.setCalendar(calendar);
550: }
551: formatValid = true;
552: }
553: date.setTime(value);
554: final String label = format.format(date);
555: // Remet 'calendar' dans l'état qu'il est sencé avoir
556: // d'après la spécification du champ {@link #calendar}.
557: date.setTime(nextSubTick);
558: calendar.setTime(date);
559: return label;
560: }
561:
562: /**
563: * Passe à la graduation suivante.
564: */
565: public void next() {
566: assert calendar.getTime().getTime() == nextSubTick;
567: if (nextSubTick < nextTick) {
568: isMajorTick = false;
569: value = nextSubTick;
570: /*
571: * IMPORTANT: On suppose ici que 'calendar' a déjà la date 'nextSubTick'. Si ce
572: * n'était pas le cas, il faudrait ajouter les lignes suivantes:
573: */
574: if (false) {
575: date.setTime(value);
576: calendar.setTime(date);
577: // 'setTime' oblige 'calendar' à recalculer ses
578: // champs, ce qui a un impact sur la performance.
579: }
580: calendar.add(subTickField, subTickAdd);
581: nextSubTick = calendar.getTime().getTime();
582: // 'calendar' contient maintenant la date 'nextSubTick',
583: // comme le veut la spécification du champ {@link #calendar}.
584: } else {
585: nextMajor();
586: }
587: }
588:
589: /**
590: * Passe directement à la graduation majeure suivante.
591: */
592: public void nextMajor() {
593: isMajorTick = true;
594: value = nextTick;
595: date.setTime(value);
596:
597: calendar.setTime(date);
598: calendar.add(tickField, tickAdd);
599: truncate(calendar, tickField);
600: nextTick = calendar.getTime().getTime();
601:
602: calendar.setTime(date);
603: calendar.add(subTickField, subTickAdd);
604: nextSubTick = calendar.getTime().getTime();
605: // 'calendar' contient maintenant la date 'nextSubTick',
606: // comme le veut la spécification du champ {@link #calendar}.
607: }
608:
609: /**
610: * Replace l'itérateur sur la première graduation.
611: */
612: public void rewind() {
613: this .value = value0;
614: this .nextTick = nextTick0;
615: this .nextSubTick = nextSubTick0;
616: this .isMajorTick = isMajorTick0;
617: // Pour être en accord avec la spécification
618: // du champs {@link #calendar}...
619: date.setTime(nextSubTick);
620: calendar.setTime(date);
621: }
622:
623: /**
624: * Retourne les conventions à utiliser pour écrire les étiquettes de graduation.
625: */
626: public Locale getLocale() {
627: return locale;
628: }
629:
630: /**
631: * Modifie les conventions à utiliser pour écrire les étiquettes de graduation.
632: */
633: public void setLocale(final Locale locale) {
634: if (!locale.equals(this .locale)) {
635: calendar = Calendar.getInstance(getTimeZone(), locale);
636: format = null;
637: formatValid = false;
638: // Pour être en accord avec la spécification du champs {@link #calendar}...
639: date.setTime(nextSubTick);
640: calendar.setTime(date);
641: }
642: assert calendar.getTime().getTime() == nextSubTick;
643: }
644:
645: /**
646: * Retourne le fuseau horaire utilisé pour exprimer les dates dans la graduation.
647: */
648: public TimeZone getTimeZone() {
649: return calendar.getTimeZone();
650: }
651:
652: /**
653: * Modifie le fuseau horaire utilisé pour exprimer les dates dans la graduation.
654: */
655: public void setTimeZone(final TimeZone timezone) {
656: if (!timezone.equals(getTimeZone())) {
657: calendar.setTimeZone(timezone);
658: format = null;
659: formatValid = false;
660: // Pour être en accord avec la spécification
661: // du champs {@link #calendar}...
662: date.setTime(nextSubTick);
663: calendar.setTime(date);
664: }
665: assert calendar.getTime().getTime() == nextSubTick;
666: }
667:
668: /**
669: * Retourne le nom du champ de {@link Calendar} correspondant à la valeur spécifiée.
670: */
671: private static String getFieldName(final int field) {
672: for (int i = 0; i < FIELD.length; i++) {
673: if (FIELD[i] == field) {
674: return FIELD_NAME[i];
675: }
676: }
677: return String.valueOf(field);
678: }
679:
680: /**
681: * Returns a string representation of this iterator. Used for debugging purpose only.
682: */
683: public String toString() {
684: if (true) {
685: // Note: in this particular case, using PrintWriter with 'println' generates
686: // less bytecodes than chaining StringBuffer.append(...) calls.
687: final StringWriter buf = new StringWriter();
688: final PrintWriter out = new PrintWriter(buf);
689: final DateFormat format = DateFormat.getDateTimeInstance();
690: format.setTimeZone(calendar.getTimeZone());
691: out.print("Minimum = ");
692: out.println(format.format(new Date(minimum)));
693: out.print("Maximum = ");
694: out.println(format.format(new Date(maximum)));
695: out.print("Increment = ");
696: out.print(increment / (24 * 3600000f));
697: out.println(" days");
698: out.print("Tick inc. = ");
699: out.print(tickAdd);
700: out.print(' ');
701: out.println(getFieldName(tickField));
702: out.print("SubTick inc. = ");
703: out.print(subTickAdd);
704: out.print(' ');
705: out.println(getFieldName(subTickField));
706: out.print("Next tick = ");
707: out.println(format.format(new Date(nextTick)));
708: out.print("Next subtick = ");
709: out.println(format.format(new Date(nextSubTick)));
710: out.flush();
711: return buf.toString();
712: } else {
713: return super.toString();
714: }
715: }
716: }
|