My experience with JFreeChart

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
grzegorz.majer
Posts: 8
Joined: Tue Jul 08, 2003 11:33 am
Location: Poland

My experience with JFreeChart

Post by grzegorz.majer » Sun Jan 18, 2004 2:03 am

Hi,
One month ago I used the JFreeChart library. I think it is great. I’ve found some bugs there and because I had some time I decided do describe them. I hope it will be helpful to remove this bugs in futher versions and maybe it will be interesting for people who use the JFreeChart. This problem occurs in 0.9.14 version (I haven’t checked it in new versions).

1) Chart with a BarRender draws incorrectly for the secondaryDataset and secondaryRangeAxis when values are less then 0.

Example:
In file OverlaidXYPlotDemo2.java in createDataset2B(...) replace code:
I.
From:

Code: Select all

72.6
To:

Code: Select all

-72.6
II.
From:

Code: Select all

plot.setSecondaryRenderer(1, new StandardXYItemRenderer());
To:

Code: Select all

plot.setSecondaryRenderer(1, new XYBarRenderer());
Incorrect Result:
Image


Problem solution:
In file XYBarRenderer.java in drawItem(...) add to first line this code:

Code: Select all

this.translatedRangeZero = rangeAxis.translateValueToJava2D(0.0, dataArea,plot.getRangeAxisEdge());
Result after fix:
Image


2) When only horizontal zoom is enabled: draging mouse up cause trigger autoRange.
Example:
In file OverlaidXYPlotDemo2.java after this line:

Code: Select all

ChartPanel panel = new ChartPanel(chart, true, true, true, true, true);
Add:

Code: Select all

panel.setVerticalZoom(false);
panel.setHorizontalZoom(true);
Solution:
In ChartPanel in mouseReleased(...) replace:
From:

Code: Select all

    if (e.getX() < zoomPoint.getX() || e.getY() < zoomPoint.getY()) {
To:

Code: Select all

if (e.getX() < zoomPoint.getX() && horizontalZoom ||
    e.getY() < zoomPoint.getY() && verticalZoom) {
3) Bugs connected with SegmentedTimeLine: this problem happens for adjustForDaylightSaving = true (Well, it requires recompiling this class).

Problems:
I. Incorrectly working zoom (area after zoom is different from the selected area),
II. No visible houres on the time axis.
III. For Daylight Saving Time the first hour is cut.
IV. For Daylight Saving Time incorrectly works time exception (the exception is shifted one day).
V. No visible values on the time axis – it requires implement WeekTickUnit.
V. Some others which I’ve forgotten

Example:

Code: Select all

package org.jfree.chart.demo;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.renderer.XYBarRenderer;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.axis.*;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.Hour;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.ui.ApplicationFrame;

public class SegmentedHighLowChartDemo extends ApplicationFrame {

    public SegmentedHighLowChartDemo(String title) {

        super(title);
        Calendar cal = GregorianCalendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);

        cal.set(2004, 7, 2);
        Date startDate = cal.getTime();
        cal.set(2004, 7, 15);
        Date endDate = cal.getTime();
        Date[] holidays = new Date[1];
        cal.set(2004, 7, 12);
        holidays[0] = cal.getTime();

        SegmentedTimeline timeline = SegmentedTimeline.newFifteenMinuteTimeline();
        for (int i = 0; i < holidays.length; i++) {
            timeline.addBaseTimelineException(holidays[i]);
        }
        timeline.addBaseTimelineExclusions(startDate.getTime(), endDate.getTime());

        JFreeChart chart;
        TimeSeriesCollection timeCollection = new TimeSeriesCollection();
        timeCollection.addSeries(createTimeSeries(startDate, endDate, timeline));

        chart = ChartFactory.createTimeSeriesChart(title, "Time", "Value", timeCollection, true, true, true);
        ((XYPlot) chart.getPlot()).setRenderer(new XYBarRenderer());
        DateAxis axis = (DateAxis) chart.getXYPlot().getDomainAxis();
        axis.setTimeline(timeline);
        axis.setVerticalTickLabels(true);
        axis.setAutoRange(true);
        TickUnits units = new TickUnits();
        units.add(new DateTickUnit(DateTickUnit.HOUR, 1, DateTickUnit.MINUTE, 5, 
                new SimpleDateFormat("MM-dd, HH:mm")));
        units.add(new DateTickUnit(DateTickUnit.HOUR, 2, DateTickUnit.MINUTE, 10, 
                new SimpleDateFormat("MM-dd, HH:mm")));
        units.add(new DateTickUnit(DateTickUnit.HOUR, 3, DateTickUnit.MINUTE, 30, 
                new SimpleDateFormat("MM-dd, HH:mm")));
        units.add(new DateTickUnit(DateTickUnit.HOUR, 4, DateTickUnit.HOUR, 1, 
                new SimpleDateFormat("MM-dd, HH:mm")));
        units.add(new DateTickUnit(DateTickUnit.DAY, 1, DateTickUnit.HOUR, 1, 
                new SimpleDateFormat("MM-dd")));
        units.add(new DateTickUnit(DateTickUnit.DAY, 2, DateTickUnit.HOUR, 1, 
                new SimpleDateFormat("MM-dd")));
        units.add(new DateTickUnit(DateTickUnit.DAY, 7, DateTickUnit.DAY, 1, new SimpleDateFormat("MM-dd")));
        units.add(new DateTickUnit(DateTickUnit.DAY, 15, DateTickUnit.DAY, 1, 
                new SimpleDateFormat("MM-dd")));
        axis.setStandardTickUnits(units);

        NumberAxis vaxis = (NumberAxis) chart.getXYPlot().getRangeAxis();
        vaxis.setAutoRangeIncludesZero(false);

        ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
        chartPanel.setHorizontalZoom(true);

        setContentPane(chartPanel);

    }


    /**
     * Starting point for the demonstration application.
     *
     * @param args  ignored.
     */
    public static void main(String[] args) {

        ApplicationFrame frame;
        frame = new SegmentedHighLowChartDemo("Segmented chart");
        frame.pack();
        frame.setVisible(true);

    }


    public static TimeSeries createTimeSeries(Date startDate, Date endDate, Timeline timeline) {

        TimeSeries t1 = new TimeSeries("Test", Hour.class);
        Calendar cal = GregorianCalendar.getInstance();
        cal.setTime(startDate);
        for (; cal.getTime().before(endDate);) {
            cal.add(Calendar.HOUR_OF_DAY, 1);
            if (timeline.containsDomainValue(cal.getTimeInMillis() + (1000 * 60 * 60))) {
                System.out.println("adding =" + cal.getTime());
                t1.add(new Hour(cal.getTime()), new Double(cal.get(Calendar.HOUR_OF_DAY) + 0.3));
            }
        }
        return t1;
    }
}
Incorrect Result:
Image

Image


Solution:
a) Add to DateAxis.java method:

Code: Select all

    
   /**
     * Zooms in on the current range.
     *
     * @param lowerPercent  the new lower bound.
     * @param upperPercent  the new upper bound.
     */
    public void zoomRange(double lowerPercent, double upperPercent) {
        double start = timeline.toTimelineValue(new Date((long)getRange().getLowerBound()));
        double length = (timeline.toTimelineValue((long)getRange().getUpperBound()) - 
                timeline.toTimelineValue((long)getRange().getLowerBound()));
        Range adjusted = null;
        if (isInverted()) {
            adjusted = new DateRange(timeline.toMillisecond((long)(start +
                    (length * (1 - upperPercent)))),
                                 timeline.toMillisecond((long)(start + (length * (1 - lowerPercent)))));
        }
        else {
            adjusted = new DateRange(timeline.toMillisecond((long)(start + length * lowerPercent)),
                    timeline.toMillisecond((long)(start + length * upperPercent)));
        }
        setRange(adjusted);
    }
b) In SegmentedTimeline.java replace:
From:

Code: Select all

    public void addBaseTimelineException(Date date) {
        //addBaseTimelineException(getTime(date));
        addBaseTimelineException(date.getTime());
    }
To:

Code: Select all

    
    public void addBaseTimelineException(Date date) {
        addBaseTimelineException(getTime(date));
    }
c) In SegmentedTimeline.java in toMillisecond(...) replace:
From:

Code: Select all

    getExceptionSegmentCount(lastIndex, result.millisecond - 1)) > 0) {
To:

Code: Select all

    getExceptionSegmentCount(lastIndex, (result.millisecond / segmentSize)* segmentSize - 1)) > 0) {
AND
From:

Code: Select all

    return result.millisecond;
To:

Code: Select all

    return getTimeFromLong(result.millisecond);

d) In SegmentedTimeline.java add:

Code: Select all

    public long getTimeFromLong(long date) {
        long result = date;
        if (this.adjustForDaylightSaving) {
            workingCalendarNoDST.setTimeInMillis(date);
            workingCalendar.set(workingCalendarNoDST.get(Calendar.YEAR),
                                     workingCalendarNoDST.get(Calendar.MONTH),
                                     workingCalendarNoDST.get(Calendar.DATE),
                                     workingCalendarNoDST.get(Calendar.HOUR_OF_DAY),
                                     workingCalendarNoDST.get(Calendar.MINUTE),
                                     workingCalendarNoDST.get(Calendar.SECOND));
            workingCalendar.set(Calendar.MILLISECOND,
                                     workingCalendarNoDST.get(Calendar.MILLISECOND));
            result = workingCalendar.getTimeInMillis();
        }
        return result;
    }

e) In DateAxis.java replace:
From:

Code: Select all

    public boolean isHiddenValue(long millis) {
        return (this.timeline.containsDomainValue(millis) == false);
    }
To:

Code: Select all

    public boolean isHiddenValue(long millis) {
        return (this.timeline.containsDomainValue(new Date(millis)) == false);
    }


f) In DateAxis.java replace:
From:

Code: Select all

    public double translateValueToJava2D(double value, Rectangle2D dataArea,
                                         RectangleEdge edge) {

        value = timeline.toTimelineValue((long) value);
        
        DateRange range = (DateRange) getRange();
        double axisMin = timeline.toTimelineValue(range.getLowerDate().getTime());
        double axisMax = timeline.toTimelineValue(range.getUpperDate());
        ...
To:

Code: Select all

    public double translateValueToJava2D(double value, Rectangle2D dataArea,
                                         RectangleEdge edge) {

        value = timeline.toTimelineValue(new Date((long) value));
        DateRange range = (DateRange) getRange();
        double axisMin = timeline.toTimelineValue(range.getLowerDate());
        double axisMax = timeline.toTimelineValue(range.getUpperDate());

g) In DateAxis.java in translateJava2DtoValue(...) replace:
From:

Code: Select all

        result = timeline.toTimelineValue((long) result);
        return result;
To:

Code: Select all

         return timeline.toMillisecond((long) result);

h) In DateAxis.java replace:
From:

Code: Select all

    protected void autoAdjustRange() {

        Plot plot = getPlot();

        if (plot == null) {
            return;  // no plot, no data
        }

        if (plot instanceof ValueAxisPlot) {
            ValueAxisPlot vap = (ValueAxisPlot) plot;

            Range r = vap.getDataRange(this);
            if (r == null) {
                r = new DateRange();
            }

            long upper = timeline.toTimelineValue((long) r.getUpperBound());
            long lower;
            long fixedAutoRange = (long) getFixedAutoRange();
            if (fixedAutoRange > 0.0) {
                lower = upper - fixedAutoRange;
            }
            else {
                lower = timeline.toTimelineValue((long) r.getLowerBound());
            ...

To:

Code: Select all

    protected void autoAdjustRange() {

        Plot plot = getPlot();

        if (plot == null) {
            return;  // no plot, no data
        }

        if (plot instanceof ValueAxisPlot) {
            ValueAxisPlot vap = (ValueAxisPlot) plot;

            Range r = vap.getDataRange(this);
            if (r == null) {
                if (timeline instanceof SegmentedTimeline) { //Timeline hasn't method getStartTime()
                    r = new DateRange(((SegmentedTimeline)timeline).getStartTime(), 
                            ((SegmentedTimeline)timeline).getStartTime() + 1);
                } else {
                    r = new DateRange();
                }
            }

            long upper = timeline.toTimelineValue(new Date((long) r.getUpperBound()));
            long lower;
            long fixedAutoRange = (long) getFixedAutoRange();
            if (fixedAutoRange > 0.0) {
                lower = upper - fixedAutoRange;
            }
            else {
                lower = timeline.toTimelineValue(new Date((long) r.getLowerBound()));
i) In DateAxis.java replace:
From:

Code: Select all

    protected void selectHorizontalAutoTickUnit(Graphics2D g2, Rectangle2D drawArea,
                                                Rectangle2D dataArea, RectangleEdge edge) {

        double zero = translateValueToJava2D(0.0, dataArea, edge);
        double tickLabelWidth = estimateMaximumTickLabelWidth(g2, getTickUnit());

        // start with the current tick unit...
        TickUnitSource tickUnits = getStandardTickUnits();
        TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
        double x1 = translateValueToJava2D(unit1.getSize(), dataArea, edge);
        double unit1Width = Math.abs(x1 - zero);

        // then extrapolate...
        double guess = (tickLabelWidth / unit1Width) * unit1.getSize();

        DateTickUnit unit2 = (DateTickUnit) tickUnits.getCeilingTickUnit(guess);
        double x2 = translateValueToJava2D(unit2.getSize(), dataArea, edge);
        double unit2Width = Math.abs(x2 - zero);

        tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
        if (tickLabelWidth > unit2Width) {
            unit2 = (DateTickUnit) tickUnits.getLargerTickUnit(unit2);
        }

        setTickUnit(unit2, false, false);

    }

To:

Code: Select all

    protected void selectHorizontalAutoTickUnit(Graphics2D g2, Rectangle2D drawArea,
                                                Rectangle2D dataArea, RectangleEdge edge) {
        long shift = 0;
        if (timeline instanceof SegmentedTimeline) {
            shift = ((SegmentedTimeline)timeline).getStartTime();
        }
        double zero = translateValueToJava2D(shift + 0.0, dataArea, edge);
        double tickLabelWidth = estimateMaximumTickLabelWidth(g2, getTickUnit());
        // start with the current tick unit...
        TickUnitSource tickUnits = getStandardTickUnits();
        TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
        double x1 = translateValueToJava2D(shift + unit1.getSize(), dataArea, edge);
        double unit1Width = Math.abs(x1 - zero);

        // then extrapolate...
        double guess = (tickLabelWidth / unit1Width) * unit1.getSize();

        DateTickUnit unit2 = (DateTickUnit) tickUnits.getCeilingTickUnit(guess);
        double x2 = translateValueToJava2D(shift + unit2.getSize(), dataArea, edge); // TODO: _2
        double unit2Width = Math.abs(x2 - zero);

        tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
        if (tickLabelWidth > unit2Width) {
            unit2 = (DateTickUnit) tickUnits.getLargerTickUnit(unit2);
        }

        setTickUnit(unit2, false, false);

    }
Result after fix:
Image

Image


Best regards
Grzegorz Majer

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Mon Jan 19, 2004 9:57 am

Thanks for taking the time to report these. I've opened a bug report in the SourceForge database for each item:

http://sourceforge.net/tracker/index.ph ... tid=115494

http://sourceforge.net/tracker/index.ph ... tid=115494

http://sourceforge.net/tracker/index.ph ... tid=115494
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

fredatwork
Posts: 27
Joined: Mon Mar 08, 2004 11:28 am

Post by fredatwork » Wed May 19, 2004 10:22 pm

Many thanks to Grzegorz for these fixes, especially those regarding SegmentedTimeline and DateAxis.

I tested these fixes with daylight savings on and it works really better.

I strongly advise David to put these fixes on top of his priority list, espacially the fix regarding method toMilliseconds() of SegmentedTimeline class that corrects an OBVIOUS bug that disables JfreeChart work correctly with segemented timelines (essential for financial charting...).

Below is a junit test case for testing the segmented timeline basic methods : toTimelineValue() and toMilisecond(). This test case uses of copy of SegementedTimeline class (MySegmentedTimeline) that basically activates daylight savings observance.

Also the fixes of DateAxis are OK for daylight savings calendars.

Good work !

Fred

Code: Select all

/**
 * Created on 18 mai 2004
 */
package com.rubis.tests.timeperiodcollection;

import java.util.Calendar;
import java.util.Date;

import junit.framework.TestCase;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

/**
 * @author fredatwork
 */
public class TestSegmentedTimeline3 extends TestCase {

	/**
	 * Constructor
	 * 
	 */
	public TestSegmentedTimeline3() {
		super();
		PropertyConfigurator.configure("G:\\rubis_client\\src\\com\\rubis\\client\\log4j\\log4j.properties");
	}

	/** Logger **/
	static private Logger logger = Logger.getLogger(TestTimePeriodCollection.class);
	
	public void test1() {
				
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2004);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 26);
		cal.set(Calendar.HOUR_OF_DAY, 9);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date date = cal.getTime();
		logger.info("date = " + date + "[" + date.getTime() + "]");						
						
		MySegmentedTimeline timeline = this.getTimeline();		

		long value = timeline.toTimelineValue(date);	
		logger.info("value = " + value);
		
		long ms = timeline.toMillisecond(value);
		logger.info("ms = " + ms);
		
		Calendar cal2 = Calendar.getInstance();
		cal2.setTimeInMillis(ms);
		Date reverted = cal2.getTime();
		logger.info("reverted = " + reverted + '[' + reverted.getTime() + "]");	
		logger.info("");
		
		assertTrue("test1", value == (900000 * 34) && date.getTime() == reverted.getTime());
	}

	public void test2() {
				
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2004);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 26);
		cal.set(Calendar.HOUR_OF_DAY, 9);
		cal.set(Calendar.MINUTE, 15);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date date = cal.getTime();
		logger.info("date = " + date + "[" + date.getTime() + "]");						
						
		MySegmentedTimeline timeline = this.getTimeline();		

		long value = timeline.toTimelineValue(date);	
		logger.info("value = " + value);
		
		long ms = timeline.toMillisecond(value);
		logger.info("ms = " + ms);
		
		Calendar cal2 = Calendar.getInstance();
		cal2.setTimeInMillis(ms);
		Date reverted = cal2.getTime();
		logger.info("reverted = " + reverted + '[' + reverted.getTime() + "]");	
		logger.info("");
		
		assertTrue("test2", value == (900000 * 34 + 900000) && date.getTime() == reverted.getTime());
	}

	public void test3() {
				
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2004);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 26);
		cal.set(Calendar.HOUR_OF_DAY, 9);
		cal.set(Calendar.MINUTE, 30);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date date = cal.getTime();
		logger.info("date = " + date + "[" + date.getTime() + "]");						
						
		MySegmentedTimeline timeline = this.getTimeline();		

		long value = timeline.toTimelineValue(date);	
		logger.info("value = " + value);
		
		long ms = timeline.toMillisecond(value);
		logger.info("ms = " + ms);
		
		Calendar cal2 = Calendar.getInstance();
		cal2.setTimeInMillis(ms);
		Date reverted = cal2.getTime();
		logger.info("reverted = " + reverted + '[' + reverted.getTime() + "]");	
		logger.info("");
		
		assertTrue("test2", value == (900000 * 34 + 900000 * 2) && date.getTime() == reverted.getTime());
	}

	public void test4() {
				
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2004);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 26);
		cal.set(Calendar.HOUR_OF_DAY, 9);
		cal.set(Calendar.MINUTE, 30);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 1);
		Date date = cal.getTime();
		logger.info("date = " + date + "[" + date.getTime() + "]");						
						
		MySegmentedTimeline timeline = this.getTimeline();		

		long value = timeline.toTimelineValue(date);	
		logger.info("value = " + value);
		
		long ms = timeline.toMillisecond(value);
		logger.info("ms = " + ms);
		
		Calendar cal2 = Calendar.getInstance();
		cal2.setTimeInMillis(ms);
		Date reverted = cal2.getTime();
		logger.info("reverted = " + reverted + '[' + reverted.getTime() + "]");	
		logger.info("");
		
		assertTrue("test4", value == (900000 * 34 + 900000 * 2 + 1) && date.getTime() == reverted.getTime());
	}

	public void test5() {
				
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2004);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 25);
		cal.set(Calendar.HOUR_OF_DAY, 17);
		cal.set(Calendar.MINUTE, 30);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date date = cal.getTime();
		logger.info("date = " + date + "[" + date.getTime() + "]");						
						
		MySegmentedTimeline timeline = this.getTimeline();		

		long value = timeline.toTimelineValue(date);	
		logger.info("value = " + value);
		
		long ms = timeline.toMillisecond(value);
		logger.info("ms = " + ms);
		
		Calendar cal2 = Calendar.getInstance();
		cal2.setTimeInMillis(ms);
		Date reverted = cal2.getTime();
		logger.info("reverted = " + reverted + '[' + reverted.getTime() + "]");	
		logger.info("");
		
		Calendar expectedReverted = Calendar.getInstance();
		expectedReverted.set(Calendar.YEAR, 2004);
		expectedReverted.set(Calendar.MONTH, Calendar.MARCH);
		expectedReverted.set(Calendar.DAY_OF_MONTH, 26);
		expectedReverted.set(Calendar.HOUR_OF_DAY, 9);
		expectedReverted.set(Calendar.MINUTE, 0);
		expectedReverted.set(Calendar.SECOND, 0);
		expectedReverted.set(Calendar.MILLISECOND, 0);
		
		assertTrue("test5", value == (900000 * 34) && expectedReverted.getTimeInMillis() == reverted.getTime());
	}

	public void test7() {
				
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2004);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 29);
		cal.set(Calendar.HOUR_OF_DAY, 9);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date date = cal.getTime();
		logger.info("date = " + date + "[" + date.getTime() + "]");						
						
		MySegmentedTimeline timeline = this.getTimeline();		

		long value = timeline.toTimelineValue(date);	
		logger.info("value = " + value);
		
		long ms = timeline.toMillisecond(value);
		logger.info("ms = " + ms);
		
		Calendar cal2 = Calendar.getInstance();
		cal2.setTimeInMillis(ms);
		Date reverted = cal2.getTime();
		logger.info("reverted = " + reverted + '[' + reverted.getTime() + "]");	
		logger.info("");
		
		Calendar expectedReverted = Calendar.getInstance();
		expectedReverted.set(Calendar.YEAR, 2004);
		expectedReverted.set(Calendar.MONTH, Calendar.MARCH);
		expectedReverted.set(Calendar.DAY_OF_MONTH, 29);
		expectedReverted.set(Calendar.HOUR_OF_DAY, 9);
		expectedReverted.set(Calendar.MINUTE, 0);
		expectedReverted.set(Calendar.SECOND, 0);
		expectedReverted.set(Calendar.MILLISECOND, 0);
		
		assertTrue("test7", value == (900000 * 34 * 2) && expectedReverted.getTimeInMillis() == reverted.getTime());
	}

	public void test8() {
				
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2004);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 29);
		cal.set(Calendar.HOUR_OF_DAY, 10);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date date = cal.getTime();
		logger.info("date = " + date + "[" + date.getTime() + "]");						
						
		MySegmentedTimeline timeline = this.getTimeline();		
		
		// Add exception in included segments
		cal.set(Calendar.YEAR, 2004);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 29);
		cal.set(Calendar.HOUR_OF_DAY, 9);
		cal.set(Calendar.MINUTE, 15);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		timeline.addException(cal.getTime());
				
		long value = timeline.toTimelineValue(date);	
		logger.info("value = " + value);
		
		long ms = timeline.toMillisecond(value);
		logger.info("ms = " + ms);
		
		Calendar cal2 = Calendar.getInstance();
		cal2.setTimeInMillis(ms);
		Date reverted = cal2.getTime();
		logger.info("reverted = " + reverted + '[' + reverted.getTime() + "]");	
		logger.info("");
		
		Calendar expectedReverted = Calendar.getInstance();
		expectedReverted.set(Calendar.YEAR, 2004);
		expectedReverted.set(Calendar.MONTH, Calendar.MARCH);
		expectedReverted.set(Calendar.DAY_OF_MONTH, 29);
		expectedReverted.set(Calendar.HOUR_OF_DAY, 10);
		expectedReverted.set(Calendar.MINUTE, 0);
		expectedReverted.set(Calendar.SECOND, 0);
		expectedReverted.set(Calendar.MILLISECOND, 0);
		
		assertTrue("test8", value == (900000 * 34 * 2 + 900000 * (4 - 1)) && expectedReverted.getTimeInMillis() == reverted.getTime());
	}
	
	public void test6() {
				
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2004);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 28);
		cal.set(Calendar.HOUR_OF_DAY, 9);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date date = cal.getTime();
		logger.info("date = " + date + "[" + date.getTime() + "]");						
						
		MySegmentedTimeline timeline = this.getTimeline();		

		long value = timeline.toTimelineValue(date);	
		logger.info("value = " + value);
		
		long ms = timeline.toMillisecond(value);
		logger.info("ms = " + ms);
		
		Calendar cal2 = Calendar.getInstance();
		cal2.setTimeInMillis(ms);
		Date reverted = cal2.getTime();
		logger.info("reverted = " + reverted + '[' + reverted.getTime() + "]");	
		logger.info("");
		
		Calendar expectedReverted = Calendar.getInstance();
		expectedReverted.set(Calendar.YEAR, 2004);
		expectedReverted.set(Calendar.MONTH, Calendar.MARCH);
		expectedReverted.set(Calendar.DAY_OF_MONTH, 29);
		expectedReverted.set(Calendar.HOUR_OF_DAY, 9);
		expectedReverted.set(Calendar.MINUTE, 0);
		expectedReverted.set(Calendar.SECOND, 0);
		expectedReverted.set(Calendar.MILLISECOND, 0);
		
		assertTrue("test5", value == (900000 * 34 * 2) && expectedReverted.getTimeInMillis() == reverted.getTime());
	}
							
	private MySegmentedTimeline getTimeline() {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2004);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 25);
		cal.set(Calendar.HOUR_OF_DAY, 9);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date from = cal.getTime();

		cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 2004);
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 30);
		cal.set(Calendar.HOUR_OF_DAY, 17);
		cal.set(Calendar.MINUTE, 30);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date to = cal.getTime();

		return this.getTimeline(from, to);
	}
	
	private MySegmentedTimeline getTimeline(
		Date									start,
		Date 									end) {
		
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 1970);
		cal.set(Calendar.MONTH, Calendar.JANUARY);
		cal.set(Calendar.DAY_OF_MONTH, 1);
		cal.set(Calendar.HOUR_OF_DAY, 9);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date open = cal.getTime();

		cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 1970);
		cal.set(Calendar.MONTH, Calendar.JANUARY);
		cal.set(Calendar.DAY_OF_MONTH, 1);
		cal.set(Calendar.HOUR_OF_DAY, 17);
		cal.set(Calendar.MINUTE, 30);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		Date close = cal.getTime();
								
		MySegmentedTimeline result = null;		
		// Create a segmented time line (segment size : 15 minutes)
		long quarterHourCount =
			(close.getTime() - open.getTime())
			/
			MySegmentedTimeline.FIFTEEN_MINUTE_SEGMENT_SIZE;
		long totalQuarterHourCount = MySegmentedTimeline.DAY_SEGMENT_SIZE / MySegmentedTimeline.FIFTEEN_MINUTE_SEGMENT_SIZE;
		result = new MySegmentedTimeline(
			MySegmentedTimeline.FIFTEEN_MINUTE_SEGMENT_SIZE,
			(int) quarterHourCount,
			(int) (totalQuarterHourCount - quarterHourCount));
		// Set start time
		result.setStartTime(start.getTime());
		// Saturday and Sundays are non business hours
		result.setBaseTimeline(
			MySegmentedTimeline.newMondayThroughFridayTimeline());
		/* PUT exclusions in test */
		if (start != null && end != null)
			result.addBaseTimelineExclusions(start.getTime(), end.getTime());	 
		return result;	
	}
		
}
Fred

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Thu May 20, 2004 2:32 pm

Hi Fred,

Thanks for the reminder and feedback about that fix. It helps a lot to know that someone else has double-checked it already. I will do my best to get it incorporated for the 0.9.19 release.

The other two items are done (in CVS).
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Fri May 21, 2004 12:01 pm

This is done and committed to CVS now.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

fredatwork
Posts: 27
Joined: Mon Mar 08, 2004 11:28 am

getDate() to be fixed & Proposal of an API change

Post by fredatwork » Fri May 21, 2004 12:27 pm

Hi David,

The method getDate() of class SegmentedTimeline must be fixed too (in case the user recompiles the class and sets adjustForDaylightSaving to true):

From :

Code: Select all

    public Date getDate(long value) {
        this.workingCalendarNoDST.setTime(new Date(value));
        return (this.workingCalendarNoDST.getTime());
    }
To :

Code: Select all

    public Date getDate(long value) {
    	workingCalendar.setTime(value);
    	if (this.adjustForDaylightSaving) {
			workingCalendarNoDST.setTimeInMillis(value);
			workingCalendar.set(
				this.workingCalendarNoDST.get(Calendar.YEAR),
				this.workingCalendarNoDST.get(Calendar.MONTH),
				this.workingCalendarNoDST.get(Calendar.DATE),
				this.workingCalendarNoDST.get(Calendar.HOUR_OF_DAY),
				this.workingCalendarNoDST.get(Calendar.MINUTE),
				this.workingCalendarNoDST.get(Calendar.SECOND));						
			workingCalendar.set(
				Calendar.MILLISECOND, 
				this.workingCalendarNoDST.get(Calendar.MILLISECOND));	
    	}
		return workingCalendar.getTime();
    }
It is the "reversed" function of getTime() :
- getTime() helps storing long time values expressed in a calendar that does NOT observe daylight savings
- getDate() returns a date expressed in a calendar that does observe daylight savings. This method is not used by the jfreechart code itself (except by one demo class), but is is very helpful for users that code something like :

Code: Select all

SegmentedTimeline timeline = ...;
Segment segment = ....;
Date start = timeline.getDate(segment.getSegmentStart());
Date end = timeline.getDate(segment.getSegmentEnd());
The API documentation should explain the usage of the getDate() function if the user recompiles the class and sets adjustForDaylightSaving to true, especially the fact that the following code is then misleading :

Code: Select all

Timeline timeline = ...;
Segment segment = ...;
Calendar calendar = Calendar.getInstance();
calendar.setTime(segment.getSegmentStart());
Date date = calendar.getTime();
In this code snipet, we get a date expressed in a calendar that does NOT observe daylight savings time. In order to get a "good" date (expressed in a calendar that DOES observe DST):

Code: Select all

Timeline timeline = ...;
Segment segment = ...;
Calendar calendar = Calendar.getInstance();
Date date = timeline.getDate(segment.getSegmentStart());
As a result, I would like to propose a change to the SegmentedTimeline API:
- addition of a constructor to the class with a TimeZone as its first argument. The time zone determines if daylight saving time should be observed. The existing constructor is kept as well (default time zone is passed to the new constructor).
- removal of static members FIRST_MONDAY_AFTER_1900, NO_DST_TIME_ZONE, DEFAULT_TIME_ZONE
- addition of public methods getFirstMondayAfter1900(), getNoDSTTimeZone(), getTimeZone()
Also, the * completely * private member adjustForDaylightSaving is removed. The timezone of the timeline knows if DST should be observed.
=> The primary goal of this API change is to make observance of daylight saving time AUTOMATIC based on a time zone, rather than on a member such as adjustForDaylightSaving , that by the way cannot be changed from the outside of the class
Here are the modifications of the segmented timeline that I use successfully (a copy of original SegmentedTimeline class that I change) :

instead of (current code)

Code: Select all

    ////////////////////////////////////////////////////////////////////////////
    // other constants
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Utility constant that defines the startTime as the first monday after 1/1/1970.
     * This should be used when creating a SegmentedTimeline for Monday through
     * Friday. See static block below for calculation of this constant.
     */
    public static long FIRST_MONDAY_AFTER_1900;

    /**
     * Utility TimeZone object that has no DST and an offset equal to the default
     * TimeZone. This allows easy arithmetic between days as each one will have
     * equal size.
     */
    public static TimeZone NO_DST_TIME_ZONE;

    /**
     * This is the default time zone where the application is running. See getTime() below
     * where we make use of certain transformations between times in the default time zone and
     * the no-dst time zone used for our calculations.
     */
    public static TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault();

    /**
     * This will be a utility calendar that has no DST but is shifted relative to
     * the default time zone's offset.
     */
    private Calendar workingCalendarNoDST = new GregorianCalendar(NO_DST_TIME_ZONE);

    /**
     * This will be a utility calendar that used the default time zone.
     */
    private Calendar workingCalendar = Calendar.getInstance();
use (new proposed code):

Code: Select all

   ////////////////////////////////////////////////////////////////////////////
    // other constants
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Utility constant that defines the startTime as the first monday after 1/1/1970.
     * This should be used when creating a SegmentedTimeline for Monday through
     * Friday. See static block below for calculation of this constant.
     */
    private long first_monday_after_1900;

    /**
     * Utility TimeZone object that has no DST and an offset equal to the default
     * TimeZone. This allows easy arithmetic between days as each one will have
     * equal size.
     */
    private TimeZone noDstTimeZone;

    /**
     * This is the default time zone where the application is running. See getTime() below
     * where we make use of certain transformations between times in the default time zone and
     * the no-dst time zone used for our calculations.
     */
    private TimeZone timeZone;

    /**
     * This will be a utility calendar that has no DST but is shifted relative to
     * the default time zone's offset.
     */
    private Calendar workingCalendarNoDST;

    /**
     * This will be a utility calendar that used the default time zone.
     */
    private Calendar workingCalendar;
Instead of (current code)

Code: Select all

    ////////////////////////////////////////////////////////////////////////////
    // static block
    ////////////////////////////////////////////////////////////////////////////

    static {
        // make a time zone with no DST for our Calendar calculations
        int offset = TimeZone.getDefault().getRawOffset();
        NO_DST_TIME_ZONE = new SimpleTimeZone(offset, "UTC-" + offset);
        
        // calculate midnight of first monday after 1/1/1900 relative to current locale
        Calendar cal = new GregorianCalendar(NO_DST_TIME_ZONE);
        cal.set(1900, 0, 1, 0, 0, 0);
        cal.set(Calendar.MILLISECOND, 0);
        while (cal.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
            cal.add(Calendar.DATE, 1);
        }
        FIRST_MONDAY_AFTER_1900 = cal.getTime().getTime();
    }

    ////////////////////////////////////////////////////////////////////////////
    // constructors and factory methods
    ////////////////////////////////////////////////////////////////////////////

    /**
     * Constructs a new segmented timeline, optionaly using another segmented
     * timeline as its base. This chaning of SegmentedTimelines allows further
     * segmentation into smaller timelines.
     *
     * If a base
     *
     * @param segmentSize the size of a segment in ms. This time unit will be
     *        used to compute the included and excluded segments of the timeline.
     * @param segmentsIncluded Number of consecutive segments to include.
     * @param segmentsExcluded Number of consecutive segments to exclude.
     */
    public SegmentedTimeline(long segmentSize,
                             int segmentsIncluded,
                             int segmentsExcluded) {

        this.segmentSize = segmentSize;
        this.segmentsIncluded = segmentsIncluded;
        this.segmentsExcluded = segmentsExcluded;

        this.groupSegmentCount = this.segmentsIncluded + this.segmentsExcluded;
        this.segmentsIncludedSize = this.segmentsIncluded * this.segmentSize;
        this.segmentsExcludedSize = this.segmentsExcluded * this.segmentSize;
        this.segmentsGroupSize = this.segmentsIncludedSize + this.segmentsExcludedSize;

    }

    /**
     * Factory method to create a Monday through Friday SegmentedTimeline.
     * <P>
     * The <code>startTime</code> of the resulting timeline will be midnight of the
     * firt Monday after 1/1/1900.
     *
     * @return A fully initialized SegmentedTimeline.
     */
    public static SegmentedTimeline newMondayThroughFridayTimeline() {
        SegmentedTimeline timeline = new SegmentedTimeline(DAY_SEGMENT_SIZE, 5, 2);
        timeline.setStartTime(FIRST_MONDAY_AFTER_1900);
        return timeline;
    }

    /**
     * Factory method to create a 15-min, 9:00 AM thought 4:00 PM, Monday through
     * Friday SegmentedTimeline.
     * <P>
     * This timeline uses a segmentSize of FIFTEEN_MIN_SEGMENT_SIZE. The segment group
     * is defined as 28 included segments (9:00 AM through 4:00 PM) and 68 excluded
     * segments (4:00 PM through 9:00 AM the next day).
     * <P>
     * In order to exclude Saturdays and Sundays it uses a baseTimeline that only
     * includes Monday through Friday days.
     * <P>
     * The <code>startTime</code> of the resulting timeline will be 9:00 AM after
     * the startTime of the baseTimeline. This will correspond to 9:00 AM of the
     * firt Monday after 1/1/1900.
     *
     * @return A fully initialized SegmentedTimeline.
     */
    public static SegmentedTimeline newFifteenMinuteTimeline() {
        SegmentedTimeline timeline = new SegmentedTimeline(FIFTEEN_MINUTE_SEGMENT_SIZE, 28, 68);
        timeline.setStartTime(FIRST_MONDAY_AFTER_1900 + 36 * timeline.getSegmentSize());
        timeline.setBaseTimeline(newMondayThroughFridayTimeline());
        return timeline;
    }
Use (new proposed code):

Code: Select all

    ////////////////////////////////////////////////////////////////////////////
    // constructors and factory methods
    ////////////////////////////////////////////////////////////////////////////

	/**
	  * Constructs a new segmented timeline, optionaly using another segmented
	  * timeline as its base. This chaning of SegmentedTimelines allows further
	  * segmentation into smaller timelines.
	  *
	  * If a base
	  * 
	  * @param segmentSize the size of a segment in ms. This time unit will be
	  *        used to compute the included and excluded segments of the timeline.
	  * @param segmentsIncluded Number of consecutive segments to include.
	  * @param segmentsExcluded Number of consecutive segments to exclude.
	  */
	 public XSegmentedTimeline(
							 long segmentSize,
							 int segmentsIncluded,
							 int segmentsExcluded) {

		this(TimeZone.getDefault(), segmentSize, segmentsIncluded, segmentsExcluded);
	}
							 
    /**
     * Constructs a new segmented timeline, optionaly using another segmented
     * timeline as its base. This chaning of SegmentedTimelines allows further
     * segmentation into smaller timelines.
     *
     * If a base
     * 
     * @param timeZone - the timezone of the calendars used by this timeline
     * @param segmentSize the size of a segment in ms. This time unit will be
     *        used to compute the included and excluded segments of the timeline.
     * @param segmentsIncluded Number of consecutive segments to include.
     * @param segmentsExcluded Number of consecutive segments to exclude.
     */
    public XSegmentedTimeline(
    						TimeZone timeZone,
    						long segmentSize,
                            int segmentsIncluded,
                            int segmentsExcluded) {

        this.segmentSize = segmentSize;
        this.segmentsIncluded = segmentsIncluded;
        this.segmentsExcluded = segmentsExcluded;

        this.groupSegmentCount = this.segmentsIncluded + this.segmentsExcluded;
        this.segmentsIncludedSize = this.segmentsIncluded * this.segmentSize;
        this.segmentsExcludedSize = this.segmentsExcluded * this.segmentSize;
        this.segmentsGroupSize = this.segmentsIncludedSize + this.segmentsExcludedSize;

		this.init(timeZone);
    }

	/**
	 * Initializes calendars for this timeline
	 * @param timeZone - the timezone of the calendars used by this timeline
	 */
	private void init(TimeZone timeZone) {
		// Set time zone
		this.timeZone = timeZone;
		// make a time zone with no DST for our Calendar calculations
		int offset = timeZone.getRawOffset();
		this.noDstTimeZone = new SimpleTimeZone(offset, "UTC-" + offset);
        // Create calendars
		this.workingCalendarNoDST = new GregorianCalendar(this.noDstTimeZone);
		this.workingCalendar = Calendar.getInstance(timeZone);
		// Calculate midnight of first monday after 1/1/1900 relative to current locale
		Calendar cal = new GregorianCalendar(noDstTimeZone);
		cal.set(1900, 0, 1, 0, 0, 0);
		cal.set(Calendar.MILLISECOND, 0);
		while (cal.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) {
			cal.add(Calendar.DATE, 1);
		}
		this.first_monday_after_1900 = cal.getTime().getTime();
	}

	/**
	 * Factory method to create a Monday through Friday SegmentedTimeline.
	 * <P>
	 * The <code>startTime</code> of the resulting timeline will be midnight of the
	 * firt Monday after 1/1/1900.
	 *
	 * @return A fully initialized SegmentedTimeline.
	 */
	public static XSegmentedTimeline newMondayThroughFridayTimeline() {
		return newMondayThroughFridayTimeline(TimeZone.getDefault());
	}
		
    /**
     * Factory method to create a Monday through Friday SegmentedTimeline.
     * <P>
     * The <code>startTime</code> of the resulting timeline will be midnight of the
     * firt Monday after 1/1/1900.
     * @param timeZone - the timezone of the calendars used by this timeline
     * @return A fully initialized SegmentedTimeline.
     */
    public static XSegmentedTimeline newMondayThroughFridayTimeline(TimeZone timeZone) {
        XSegmentedTimeline timeline = new XSegmentedTimeline(timeZone, DAY_SEGMENT_SIZE, 5, 2);
        timeline.setStartTime(timeline.first_monday_after_1900);
        return timeline;
    }

    /**
     * Factory method to create a 15-min, 9:00 AM thought 4:00 PM, Monday through
     * Friday SegmentedTimeline.
     * <P>
     * This timeline uses a segmentSize of FIFTEEN_MIN_SEGMENT_SIZE. The segment group
     * is defined as 28 included segments (9:00 AM through 4:00 PM) and 68 excluded
     * segments (4:00 PM through 9:00 AM the next day).
     * <P>
     * In order to exclude Saturdays and Sundays it uses a baseTimeline that only
     * includes Monday through Friday days.
     * <P>
     * The <code>startTime</code> of the resulting timeline will be 9:00 AM after
     * the startTime of the baseTimeline. This will correspond to 9:00 AM of the
     * firt Monday after 1/1/1900.
     * @return A fully initialized SegmentedTimeline.
     */
    public static XSegmentedTimeline newFifteenMinuteTimeline() {
        return newFifteenMinuteTimeline(TimeZone.getDefault());
    }

	/**
	  * Factory method to create a 15-min, 9:00 AM thought 4:00 PM, Monday through
	  * Friday SegmentedTimeline.
	  * <P>
	  * This timeline uses a segmentSize of FIFTEEN_MIN_SEGMENT_SIZE. The segment group
	  * is defined as 28 included segments (9:00 AM through 4:00 PM) and 68 excluded
	  * segments (4:00 PM through 9:00 AM the next day).
	  * <P>
	  * In order to exclude Saturdays and Sundays it uses a baseTimeline that only
	  * includes Monday through Friday days.
	  * <P>
	  * The <code>startTime</code> of the resulting timeline will be 9:00 AM after
	  * the startTime of the baseTimeline. This will correspond to 9:00 AM of the
	  * firt Monday after 1/1/1900.
	  * @param timeZone - the timezone of the calendars used by this timeline
	  * @return A fully initialized SegmentedTimeline.
	  */
	 public static XSegmentedTimeline newFifteenMinuteTimeline(TimeZone timeZone) {
		 XSegmentedTimeline timeline = new XSegmentedTimeline(timeZone, FIFTEEN_MINUTE_SEGMENT_SIZE, 28, 68);
		 timeline.setStartTime(timeline.first_monday_after_1900 + 36 * timeline.getSegmentSize());
		 timeline.setBaseTimeline(newMondayThroughFridayTimeline(timeZone));
		 return timeline;
	 }
Remove (from current code) :

Code: Select all

    /** A flag that controls whether or not to adjust for daylight saving. */
    private boolean adjustForDaylightSaving = true;
Add (to new proposed code) :

Code: Select all

	/**
	 * Gets the time zone used by this timeline
	 * @return the tie zone used by this time line
	 */
	public TimeZone getTimeZone() {
		return this.timeZone;	
	}
	
	/**
	 * Gets the associated time zone that does not observe daylight savings time
	 * @return a time zone with raw offset identifical to this timeline's timezone.
	 */
	public TimeZone getNoDSTTimeZone() {
		return this.noDstTimeZone;
	}
	
	/**
	 * Gets the first monday after 1900 expressed in the calendar based on the no DST timezone
	 * @return long represening the first monday after 1900 expressed in the calendar based on the no DST timezone
	 */
	public long getFirstMondayAfter1900() {
		return this.first_monday_after_1900;
	}
Instead of (current code):

Code: Select all

  /**
     * Special method that handles conversion between the Default Time Zone and a UTC time zone
     * with no DST. This is needed so all days have the same size. This method is the prefered
     * way of converting a Data into milliseconds for usage in this class.
     *
     * @param date Date to convert to long.
     * 
     * @return The milliseconds.
     */
    public long getTime(Date date) {
        long result = date.getTime();
        if (this.adjustForDaylightSaving) {
            this.workingCalendar.setTime(date);
            this.workingCalendarNoDST.set(this.workingCalendar.get(Calendar.YEAR),
                    this.workingCalendar.get(Calendar.MONTH),
                    this.workingCalendar.get(Calendar.DATE),
                    this.workingCalendar.get(Calendar.HOUR_OF_DAY),
                    this.workingCalendar.get(Calendar.MINUTE),
                    this.workingCalendar.get(Calendar.SECOND));
            this.workingCalendarNoDST.set(Calendar.MILLISECOND, 
                    this.workingCalendar.get(Calendar.MILLISECOND));
            Date revisedDate = this.workingCalendarNoDST.getTime();
            result = revisedDate.getTime();
        }
        
        return result;
    }

    /** 
     * Converts a millisecond value into a {@link Date} object.
     * 
     * @param value  the millisecond value.
     * 
     * @return The date.
     */
    public Date getDate(long value) {
        this.workingCalendarNoDST.setTime(new Date(value));
        return (this.workingCalendarNoDST.getTime());
    }
Use (proposed new code)

Code: Select all

    /**
     * Special method that handles conversion between the Default Time Zone and a UTC time zone
     * with no DST. This is needed so all days have the same size. This method is the prefered
     * way of converting a Data into milliseconds for usage in this class.
     *
     * @param date Date to convert to long.
     * 
     * @return The milliseconds.
     */
    public long getTime(Date date) {
        this.workingCalendar.setTime(date);
        this.workingCalendarNoDST.set(
        	this.workingCalendar.get(Calendar.YEAR),
	        this.workingCalendar.get(Calendar.MONTH),
	        this.workingCalendar.get(Calendar.DATE),
	        this.workingCalendar.get(Calendar.HOUR_OF_DAY),
	        this.workingCalendar.get(Calendar.MINUTE),
	        this.workingCalendar.get(Calendar.SECOND));
        this.workingCalendarNoDST.set(
        	Calendar.MILLISECOND, 
        	this.workingCalendar.get(Calendar.MILLISECOND));
        Date revisedDate = this.workingCalendarNoDST.getTime();
        long result = revisedDate.getTime();  
        return result;
    }

    /** 
     * Converts a millisecond value into a {@link Date} object.
     * 
     * @param value  the millisecond value.
     * 
     * @return The date.
     */
    public Date getDate(long value) {
    	workingCalendarNoDST.setTimeInMillis(value);
		workingCalendar.set(
			this.workingCalendarNoDST.get(Calendar.YEAR),
			this.workingCalendarNoDST.get(Calendar.MONTH),
			this.workingCalendarNoDST.get(Calendar.DATE),
			this.workingCalendarNoDST.get(Calendar.HOUR_OF_DAY),
			this.workingCalendarNoDST.get(Calendar.MINUTE),
			this.workingCalendarNoDST.get(Calendar.SECOND));						
		workingCalendar.set(
			Calendar.MILLISECOND, 
			this.workingCalendarNoDST.get(Calendar.MILLISECOND));	
		return workingCalendar.getTime();
    }
Also remember to implement the fixes proposed by Gregory to the SegmentedTimeline class.

I hope this proposal of API change will convince you for a further release.
Fred

Alfred63
Posts: 22
Joined: Mon Jun 21, 2004 9:38 pm

Post by Alfred63 » Tue Oct 12, 2004 9:55 pm

Curious whether these changes made it into 20 or 21?

muentzer
Posts: 5
Joined: Tue Sep 14, 2004 3:42 pm

Post by muentzer » Wed Oct 13, 2004 8:51 am

[quote]Curious whether these changes made it into 20 or 21?[/quote]

Not hard to tell. Just look into the code and you'll see, that they don't appear in 20 or 21.

Michael

Locked