org.jasig.schedassist.model.AvailableBlockBuilderTest.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.schedassist.model.AvailableBlockBuilderTest.java

Source

/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a
 * copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.jasig.schedassist.model;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.lang.time.DateUtils;
import org.junit.Assert;
import org.junit.Test;

/**
 * Test bench for {@link AvailableBlockBuilder}.
 * 
 * @author Nicholas Blair, nblair@doit.wisc.edu
 * @version $Id: AvailableBlockBuilderTest.java 2516 2010-09-09 18:32:23Z npblair $
 */
public class AvailableBlockBuilderTest {

    /**
     * Validate IllegalArgumentException thrown for all invalid arguments to createBlock.
     * @throws Exception
     */
    @Test
    public void testCreateBlockInvalid() throws Exception {
        Date nullDate = null;
        try {
            AvailableBlockBuilder.createBlock(nullDate, nullDate, 1);
            fail("expected IllegalArgumentException not thrown for null startTime");
        } catch (IllegalArgumentException e) {
            //success
        }

        try {
            AvailableBlockBuilder.createBlock(new Date(), nullDate, 1);
            fail("expected IllegalArgumentException not thrown for null endTime");
        } catch (IllegalArgumentException e) {
            //success
        }

        Date now = new Date();
        try {
            AvailableBlockBuilder.createBlock(now, now, 1);
            fail("expected IllegalArgumentException not thrown for equivalent start/endtime");
        } catch (IllegalArgumentException e) {
            //success
        }

        try {
            AvailableBlockBuilder.createBlock(now, DateUtils.addMinutes(now, 1), 0);
            fail("expected IllegalArgumentException not thrown for visitorLimit less than 1");
        } catch (IllegalArgumentException e) {
            //success
        }
    }

    /**
     * Every monday from June 1 2008 to July 1 2008.
     * 
     * @throws Exception
     */
    @Test
    public void testCreateBlocksExample1() throws Exception {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();

        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("10:30 AM", "1:00 PM", "M",
                dateFormat.parse("20080601"), dateFormat.parse("20080701"));
        // there are 5 mondays in between June 1 2008 and July 1 2008
        assertEquals(5, blocks.size());
        for (AvailableBlock block : blocks) {
            Calendar cal = Calendar.getInstance();
            cal.setTime(block.getStartTime());
            assertEquals(10, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(30, cal.get(Calendar.MINUTE));
            cal.setTime(block.getEndTime());
            assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(0, cal.get(Calendar.MINUTE));
            assertEquals(Calendar.MONDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertEquals(1, block.getVisitorLimit());
        }

        blocks = AvailableBlockBuilder.createBlocks("10:30 AM", "1:00 PM", "M", dateFormat.parse("20080601"),
                dateFormat.parse("20080701"), 5);
        // there are 5 mondays in between June 1 2008 and July 1 2008
        assertEquals(5, blocks.size());
        for (AvailableBlock block : blocks) {
            assertEquals(150, block.getDurationInMinutes());
            Calendar cal = Calendar.getInstance();
            cal.setTime(block.getStartTime());
            assertEquals(10, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(30, cal.get(Calendar.MINUTE));
            cal.setTime(block.getEndTime());
            assertEquals(13, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(0, cal.get(Calendar.MINUTE));
            assertEquals(Calendar.MONDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertEquals(5, block.getVisitorLimit());
        }
    }

    /**
     * Every week day from June 24 2008 to August 6 2008.
     * 
     * @throws Exception
     */
    @Test
    public void testCreateBlocksExample2() throws Exception {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();
        Date startDate = dateFormat.parse("20080624");
        Date endDate = dateFormat.parse("20080806");
        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("9:00 AM", "11:30 AM", "MTWRF", startDate,
                endDate);
        // 32 weekdays between June 24 2008 and August 6 2008 (including Aug 6 2008)
        assertEquals(32, blocks.size());
        for (AvailableBlock block : blocks) {
            assertEquals(150, block.getDurationInMinutes());
            Calendar cal = Calendar.getInstance();
            cal.setTime(block.getStartTime());
            assertEquals(9, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(0, cal.get(Calendar.MINUTE));
            cal.setTime(block.getEndTime());
            assertEquals(11, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(30, cal.get(Calendar.MINUTE));
            assertNotSame(Calendar.SATURDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertNotSame(Calendar.SUNDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertEquals(1, block.getVisitorLimit());
        }

        blocks = AvailableBlockBuilder.createBlocks("9:00 AM", "11:30 AM", "MTWRF", startDate, endDate, 64);
        // 32 weekdays between June 24 2008 and August 6 2008 (including Aug 6 2008)
        assertEquals(32, blocks.size());
        for (AvailableBlock block : blocks) {
            assertEquals(150, block.getDurationInMinutes());
            Calendar cal = Calendar.getInstance();
            cal.setTime(block.getStartTime());
            assertEquals(9, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(0, cal.get(Calendar.MINUTE));
            cal.setTime(block.getEndTime());
            assertEquals(11, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(30, cal.get(Calendar.MINUTE));
            assertNotSame(Calendar.SATURDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertNotSame(Calendar.SUNDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertEquals(64, block.getVisitorLimit());
        }
    }

    /** 
     * Test times near midnight.
     * 
     * @throws Exception
     */
    @Test
    public void testCreateBlocksExample3() throws Exception {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();
        Date startDate = dateFormat.parse("20080624");
        Date endDate = dateFormat.parse("20080630");
        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("12:00 AM", "12:59 AM", "MTWRF", startDate,
                endDate);

        assertEquals(5, blocks.size());
        for (AvailableBlock block : blocks) {
            assertEquals(59, block.getDurationInMinutes());
            Calendar cal = Calendar.getInstance();
            cal.setTime(block.getStartTime());
            assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(0, cal.get(Calendar.MINUTE));
            cal.setTime(block.getEndTime());
            assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(59, cal.get(Calendar.MINUTE));
            assertNotSame(Calendar.SATURDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertNotSame(Calendar.SUNDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertEquals(1, block.getVisitorLimit());
        }

        blocks = AvailableBlockBuilder.createBlocks("12:00 AM", "12:59 AM", "MTWRF", startDate, endDate, 27);

        assertEquals(5, blocks.size());
        for (AvailableBlock block : blocks) {
            assertEquals(59, block.getDurationInMinutes());
            Calendar cal = Calendar.getInstance();
            cal.setTime(block.getStartTime());
            assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(0, cal.get(Calendar.MINUTE));
            cal.setTime(block.getEndTime());
            assertEquals(0, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(59, cal.get(Calendar.MINUTE));
            assertNotSame(Calendar.SATURDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertNotSame(Calendar.SUNDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertEquals(27, block.getVisitorLimit());
        }
    }

    /**
     * Create blocks that span noon.
     * 
     * @throws Exception
     */
    @Test
    public void testCreateBlocksExample4() throws Exception {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();
        Date startDate = dateFormat.parse("20080601");
        Date endDate = dateFormat.parse("20080630");

        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("11:45 AM", "12:15 PM", "MTWRF", startDate,
                endDate);
        assertEquals(21, blocks.size());
        for (AvailableBlock block : blocks) {
            assertEquals(30, block.getDurationInMinutes());
            Calendar cal = Calendar.getInstance();
            cal.setTime(block.getStartTime());
            assertEquals(11, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(45, cal.get(Calendar.MINUTE));
            cal.setTime(block.getEndTime());
            assertEquals(12, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(15, cal.get(Calendar.MINUTE));
            assertNotSame(Calendar.SATURDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertNotSame(Calendar.SUNDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertEquals(1, block.getVisitorLimit());
        }

        blocks = AvailableBlockBuilder.createBlocks("11:45 AM", "12:15 PM", "MTWRF", startDate, endDate, 2);
        assertEquals(21, blocks.size());
        for (AvailableBlock block : blocks) {
            assertEquals(30, block.getDurationInMinutes());
            Calendar cal = Calendar.getInstance();
            cal.setTime(block.getStartTime());
            assertEquals(11, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(45, cal.get(Calendar.MINUTE));
            cal.setTime(block.getEndTime());
            assertEquals(12, cal.get(Calendar.HOUR_OF_DAY));
            assertEquals(15, cal.get(Calendar.MINUTE));
            assertNotSame(Calendar.SATURDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertNotSame(Calendar.SUNDAY, cal.get(Calendar.DAY_OF_WEEK));
            assertEquals(2, block.getVisitorLimit());
        }
    }

    /**
     * Create {@link AvailableBlock}s around daylight savings conversions, verify
     * expected behavior.
     * 
     * @throws Exception
     */
    @Test
    public void testCreateBlocksOverDaylightSavings() throws Exception {
        AvailableBlock block = AvailableBlockBuilder.createBlock("20091101-0130", "20091101-0200");
        assertNotNull(block);
        assertEquals(30, block.getDurationInMinutes());

        block = AvailableBlockBuilder.createBlock("20091101-0200", "20091101-0230");
        assertNotNull(block);
        assertEquals(30, block.getDurationInMinutes());

        block = AvailableBlockBuilder.createBlock("20091101-0145", "20091101-0215");
        assertNotNull(block);
        assertEquals(30, block.getDurationInMinutes());

        block = AvailableBlockBuilder.createBlock("20090308-0130", "20090308-0200");
        assertNotNull(block);
        assertEquals(30, block.getDurationInMinutes());
        block = AvailableBlockBuilder.createBlock("20090308-0200", "20090308-0230");
        assertNotNull(block);
        assertEquals(30, block.getDurationInMinutes());
        block = AvailableBlockBuilder.createBlock("20090308-0145", "20090308-0215");
        assertNotNull(block);
        assertEquals(30, block.getDurationInMinutes());
    }

    /**
     * Expand an {@link AvailableBlock} that was created with 9:00 AM and 3:00 PM as
     * start and end times, respectively.
     * 
     * @throws Exception
     */
    @Test
    public void testExpand() throws Exception {
        SimpleDateFormat dateTimeFormat = CommonDateOperations.getDateTimeFormat();
        Date startTime = dateTimeFormat.parse("20080601-0900");
        Date endTime = dateTimeFormat.parse("20080601-1500");

        AvailableBlock original = AvailableBlockBuilder.createBlock(startTime, endTime);
        assertNotNull(original);
        Set<AvailableBlock> expanded = AvailableBlockBuilder.expand(original, 30);
        assertEquals(12, expanded.size());

        Date currentStart = startTime;
        for (AvailableBlock block : expanded) {
            assertEquals(30, block.getDurationInMinutes());
            assertEquals(currentStart, block.getStartTime());
            currentStart = DateUtils.addMinutes(currentStart, 30);
            assertEquals(currentStart, block.getEndTime());
        }
    }

    /**
     * Asserts expand called on an {@link AvailableBlock} from 9:00 AM to 9:30 AM returns
     * only 1 block, equivalent to the original.
     * 
     * @throws Exception
     */
    @Test
    public void testExpand2() throws Exception {
        SimpleDateFormat dateTimeFormat = CommonDateOperations.getDateTimeFormat();
        Date startTime = dateTimeFormat.parse("20080601-0900");
        Date endTime = dateTimeFormat.parse("20080601-0930");

        AvailableBlock original = AvailableBlockBuilder.createBlock(startTime, endTime);
        assertNotNull(original);
        Set<AvailableBlock> expanded = AvailableBlockBuilder.expand(original, 30);
        assertEquals(1, expanded.size());
        assertTrue(expanded.contains(original));
    }

    /**
     * Assert expand function preserves original visitorLimit.
     * 
     * @throws Exception
     */
    @Test
    public void testExpandPreserveVisitorLimit() throws Exception {
        SimpleDateFormat dateTimeFormat = CommonDateOperations.getDateTimeFormat();
        Date startTime = dateTimeFormat.parse("20080601-0900");
        Date endTime = dateTimeFormat.parse("20080601-1500");

        AvailableBlock toExpand = AvailableBlockBuilder.createBlock(startTime, endTime, 10);

        SortedSet<AvailableBlock> expanded20 = AvailableBlockBuilder.expand(toExpand, 20);
        assertEquals(18, expanded20.size());
        Date currentStart = startTime;
        for (AvailableBlock block : expanded20) {
            assertEquals(20, block.getDurationInMinutes());
            assertEquals(10, block.getVisitorLimit());
            assertEquals(currentStart, block.getStartTime());
            currentStart = DateUtils.addMinutes(currentStart, 20);
            assertEquals(currentStart, block.getEndTime());
        }

        SortedSet<AvailableBlock> expanded40 = AvailableBlockBuilder.expand(toExpand, 40);
        assertEquals(9, expanded40.size());
        currentStart = startTime;
        for (AvailableBlock block : expanded40) {
            assertEquals(40, block.getDurationInMinutes());
            assertEquals(10, block.getVisitorLimit());
            assertEquals(currentStart, block.getStartTime());
            currentStart = DateUtils.addMinutes(currentStart, 40);
            assertEquals(currentStart, block.getEndTime());
        }

        SortedSet<AvailableBlock> expanded60 = AvailableBlockBuilder.expand(toExpand, 60);
        assertEquals(6, expanded60.size());
        currentStart = startTime;
        for (AvailableBlock block : expanded60) {
            assertEquals(60, block.getDurationInMinutes());
            assertEquals(10, block.getVisitorLimit());
            assertEquals(currentStart, block.getStartTime());
            currentStart = DateUtils.addMinutes(currentStart, 60);
            assertEquals(currentStart, block.getEndTime());
        }
    }

    /**
     * Create a series of (combined) blocks. 
     * Expand that series, then re-combined.
     * Assert the re-combined set matches the original set.
     * 
     * @throws Exception
     */
    @Test
    public void testCombine() throws Exception {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();
        Date startDate = dateFormat.parse("20080624");
        Date endDate = dateFormat.parse("20080806");
        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("9:00 AM", "11:30 AM", "MTWRF", startDate,
                endDate);

        // 32 weekdays between June 24 2008 and August 6 2008
        assertEquals(32, blocks.size());

        SortedSet<AvailableBlock> expandedBlocks = AvailableBlockBuilder.expand(blocks, 30);
        assertEquals(160, expandedBlocks.size());

        Set<AvailableBlock> recombinedBlocks = AvailableBlockBuilder.combine(expandedBlocks);
        Iterator<AvailableBlock> originalIterator = blocks.iterator();
        Iterator<AvailableBlock> recombinedIterator = recombinedBlocks.iterator();
        for (int i = 0; i < 32; i++) {
            assertEquals(originalIterator.next(), recombinedIterator.next());
        }

        assertEquals(32, recombinedBlocks.size());
    }

    /** 
     * Create 1 years worth of (combined) blocks.
     * Expand the set.
     * Re-combine the expanded set, assert matches original set.
     * 
     * @throws Exception
     */
    @Test
    public void testCombine2() throws Exception {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();
        Date startDate = dateFormat.parse("20080101");
        Date endDate = dateFormat.parse("20081231");
        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("12:00 AM", "11:30 PM", "NMTWRFS",
                startDate, endDate);

        // 366 days in 2008
        assertEquals(366, blocks.size());

        SortedSet<AvailableBlock> expandedBlocks = AvailableBlockBuilder.expand(blocks, 30);
        assertEquals(17202, expandedBlocks.size());

        Set<AvailableBlock> recombinedBlocks = AvailableBlockBuilder.combine(expandedBlocks);
        Iterator<AvailableBlock> originalIterator = blocks.iterator();
        Iterator<AvailableBlock> recombinedIterator = recombinedBlocks.iterator();
        for (int i = 0; i < 366; i++) {
            assertEquals(originalIterator.next(), recombinedIterator.next());
        }

        assertEquals(366, recombinedBlocks.size());
    }

    /**
     * Create 50 years worth of (combined) blocks.
     * Expand the set.
     * Re-combine the expanded set, assert matches original set.
     * 
     * @throws Exception
     */
    //@Test
    public void testCombine50Years() throws Exception {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();
        Date startDate = dateFormat.parse("20080101");
        Date endDate = dateFormat.parse("20581231");
        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("12:00 AM", "11:30 PM", "NMTWRFS",
                startDate, endDate);

        assertEquals(18628, blocks.size());

        SortedSet<AvailableBlock> expandedBlocks = AvailableBlockBuilder.expand(blocks, 30);
        assertEquals(875516, expandedBlocks.size());

        Set<AvailableBlock> recombinedBlocks = AvailableBlockBuilder.combine(expandedBlocks);
        Iterator<AvailableBlock> originalIterator = blocks.iterator();
        Iterator<AvailableBlock> recombinedIterator = recombinedBlocks.iterator();
        for (int i = 0; i < 18628; i++) {
            assertEquals(originalIterator.next(), recombinedIterator.next());
        }

        assertEquals(18628, recombinedBlocks.size());
    }

    /**
     * Create a set of blocks with createBlocks.
     * expand, then remove some of the expanded elements.
     * call combine and verify blocks are created properly.
     * 
     * @throws Exception
     */
    @Test
    public void testCombine3() throws Exception {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();
        Date startDate = dateFormat.parse("20080720");
        Date endDate = dateFormat.parse("20080726");
        Set<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("8:00 AM", "4:00 PM", "MWF", startDate,
                endDate);

        // Mon/Wed/Fri from July 20 to July 26
        // 3 days in this span
        assertEquals(3, blocks.size());
        // 16 blocks per day (8 hours)
        SortedSet<AvailableBlock> expandedBlocks = AvailableBlockBuilder.expand(blocks, 30);
        assertEquals(48, expandedBlocks.size());

        // remove 12:00 to 12:30 on Wednesday
        expandedBlocks.remove(AvailableBlockBuilder.createBlock("20080723-1200", "20080723-1230"));

        assertEquals(47, expandedBlocks.size());

        SortedSet<AvailableBlock> recombinedBlocks = AvailableBlockBuilder.combine(expandedBlocks);
        assertEquals(4, recombinedBlocks.size());

        SortedSet<AvailableBlock> reexpandedBlocks = AvailableBlockBuilder.expand(recombinedBlocks, 30);
        assertEquals(47, reexpandedBlocks.size());

        // remove 3:30 to 4:00 on Friday
        reexpandedBlocks.remove(AvailableBlockBuilder.createBlock("20080725-1530", "20080725-1600"));
        assertEquals(46, reexpandedBlocks.size());
        // recombining shouldn't change the size; we're just trimming the length of the last large block
        assertEquals(4, AvailableBlockBuilder.combine(reexpandedBlocks).size());
    }

    /**
     * 
     * @throws Exception
     */
    @Test
    public void testCombine15s() throws Exception {
        SimpleDateFormat timeFormat = CommonDateOperations.getDateTimeFormat();
        Date time1 = timeFormat.parse("20090211-1100");

        Date time2 = timeFormat.parse("20090211-1105");
        AvailableBlock block1 = AvailableBlockBuilder.createSmallestAllowedBlock(time1);
        assertEquals(time2, block1.getEndTime());

        AvailableBlock block2 = AvailableBlockBuilder.createSmallestAllowedBlock(time2);
        SortedSet<AvailableBlock> smallBlocks = new TreeSet<AvailableBlock>();
        smallBlocks.add(block1);
        smallBlocks.add(block2);

        Set<AvailableBlock> combined1 = AvailableBlockBuilder.combine(smallBlocks);
        assertEquals(1, combined1.size());
        AvailableBlock expected = AvailableBlockBuilder.createBlock(block1.getStartTime(), block2.getEndTime());
        assertTrue(combined1.contains(expected));
    }

    /**
     * Create a regular schedule and add a single 15 minute outlier.
     * Combine and verify the outlier is not lost.
     * 
     * @throws Exception
     */
    @Test
    public void testCombinePreservesOutliersSmall() throws Exception {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();
        SimpleDateFormat timeFormat = CommonDateOperations.getDateTimeFormat();
        Set<AvailableBlock> regularSchedule = AvailableBlockBuilder.createBlocks("9:00 AM", "3:00 PM", "MWF",
                dateFormat.parse("20090309"), dateFormat.parse("20090313"));

        assertEquals(3, regularSchedule.size());
        SortedSet<AvailableBlock> regularScheduleExpanded = AvailableBlockBuilder.expand(regularSchedule, 15);
        assertEquals(72, regularScheduleExpanded.size());
        Set<AvailableBlock> assertCombine = AvailableBlockBuilder.combine(regularScheduleExpanded);
        assertEquals(3, assertCombine.size());

        AvailableBlock outlier = AvailableBlockBuilder.createBlock(timeFormat.parse("20090312-1200"),
                timeFormat.parse("20090312-1215"));

        regularScheduleExpanded.add(outlier);
        assertEquals(73, regularScheduleExpanded.size());

        Set<AvailableBlock> recombined = AvailableBlockBuilder.combine(regularScheduleExpanded);
        assertEquals(4, recombined.size());

        assertTrue(recombined.contains(outlier));
    }

    /**
     * Create a regular schedule and add a single 15 minute outlier.
     * Combine and verify the outlier is not lost.
     * 
     * @throws Exception
     */
    @Test
    public void testCombinePreservesOutliersLarge() throws Exception {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();
        SimpleDateFormat timeFormat = CommonDateOperations.getDateTimeFormat();
        Set<AvailableBlock> regularSchedule = AvailableBlockBuilder.createBlocks("9:00 AM", "3:00 PM", "MWF",
                dateFormat.parse("20090101"), dateFormat.parse("20090601"));

        assertEquals(65, regularSchedule.size());
        SortedSet<AvailableBlock> regularScheduleExpanded = AvailableBlockBuilder.expand(regularSchedule, 15);
        assertEquals(1560, regularScheduleExpanded.size());
        Set<AvailableBlock> assertCombine = AvailableBlockBuilder.combine(regularScheduleExpanded);
        assertEquals(65, assertCombine.size());

        AvailableBlock outlier = AvailableBlockBuilder.createBlock(timeFormat.parse("20090312-1200"),
                timeFormat.parse("20090312-1215"));

        regularScheduleExpanded.add(outlier);
        assertEquals(1561, regularScheduleExpanded.size());

        Set<AvailableBlock> recombined = AvailableBlockBuilder.combine(regularScheduleExpanded);
        assertEquals(66, recombined.size());

        assertTrue(recombined.contains(outlier));
    }

    /**
     * Create 2 adjacent blocks visitor limit set to 10.
     * Pass them into combine, assert they come out 1 combined block.
     * 
     * @throws Exception
     */
    @Test
    public void testCombineVisitorLimit10() throws Exception {
        AvailableBlock block1 = AvailableBlockBuilder.createBlock("20091007-1200", "20091007-1230", 10);
        assertEquals(30, block1.getDurationInMinutes());
        AvailableBlock block2 = AvailableBlockBuilder.createBlock("20091007-1230", "20091007-1300", 10);
        assertEquals(30, block2.getDurationInMinutes());
        SortedSet<AvailableBlock> smallBlocks = new TreeSet<AvailableBlock>();
        smallBlocks.add(block1);
        smallBlocks.add(block2);
        SortedSet<AvailableBlock> resultCombined = AvailableBlockBuilder.combine(smallBlocks);
        assertEquals(resultCombined.size(), 1);
        AvailableBlock expectedCombined = AvailableBlockBuilder.createBlock("20091007-1200", "20091007-1300", 10);
        assertTrue(resultCombined.contains(expectedCombined));
        assertEquals(60, expectedCombined.getDurationInMinutes());
    }

    /**
     * Create 2 adjacent blocks with different visitor limits.
     * Pass them into combine, assert they come out as 2 separate blocks.
     * 
     * @throws Exception
     */
    @Test
    public void testCombineMismatchedVisitorLimit() throws Exception {
        AvailableBlock block1 = AvailableBlockBuilder.createBlock("20091007-1200", "20091007-1230", 10);
        AvailableBlock block2 = AvailableBlockBuilder.createBlock("20091007-1230", "20091007-1300", 9);
        SortedSet<AvailableBlock> smallBlocks = new TreeSet<AvailableBlock>();
        smallBlocks.add(block1);
        smallBlocks.add(block2);
        SortedSet<AvailableBlock> resultCombined = AvailableBlockBuilder.combine(smallBlocks);
        assertEquals(resultCombined.size(), 2);
        assertTrue(resultCombined.contains(block1));
        assertTrue(resultCombined.contains(block2));
    }

    /**
     * Create 2 adjacent blocks with different meeting locations.
     * Pass them into combine, assert they come out as 2 separate blocks.
     * @throws Exception
     */
    @Test
    public void testCombineMismatchedMeetingLocation() throws Exception {
        AvailableBlock block1 = AvailableBlockBuilder.createBlock("20091007-1200", "20091007-1230", 1,
                "alternate location");
        AvailableBlock block2 = AvailableBlockBuilder.createBlock("20091007-1230", "20091007-1300", 1, null);
        SortedSet<AvailableBlock> smallBlocks = new TreeSet<AvailableBlock>();
        smallBlocks.add(block1);
        smallBlocks.add(block2);
        SortedSet<AvailableBlock> resultCombined = AvailableBlockBuilder.combine(smallBlocks);
        assertEquals(resultCombined.size(), 2);
        assertTrue(resultCombined.contains(block1));
        assertTrue(resultCombined.contains(block2));
    }

    /**
     * Test the overloaded createSmallestAllowedBlock methods.
     * 
     * @throws Exception
     */
    @Test
    public void testCreateSmallestAllowedBlock() throws Exception {
        Date date = CommonDateOperations.parseDateTimePhrase("20091103-1500");
        AvailableBlock block = AvailableBlockBuilder.createSmallestAllowedBlock(date);
        assertEquals(date, block.getStartTime());
        assertEquals(DateUtils.addMinutes(date, AvailableBlockBuilder.MINIMUM_MINUTES), block.getEndTime());
        assertEquals(1, block.getVisitorLimit());

        block = AvailableBlockBuilder.createSmallestAllowedBlock(date, 10);
        assertEquals(date, block.getStartTime());
        assertEquals(DateUtils.addMinutes(date, AvailableBlockBuilder.MINIMUM_MINUTES), block.getEndTime());
        assertEquals(10, block.getVisitorLimit());

        block = AvailableBlockBuilder.createSmallestAllowedBlock("20091103-1500");
        assertEquals(date, block.getStartTime());
        assertEquals(DateUtils.addMinutes(date, AvailableBlockBuilder.MINIMUM_MINUTES), block.getEndTime());
        assertEquals(1, block.getVisitorLimit());
    }

    /**
     * @throws ParseException 
     * @throws InputFormatException 
     * 
     */
    @Test
    public void testTwentyMinuteDuration() throws InputFormatException, ParseException {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();
        SortedSet<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("9:00 AM", "11:40 AM", "MW",
                dateFormat.parse("20100830"), dateFormat.parse("20100903"));
        Assert.assertEquals(2, blocks.size());
        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100830-0900"),
                blocks.first().getStartTime());
        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100830-1140"),
                blocks.first().getEndTime());

        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100901-0900"),
                blocks.last().getStartTime());
        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100901-1140"),
                blocks.last().getEndTime());

        SortedSet<AvailableBlock> expanded = AvailableBlockBuilder.expand(blocks, 20);
        Assert.assertEquals(16, expanded.size());

        Date originalStart = CommonDateOperations.getDateTimeFormat().parse("20100830-0900");

        Date currentStart = originalStart;
        for (AvailableBlock e : expanded) {
            if (!DateUtils.isSameDay(e.getStartTime(), currentStart)) {
                currentStart = DateUtils.addDays(originalStart, 2);
            }
            Assert.assertEquals(currentStart, e.getStartTime());
            currentStart = DateUtils.addMinutes(currentStart, 20);
            Assert.assertEquals(currentStart, e.getEndTime());
        }
    }

    /**
     * Test that highlights the problem when a customer specifies a start/end range
     * for blocks and a meeting duration that leaves a remainder.
     * 
     * @throws ParseException 
     * @throws InputFormatException 
     */
    @Test
    public void testDurationLeavesRemainder() throws InputFormatException, ParseException {
        SimpleDateFormat dateFormat = CommonDateOperations.getDateFormat();
        SortedSet<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("9:03 AM", "3:19 PM", "MWF",
                dateFormat.parse("20100830"), dateFormat.parse("20100903"));
        Assert.assertEquals(3, blocks.size());

        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100830-0903"),
                blocks.first().getStartTime());
        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100830-1519"),
                blocks.first().getEndTime());

        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100903-0903"),
                blocks.last().getStartTime());
        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100903-1519"),
                blocks.last().getEndTime());

        SortedSet<AvailableBlock> expanded = AvailableBlockBuilder.expand(blocks, 17);
        AvailableBlock last = null;

        long millisIn17Minutes = 1020000L;
        for (AvailableBlock e : expanded) {
            if (last != null) {
                if (DateUtils.isSameDay(e.getStartTime(), last.getStartTime())) {
                    Assert.assertEquals(last.getEndTime(), e.getStartTime());
                    Assert.assertEquals(e.getEndTime().getTime() - last.getEndTime().getTime(), millisIn17Minutes);
                } else {
                    Calendar cal = Calendar.getInstance();
                    cal.setTime(e.getStartTime());
                    Assert.assertEquals(9, cal.get(Calendar.HOUR_OF_DAY));
                    Assert.assertEquals(3, cal.get(Calendar.MINUTE));

                    cal.setTime(e.getEndTime());
                    Assert.assertEquals(9, cal.get(Calendar.HOUR_OF_DAY));
                    Assert.assertEquals(20, cal.get(Calendar.MINUTE));

                    // double check yesterday's endpoint
                    cal.setTime(last.getStartTime());
                    Assert.assertEquals(15, cal.get(Calendar.HOUR_OF_DAY));
                    Assert.assertEquals(0, cal.get(Calendar.MINUTE));

                    cal.setTime(last.getEndTime());
                    Assert.assertEquals(15, cal.get(Calendar.HOUR_OF_DAY));
                    Assert.assertEquals(17, cal.get(Calendar.MINUTE));
                }
            } else {
                // first block in the series
                Calendar cal = Calendar.getInstance();
                cal.setTime(e.getStartTime());
                Assert.assertEquals(9, cal.get(Calendar.HOUR_OF_DAY));
                Assert.assertEquals(3, cal.get(Calendar.MINUTE));

                cal.setTime(e.getEndTime());
                Assert.assertEquals(9, cal.get(Calendar.HOUR_OF_DAY));
                Assert.assertEquals(20, cal.get(Calendar.MINUTE));
            }
            last = e;
        }

        Assert.assertEquals(66, expanded.size());

        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100830-0903"),
                expanded.first().getStartTime());
        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100830-0920"),
                expanded.first().getEndTime());

        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100903-1500"),
                expanded.last().getStartTime());
        Assert.assertEquals(CommonDateOperations.getDateTimeFormat().parse("20100903-1517"),
                expanded.last().getEndTime());

    }

    @Test
    public void testMeetingLocationEffectHashCodeEquals() throws InputFormatException {
        AvailableBlock blockNoLocation = AvailableBlockBuilder.createBlock("20110810-0900", "20110810-0930");
        AvailableBlock blockWithLocation = AvailableBlockBuilder.createBlock("20110810-0900", "20110810-0930", 1,
                "different location");

        Assert.assertEquals(blockNoLocation, blockWithLocation);
        Assert.assertEquals(blockNoLocation.hashCode(), blockWithLocation.hashCode());
    }

    @Test
    public void textExpandPreserveLocation() throws InputFormatException, ParseException {
        SortedSet<AvailableBlock> blocks = AvailableBlockBuilder.createBlocks("9:00 AM", "12:00 PM", "MWF",
                CommonDateOperations.getDateFormat().parse("20110801"),
                CommonDateOperations.getDateFormat().parse("20110831"), 1, "alternate location");

        for (AvailableBlock block : blocks) {
            Assert.assertEquals("alternate location", block.getMeetingLocation());
        }
    }

    /**
     * Create adjacent available blocks but set different meeting locations.
     * Confirm blocks not combined by combine method.
     * @throws InputFormatException 
     * 
     */
    @Test
    public void testDifferentMeetingLocationNotCombined() throws InputFormatException {
        AvailableBlock blockNoLocation = AvailableBlockBuilder.createBlock("20110810-0900", "20110810-0930", 1,
                "some location");
        AvailableBlock blockWithLocation = AvailableBlockBuilder.createBlock("20110810-0930", "20110810-1000", 1,
                "different location");

        SortedSet<AvailableBlock> smallBlocks = new TreeSet<AvailableBlock>();
        smallBlocks.add(blockNoLocation);
        smallBlocks.add(blockWithLocation);

        SortedSet<AvailableBlock> combined = AvailableBlockBuilder.combine(smallBlocks);
        Assert.assertEquals(2, combined.size());
        Assert.assertEquals(blockNoLocation, combined.first());
        Assert.assertEquals(blockWithLocation, combined.last());
    }

    /**
     * 
     * @throws InputFormatException
     */
    @Test
    public void testSafeMeetingLocationEquals() throws InputFormatException {
        AvailableBlock blockNoLocation = AvailableBlockBuilder.createBlock("20110810-0900", "20110810-0930", 1);
        AvailableBlock blockNoLocation2 = AvailableBlockBuilder.createBlock("20110810-0900", "20110810-0930", 1);
        Assert.assertTrue(AvailableBlockBuilder.safeMeetingLocationEquals(blockNoLocation, blockNoLocation2));
        AvailableBlock blockWithLocation = AvailableBlockBuilder.createBlock("20110810-0930", "20110810-1000", 1,
                "different location");
        Assert.assertFalse(AvailableBlockBuilder.safeMeetingLocationEquals(blockNoLocation, blockWithLocation));
        AvailableBlock blockWithLocation2 = AvailableBlockBuilder.createBlock("20110810-0930", "20110810-1000", 1,
                "different location");
        Assert.assertTrue(AvailableBlockBuilder.safeMeetingLocationEquals(blockWithLocation, blockWithLocation2));
    }
}