CalendarUtils.java Source code

Java tutorial

Introduction

Here is the source code for CalendarUtils.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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.apache.commons.lang;

import java.text.*;
import java.util.*;

/**
 * A suite of utilities surrounding the use of the Calendar and Date object.
 *
 * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a>
 */
public class CalendarUtils {

    /**
     * This is half a month, so this represents whether a date is in the top
     * or bottom half of the month.
     */
    public final static int SEMI_MONTH = 1001;

    private static final int[][] fields = { { Calendar.MILLISECOND }, { Calendar.SECOND }, { Calendar.MINUTE },
            { Calendar.HOUR_OF_DAY, Calendar.HOUR },
            { Calendar.DATE, Calendar.DAY_OF_MONTH,
                    Calendar.AM_PM /* Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH */ },
            { Calendar.MONTH, CalendarUtils.SEMI_MONTH }, { Calendar.YEAR }, { Calendar.ERA } };

    private static DateFormat[] dateFormats = {
            //3/31/92 10:00:07 PST
            new SimpleDateFormat("M/dd/yy h:mm:ss z"),
            //January 23, 1987 10:05pm
            new SimpleDateFormat("MMM d, yyyy h:mm a"),
            //22:00 GMT
            new SimpleDateFormat("h:mm z") };

    /**
     * A week range, starting on Sunday.
     */
    public final static int RANGE_WEEK_SUNDAY = 1;

    /**
     * A week range, starting on Monday.
     */
    public final static int RANGE_WEEK_MONDAY = 2;

    /**
     * A week range, starting on the day focused.
     */
    public final static int RANGE_WEEK_RELATIVE = 3;

    /**
     * A week range, centered around the day focused.
     */
    public final static int RANGE_WEEK_CENTER = 4;

    /**
     * A month range, the week starting on Sunday.
     */
    public final static int RANGE_MONTH_SUNDAY = 5;

    /**
     * A month range, the week starting on Monday.
     */
    public final static int RANGE_MONTH_MONDAY = 6;

    /**
     * See the other round method.  Works with a Date object.
     */
    public static Date round(Date val, int field) {
        GregorianCalendar gval = new GregorianCalendar();
        gval.setTime(val);
        modify(gval, field, true);
        return gval.getTime();
    }

    /**
     * Round this date, leaving the field specified as the most significant
     * field.  For example, if you had the datetime of 28 Mar 2002
     * 13:45:01.231, if this was passed with HOUR, it would return 28 Mar
     * 2002 14:00:00.000.  If this was passed with MONTH, it would return
     * 1 April 2002 0:00:00.000.
     */
    public static Calendar round(Calendar val, int field) {
        Calendar rounded = (Calendar) val.clone();
        modify(rounded, field, true);
        return rounded;
    }

    /**
     * See the other round method.  Works with an Object, trying to
     * use it as either a Date or Calendar.
     */
    public static Date round(Object val, int field) {
        if (val instanceof Date) {
            return round((Date) val, field);
        } else if (val instanceof Calendar) {
            return round((Calendar) val, field).getTime();
        } else {
            throw new ClassCastException("Could not round " + val);
        }
    }

    /**
     * See the other trunc method.  Works with a Date.
     */
    public static Date trunc(Date val, int field) {
        GregorianCalendar gval = new GregorianCalendar();
        gval.setTime(val);
        modify(gval, field, false);
        return gval.getTime();
    }

    /**
     * Truncate this date, leaving the field specified as the most significant
     * field.  For example, if you had the datetime of 28 Mar 2002
     * 13:45:01.231, if you passed with HOUR, it would return 28 Mar
     * 2002 13:00:00.000.  If this was passed with MONTH, it would return
     * 1 Mar 2002 0:00:00.000.
     */
    public static Calendar trunc(Calendar val, int field) {
        Calendar truncated = (Calendar) val.clone();
        modify(truncated, field, false);
        return truncated;
    }

    /**
     * See the other trunc method.  Works with an Object, trying to
     * use it as either a Date or Calendar.
     */
    public static Date trunc(Object val, int field) {
        if (val instanceof Date) {
            return trunc((Date) val, field);
        } else if (val instanceof Calendar) {
            return trunc((Calendar) val, field).getTime();
        } else {
            throw new ClassCastException("Could not trunc " + val);
        }
    }

    private static void modify(Calendar val, int field, boolean round) {
        boolean roundUp = false;
        for (int i = 0; i < fields.length; i++) {
            for (int j = 0; j < fields[i].length; j++) {
                if (fields[i][j] == field) {
                    //This is our field... we stop looping
                    if (round && roundUp) {
                        if (field == CalendarUtils.SEMI_MONTH) {
                            //This is a special case that's hard to generalize
                            //If the date is 1, we round up to 16, otherwise
                            //  we subtract 15 days and add 1 month
                            if (val.get(Calendar.DATE) == 1) {
                                val.add(Calendar.DATE, 15);
                            } else {
                                val.add(Calendar.DATE, -15);
                                val.add(Calendar.MONTH, 1);
                            }
                        } else {
                            //We need at add one to this field since the
                            //  last number causes us to round up
                            val.add(fields[i][0], 1);
                        }
                    }
                    return;
                }
            }
            //We have various fields that are not easy roundings
            int offset = 0;
            boolean offsetSet = false;
            //These are special types of fields that require different rounding rules
            switch (field) {
            case CalendarUtils.SEMI_MONTH:
                if (fields[i][0] == Calendar.DATE) {
                    //If we're going to drop the DATE field's value,
                    //  we want to do this our own way.
                    //We need to subtrace 1 since the date has a minimum of 1
                    offset = val.get(Calendar.DATE) - 1;
                    //If we're above 15 days adjustment, that means we're in the
                    //  bottom half of the month and should stay accordingly.
                    if (offset >= 15) {
                        offset -= 15;
                    }
                    //Record whether we're in the top or bottom half of that range
                    roundUp = offset > 7;
                    offsetSet = true;
                }
                break;
            case Calendar.AM_PM:
                if (fields[i][0] == Calendar.HOUR) {
                    //If we're going to drop the HOUR field's value,
                    //  we want to do this our own way.
                    offset = val.get(Calendar.HOUR);
                    if (offset >= 12) {
                        offset -= 12;
                    }
                    roundUp = offset > 6;
                    offsetSet = true;
                }
                break;
            }
            if (!offsetSet) {
                int min = val.getActualMinimum(fields[i][0]);
                int max = val.getActualMaximum(fields[i][0]);
                //Calculate the offset from the minimum allowed value
                offset = val.get(fields[i][0]) - min;
                //Set roundUp if this is more than half way between the minimum and maximum
                roundUp = offset > ((max - min) / 2);
            }
            //We need to remove this field
            val.add(fields[i][0], -offset);
        }
        throw new RuntimeException("We do not support that field.");

    }

    /**
     * Parses strings the way that CVS supports it... very human readable
     */
    public static Calendar parse(String original) {
        return parse(original, Locale.getDefault());
    }

    /**
     * Parses strings the way that CVS supports it... very human readable
     */
    public static Calendar parse(String original, Locale locale) {
        //Get the symbol names
        DateFormatSymbols symbols = new DateFormatSymbols(locale);

        //Prep the string to parse
        String value = original.toLowerCase().trim();

        //Get the current date/time
        Calendar now = Calendar.getInstance();
        if (value.endsWith(" ago")) {
            //If this was a date that was "ago" the current time...
            //Strip out the ' ago' part
            value = value.substring(0, value.length() - 4);

            //Split the value and unit
            int start = value.indexOf(" ");
            if (start < 0) {
                throw new RuntimeException("Could not find space in between value and unit");
            }
            String unit = value.substring(start + 1);
            value = value.substring(0, start);
            //We support "a week", so we need to parse the value as "a"
            int val = 0;
            if (value.equals("a") || value.equals("an")) {
                val = 1;
            } else {
                val = Integer.parseInt(value);
            }

            //Determine the unit
            if (unit.equals("milliseconds") || unit.equals("millisecond")) {
                now.add(Calendar.MILLISECOND, -val);
            } else if (unit.equals("seconds") || unit.equals("second")) {
                now.add(Calendar.SECOND, -val);
            } else if (unit.equals("minutes") || unit.equals("minute")) {
                now.add(Calendar.MINUTE, -val);
            } else if (unit.equals("hours") || unit.equals("hour")) {
                now.add(Calendar.HOUR, -val);
            } else if (unit.equals("days") || unit.equals("day")) {
                now.add(Calendar.DATE, -val);
            } else if (unit.equals("weeks") || unit.equals("week")) {
                now.add(Calendar.DATE, -val * 7);
            } else if (unit.equals("fortnights") || unit.equals("fortnight")) {
                now.add(Calendar.DATE, -val * 14);
            } else if (unit.equals("months") || unit.equals("month")) {
                now.add(Calendar.MONTH, -val);
            } else if (unit.equals("years") || unit.equals("year")) {
                now.add(Calendar.YEAR, -val);
            } else {
                throw new RuntimeException("We do not understand that many units ago");
            }
            return now;
        } else if (value.startsWith("last ")) {
            //If this was the last time a certain field was met
            //Strip out the 'last ' part
            value = value.substring(5);
            //Get the current date/time
            String[] strings = symbols.getWeekdays();
            for (int i = 0; i < strings.length; i++) {
                if (value.equalsIgnoreCase(strings[i])) {
                    //How many days after Sunday
                    int daysAgo = now.get(Calendar.DAY_OF_WEEK) - i;
                    if (daysAgo <= 0) {
                        daysAgo += 7;
                    }
                    now.add(Calendar.DATE, -daysAgo);
                    return now;
                }
            }
            strings = symbols.getMonths();
            for (int i = 0; i < strings.length; i++) {
                if (value.equalsIgnoreCase(strings[i])) {
                    //How many days after January
                    int monthsAgo = now.get(Calendar.MONTH) - i;
                    if (monthsAgo <= 0) {
                        monthsAgo += 12;
                    }
                    now.add(Calendar.MONTH, -monthsAgo);
                    return now;
                }
            }
            if (value.equals("week")) {
                now.add(Calendar.DATE, -7);
                return now;
            }
        } else if (value.equals("yesterday")) {
            now.add(Calendar.DATE, -1);
            return now;
        } else if (value.equals("tomorrow")) {
            now.add(Calendar.DATE, 1);
            return now;
        }
        //Try to parse the date a number of different ways
        for (int i = 0; i < dateFormats.length; i++) {
            try {
                Date datetime = dateFormats[i].parse(original);
                Calendar cal = Calendar.getInstance();
                cal.setTime(datetime);
                return cal;
            } catch (ParseException pe) {
                //we ignore this and just keep trying
            }
        }

        throw new RuntimeException("Unable to parse '" + original + "'.");
    }

    /**
     * This constructs an Iterator that will start and stop over a date
     * range based on the focused date and the range style.  For instance,
     * passing Thursday, July 4, 2002 and a RANGE_MONTH_SUNDAY will return
     * an Iterator that starts with Sunday, June 30, 2002 and ends with
     * Saturday, August 3, 2002.
     */
    public static Iterator getCalendarIterator(Calendar focus, int rangeStyle) {
        Calendar start = null;
        Calendar end = null;
        int startCutoff = Calendar.SUNDAY;
        int endCutoff = Calendar.SATURDAY;
        switch (rangeStyle) {
        case RANGE_MONTH_SUNDAY:
        case RANGE_MONTH_MONDAY:
            //Set start to the first of the month
            start = trunc(focus, Calendar.MONTH);
            //Set end to the last of the month
            end = (Calendar) start.clone();
            end.add(Calendar.MONTH, 1);
            end.add(Calendar.DATE, -1);
            //Loop start back to the previous sunday or monday
            if (rangeStyle == RANGE_MONTH_MONDAY) {
                startCutoff = Calendar.MONDAY;
                endCutoff = Calendar.SUNDAY;
            }
            break;
        case RANGE_WEEK_SUNDAY:
        case RANGE_WEEK_MONDAY:
        case RANGE_WEEK_RELATIVE:
        case RANGE_WEEK_CENTER:
            //Set start and end to the current date
            start = trunc(focus, Calendar.DATE);
            end = trunc(focus, Calendar.DATE);
            switch (rangeStyle) {
            case RANGE_WEEK_SUNDAY:
                //already set by default
                break;
            case RANGE_WEEK_MONDAY:
                startCutoff = Calendar.MONDAY;
                endCutoff = Calendar.SUNDAY;
                break;
            case RANGE_WEEK_RELATIVE:
                startCutoff = focus.get(Calendar.DAY_OF_WEEK);
                endCutoff = startCutoff - 1;
                break;
            case RANGE_WEEK_CENTER:
                startCutoff = focus.get(Calendar.DAY_OF_WEEK) - 3;
                endCutoff = focus.get(Calendar.DAY_OF_WEEK) + 3;
                break;
            }
            break;
        default:
            throw new RuntimeException("The range style " + rangeStyle + " is not valid.");
        }
        if (startCutoff < Calendar.SUNDAY) {
            startCutoff += 7;
        }
        if (endCutoff > Calendar.SATURDAY) {
            endCutoff -= 7;
        }
        while (start.get(Calendar.DAY_OF_WEEK) != startCutoff) {
            start.add(Calendar.DATE, -1);
        }
        while (end.get(Calendar.DAY_OF_WEEK) != endCutoff) {
            end.add(Calendar.DATE, 1);
        }
        final Calendar startFinal = start;
        final Calendar endFinal = end;
        Iterator it = new Iterator() {
            Calendar spot = null;
            {
                spot = startFinal;
                spot.add(Calendar.DATE, -1);
            }

            public boolean hasNext() {
                return spot.before(endFinal);
            }

            public Object next() {
                if (spot.equals(endFinal)) {
                    throw new NoSuchElementException();
                }
                spot.add(Calendar.DATE, 1);
                return spot.clone();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
        return it;
    }

    /**
     * See the other getCalendarIterator.  Works with a Date.
     */
    public static Iterator getCalendarIterator(Date focus, int rangeStyle) {
        GregorianCalendar gval = new GregorianCalendar();
        gval.setTime(focus);
        return getCalendarIterator(gval, rangeStyle);
    }

    /**
     * See the other getCalendarIterator.  Works with an Object, trying
     * to use it as a Date or Calendar.
     */
    public static Iterator getCalendarIterator(Object focus, int rangeStyle) {
        if (focus instanceof Date) {
            return getCalendarIterator((Date) focus, rangeStyle);
        } else if (focus instanceof Calendar) {
            return getCalendarIterator((Calendar) focus, rangeStyle);
        } else {
            throw new ClassCastException("Could not iterate based on " + focus);
        }
    }

}