kr.debop4j.timeperiod.tools.Times.java Source code

Java tutorial

Introduction

Here is the source code for kr.debop4j.timeperiod.tools.Times.java

Source

/*
 * Copyright 2011-2013 the original author or authors.
 *
 * Licensed 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 kr.debop4j.timeperiod.tools;

import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import kr.debop4j.core.Function1;
import kr.debop4j.core.NotSupportException;
import kr.debop4j.core.Pair;
import kr.debop4j.core.parallelism.Parallels;
import kr.debop4j.core.tools.ArrayTool;
import kr.debop4j.timeperiod.*;
import kr.debop4j.timeperiod.timerange.*;
import lombok.Getter;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import static kr.debop4j.core.Guard.shouldBe;
import static kr.debop4j.core.Guard.shouldNotBeNull;

/**
 * {@link DateTime}  Utility Class .
 *
 * @author ? sunghyouk.bae@gmail.com
 * @since 13. 5. 11.  2:44
 */
public abstract class Times {

    private static final Logger log = LoggerFactory.getLogger(Times.class);
    private static final boolean isTraceEnabled = log.isTraceEnabled();
    private static final boolean isDebugEnabled = log.isDebugEnabled();

    private Times() {
    }

    public static final String NullString = "<null>";
    public static final DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0);

    /**
     * Current system time
     *
     * @return the date time
     */
    public static DateTime now() {
        return DateTime.now();
    }

    /**
     * Today
     *
     * @return the date time
     */
    public static DateTime today() {
        return DateTime.now().withTimeAtStartOfDay();
    }

    /**
     * Datepart datepart.
     *
     * @param moment the moment
     * @return the datepart
     */
    public static Datepart datepart(DateTime moment) {
        return new Datepart(moment);
    }

    /**
     * Timepart timepart.
     *
     * @param moment the moment
     * @return the timepart
     */
    public static Timepart timepart(DateTime moment) {
        return new Timepart(moment);
    }

    /**
     *    {@link DateTime} ? .
     *
     * @param year        ?
     * @param monthOfYear 
     * @param dayOfMonth  ?
     * @return  //??  DateTime
     */
    public static DateTime asDate(int year, int monthOfYear, int dayOfMonth) {
        return new DateTime(year, monthOfYear, dayOfMonth, 0, 0);
    }

    /**
     * As string.
     *
     * @param period the period
     * @return the string
     */
    public static String asString(ITimePeriod period) {
        return (period == null) ? NullString : period.toString();
    }

    /**
     * To date time.
     *
     * @param value the value
     * @return the date time
     */
    public static DateTime toDateTime(String value) {
        return toDateTime(value, new DateTime(0));
    }

    /**
     * To date time.
     *
     * @param value        the value
     * @param defaultValue the default value
     * @return the date time
     */
    public static DateTime toDateTime(String value, DateTime defaultValue) {
        try {
            return DateTime.parse(value);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * To time period collection.
     *
     * @param sequence the sequence
     * @return the i time period collection
     */
    public static <T extends ITimePeriod> ITimePeriodCollection toTimePeriodCollection(Iterable<T> sequence) {
        return new TimePeriodCollection(sequence);
    }

    // region << Calendar >>

    /**
     * Gets year of.
     *
     * @param moment the moment
     * @return the year of
     */
    public static int getYearOf(DateTime moment) {
        return getYearOf(moment.getYear(), moment.getMonthOfYear());
    }

    /**
     * Gets year of.
     *
     * @param moment   the moment
     * @param calendar the calendar
     * @return the year of
     */
    public static int getYearOf(DateTime moment, ITimeCalendar calendar) {
        return getYearOf(calendar.getYear(moment), calendar.getMonthOfYear(moment));
    }

    /**
     * Gets year of.
     *
     * @param year        the year
     * @param monthOfYear the month of year
     * @return the year of
     */
    public static int getYearOf(int year, int monthOfYear) {
        return monthOfYear >= 1 ? year : year - 1;
    }

    /**
     * Gets days of year.
     *
     * @param year the year
     * @return the days of year
     */
    public static int getDaysOfYear(int year) {
        return startTimeOfYear(year + 1).minusMillis(1).getDayOfYear();
    }

    /**
     * Next halfyear.
     *
     * @param startYear     the start year
     * @param startHalfyear the start halfyear
     * @return the year and halfyear
     */
    public static YearAndHalfyear nextHalfyear(int startYear, Halfyear startHalfyear) {
        return addHalfyear(startYear, startHalfyear, 1);
    }

    /**
     * Previous halfyear.
     *
     * @param startYear     the start year
     * @param startHalfyear the start halfyear
     * @return the year and halfyear
     */
    public static YearAndHalfyear previousHalfyear(int startYear, Halfyear startHalfyear) {
        return addHalfyear(startYear, startHalfyear, -1);
    }

    /**
     * Add halfyear.
     *
     * @param startYear     the start year
     * @param startHalfyear the start halfyear
     * @param halfyearCount the halfyear count
     * @return the year and halfyear
     */
    public static YearAndHalfyear addHalfyear(int startYear, Halfyear startHalfyear, int halfyearCount) {
        int offsetYear = (Math.abs(halfyearCount) / TimeSpec.HalfyearsPerYear) + 1;
        int startHalfyearCount = ((startYear + offsetYear) * TimeSpec.HalfyearsPerYear)
                + (startHalfyear.getValue() - 1);
        int targetHalfyearCount = startHalfyearCount + halfyearCount;

        int year = (targetHalfyearCount / TimeSpec.HalfyearsPerYear) - offsetYear;
        Halfyear halfyear = Halfyear.valueOf((targetHalfyearCount % TimeSpec.HalfyearsPerYear) + 1);

        if (isTraceEnabled)
            log.trace(
                    "addHalfyear. startYear=[{}], startHalfyear=[{}], halfyearCount=[{}], year=[{}], halfyear=[{}]",
                    startYear, startHalfyear, halfyearCount, year, halfyear);

        return new YearAndHalfyear(year, halfyear);
    }

    /**
     * Gets halfyear of month.
     *
     * @param monthOfYear the month of year
     * @return the halfyear of month
     */
    public static Halfyear getHalfyearOfMonth(int monthOfYear) {
        assert monthOfYear >= 1 && monthOfYear <= 12;

        return (monthOfYear <= TimeSpec.MonthsPerHalfyear) ? Halfyear.First : Halfyear.Second;
    }

    /**
     * Get months of halfyear.
     *
     * @param halfyear the halfyear
     * @return the int [ ]
     */
    public static int[] getMonthsOfHalfyear(Halfyear halfyear) {
        return (halfyear == Halfyear.First) ? TimeSpec.FirstHalfyearMonths : TimeSpec.SecondHalfyearMonths;
    }

    /**
     * Next quarter.
     *
     * @param year    the year
     * @param quarter the quarter
     * @return the year and quarter
     */
    public static YearAndQuarter nextQuarter(int year, Quarter quarter) {
        return addQuarter(year, quarter, 1);
    }

    /**
     * Previous quarter.
     *
     * @param year    the year
     * @param quarter the quarter
     * @return the year and quarter
     */
    public static YearAndQuarter previousQuarter(int year, Quarter quarter) {
        return addQuarter(year, quarter, -1);
    }

    /**
     * Add quarter.
     *
     * @param year    the year
     * @param quarter the quarter
     * @param count   the count
     * @return the year and quarter
     */
    public static YearAndQuarter addQuarter(int year, Quarter quarter, int count) {
        int offsetYear = Math.abs(count) / TimeSpec.QuartersPerYear + 1;
        int startQuarters = (year + offsetYear) * TimeSpec.QuartersPerYear + quarter.getValue() - 1;
        int targetQuarters = startQuarters + count;

        int y = targetQuarters / TimeSpec.QuartersPerYear - offsetYear;
        int q = (targetQuarters % TimeSpec.QuartersPerYear) + 1;

        return new YearAndQuarter(y, q);
    }

    /**
     * Gets quarter of month.
     *
     * @param monthOfYear the month of year
     * @return the quarter of month
     */
    public static Quarter getQuarterOfMonth(int monthOfYear) {
        int quarter = (monthOfYear - 1) / TimeSpec.MonthsPerQuarter + 1;
        return Quarter.valueOf(quarter);
    }

    /**
     * Get months of quarter.
     *
     * @param quarter the quarter
     * @return the int [ ]
     */
    public static int[] getMonthsOfQuarter(Quarter quarter) {
        switch (quarter) {
        case First:
            return TimeSpec.FirstQuarterMonths;
        case Second:
            return TimeSpec.SecondQuarterMonths;
        case Third:
            return TimeSpec.ThirdQuarterMonths;
        case Fourth:
            return TimeSpec.FourthQuarterMonths;
        default:
            throw new IllegalArgumentException("invalid quarter. quarter=" + quarter);
        }
    }

    /**
     * Next month.
     *
     * @param year        the year
     * @param monthOfYear the month of year
     * @return the year and month
     */
    public static YearAndMonth nextMonth(int year, int monthOfYear) {
        return addMonth(year, monthOfYear, 1);
    }

    /**
     * Previous month.
     *
     * @param year        the year
     * @param monthOfYear the month of year
     * @return the year and month
     */
    public static YearAndMonth previousMonth(int year, int monthOfYear) {
        return addMonth(year, monthOfYear, -1);
    }

    /**
     * Add month.
     *
     * @param year        the year
     * @param monthOfYear the month of year
     * @param count       the count
     * @return the year and month
     */
    public static YearAndMonth addMonth(int year, int monthOfYear, int count) {
        int offset = Math.abs(count) / TimeSpec.MonthsPerYear + 1;
        int startMonths = (year + offset) * TimeSpec.MonthsPerYear + monthOfYear - 1;
        int endMonths = startMonths + count;

        int y = endMonths / TimeSpec.MonthsPerYear - offset;
        int m = (endMonths % TimeSpec.MonthsPerYear) + 1;

        return new YearAndMonth(y, m);
    }

    /**
     * Gets days in month.
     *
     * @param year  the year
     * @param month the month
     * @return the days in month
     */
    public static int getDaysInMonth(int year, int month) {
        DateTime firstDay = new DateTime(year, month, 1, 0, 0);
        return firstDay.plusMonths(1).plusDays(-1).getDayOfMonth();
    }

    /**
     * Gets start of week.
     *
     * @param time the time
     * @return the start of week
     */
    public static DateTime getStartOfWeek(DateTime time) {
        return getStartOfWeek(time, TimeSpec.FirstDayOfWeek);
    }

    /**
     * Gets start of week.
     *
     * @param time           the time
     * @param firstDayOfWeek the first day of week
     * @return the start of week
     */
    public static DateTime getStartOfWeek(DateTime time, DayOfWeek firstDayOfWeek) {
        DateTime current = time.withTimeAtStartOfDay();
        while (current.getDayOfWeek() != firstDayOfWeek.getValue()) {
            current = current.minusDays(1);
        }
        return current;
    }

    /**
     * Gets week of year.
     *
     * @param moment the moment
     * @return the week of year
     */
    public static YearAndWeek getWeekOfYear(DateTime moment) {
        return getWeekOfYear(moment, TimeCalendar.getDefault());
    }

    /**
     * Gets week of year.
     *
     * @param moment       the moment
     * @param timeCalendar the time calendar
     * @return the week of year
     */
    public static YearAndWeek getWeekOfYear(DateTime moment, ITimeCalendar timeCalendar) {
        return new YearAndWeek(moment.getWeekyear(), moment.getWeekOfWeekyear());
    }

    /**
     * Gets weeks of year.
     *
     * @param year the year
     * @return the weeks of year
     */
    public static int getWeeksOfYear(int year) {
        return getWeeksOfYear(year, TimeCalendar.getDefault());
    }

    /**
     * Gets weeks of year.
     *
     * @param year         the year
     * @param timeCalendar the time calendar
     * @return the weeks of year
     */
    public static int getWeeksOfYear(int year, ITimeCalendar timeCalendar) {
        return asDate(year, 12, 28).getWeekOfWeekyear();
    }

    /**
     * Gets start of year week.
     *
     * @param year       the year
     * @param weekOfYear the week of year
     * @return the start of year week
     */
    public static DateTime getStartOfYearWeek(int year, int weekOfYear) {
        return getStartOfYearWeek(year, weekOfYear, null);
    }

    /**
     * Gets start of year week.
     *
     * @param year         the year
     * @param weekOfYear   the week of year
     * @param timeCalendar the time calendar
     * @return the start of year week
     */
    public static DateTime getStartOfYearWeek(int year, int weekOfYear, ITimeCalendar timeCalendar) {
        return new DateTime().withYear(year).withWeekOfWeekyear(weekOfYear);
    }

    /**
     * Day start.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime dayStart(DateTime moment) {
        return moment.withTimeAtStartOfDay();
    }

    /**
     * Next day of week.
     *
     * @param day the day
     * @return the day of week
     */
    public static DayOfWeek nextDayOfWeek(DayOfWeek day) {
        return addDayOfWeek(day, 1);
    }

    /**
     * Previous day of week.
     *
     * @param day the day
     * @return the day of week
     */
    public static DayOfWeek previousDayOfWeek(DayOfWeek day) {
        return addDayOfWeek(day, -1);
    }

    /**
     * Add day of week.
     *
     * @param day  the day
     * @param days the days
     * @return the day of week
     */
    public static DayOfWeek addDayOfWeek(DayOfWeek day, int days) {
        if (days == 0)
            return day;

        int weeks = Math.abs(days) / TimeSpec.DaysPerWeek + 1;
        int offset = weeks * TimeSpec.DaysPerWeek + day.getValue() - 1 + days;
        return DayOfWeek.valueOf((offset % TimeSpec.DaysPerWeek) + 1);
    }

    // endregion << Calendar >>

    // region << Compare >>

    /**
     * ? ??? ? {@link kr.debop4j.timeperiod.PeriodUnit}  ? ?. (??  .)
     *
     * @param left       the left
     * @param right      the right
     * @param periodUnit the period unit
     * @return the boolean
     */
    public static boolean isSameTime(DateTime left, DateTime right, PeriodUnit periodUnit) {
        if (isTraceEnabled)
            log.trace("? ?? ? ?. left=[{}], right=[{}], periodUnit=[{}]", left,
                    right, periodUnit);

        switch (periodUnit) {
        case Year:
            return isSameYear(left, right);
        case Halfyear:
            return isSameHalfyear(left, right);
        case Quarter:
            return isSameQuarter(left, right);
        case Month:
            return isSameMonth(left, right);
        case Week:
            return isSameWeek(left, right);
        case Day:
            return isSameDay(left, right);
        case Hour:
            return isSameHour(left, right);
        case Minute:
            return isSameMinute(left, right);
        case Second:
            return isSameSecond(left, right);
        case Millisecond:
        default:
            return isSameDateTime(left, right);
        }
    }

    /**
     * ? ??? (Year)  ? ?.
     *
     * @param left  the left
     * @param right the right
     * @return the boolean
     */
    public static boolean isSameYear(DateTime left, DateTime right) {
        return left.getYear() == right.getYear();
    }

    /**
     * ? ?? (halfyear)   true  false .
     *
     * @param left  the left
     * @param right the right
     * @return the boolean
     */
    public static boolean isSameHalfyear(DateTime left, DateTime right) {
        return isSameYear(left, right)
                && getHalfyearOfMonth(left.getMonthOfYear()) == getHalfyearOfMonth(right.getMonthOfYear());
    }

    /**
     * ? ?? (quarter)   true  false .
     *
     * @param left  the left
     * @param right the right
     * @return the boolean
     */
    public static boolean isSameQuarter(DateTime left, DateTime right) {
        return isSameYear(left, right)
                && getQuarterOfMonth(left.getMonthOfYear()) == getQuarterOfMonth(right.getMonthOfYear());
    }

    /**
     * ? ?? (Month)   true  false .
     *
     * @param left  the left
     * @param right the right
     * @return the boolean
     */
    public static boolean isSameMonth(DateTime left, DateTime right) {
        return isSameYear(left, right) && left.getMonthOfYear() == right.getMonthOfYear();
    }

    /**
     * ? ?? (Week)   true  false .
     *
     * @param left  the left
     * @param right the right
     * @return the boolean
     */
    public static boolean isSameWeek(DateTime left, DateTime right) {
        return isSameYear(left, right) && left.getWeekOfWeekyear() == right.getWeekOfWeekyear();
    }

    /**
     * ? ?? ?(Day)   true  false .
     *
     * @param left  the left
     * @param right the right
     * @return the boolean
     */
    public static boolean isSameDay(DateTime left, DateTime right) {
        return isSameMonth(left, right) && left.getDayOfMonth() == right.getDayOfMonth();
    }

    /**
     * ? ?? (Hour)   true  false .
     *
     * @param left  the left
     * @param right the right
     * @return the boolean
     */
    public static boolean isSameHour(DateTime left, DateTime right) {
        return isSameDay(left, right) && left.getHourOfDay() == right.getHourOfDay();
    }

    /**
     * ? ??    true  false .
     *
     * @param left  the left
     * @param right the right
     * @return the boolean
     */
    public static boolean isSameMinute(DateTime left, DateTime right) {
        return isSameDay(left, right) && left.getMinuteOfDay() == right.getMinuteOfDay();
    }

    /**
     * ? ??    true  false .
     *
     * @param left  the left
     * @param right the right
     * @return the boolean
     */
    public static boolean isSameSecond(DateTime left, DateTime right) {
        return isSameDay(left, right) && left.getSecondOfDay() == right.getSecondOfDay();
    }

    /**
     * ? ??  true  false .
     *
     * @param left  the left
     * @param right the right
     * @return the boolean
     */
    public static boolean isSameDateTime(DateTime left, DateTime right) {
        return (left != null) && left.equals(right);
    }

    // endregion << Compare >>

    // region << Current >>

    /**  ?? ? ?? ? */
    public static DateTime currentYear() {
        return asDate(now().getYear(), 1, 1);
    }

    /**  ?? ? ? ? */
    public static DateTime currentHalfyear() {
        DateTime now = now();
        Halfyear halfyear = getHalfyearOfMonth(now.getMonthOfYear());
        int month = getMonthsOfHalfyear(halfyear)[0];

        return asDate(now.getYear(), month, 1);
    }

    /**  ?? ? ? ? */
    public static DateTime currentQuarter() {
        DateTime now = now();
        Quarter quarter = getQuarterOfMonth(now.getMonthOfYear());
        int month = getMonthsOfQuarter(quarter)[0];

        return asDate(now.getYear(), month, 1);
    }

    /**  ?? ? ? ? */
    public static DateTime currentMonth() {
        return trimToDay(now());
    }

    /**  ?? ? ? ? */
    public static DateTime currentWeek() {
        return currentWeek(TimeSpec.FirstDayOfWeek);
    }

    /**  ?? ? ? ? */
    public static DateTime currentWeek(DayOfWeek firstDayOfWeek) {
        return getStartOfWeek(now(), firstDayOfWeek);
    }

    /**  */
    public static DateTime currentDay() {
        return today();
    }

    /**  ?? ? ?? ? */
    public static DateTime currentHour() {
        return trimToMinute(now());
    }

    /**  ?? ? ? ? */
    public static DateTime currentMinute() {
        return trimToSecond(now());
    }

    /**  ?? ? ? ? */
    public static DateTime currentSecond() {
        return trimToMillis(now());
    }

    // endregion << Current >>

    // region << DateTime >>

    /** moment  ? ??  ? */
    public static DateTime startTimeOfYear(DateTime moment) {
        return new DateTime(moment.getYear(), 1, 1, 0, 0);
    }

    /**  ??  ? */
    public static DateTime startTimeOfYear(int year) {
        return new DateTime(year, 1, 1, 0, 0);
    }

    /** moment ? ??  ? */
    public static DateTime endTimeOfYear(DateTime moment) {
        return startTimeOfYear(moment.getYear() + 1).plusMillis(-1);
    }

    /**  ??  ? */
    public static DateTime endTimeOfYear(int year) {
        return startTimeOfYear(year + 1).plusMillis(-1);
    }

    /**   ? */
    public static DateTime startTimeOfLastYear(DateTime moment) {
        return startTimeOfYear(moment.getYear() - 1);
    }

    /**   ? */
    public static DateTime endTimeOfLastYear(DateTime moment) {
        return endTimeOfYear(moment.getYear() - 1);
    }

    /**
     *  ?? ? ?  ?
     *
     * @param moment ??
     * @return ?  ?
     */
    public static DateTime startTimeOfHalfyear(DateTime moment) {
        return startTimeOfHalfyear(moment.getYear(), moment.getMonthOfYear());
    }

    /**
     *  /? ? ?  ?
     *
     * @param year        
     * @param monthOfYear 
     * @return ?  ?
     */
    public static DateTime startTimeOfHalfyear(int year, int monthOfYear) {
        return startTimeOfHalfyear(year, getHalfyearOfMonth(monthOfYear));
    }

    /**
     *   / ?  ?
     *
     * @param year     year
     * @param halfyear halfyear
     * @return start time of half year
     */
    public static DateTime startTimeOfHalfyear(int year, Halfyear halfyear) {
        return new DateTime(year, getMonthsOfHalfyear(halfyear)[0], 1, 0, 0);
    }

    /**
     * End time of halfyear.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime endTimeOfHalfyear(DateTime moment) {
        return endTimeOfHalfyear(moment.getYear(), moment.getMonthOfYear());
    }

    /**
     * End time of halfyear.
     *
     * @param year        the year
     * @param monthOfYear the month of year
     * @return the date time
     */
    public static DateTime endTimeOfHalfyear(int year, int monthOfYear) {
        return startTimeOfHalfyear(year, monthOfYear).plusMonths(TimeSpec.MonthsPerHalfyear)
                .minus(TimeSpec.MinPositiveDuration);
    }

    /**
     * Start time of quarter.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime startTimeOfQuarter(DateTime moment) {
        return startTimeOfQuarter(moment.getYear(), moment.getMonthOfYear());
    }

    /**
     * Start time of quarter.
     *
     * @param year        the year
     * @param monthOfYear the month of year
     * @return the date time
     */
    public static DateTime startTimeOfQuarter(int year, int monthOfYear) {
        return startTimeOfQuarter(year, getQuarterOfMonth(monthOfYear));
    }

    /**
     * Start time of quarter.
     *
     * @param year    the year
     * @param quarter the quarter
     * @return the date time
     */
    public static DateTime startTimeOfQuarter(int year, Quarter quarter) {
        return new DateTime(year, getMonthsOfQuarter(quarter)[0], 1, 0, 0);
    }

    /**
     * End time of quarter.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime endTimeOfQuarter(DateTime moment) {
        return endTimeOfQuarter(moment.getYear(), moment.getMonthOfYear());
    }

    /**
     * End time of quarter.
     *
     * @param year        the year
     * @param monthOfYear the month of year
     * @return the date time
     */
    public static DateTime endTimeOfQuarter(int year, int monthOfYear) {
        return startTimeOfQuarter(year, monthOfYear).plusMonths(TimeSpec.MonthsPerQuarter)
                .minus(TimeSpec.MinPositiveDuration);
    }

    /**
     * Start time of last quarter.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime startTimeOfLastQuarter(DateTime moment) {
        return startTimeOfQuarter(moment.minusMonths(TimeSpec.MonthsPerQuarter));
    }

    /**
     * End time of last quarter.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime endTimeOfLastQuarter(DateTime moment) {
        return endTimeOfQuarter(moment.minusMonths(TimeSpec.MonthsPerQuarter));
    }

    /**
     * Start time of month.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime startTimeOfMonth(DateTime moment) {
        return new DateTime(moment.getYear(), moment.getMonthOfYear(), 1, 0, 0);
    }

    /**
     * Start time of month.
     *
     * @param year  the year
     * @param month the month
     * @return the date time
     */
    public static DateTime startTimeOfMonth(int year, Month month) {
        return new DateTime(year, month.getValue(), 1, 0, 0);
    }

    /**
     * Start time of month.
     *
     * @param year  the year
     * @param month the month
     * @return the date time
     */
    public static DateTime startTimeOfMonth(int year, int month) {
        return new DateTime(year, month, 1, 0, 0);
    }

    /**
     * End time of month.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime endTimeOfMonth(DateTime moment) {
        return startTimeOfMonth(moment).plusMonths(1).minus(TimeSpec.MinPositiveDuration);
    }

    /**
     * End time of month.
     *
     * @param year  the year
     * @param month the month
     * @return the date time
     */
    public static DateTime endTimeOfMonth(int year, Month month) {
        return startTimeOfMonth(year, month).plusMonths(1).minus(TimeSpec.MinPositiveDuration);
    }

    /**
     * End time of month.
     *
     * @param year  the year
     * @param month the month
     * @return the date time
     */
    public static DateTime endTimeOfMonth(int year, int month) {
        return startTimeOfMonth(year, month).plusMonths(1).minus(TimeSpec.MinPositiveDuration);
    }

    /**
     * Start time of last month.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime startTimeOfLastMonth(DateTime moment) {
        return startTimeOfMonth(moment.minusMonths(1));
    }

    /**
     * End time of last month.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime endTimeOfLastMonth(DateTime moment) {
        return endTimeOfMonth(moment.minusMonths(1));
    }

    /**
     * Start time of week.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime startTimeOfWeek(DateTime moment) {
        return startTimeOfWeek(moment, TimeSpec.FirstDayOfWeek);
    }

    /**
     * Start time of week.
     *
     * @param moment         the moment
     * @param firstDayOfWeek the first day of week
     * @return the date time
     */
    public static DateTime startTimeOfWeek(DateTime moment, DayOfWeek firstDayOfWeek) {
        return getStartOfWeek(moment, firstDayOfWeek);
    }

    /**
     * Start time of week.
     *
     * @param year       the year
     * @param weekOfYear the week of year
     * @return the date time
     */
    public static DateTime startTimeOfWeek(int year, int weekOfYear) {
        return startTimeOfWeek(year, weekOfYear, TimeCalendar.getDefault());
    }

    /**
     * Start time of week.
     *
     * @param year         the year
     * @param weekOfYear   the week of year
     * @param timeCalendar the time calendar
     * @return the date time
     */
    public static DateTime startTimeOfWeek(int year, int weekOfYear, ITimeCalendar timeCalendar) {
        DateTime current = startTimeOfYear(year).minusWeeks(1);
        while (current.getYear() < year + 2) {
            if (current.getWeekyear() == year && current.getWeekOfWeekyear() == weekOfYear)
                break;
            current = current.plusDays(1);
        }
        return current;
    }

    /**
     * End time of week.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime endTimeOfWeek(DateTime moment) {
        return endTimeOfWeek(moment, TimeSpec.FirstDayOfWeek);
    }

    /**
     * End time of week.
     *
     * @param moment         the moment
     * @param firstDayOfWeek the first day of week
     * @return the date time
     */
    public static DateTime endTimeOfWeek(DateTime moment, DayOfWeek firstDayOfWeek) {
        return startTimeOfWeek(moment, firstDayOfWeek).plusWeeks(1).minus(TimeSpec.MinPositiveDuration);
    }

    /**
     * End time of week.
     *
     * @param year       the year
     * @param weekOfYear the week of year
     * @return the date time
     */
    public static DateTime endTimeOfWeek(int year, int weekOfYear) {
        return endTimeOfWeek(year, weekOfYear, TimeCalendar.getDefault());
    }

    /**
     * End time of week.
     *
     * @param year         the year
     * @param weekOfYear   the week of year
     * @param timeCalendar the time calendar
     * @return the date time
     */
    public static DateTime endTimeOfWeek(int year, int weekOfYear, ITimeCalendar timeCalendar) {
        return startTimeOfWeek(year, weekOfYear, timeCalendar).plusWeeks(1).minus(TimeSpec.MinPositiveDuration);
    }

    /**
     * Start time of last week.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime startTimeOfLastWeek(DateTime moment) {
        return startTimeOfLastWeek(moment, TimeSpec.FirstDayOfWeek);
    }

    /**
     * Start time of last week.
     *
     * @param moment         the moment
     * @param firstDayOfWeek the first day of week
     * @return the date time
     */
    public static DateTime startTimeOfLastWeek(DateTime moment, DayOfWeek firstDayOfWeek) {
        return startTimeOfWeek(moment, firstDayOfWeek).minusWeeks(1);
    }

    /**
     * End time of last week.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime endTimeOfLastWeek(DateTime moment) {
        return endTimeOfLastWeek(moment, TimeSpec.FirstDayOfWeek);
    }

    /**
     * End time of last week.
     *
     * @param moment         the moment
     * @param firstDayOfWeek the first day of week
     * @return the date time
     */
    public static DateTime endTimeOfLastWeek(DateTime moment, DayOfWeek firstDayOfWeek) {
        return endTimeOfWeek(moment, firstDayOfWeek).minusWeeks(1);
    }

    /**
     * Start time of day.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime startTimeOfDay(DateTime moment) {
        return moment.withTimeAtStartOfDay();
    }

    /**
     * End time of day.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime endTimeOfDay(DateTime moment) {
        return startTimeOfDay(moment).plusDays(1).minus(TimeSpec.MinPositiveDuration);
    }

    /**
     * Start time of hour.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime startTimeOfHour(DateTime moment) {
        return trimToMinute(moment);
    }

    /**
     * End time of hour.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime endTimeOfHour(DateTime moment) {
        return startTimeOfHour(moment).plusHours(1).minus(TimeSpec.MinPositiveDuration);
    }

    /**
     * Start time of minute.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime startTimeOfMinute(DateTime moment) {
        return trimToSecond(moment);
    }

    /**
     * End time of minute.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime endTimeOfMinute(DateTime moment) {
        return startTimeOfMinute(moment).plusMinutes(1).minus(TimeSpec.MinPositiveDuration);
    }

    /**
     * Start time of second.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime startTimeOfSecond(DateTime moment) {
        return trimToMillis(moment);
    }

    /**
     * End time of second.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime endTimeOfSecond(DateTime moment) {
        return startTimeOfSecond(moment).plusSeconds(1).minus(TimeSpec.MinPositiveDuration);
    }

    /**
     * Halfyear of.
     *
     * @param monthOfYear the month of year
     * @return the halfyear
     */
    public static Halfyear halfyearOf(int monthOfYear) {
        return (monthOfYear < 7) ? Halfyear.First : Halfyear.Second;
    }

    /**
     * Halfyear of.
     *
     * @param moment the moment
     * @return the halfyear
     */
    public static Halfyear halfyearOf(DateTime moment) {
        return halfyearOf(moment.getMonthOfYear());
    }

    /**
     * Start month of quarter.
     *
     * @param quarter the quarter
     * @return the int
     */
    public static int startMonthOfQuarter(Quarter quarter) {
        return (quarter.getValue() - 1) * TimeSpec.MonthsPerQuarter + 1;
    }

    /**
     * End month of quarter.
     *
     * @param quarter the quarter
     * @return the int
     */
    public static int endMonthOfQuarter(Quarter quarter) {
        return quarter.getValue() * TimeSpec.MonthsPerQuarter;
    }

    /**
     * Quarter of.
     *
     * @param monthOfYear the month of year
     * @return the quarter
     */
    public static Quarter quarterOf(int monthOfYear) {
        return Quarter.valueOf((monthOfYear - 1) / TimeSpec.MonthsPerQuarter + 1);
    }

    /**
     * Quarter of.
     *
     * @param moment the moment
     * @return the quarter
     */
    public static Quarter quarterOf(DateTime moment) {
        return quarterOf(moment.getMonthOfYear());
    }

    /**
     * Previous quarter of.
     *
     * @param moment the moment
     * @return the quarter
     */
    public static Quarter previousQuarterOf(DateTime moment) {
        return previousQuarter(moment.getYear(), quarterOf(moment)).getQuarter();
    }

    /**
     *  ??? ?  ? ?? .
     *
     * @param moment  ??
     */
    public static DateTime nextDayOfWeek(DateTime moment) {
        return nextDayOfWeek(moment, DayOfWeek.valueOf(moment.getDayOfWeek()));
    }

    /**
     * ? ?  ??  ?? .
     *
     * @param moment     ??
     * @param dayOfWeek ? ?
     */
    public static DateTime nextDayOfWeek(final DateTime moment, final DayOfWeek dayOfWeek) {
        final int dow = dayOfWeek.getValue();
        DateTime next = moment.plusDays(1);

        while (next.getDayOfWeek() != dow) {
            next = next.plusDays(1);
        }
        return next;
    }

    /**
     *  ??? ? ? ?? .
     *
     * @param moment  ??
     */
    public static DateTime previousDayOfWeek(DateTime moment) {
        return previousDayOfWeek(moment, DayOfWeek.valueOf(moment.getDayOfWeek()));
    }

    /**
     *  ?? ??  ??  ?? .
     *
     * @param moment     ??
     * @param dayOfWeek ? ?
     */
    public static DateTime previousDayOfWeek(DateTime moment, DayOfWeek dayOfWeek) {
        int dow = dayOfWeek.getValue();
        DateTime prev = moment.minusDays(1);

        while (prev.getDayOfWeek() != dow) {
            prev = prev.minusDays(1);
        }
        return prev;
    }

    /** ?     */
    public static DateTime getDate(DateTime moment) {
        return moment.withTimeAtStartOfDay();
    }

    /** ???  */
    public static boolean hasDate(DateTime moment) {
        return moment.withTimeAtStartOfDay().getMillis() > 0;
    }

    /**  ? ?(//?) ?  datePart . */
    public static DateTime setDate(DateTime moment, DateTime datepart) {
        return new Datepart(datepart).getDateTime(new Timepart(moment));
    }

    /**  ? , , ?? . */
    public static DateTime setDate(DateTime moment, int year, int month, int day) {
        return setDate(moment, new DateTime(year, month, day, 0, 0));
    }

    /**  ??? ? . */
    public static DateTime setYear(DateTime moment, int year) {
        return setDate(moment, year, moment.getMonthOfYear(), moment.getDayOfMonth());
    }

    /**  ???  . */
    public static DateTime setMonth(DateTime moment, int monthOfYear) {
        return setDate(moment, moment.getYear(), monthOfYear, moment.getDayOfMonth());
    }

    /**  ??? ? . */
    public static DateTime setDay(DateTime moment, int dayOfMonth) {
        return setDate(moment, moment.getYear(), moment.getMonthOfYear(), dayOfMonth);
    }

    /** ??   ? . */
    public static DateTime combine(DateTime datepart, DateTime timepart) {
        return setTime(datepart, timepart);
    }

    /** ???  ? . */
    public static Duration getTime(DateTime moment) {
        return new Duration(moment.getMillisOfDay());
    }

    /** ?? ? ?   */
    public static boolean hasTime(DateTime moment) {
        return moment.getMillisOfDay() > 0;
    }

    /**
     *  ???  ?   .
     *
     * @param moment    ??
     * @param timepart   
     * @return  ??
     */
    public static DateTime setTime(DateTime moment, DateTime timepart) {
        return setTime(moment, timepart.getMillisOfDay());
    }

    /**
     * Sets time.
     *
     * @param moment the moment
     * @param millis the millis
     * @return the time
     */
    public static DateTime setTime(DateTime moment, int millis) {
        return moment.withTimeAtStartOfDay().plusMillis(millis);
    }

    /**
     * Sets time.
     *
     * @param moment       the moment
     * @param hourOfDay    the hour of day
     * @param minuteOfHour the minute of hour
     * @return the time
     */
    public static DateTime setTime(DateTime moment, int hourOfDay, int minuteOfHour) {
        return moment.withTimeAtStartOfDay().withTime(hourOfDay, minuteOfHour, 0, 0);
    }

    /**
     * Sets time.
     *
     * @param moment         the moment
     * @param hourOfDay      the hour of day
     * @param minuteOfHour   the minute of hour
     * @param secondOfMinute the second of minute
     * @return the time
     */
    public static DateTime setTime(DateTime moment, int hourOfDay, int minuteOfHour, int secondOfMinute) {
        return moment.withTimeAtStartOfDay().withTime(hourOfDay, minuteOfHour, secondOfMinute, 0);
    }

    /**
     * Sets time.
     *
     * @param moment         the moment
     * @param hourOfDay      the hour of day
     * @param minuteOfHour   the minute of hour
     * @param secondOfMinute the second of minute
     * @param millisOfSecond the millis of second
     * @return the time
     */
    public static DateTime setTime(DateTime moment, int hourOfDay, int minuteOfHour, int secondOfMinute,
            int millisOfSecond) {
        return moment.withTimeAtStartOfDay().withTime(hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond);
    }

    /**
     * Sets hour.
     *
     * @param moment    the moment
     * @param hourOfDay the hour of day
     * @return the hour
     */
    public static DateTime setHour(DateTime moment, int hourOfDay) {
        return setTime(moment, hourOfDay, moment.getMinuteOfHour(), moment.getSecondOfMinute(),
                moment.getMillisOfSecond());
    }

    /**
     * Sets minute.
     *
     * @param moment       the moment
     * @param minuteOfHour the minute of hour
     * @return the minute
     */
    public static DateTime setMinute(DateTime moment, int minuteOfHour) {
        return setTime(moment, moment.getHourOfDay(), minuteOfHour, moment.getSecondOfMinute(),
                moment.getMillisOfSecond());
    }

    /**
     * Sets second.
     *
     * @param moment         the moment
     * @param secondOfMinute the second of minute
     * @return the second
     */
    public static DateTime setSecond(DateTime moment, int secondOfMinute) {
        return setTime(moment, moment.getHourOfDay(), moment.getMinuteOfHour(), secondOfMinute,
                moment.getMillisOfSecond());
    }

    /**
     * Sets millisecond.
     *
     * @param moment         the moment
     * @param millisOfSecond the millis of second
     * @return the millisecond
     */
    public static DateTime setMillisecond(DateTime moment, int millisOfSecond) {
        return setTime(moment, moment.getHourOfDay(), moment.getMinuteOfHour(), moment.getSecondOfMinute(),
                millisOfSecond);
    }

    /**  */
    public static DateTime noon(DateTime moment) {
        return moment.withTimeAtStartOfDay().plusHours(12);
    }

    /**  ??   ?? ? */
    public static DateTime ago(DateTime moment, Duration duration) {
        return moment.minus(duration);
    }

    /**  ??   ?? ? */
    public static DateTime from(DateTime moment, Duration duration) {
        return moment.plus(duration);
    }

    public static DateTime fromNow(Duration duration) {
        return from(DateTime.now(), duration);
    }

    /**  ??   ?? ? */
    public static DateTime since(DateTime moment, Duration duration) {
        return moment.plus(duration);
    }

    // endregion << DateTime >>

    // region << Math >>

    /**
     * Min date time.
     *
     * @param a the a
     * @param b the b
     * @return the date time
     */
    public static DateTime min(DateTime a, DateTime b) {
        if (a == null && b == null)
            return null;
        if (a == null)
            return b;
        if (b == null)
            return a;
        return a.compareTo(b) < 0 ? a : b;
    }

    /**
     * Max date time.
     *
     * @param a the a
     * @param b the b
     * @return the date time
     */
    public static DateTime max(DateTime a, DateTime b) {
        if (a == null && b == null)
            return null;
        if (a == null)
            return b;
        if (b == null)
            return a;
        return a.compareTo(b) > 0 ? a : b;
    }

    /**
     * Min duration.
     *
     * @param a the a
     * @param b the b
     * @return the duration
     */
    public static Duration min(Duration a, Duration b) {
        if (a == null && b == null)
            return null;
        if (a == null)
            return b;
        if (b == null)
            return a;
        return a.compareTo(b) < 0 ? a : b;
    }

    /**
     * Max duration.
     *
     * @param a the a
     * @param b the b
     * @return the duration
     */
    public static Duration max(Duration a, Duration b) {
        if (a == null && b == null)
            return null;
        if (a == null)
            return b;
        if (b == null)
            return a;
        return a.compareTo(b) > 0 ? a : b;
    }

    /**
     * Adjust period.
     *
     * @param start the start
     * @param end   the end
     * @return the pair
     */
    public static Pair<DateTime, DateTime> adjustPeriod(final DateTime start, final DateTime end) {
        return Pair.create(min(start, end), max(start, end));
    }

    /**
     * Adjust period.
     *
     * @param start    the start
     * @param duration the duration
     * @return the pair
     */
    public static Pair<DateTime, Duration> adjustPeriod(DateTime start, Duration duration) {
        shouldNotBeNull(start, "start");
        shouldNotBeNull(duration, "duration");
        return (duration.getMillis() < 0) ? Pair.create(start.plus(duration), new Duration(-duration.getMillis()))
                : Pair.create(start, duration);
    }

    // endregion << Math >>

    // region << Period >>

    /**
     * Gets time block.
     *
     * @param start    the start
     * @param duration the duration
     * @return the time block
     */
    public static TimeBlock getTimeBlock(DateTime start, Duration duration) {
        return new TimeBlock(start, duration, false);
    }

    /**
     * Gets time block.
     *
     * @param start the start
     * @param end   the end
     * @return the time block
     */
    public static TimeBlock getTimeBlock(DateTime start, DateTime end) {
        return new TimeBlock(start, end, false);
    }

    /**
     * Gets time range.
     *
     * @param start    the start
     * @param duration the duration
     * @return the time range
     */
    public static TimeRange getTimeRange(DateTime start, Duration duration) {
        return new TimeRange(start, duration);
    }

    /**
     * Gets time range.
     *
     * @param start the start
     * @param end   the end
     * @return the time range
     */
    public static TimeRange getTimeRange(DateTime start, DateTime end) {
        return new TimeRange(start, end);
    }

    /**  ?  ?(years) ??  */
    public static TimeRange getRelativeYearPeriod(DateTime start, int years) {
        return getTimeRange(start, start.plusYears(years));
    }

    /**  ?  (months) ??  */
    public static TimeRange getRelativeMonthPeriod(DateTime start, int months) {
        return getTimeRange(start, start.plusMonths(months));
    }

    /**  ?  (weeks) ??  */
    public static TimeRange getRelativeWeekPeriod(DateTime start, int weeks) {
        return getTimeRange(start, start.plusWeeks(weeks));
    }

    /**  ?  ?(days) ??  */
    public static TimeRange getRelativeDayPeriod(DateTime start, int days) {
        return getTimeRange(start, start.plusDays(days));
    }

    /**  ?  (hours) ??  */
    public static TimeRange getRelativeHourPeriod(DateTime start, int hours) {
        return getTimeRange(start, start.plusHours(hours));
    }

    /**  ?  (minutes) ??  */
    public static TimeRange getRelativeMinutePeriod(DateTime start, int minutes) {
        return getTimeRange(start, start.plusMinutes(minutes));
    }

    /**  ?  (seconds) ??  */
    public static TimeRange getRelativeSecondPeriod(DateTime start, int seconds) {
        return getTimeRange(start, start.plusSeconds(seconds));
    }

    /** moment ?  ?  */
    public static ITimePeriod getPeriodOf(DateTime moment, PeriodUnit periodUnit) {
        return getPeriodOf(moment, periodUnit, TimeCalendar.getDefault());
    }

    /** moment ?  ?  */
    public static ITimePeriod getPeriodOf(DateTime moment, PeriodUnit periodUnit, ITimeCalendar timeCalendar) {
        if (isTraceEnabled)
            log.trace("??[{}] ?  [{}]? ? .", moment, periodUnit);

        if (timeCalendar == null)
            timeCalendar = TimeCalendar.getDefault();

        switch (periodUnit) {
        case Year:
            return getYearRange(moment, timeCalendar);
        case Halfyear:
            return getHalfyearRange(moment, timeCalendar);
        case Quarter:
            return getQuarterRange(moment, timeCalendar);
        case Month:
            return getMonthRange(moment, timeCalendar);
        case Week:
            return getWeekRange(moment, timeCalendar);
        case Day:
            return getDayRange(moment, timeCalendar);
        case Hour:
            return getHourRange(moment, timeCalendar);
        case Minute:
            return getMinuteRange(moment, timeCalendar);
        case Second:
            return new TimeRange(trimToMillis(moment), Durations.Second);

        default:
            throw new NotSupportException("?  Period . periodUnit=" + periodUnit);
        }
    }

    /** moment  ?  ? ?  periodCount ??    . */
    public static ICalendarTimeRange getPeriodsOf(DateTime moment, PeriodUnit periodUnit, int periodCount) {
        return getPeriodsOf(moment, periodUnit, periodCount, TimeCalendar.getDefault());
    }

    /** moment  ?  ? ?  periodCount ??    . */
    public static ICalendarTimeRange getPeriodsOf(DateTime moment, PeriodUnit periodUnit, int periodCount,
            ITimeCalendar timeCalendar) {
        if (isTraceEnabled)
            log.trace("??[{}] ?  [{}]? ? .", moment, periodUnit);

        if (timeCalendar == null)
            timeCalendar = TimeCalendar.getDefault();

        switch (periodUnit) {
        case Year:
            return getYearRanges(moment, periodCount, timeCalendar);
        case Halfyear:
            return getHalfyearRanges(moment, periodCount, timeCalendar);
        case Quarter:
            return getQuarterRanges(moment, periodCount, timeCalendar);
        case Month:
            return getMonthRanges(moment, periodCount, timeCalendar);
        case Week:
            return getWeekRanges(moment, periodCount, timeCalendar);
        case Day:
            return getDayRanges(moment, periodCount, timeCalendar);
        case Hour:
            return getHourRanges(moment, periodCount, timeCalendar);
        case Minute:
            return getMinuteRanges(moment, periodCount, timeCalendar);
        case Second:
            return new CalendarTimeRange(trimToMillis(moment), trimToMillis(moment).plusSeconds(periodCount),
                    timeCalendar);

        default:
            throw new NotSupportException("?  Period . periodUnit=" + periodUnit);
        }
    }

    /**
     * Gets year range.
     *
     * @param moment       the moment
     * @param timeCalendar the time calendar
     * @return the year range
     */
    public static YearRange getYearRange(DateTime moment, ITimeCalendar timeCalendar) {
        return new YearRange(moment, (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets year ranges.
     *
     * @param moment       the moment
     * @param years        the years
     * @param timeCalendar the time calendar
     * @return the year ranges
     */
    public static YearRangeCollection getYearRanges(DateTime moment, int years, ITimeCalendar timeCalendar) {
        return new YearRangeCollection(moment, years,
                (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets halfyear range.
     *
     * @param moment       the moment
     * @param timeCalendar the time calendar
     * @return the halfyear range
     */
    public static HalfyearRange getHalfyearRange(DateTime moment, ITimeCalendar timeCalendar) {
        return new HalfyearRange(moment, (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets halfyear ranges.
     *
     * @param moment       the moment
     * @param halfyears    the halfyears
     * @param timeCalendar the time calendar
     * @return the halfyear ranges
     */
    public static HalfyearRangeCollection getHalfyearRanges(DateTime moment, int halfyears,
            ITimeCalendar timeCalendar) {
        return new HalfyearRangeCollection(moment, halfyears,
                (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets quarter range.
     *
     * @param moment       the moment
     * @param timeCalendar the time calendar
     * @return the quarter range
     */
    public static QuarterRange getQuarterRange(DateTime moment, ITimeCalendar timeCalendar) {
        return new QuarterRange(moment, (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets quarter ranges.
     *
     * @param moment       the moment
     * @param quarters     the quarters
     * @param timeCalendar the time calendar
     * @return the quarter ranges
     */
    public static QuarterRangeCollection getQuarterRanges(DateTime moment, int quarters,
            ITimeCalendar timeCalendar) {
        return new QuarterRangeCollection(moment, quarters,
                (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets month range.
     *
     * @param moment       the moment
     * @param timeCalendar the time calendar
     * @return the month range
     */
    public static MonthRange getMonthRange(DateTime moment, ITimeCalendar timeCalendar) {
        return new MonthRange(moment, (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets month ranges.
     *
     * @param moment       the moment
     * @param months       the months
     * @param timeCalendar the time calendar
     * @return the month ranges
     */
    public static MonthRangeCollection getMonthRanges(DateTime moment, int months, ITimeCalendar timeCalendar) {
        return new MonthRangeCollection(moment, months,
                (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets week range.
     *
     * @param moment       the moment
     * @param timeCalendar the time calendar
     * @return the week range
     */
    public static WeekRange getWeekRange(DateTime moment, ITimeCalendar timeCalendar) {
        return new WeekRange(moment, (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets week ranges.
     *
     * @param moment       the moment
     * @param weeks        the weeks
     * @param timeCalendar the time calendar
     * @return the week ranges
     */
    public static WeekRangeCollection getWeekRanges(DateTime moment, int weeks, ITimeCalendar timeCalendar) {
        return new WeekRangeCollection(moment, weeks,
                (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets day range.
     *
     * @param moment       the moment
     * @param timeCalendar the time calendar
     * @return the day range
     */
    public static DayRange getDayRange(DateTime moment, ITimeCalendar timeCalendar) {
        return new DayRange(moment, (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets day ranges.
     *
     * @param moment       the moment
     * @param days         the days
     * @param timeCalendar the time calendar
     * @return the day ranges
     */
    public static DayRangeCollection getDayRanges(DateTime moment, int days, ITimeCalendar timeCalendar) {
        return new DayRangeCollection(moment, days,
                (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets hour range.
     *
     * @param moment       the moment
     * @param timeCalendar the time calendar
     * @return the hour range
     */
    public static HourRange getHourRange(DateTime moment, ITimeCalendar timeCalendar) {
        return new HourRange(moment, (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets hour ranges.
     *
     * @param moment       the moment
     * @param hours        the hours
     * @param timeCalendar the time calendar
     * @return the hour ranges
     */
    public static HourRangeCollection getHourRanges(DateTime moment, int hours, ITimeCalendar timeCalendar) {
        return new HourRangeCollection(moment, hours,
                (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    /**
     * Gets minute range.
     *
     * @param moment       the moment
     * @param timeCalendar the time calendar
     * @return the minute range
     */
    public static MinuteRange getMinuteRange(DateTime moment, ITimeCalendar timeCalendar) {
        return new MinuteRange(moment, timeCalendar);
    }

    /**
     * Gets minute ranges.
     *
     * @param moment       the moment
     * @param minutes      the minutes
     * @param timeCalendar the time calendar
     * @return the minute ranges
     */
    public static MinuteRangeCollection getMinuteRanges(DateTime moment, int minutes, ITimeCalendar timeCalendar) {
        return new MinuteRangeCollection(moment, minutes,
                (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault());
    }

    // endregion << Period >>

    // region << Relation >>

    /** ?  ? ??(target)?   */
    public static boolean hasInside(ITimePeriod period, DateTime target) {
        shouldNotBeNull(period, "period");
        boolean isInside = target.compareTo(period.getStart()) >= 0 && target.compareTo(period.getEnd()) <= 0;

        if (isTraceEnabled)
            log.trace(" [{}] ? target[{}]? ??? [{}]", period, target, isInside);

        return isInside;
    }

    /** ?  ? ? ?   */
    public static boolean hasInside(ITimePeriod period, ITimePeriod target) {
        shouldNotBeNull(period, "period");
        shouldNotBeNull(target, "target");
        boolean isInside = hasInside(period, target.getStart()) && hasInside(period, target.getEnd());

        if (isTraceEnabled)
            log.trace(" [{}] ? target[{}]? ??? [{}]", period, target, isInside);

        return isInside;
    }

    /**
     * Has pure inside.
     *
     * @param period the period
     * @param target the target
     * @return the boolean
     */
    public static boolean hasPureInside(ITimePeriod period, DateTime target) {
        shouldNotBeNull(period, "period");

        boolean isInside = target.compareTo(period.getStart()) > 0 && target.compareTo(period.getEnd()) < 0;

        if (isTraceEnabled)
            log.trace(" [{}] ? target[{}]?  , ??? [{}]", period,
                    target, isInside);

        return isInside;
    }

    /**
     * Has pure inside.
     *
     * @param period the period
     * @param target the target
     * @return the boolean
     */
    public static boolean hasPureInside(ITimePeriod period, ITimePeriod target) {
        shouldNotBeNull(period, "period");
        shouldNotBeNull(target, "target");

        boolean isInside = hasPureInside(period, target.getStart()) && hasPureInside(period, target.getEnd());

        if (isTraceEnabled)
            log.trace(" [{}] ? target[{}]?  , ??? [{}]", period,
                    target, isInside);

        return isInside;
    }

    /**
     * Is anytime.
     *
     * @param period the period
     * @return the boolean
     */
    public static boolean isAnytime(ITimePeriod period) {
        return period != null && period.isAnytime();
    }

    /**
     * Is not anytime.
     *
     * @param period the period
     * @return the boolean
     */
    public static boolean isNotAnytime(ITimePeriod period) {
        return period != null && !period.isAnytime();
    }

    /**   ? ?  . */
    public static PeriodRelation getRelation(ITimePeriod period, ITimePeriod target) {
        shouldNotBeNull(period, "period");
        shouldNotBeNull(target, "target");

        PeriodRelation relation = PeriodRelation.NoRelation;

        if (period.getStart().compareTo(target.getEnd()) > 0) {
            relation = PeriodRelation.After;
        } else if (period.getEnd().compareTo(target.getStart()) < 0) {
            relation = PeriodRelation.Before;
        } else if (period.getStart().equals(target.getStart()) && period.getEnd().equals(target.getEnd())) {
            relation = PeriodRelation.ExactMatch;
        } else if (period.getStart().equals(target.getEnd())) {
            relation = PeriodRelation.StartTouching;
        } else if (period.getEnd().equals(target.getStart())) {
            relation = PeriodRelation.EndTouching;
        } else if (hasInside(period, target)) {
            if (Objects.equal(period.getStart(), target.getStart()))
                relation = PeriodRelation.EnclosingStartTouching;
            else
                relation = Objects.equal(period.getEnd(), target.getEnd()) ? PeriodRelation.EnclosingEndTouching
                        : PeriodRelation.Enclosing;
        } else {
            // ? ? ? ? 
            boolean insideStart = hasInside(target, period.getStart());
            boolean insideEnd = hasInside(target, period.getEnd());

            if (insideStart && insideEnd) {
                relation = Objects.equal(period.getStart(), target.getStart()) ? PeriodRelation.InsideStartTouching
                        : (Objects.equal(period.getEnd(), target.getEnd()) ? PeriodRelation.InsideEndTouching
                                : PeriodRelation.Inside);
            } else if (insideStart) {
                relation = PeriodRelation.StartInside;
            } else if (insideEnd) {
                relation = PeriodRelation.EndInside;
            }
        }

        if (isDebugEnabled)
            log.debug("period [{}], target [{}]?  [{}].", period, target, relation);

        return relation;
    }

    /** ? ? ?, period target?  ? true . */
    public static boolean intersectsWith(ITimePeriod period, ITimePeriod target) {
        shouldNotBeNull(period, "period");
        shouldNotBeNull(target, "target");

        boolean isIntersected = hasInside(period, target.getStart()) || hasInside(period, target.getEnd())
                || hasPureInside(target, period);

        if (isTraceEnabled)
            log.trace("period[{}] target[{}]? ? ?? result=[{}]", period, target,
                    isIntersected);
        return isIntersected;
    }

    /**
     * ? ?  ?  true .
     *
     * @param period  
     * @param target ? 
     * @return  
     */
    public static boolean overlapsWith(ITimePeriod period, ITimePeriod target) {
        shouldNotBeNull(period, "period");
        shouldNotBeNull(target, "target");

        PeriodRelation relation = getRelation(period, target);

        boolean isOverlaps = relation != PeriodRelation.After && relation != PeriodRelation.StartTouching
                && relation != PeriodRelation.EndTouching && relation != PeriodRelation.Before;

        if (isTraceEnabled)
            log.trace("period[{}] target[{}]? overlap ?? [{}]", period, target, isOverlaps);

        return isOverlaps;
    }

    /** ? ? ? (? )? . */
    public static TimeBlock getIntersectionBlock(ITimePeriod period, ITimePeriod target) {
        shouldNotBeNull(period, "period");
        shouldNotBeNull(target, "target");

        TimeBlock intersection = null;
        if (intersectsWith(period, target)) {
            DateTime start = max(period.getStart(), target.getStart());
            DateTime end = min(period.getEnd(), target.getEnd());
            intersection = new TimeBlock(start, end, period.isReadonly());
        }
        if (isTraceEnabled)
            log.trace("period[{}], target[{}]? ? [{}]", period, target, intersection);

        return intersection;
    }

    /** ? ?  ? . */
    public static TimeBlock getUnionBlock(ITimePeriod period, ITimePeriod target) {
        shouldNotBeNull(period, "period");
        shouldNotBeNull(target, "target");

        DateTime start = min(period.getStart(), target.getStart());
        DateTime end = max(period.getEnd(), target.getEnd());
        TimeBlock union = new TimeBlock(start, end, period.isReadonly());

        if (isTraceEnabled)
            log.trace("period[{}], target[{}]?  [{}]", period, target, union);

        return union;
    }

    /** ? ? ? (? )? . */
    public static TimeRange getIntersectionRange(ITimePeriod period, ITimePeriod target) {
        shouldNotBeNull(period, "period");
        shouldNotBeNull(target, "target");

        TimeRange intersection = null;
        if (intersectsWith(period, target)) {
            DateTime start = max(period.getStart(), target.getStart());
            DateTime end = min(period.getEnd(), target.getEnd());
            intersection = new TimeRange(start, end, period.isReadonly());
        }
        if (isTraceEnabled)
            log.trace("period[{}], target[{}]? ? [{}]", period, target, intersection);

        return intersection;
    }

    /** ? ?  ? . */
    public static TimeRange getUnionRange(ITimePeriod period, ITimePeriod target) {
        shouldNotBeNull(period, "period");
        shouldNotBeNull(target, "target");

        DateTime start = min(period.getStart(), target.getStart());
        DateTime end = max(period.getEnd(), target.getEnd());
        TimeRange union = new TimeRange(start, end, period.isReadonly());

        if (isTraceEnabled)
            log.trace("period[{}], target[{}]?  [{}]", period, target, union);

        return union;
    }

    // endregion << Relation >>

    // region << Trim >>

    /**
     * Trim to year. (??? ??  ?? )
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime trimToYear(DateTime moment) {
        return new DateTime(moment.getYear(), 1, 1, 0, 0);
    }

    /**
     *  ??? ??  ?? 
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime trimToMonth(DateTime moment) {
        return trimToMonth(moment, 1);
    }

    /**
     *  ??? ??  ?? 
     *
     * @param moment      the moment
     * @param monthOfYear the month of year
     * @return the date time
     */
    public static DateTime trimToMonth(DateTime moment, int monthOfYear) {
        return asDate(moment.getYear(), monthOfYear, 1);
    }

    /**
     * @param moment the moment
     * @return the date time
     */
    public static DateTime trimToDay(DateTime moment) {
        return trimToDay(moment, 1);
    }

    /**
     *  ??  //??  ?? .
     *
     * @param moment     the moment
     * @param dayOfMonth the day of month
     * @return the date time
     */
    public static DateTime trimToDay(DateTime moment, int dayOfMonth) {
        return asDate(moment.getYear(), moment.getMonthOfYear(), dayOfMonth);
    }

    /**
     * Trim to hour.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime trimToHour(DateTime moment) {
        return trimToHour(moment, 0);

    }

    /**
     * Trim to hour.
     *
     * @param moment    the moment
     * @param hourOfDay the hour of day
     * @return the date time
     */
    public static DateTime trimToHour(DateTime moment, int hourOfDay) {
        return getDate(moment).withHourOfDay(hourOfDay);
    }

    /**
     * Trim to minute.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime trimToMinute(DateTime moment) {
        return trimToMinute(moment, 0);

    }

    /**
     * Trim to minute.
     *
     * @param moment       the moment
     * @param minuteOfHour the minute of hour
     * @return the date time
     */
    public static DateTime trimToMinute(DateTime moment, int minuteOfHour) {
        return trimToHour(moment, moment.getHourOfDay()).withMinuteOfHour(minuteOfHour);
    }

    /**
     * Trim to second.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime trimToSecond(DateTime moment) {
        return trimToSecond(moment, 0);

    }

    /**
     * Trim to second.
     *
     * @param moment         the moment
     * @param secondOfMinute the second of minute
     * @return the date time
     */
    public static DateTime trimToSecond(DateTime moment, int secondOfMinute) {
        return trimToMinute(moment, moment.getMinuteOfHour()).withSecondOfMinute(secondOfMinute);
    }

    /**
     * Trim to millis.
     *
     * @param moment the moment
     * @return the date time
     */
    public static DateTime trimToMillis(DateTime moment) {
        return trimToMillis(moment, 0);

    }

    /**
     * Trim to millis.
     *
     * @param moment         the moment
     * @param millisOfSecond the millis of second
     * @return the date time
     */
    public static DateTime trimToMillis(DateTime moment, int millisOfSecond) {
        return moment.withMillisOfSecond(millisOfSecond);
    }

    // endregion << Trim >>

    // region << Validation >>

    /**
     * Assert valid period.
     *
     * @param start the start
     * @param end   the end
     */
    public static void assertValidPeriod(DateTime start, DateTime end) {
        if (start != null && end != null)
            assert start.compareTo(end) <= 0 : String.format(
                    "?? ? ?? . start=[%s], end=[%s]", start,
                    end);
    }

    /**
     * Assert mutable.
     *
     * @param period the period
     */
    public static void assertMutable(ITimePeriod period) {
        shouldNotBeNull(period, "period");
        assert !period.isReadonly() : "TimePeriod ?. period=" + period;
    }

    /**
     * All items are equal.
     *
     * @param left  the left
     * @param right the right
     * @return the boolean
     */
    public static boolean allItemsAreEqual(Iterable<ITimePeriod> left, Iterable<ITimePeriod> right) {
        shouldNotBeNull(left, "left");
        shouldNotBeNull(right, "right");

        if (Iterables.size(left) != Iterables.size(right))
            return false;

        Iterator<ITimePeriod> leftIter = left.iterator();
        Iterator<ITimePeriod> rightIter = right.iterator();
        while (leftIter.hasNext() && rightIter.hasNext()) {
            if (!Objects.equal(leftIter.next(), rightIter.next()))
                return false;
        }
        return true;
    }

    /**
     * Is weekday.
     *
     * @param dayOfWeek the day of week
     * @return the boolean
     */
    public static boolean isWeekday(DayOfWeek dayOfWeek) {
        return ArrayTool.contains(TimeSpec.Weekdays, dayOfWeek);
    }

    /**
     * Is weekend.
     *
     * @param dayOfWeek the day of week
     * @return the boolean
     */
    public static boolean isWeekend(DayOfWeek dayOfWeek) {
        return ArrayTool.contains(TimeSpec.Weekends, dayOfWeek);
    }

    // endregion << Validation >>

    // region << Comparator >>

    @Getter(lazy = true)
    private static final StartComparator startComparator = new StartComparator();
    @Getter(lazy = true)
    private static final StartDescComparator startDescComparator = new StartDescComparator();
    @Getter(lazy = true)
    private static final EndComparator endComparator = new EndComparator();
    @Getter(lazy = true)
    private static final EndDescComparator endDescComparator = new EndDescComparator();
    @Getter(lazy = true)
    private static final DurationComparator durationComparator = new DurationComparator();
    @Getter(lazy = true)
    private static final DurationDescComparator durationDescComparator = new DurationDescComparator();

    /** The type Start comparator. */
    public static class StartComparator implements Comparator<ITimePeriod> {
        @Override
        public int compare(ITimePeriod o1, ITimePeriod o2) {
            return o1.getStart().compareTo(o2.getStart());
        }
    }

    /** The type Start desc comparator. */
    public static class StartDescComparator implements Comparator<ITimePeriod> {
        @Override
        public int compare(ITimePeriod o1, ITimePeriod o2) {
            return o2.getStart().compareTo(o1.getStart());
        }
    }

    /** The type End comparator. */
    public static class EndComparator implements Comparator<ITimePeriod> {
        @Override
        public int compare(ITimePeriod o1, ITimePeriod o2) {
            return o1.getEnd().compareTo(o2.getEnd());
        }
    }

    /** The type End desc comparator. */
    public static class EndDescComparator implements Comparator<ITimePeriod> {
        @Override
        public int compare(ITimePeriod o1, ITimePeriod o2) {
            return o2.getEnd().compareTo(o1.getEnd());
        }
    }

    /** The type Duration comparator. */
    public static class DurationComparator implements Comparator<ITimePeriod> {
        @Override
        public int compare(ITimePeriod o1, ITimePeriod o2) {
            return o1.getDuration().compareTo(o2.getDuration());
        }
    }

    /** The type Duration desc comparator. */
    public static class DurationDescComparator implements Comparator<ITimePeriod> {
        @Override
        public int compare(ITimePeriod o1, ITimePeriod o2) {
            return o2.getDuration().compareTo(o1.getDuration());
        }
    }

    // endregion

    // region << ForEach >>

    /** ? ?    ? . */
    public static List<ITimePeriod> foreachPeriods(ITimePeriod period, PeriodUnit periodUnit) {
        switch (periodUnit) {
        case Year:
            return foreachYears(period);

        case Halfyear:
            return foreachHalfyears(period);

        case Quarter:
            return foreachQuarters(period);

        case Month:
            return foreachMonths(period);

        case Week:
            return foreachWeeks(period);

        case Day:
            return foreachDays(period);

        case Hour:
            return foreachHours(period);

        case Minute:
            return foreachMinutes(period);

        default:
            throw new IllegalArgumentException("?  PeriodKind. PeriodUnit=" + periodUnit);
        }
    }

    /** ? ?  ? . */
    public static List<ITimePeriod> foreachYears(ITimePeriod period) {
        assert period != null;
        if (isTraceEnabled)
            log.trace("[{}]?  Year  ...", period);

        List<ITimePeriod> years = Lists.newArrayList();
        if (period.isAnytime())
            return years;
        assertHasPeriod(period);

        if (Times.isSameYear(period.getStart(), period.getEnd())) {
            years.add(new TimeRange(period));
            return years;
        }

        years.add(new TimeRange(period.getStart(), Times.endTimeOfYear(period.getStart())));

        DateTime current = Times.startTimeOfYear(period.getStart()).plusYears(1);
        int endYear = period.getEnd().getYear();
        ITimeCalendar calendar = TimeCalendar.getDefault();

        while (current.getYear() < endYear) {
            years.add(Times.getYearRange(current, calendar));
            current = current.plusYears(1);
        }

        if (current.compareTo(period.getEnd()) < 0) {
            years.add(new TimeRange(Times.startTimeOfYear(current), period.getEnd()));
        }

        return years;
    }

    /** ? ?   . */
    public static List<ITimePeriod> foreachHalfyears(ITimePeriod period) {
        assert period != null;
        if (isTraceEnabled)
            log.trace("[{}]?  Halfyear  ...", period);

        List<ITimePeriod> halfyears = Lists.newArrayList();
        if (period.isAnytime())
            return halfyears;

        assertHasPeriod(period);

        if (Times.isSameHalfyear(period.getStart(), period.getEnd())) {
            halfyears.add(new TimeRange(period));
            return halfyears;
        }

        DateTime current = Times.endTimeOfHalfyear(period.getStart());
        halfyears.add(new TimeRange(period.getStart(), current));

        int endHashCode = period.getEnd().getYear() * 10 + Times.halfyearOf(period.getEnd()).getValue();
        current = current.plusDays(1);
        ITimeCalendar calendar = TimeCalendar.getDefault();
        while (current.getYear() * 10 + Times.halfyearOf(current).getValue() < endHashCode) {
            halfyears.add(Times.getHalfyearRange(current, calendar));
            current = current.plusMonths(TimeSpec.MonthsPerHalfyear);
        }

        if (current.compareTo(period.getEnd()) < 0) {
            halfyears.add(new TimeRange(Times.startTimeOfHalfyear(current), period.getEnd()));
        }

        return halfyears;
    }

    /** ? ?  . */
    public static List<ITimePeriod> foreachQuarters(ITimePeriod period) {
        assert period != null;
        if (isTraceEnabled)
            log.trace("[{}]?  Quarter  ...", period);

        List<ITimePeriod> quarters = Lists.newArrayList();
        if (period.isAnytime())
            return quarters;

        assertHasPeriod(period);

        if (Times.isSameQuarter(period.getStart(), period.getEnd())) {
            quarters.add(new TimeRange(period));
            return quarters;
        }

        DateTime current = Times.endTimeOfQuarter(period.getStart());
        quarters.add(new TimeRange(period.getStart(), current));

        int endHashCode = period.getEnd().getYear() * 10 + Times.quarterOf(period.getEnd()).getValue();
        current = current.plusDays(1);
        ITimeCalendar calendar = TimeCalendar.getDefault();

        while (current.getYear() * 10 + Times.quarterOf(current).getValue() < endHashCode) {
            quarters.add(Times.getQuarterRange(current, calendar));
            current = current.plusMonths(TimeSpec.MonthsPerQuarter);
        }

        if (current.compareTo(period.getEnd()) < 0)
            quarters.add(new TimeRange(Times.startTimeOfQuarter(current), period.getEnd()));

        return quarters;
    }

    /** ? ? (Month)  . */
    public static List<ITimePeriod> foreachMonths(ITimePeriod period) {
        assert period != null;
        if (isTraceEnabled)
            log.trace("[{}]?  (Month)  ...", period);

        List<ITimePeriod> months = Lists.newArrayList();
        if (period.isAnytime())
            return months;

        assertHasPeriod(period);

        if (Times.isSameMonth(period.getStart(), period.getEnd())) {
            months.add(new TimeRange(period));
            return months;
        }

        DateTime current = Times.endTimeOfMonth(period.getStart());
        months.add(new TimeRange(period.getStart(), current));

        DateTime monthEnd = Times.startTimeOfMonth(period.getEnd());
        current = current.plusDays(1);
        ITimeCalendar calendar = TimeCalendar.getDefault();

        while (current.compareTo(monthEnd) < 0) {
            months.add(Times.getMonthRange(current, calendar));
            current = current.plusMonths(1);
        }

        current = Times.startTimeOfMonth(current);
        if (current.compareTo(period.getEnd()) < 0)
            months.add(new TimeRange(current, period.getEnd()));

        return months;
    }

    /** ? ? (Week)  . */
    public static List<ITimePeriod> foreachWeeks(ITimePeriod period) {
        assert period != null;
        if (isTraceEnabled)
            log.trace("[{}]?  (Week)  ...", period);

        List<ITimePeriod> weeks = Lists.newArrayList();
        if (period.isAnytime())
            return weeks;

        assertHasPeriod(period);

        if (Times.isSameWeek(period.getStart(), period.getEnd())) {
            weeks.add(new TimeRange(period));
            return weeks;
        }

        DateTime current = period.getStart();
        DateTime weekEnd = Times.endTimeOfWeek(current);
        if (weekEnd.compareTo(period.getEnd()) >= 0) {
            weeks.add(new TimeRange(current, period.getEnd()));
            return weeks;
        }

        weeks.add(new TimeRange(current, weekEnd));
        current = weekEnd.plusWeeks(1);
        ITimeCalendar calendar = TimeCalendar.getDefault();

        while (current.compareTo(period.getEnd()) < 0) {
            weeks.add(Times.getWeekRange(current, calendar));
            current = current.plusWeeks(1);
        }

        current = Times.startTimeOfWeek(current);
        if (current.compareTo(period.getEnd()) < 0) {
            weeks.add(new TimeRange(current, period.getEnd()));
        }
        return weeks;
    }

    /**  ? ?(Day) . */
    public static List<ITimePeriod> foreachDays(ITimePeriod period) {
        shouldNotBeNull(period, "period");
        if (isTraceEnabled)
            log.trace("[{}]?  ?(Day)  ...", period);

        List<ITimePeriod> days = Lists.newArrayList();
        if (period.isAnytime())
            return days;

        assertHasPeriod(period);

        if (Times.isSameDay(period.getStart(), period.getEnd())) {
            days.add(new TimeRange(period));
            return days;
        }

        days.add(new TimeRange(period.getStart(), Times.endTimeOfDay(period.getStart())));

        DateTime endDay = period.getEnd().withTimeAtStartOfDay();
        DateTime current = period.getStart().withTimeAtStartOfDay().plusDays(1);
        ITimeCalendar calendar = TimeCalendar.getDefault();

        while (current.compareTo(endDay) < 0) {
            days.add(Times.getDayRange(current, calendar));
            current = current.plusDays(1);
        }
        if (period.getEnd().getMillisOfDay() > 0)
            days.add(new TimeRange(endDay.withTimeAtStartOfDay(), period.getEnd()));

        return days;
    }

    /**  ? (Hour)  . */
    public static List<ITimePeriod> foreachHours(ITimePeriod period) {
        shouldNotBeNull(period, "period");

        if (isTraceEnabled)
            log.trace("[{}]?  (Hour)  ...", period);

        List<ITimePeriod> hours = Lists.newArrayList();
        if (period.isAnytime())
            return hours;

        assertHasPeriod(period);

        if (Times.isSameHour(period.getStart(), period.getEnd())) {
            hours.add(new TimeRange(period));
            return hours;
        }

        hours.add(new TimeRange(period.getStart(), Times.endTimeOfHour(period.getStart())));

        DateTime endHour = period.getEnd();
        DateTime current = Times.trimToHour(period.getStart(), period.getStart().getHourOfDay() + 1);
        ITimeCalendar calendar = TimeCalendar.getDefault();

        DateTime maxHour = endHour.minusHours(1);
        while (current.compareTo(maxHour) <= 0) {
            hours.add(Times.getHourRange(current, calendar));
            current = current.plusHours(1);
        }

        if (endHour.minusHours(endHour.getHourOfDay()).getMillisOfDay() > 0) {
            hours.add(new TimeRange(Times.startTimeOfHour(endHour), endHour));
        }

        return hours;
    }

    /**  ? (Minute)  . */
    public static List<ITimePeriod> foreachMinutes(ITimePeriod period) {
        shouldNotBeNull(period, "period");
        if (isTraceEnabled)
            log.trace("[{}]?  (Minute)  ...", period);

        List<ITimePeriod> minutes = Lists.newArrayList();
        if (period.isAnytime())
            return minutes;

        assertHasPeriod(period);

        if (Times.isSameMinute(period.getStart(), period.getEnd())) {
            minutes.add(new TimeRange(period));
            return minutes;
        }

        minutes.add(new TimeRange(period.getStart(), Times.endTimeOfMinute(period.getStart())));

        DateTime endMinute = period.getEnd();
        DateTime current = Times.trimToMinute(period.getStart(), period.getStart().getMinuteOfHour() + 1);
        ITimeCalendar calendar = TimeCalendar.getDefault();

        DateTime maxMinute = endMinute.minusMinutes(1);
        while (current.compareTo(maxMinute) <= 0) {
            minutes.add(Times.getMinuteRange(current, calendar));
            current = current.plusMinutes(1);
        }

        if (endMinute.minusMinutes(endMinute.getMinuteOfHour()).getMillisOfDay() > 0) {
            minutes.add(new TimeRange(Times.startTimeOfMinute(endMinute), endMinute));
        }

        return minutes;
    }

    private static void assertHasPeriod(ITimePeriod period) {
        assert period != null && period.hasPeriod() : "? ? . period=" + period;
    }

    /** ?    ? ?    ? . */
    public static <T> List<T> runPeriods(ITimePeriod period, PeriodUnit periodUnit,
            Function1<ITimePeriod, T> runner) {
        shouldNotBeNull(period, "period");
        shouldNotBeNull(runner, "runner");
        shouldBe(period.hasPeriod(), "period ? . period=%s", period);

        if (isDebugEnabled)
            log.debug(
                    "[{}]? [{}]  ,    .",
                    period, periodUnit);

        List<T> results = Lists.newArrayList();
        for (ITimePeriod item : foreachPeriods(period, periodUnit)) {
            results.add(runner.execute(item));
        }

        return results;
    }

    /** ?    ? ?     ? . */
    public static <T> List<T> runPeriodsAsParallel(ITimePeriod period, PeriodUnit periodUnit,
            Function1<ITimePeriod, T> runner) {
        shouldNotBeNull(period, "period");
        shouldNotBeNull(runner, "runner");
        shouldBe(period.hasPeriod(), "period ? . period=%s", period);

        if (log.isDebugEnabled())
            log.debug(
                    "[{}]? [{}]  ,     .",
                    period, periodUnit);

        return Parallels.runEach(foreachPeriods(period, periodUnit), runner);
    }

    // endregion << ForEach >>
}