    package flare.util
    {
        /**
         * Utility methods for working with Date instances.
         */
        public class Dates
        {
            // enumerated constants
            /** Constant indicating a time span of one or more years. */
            public static const YEARS:int        = 0;
            /** Constant indicating a time span on the order of months. */
            public static const MONTHS:int       = -1;
            /** Constant indicating a time span on the order of days. */
            public static const DAYS:int         = -2;
            /** Constant indicating a time span on the order of hours. */
            public static const HOURS:int        = -3;
            /** Constant indicating a time span on the order of minutes. */
            public static const MINUTES:int      = -4;
            /** Constant indicating a time span on the order of seconds. */
            public static const SECONDS:int      = -5;
            /** Constant indicating a time span on the order of milliseconds. */
            public static const MILLISECONDS:int = -6;
            /** Constant indicating a time span on the order of weeks. */
            public static const WEEKS:int = -10;

            /** Number of milliseconds in a minute. */
            public static const MS_MIN:Number  = 60*1000;
            /** Number of milliseconds in an hours. */
            public static const MS_HOUR:Number = 60*60*1000;
            /** Number of milliseconds in a day. */
            public static const MS_DAY:Number  = 24*60*60*1000;

            /**
             * Constructor, throws an error if called, as this is an abstract class.
             */
            public function Dates() {
                throw new Error("This is an abstract class.");
            }

            // -- Conversion ------------------------------------------------------

            /**
             * Given a date, returns a date instance of the same time in Univeral
             * Coordinated Time (UTC).
             * @param d the date to convert
             */
            public static function toUTC(d:Date) : Date {
                return new Date(d.fullYearUTC, d.monthUTC, d.dateUTC, d.hoursUTC,
                                d.minutesUTC, d.secondsUTC, d.millisecondsUTC);
            }

            // -- Date Arithmetic -------------------------------------------------

            /**
             * Adds years to a date instance.
             * @param d the date
             * @param x the number of years to add (can be negative to subtract)
             * @return a new Date representing the new date and time
             */
            public static function addYears(d:Date, x:int) : Date {
                return new Date(d.fullYear+x, d.month, d.date, d.hours, d.minutes, d.seconds, d.milliseconds);
            }

            /**
             * Adds months to a date instance.
             * @param d the date
             * @param x the number of months to add (can be negative to subtract)
             * @return a new Date representing the new date and time
             */
            public static function addMonths(d:Date, x:int) : Date {
                var y:Number = Math.floor(x / 12); x -= 12*y;
                var m:Number = d.month + x;
                if (m > 11) {
                    y += 1;
                    m -= 12;
                } else if (m < 0) {
                    y -= 1;
                    m += 12;
                }
                return new Date(d.fullYear+y, m, d.date, d.hours, d.minutes, d.seconds, d.milliseconds);
            }

            /**
             * Adds days to a date instance.
             * @param d the date
             * @param x the number of days to add (can be negative to subtract)
             * @return a new Date representing the new date and time
             */
            public static function addDays(d:Date, x:int) : Date {
                return new Date(d.time + MS_DAY * x);
            }

            /**
             * Adds hours to a date instance.
             * @param d the date
             * @param x the number of hours to add (can be negative to subtract)
             * @return a new Date representing the new date and time
             */
            public static function addHours(d:Date, x:int) : Date {
                return new Date(d.time + MS_HOUR * x);
            }

            /**
             * Adds minutes to a date instance.
             * @param d the date
             * @param x the number of minutes to add (can be negative to subtract)
             * @return a new Date representing the new date and time
             */
            public static function addMinutes(d:Date, x:int) : Date {
                return new Date(d.time + MS_MIN * x);
            }

            /**
             * Adds seconds to a date instance.
             * @param d the date
             * @param x the number of seconds to add (can be negative to subtract)
             * @return a new Date representing the new date and time
             */
            public static function addSeconds(d:Date, x:int) : Date {
                return new Date(d.time + 1000 * x);
            }

            // -- Time Spans ------------------------------------------------------

            /**
             * Rounds a date according to a particular time span. Date values are
             * rounded to the minimum date/time value of the time span (the first
             * day in a year, month, or week, or the beginning of a day, hours,
             * minute, second, etc).
             * @param t the date to round
             * @param span the time span to which the date should be rounded, legal
             *  values are YEARS, MONTHS, WEEKS, DAYS, HOURS, MINUTES, SECONDS, or
             *  MILLISECONDS
             * @param roundUp if true, the date will be rounded up to nearest value,
             * otherwise it will be rounded down (the default)
             * @return a new Date representing the rounded date and time.
             */
            public static function roundTime(t:Date, span:int, roundUp:Boolean=false) : Date
            {
                var d:Date = t;
                if (span > YEARS) {
                    d = new Date(t.fullYear, 0);
                    if (roundUp) d = addYears(d, 1);
                } else if (span == MONTHS) {
                    d = new Date(t.fullYear, t.month);
                    if (roundUp) d = addMonths(d, 1);
                } else if (span == DAYS) {
                    d = new Date(t.fullYear, t.month, t.date);
                    if (roundUp) d = addDays(d, 1);
                } else if (span == HOURS) {
                    d = new Date(t.fullYear, t.month, t.date, t.hours);
                    if (roundUp) d = addHours(d, 1);
                } else if (span == MINUTES) {
                    d = new Date(t.fullYear, t.month, t.date, t.hours, t.minutes);
                    if (roundUp) d = addMinutes(d, 1);
                } else if (span == SECONDS) {
                    d = new Date(t.fullYear, t.month, t.date, t.hours, t.minutes, t.seconds);
                    if (roundUp) d = addSeconds(d, 1);
                } else if (span == MILLISECONDS) {
                    d = new Date(d.time + (roundUp ? 1 : -1));
                } else if (span == WEEKS) {
                    d = new Date(t.fullYear, t.month, t.date);
                    if (roundUp) {
                        d = addDays(d, 7 - d.day);
                    } else {
                        d = addDays(d, -d.day);
                    }
                }
                return d;
            }

            /**
             * Given two dates, returns a measure of the time span between them.
             * @param t the first date to compare
             * @param s the second date to compare
             * @return an integer value indicating the time span between dates. If
             * the return value is positive, it represents the number of years
             * between dates. Otherwise, the return value is one of MONTHS, DAYS,
             * HOURS, MINUTES, SECONDS, or MILLISECONDS.
             */
            public static function timeSpan(t:Date, s:Date) : int
            {
                var span:Number = s.time - t.time;
                var days:Number = span / MS_DAY;

                if (days >= 365*2)             return (1 + s.fullYear-t.fullYear);
                else if (days >= 60)           return MONTHS;
                else if (span/MS_DAY > 1)    return DAYS;
                else if (span/MS_HOUR > 1)    return HOURS;
                else if (span/MS_MIN > 1)    return MINUTES;
                else if (span/1000.0 > 1)    return SECONDS;
                else                        return MILLISECONDS;
            }

            /**
             * Returns the number of milliseconds needed to step one time step
             * forward according to the given time span measure.
             * @param span the time span for which to return a time step value.
             *  Legal values are any positive numbers (representing years) or DAYS,
             *  HOURS, MINUTES, SECONDS, and MILLISECONDS. Note that the MONTHS
             *  time span is not supported and will result in a zero return value.
             * @return the number of milliseconds needed to more one time step
             *  ahead according to the input time span. For years (positive
             *  integer input), this step is the nearest power of ten less than the
             *  input value.
             */
            public static function timeStep(span:int):Number {
                if (span > YEARS) {
                    return Math.pow(10, Math.floor(Maths.log(Math.max(1,span-1),10)));
                } else if (span == MONTHS) {
                    return 0;
                } else if (span == DAYS) {
                    return MS_DAY;
                } else if (span == HOURS) {
                    return MS_HOUR;
                } else if (span == MINUTES) {
                    return MS_MIN;
                } else if (span == SECONDS) {
                    return 1000;
                } else {
                    return 1;
                }
            }

        } // end of class DateUtil
    }