/*
* 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.calendars;
import com.google.common.base.Objects;
import kr.debop4j.core.ValueObjectBase;
import kr.debop4j.core.tools.HashTool;
import kr.debop4j.timeperiod.ITimeCalendar;
import kr.debop4j.timeperiod.Quarter;
import kr.debop4j.timeperiod.TimeCalendar;
import kr.debop4j.timeperiod.tools.TimeSpec;
import kr.debop4j.timeperiod.tools.Times;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import java.io.Serializable;
/**
* 특정한 두 시각 사이에 {@link ITimeCalendar}, 예외 기간등을 고려한 기간을 계산합니다.
*
* @author 배성혁 sunghyouk.bae@gmail.com
* @since 13. 5. 20. 오후 5:16
*/
@Slf4j
public class DateDiff extends ValueObjectBase implements Serializable {
@Getter private final DateTime start;
@Getter private final DateTime end;
@Getter private final Duration difference;
@Getter private final ITimeCalendar timeCalendar;
@Getter(lazy = true) private final int years = calcYears();
@Getter(lazy = true) private final int quarters = calcQuarters();
@Getter(lazy = true) private final int months = calcMonths();
@Getter(lazy = true) private final int weeks = calcWeeks();
@Getter(lazy = true) private final int days = (int) Math.round(roundEx(getDifference().getStandardDays()));
@Getter(lazy = true) private final int hours = (int) Math.round(roundEx(getDifference().getStandardHours()));
@Getter(lazy = true) private final int minutes = (int) Math.round(roundEx(getDifference().getStandardMinutes()));
@Getter(lazy = true) private final int seconds = (int) Math.round(roundEx(getDifference().getStandardSeconds()));
@Getter(lazy = true) private final int elapsedYears = getYears();
@Getter(lazy = true) private final int elapsedQuarters = getQuarters();
@Getter(lazy = true) private final int elapsedMonths = getMonths() - getElapsedYears() * TimeSpec.MonthsPerYear;
@Getter(lazy = true)
private final DateTime elapsedStartDays = getStart().plusYears(getElapsedYears()).plusMonths(getElapsedMonths());
@Getter(lazy = true)
private final int elapsedDays = (int) new Duration(getElapsedStartDays(), getEnd()).getStandardDays();
@Getter(lazy = true)
private final DateTime elapsedStartHours = getStart().plusYears(getElapsedYears())
.plusMonths(getElapsedMonths()).plusDays(getElapsedDays());
@Getter(lazy = true)
private final int elapsedHours = (int) new Duration(getElapsedStartHours(), getEnd()).getStandardHours();
@Getter(lazy = true)
private final DateTime elapsedStartMinutes = getStart().plusYears(getElapsedYears())
.plusMonths(getElapsedMonths()).plusDays(getElapsedDays()).plusHours(getElapsedHours());
@Getter(lazy = true)
private final int elapsedMinutes = (int) new Duration(getElapsedStartMinutes(), getEnd()).getStandardMinutes();
@Getter(lazy = true)
private final DateTime elapsedStartSeconds = getStart().plusYears(getElapsedYears())
.plusMonths(getElapsedMonths()).plusDays(getElapsedDays())
.plusHours(getElapsedHours()).plusMinutes(getElapsedMinutes());
@Getter(lazy = true)
private final int elapsedSeconds = (int) new Duration(getElapsedStartSeconds(), getEnd()).getStandardSeconds();
public DateDiff(DateTime moment) {
this(moment, Times.now());
}
public DateDiff(DateTime moment, ITimeCalendar timeCalendar) {
this(moment, Times.now(), timeCalendar);
}
public DateDiff(DateTime start, DateTime end) {
this(start, end, TimeCalendar.getDefault());
}
public DateDiff(DateTime start, DateTime end, ITimeCalendar timeCalendar) {
this.start = start;
this.end = end;
this.difference = new Duration(start, end);
this.timeCalendar = (timeCalendar != null) ? timeCalendar : TimeCalendar.getDefault();
}
public boolean isEmpty() {
return difference.isEqual(Duration.ZERO);
}
public int getStartYear() {
return timeCalendar.getYear(getStart());
}
public int getEndYear() {
return timeCalendar.getYear(getEnd());
}
public int getStartMonthOfYear() {
return timeCalendar.getMonthOfYear(getStart());
}
public int getEndMonthOfYear() {
return timeCalendar.getMonthOfYear(getEnd());
}
private int calcYears() {
if (Objects.equal(start, end)) return 0;
int compareDay = Math.min(end.getDayOfMonth(), timeCalendar.getDaysInMonth(getStartYear(), getEndMonthOfYear()));
DateTime compareDate = new DateTime(getStartYear(), getEndMonthOfYear(), compareDay, 0, 0).plusMillis(end.getMillisOfDay());
if (end.compareTo(start) > 0) {
if (compareDate.compareTo(start) < 0)
compareDate = compareDate.plusYears(1);
} else if (compareDate.compareTo(start) > 0) {
compareDate = compareDate.plusYears(-1);
}
return getEndYear() - timeCalendar.getYear(compareDate);
}
private int calcQuarters() {
if (Objects.equal(start, end)) return 0;
int year1 = Times.getYearOf(getStartYear(), getStartMonthOfYear());
Quarter quarter1 = Times.getQuarterOfMonth(getStartMonthOfYear());
int year2 = Times.getYearOf(getEndYear(), getEndMonthOfYear());
Quarter quarter2 = Times.getQuarterOfMonth(getEndMonthOfYear());
return (year2 * TimeSpec.QuartersPerYear + quarter2.getValue())
- (year1 * TimeSpec.QuartersPerYear + quarter1.getValue());
}
private int calcMonths() {
if (Objects.equal(start, end))
return 0;
int compareDay = Math.min(end.getDayOfMonth(), timeCalendar.getDaysInMonth(getStartYear(), getStartMonthOfYear()));
DateTime compareDate = Times.asDate(getStartYear(), getStartMonthOfYear(), compareDay).plusMillis(end.getMillisOfDay());
// log.trace("compareDay=[{}], compareDate=[{}]", compareDay, compareDate);
if (end.compareTo(start) > 0) {
if (compareDate.compareTo(start) < 0)
compareDate = compareDate.plusMonths(1);
} else if (compareDate.compareTo(start) > 0) {
compareDate = compareDate.plusMonths(-1);
}
int month = (getEndYear() * TimeSpec.MonthsPerYear + getEndMonthOfYear())
- (timeCalendar.getYear(compareDate) * TimeSpec.MonthsPerYear + timeCalendar.getMonthOfYear(compareDate));
// log.trace("calc month=[{}]", month);
return month;
}
private int calcWeeks() {
if (Objects.equal(start, end))
return 0;
DateTime week1 = Times.getStartOfWeek(start, timeCalendar.getFirstDayOfWeek());
DateTime week2 = Times.getStartOfWeek(end, timeCalendar.getFirstDayOfWeek());
DateDiff.log.trace("week1=[{}], week2=[{}]", week1, week2);
if (Objects.equal(week1, week2))
return 0;
return (int) (new Duration(week1, week2).getStandardDays() / TimeSpec.DaysPerWeek);
}
@Override
public int hashCode() {
return HashTool.compute(start, end, difference, timeCalendar);
}
@Override
protected Objects.ToStringHelper buildStringHelper() {
return super.buildStringHelper()
.add("start", start)
.add("end", end)
.add("difference", difference)
.add("timeCalendar", timeCalendar);
}
private static double roundEx(double number) {
return (number >= 0.0) ? Math.round(number) : -Math.round(-number);
}
private static final long serialVersionUID = 3415272759108830763L;
}