/*
* Copyright 2001-2005 Stephen Colebourne
*
* 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 org.joda.time.chrono.gj;
import org.joda.time.Chronology;
import org.joda.time.DateTimeField;
import org.joda.time.DateTimeZone;
import org.joda.time.DurationField;
import org.joda.time.chrono.BaseChronology;
/**
* A reference Gregorian/Julian chronology implementation, intended for testing
* purposes only. Correctness is favored over performance. The key functions
* for date calculations are based on ones provided in "Calendrical
* Calculations", ISBN 0-521-77752-6.
*
* <p>In theory, this class can be used to test any other Gregorian/Julian
* chronology as long as almost all datetime fields are implemented differently
* between the two. Fields that would most likely be implemented the same are
* not supported by this class.
*
* <p>Unsupported features
* <ul>
* <li>time zones
* <li>time of day
* <li>year of era
* <li>year of century
* <li>century of era
* <li>era
* </ul>
*
* @author Brian S O'Neill
*/
abstract class TestGJChronology extends BaseChronology {
static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
/**
* Divide with round-negative behavior.
*
* @param divisor must be positive
*/
static long div(long dividend, long divisor) {
if (divisor < 0) {
throw new IllegalArgumentException("divisor must be positive: " + divisor);
}
if (dividend >= 0) {
return dividend / divisor;
} else {
return (dividend + 1) / divisor - 1;
}
}
/**
* Modulus with round-negative behavior, result is always positive.
*
* @param divisor must be positive
*/
static long mod(long dividend, long divisor) {
if (divisor < 0) {
throw new IllegalArgumentException("divisor must be positive: " + divisor);
}
if (dividend >= 0) {
return dividend % divisor;
} else {
return (dividend + 1) % divisor - 1 + divisor;
}
}
static long amod(long dividend, long divisor) {
long mod = mod(dividend, divisor);
return (mod == 0) ? divisor : mod;
}
/** Milliseconds from 0001-01-01 to the epoch. */
private final long iEpochMillis;
public TestGJChronology(int epochYear, int epochMonth, int epochDay) {
iEpochMillis = fixedFromGJ(epochYear, epochMonth, epochDay) * MILLIS_PER_DAY;
}
public DateTimeZone getZone() {
return null;
}
public Chronology withUTC() {
return this;
}
/**
* Unsupported.
*/
public Chronology withZone(DateTimeZone zone) {
throw new UnsupportedOperationException();
}
long getTimeOnlyMillis(long millis) {
return mod(millis, MILLIS_PER_DAY);
}
long getDateOnlyMillis(long millis) {
return millis - mod(millis, MILLIS_PER_DAY);
}
public DurationField days() {
return dayOfWeek().getDurationField();
}
public DateTimeField dayOfWeek() {
return new TestGJDayOfWeekField(this);
}
public DateTimeField dayOfMonth() {
return new TestGJDayOfMonthField(this);
}
public DateTimeField dayOfYear() {
return new TestGJDayOfYearField(this);
}
public DurationField weeks() {
return weekOfWeekyear().getDurationField();
}
public DateTimeField weekOfWeekyear() {
return new TestGJWeekOfWeekyearField(this);
}
public DurationField weekyears() {
return weekyear().getDurationField();
}
public DateTimeField weekyear() {
return new TestGJWeekyearField(this);
}
public DurationField months() {
return monthOfYear().getDurationField();
}
public DateTimeField monthOfYear() {
return new TestGJMonthOfYearField(this);
}
public DurationField years() {
return year().getDurationField();
}
public DateTimeField year() {
return new TestGJYearField(this);
}
abstract long millisPerYear();
abstract long millisPerMonth();
abstract boolean isLeapYear(int year);
/**
* @return days from 0001-01-01
*/
abstract long fixedFromGJ(int year, int monthOfYear, int dayOfMonth);
/**
* @param date days from 0001-01-01
* @return gj year
*/
abstract int gjYearFromFixed(long date);
/**
* @param date days from 0001-01-01
* @return gj year, monthOfYear, dayOfMonth
*/
abstract int[] gjFromFixed(long date);
abstract long fixedFromISO(int weekyear, int weekOfWeekyear, int dayOfWeek);
/**
* @param date days from 0001-01-01
* @return iso weekyear, weekOfWeekyear, dayOfWeek (1=Monday to 7)
*/
abstract int[] isoFromFixed(long date);
/**
* @param millis milliseconds from epoch
* @return days from 0001-01-01
*/
long fixedFromMillis(long millis) {
return div(millis + iEpochMillis, MILLIS_PER_DAY);
}
/**
* @param fixed days from 0001-01-01
* @return milliseconds from epoch
*/
long millisFromFixed(long fixed) {
return fixed * MILLIS_PER_DAY - iEpochMillis;
}
/**
* @return milliseconds from epoch
*/
long millisFromGJ(int year, int monthOfYear, int dayOfMonth) {
return millisFromFixed(fixedFromGJ(year, monthOfYear, dayOfMonth));
}
/**
* @param millis milliseconds from epoch
* @return gj year
*/
int gjYearFromMillis(long millis) {
return gjYearFromFixed(fixedFromMillis(millis));
}
/**
* @param millis milliseconds from epoch
* @return gj year, monthOfYear, dayOfMonth
*/
int[] gjFromMillis(long millis) {
return gjFromFixed(fixedFromMillis(millis));
}
/**
* @return milliseconds from epoch
*/
long millisFromISO(int weekyear, int weekOfWeekyear, int dayOfWeek) {
return millisFromFixed(fixedFromISO(weekyear, weekOfWeekyear, dayOfWeek));
}
/**
* @param millis milliseconds from epoch
* @return iso weekyear, weekOfWeekyear, dayOfWeek (1=Monday to 7)
*/
int[] isoFromMillis(long millis) {
return isoFromFixed(fixedFromMillis(millis));
}
/**
* @param date days from 0001-01-01
* @param weekday 0=Sunday, 1=Monday, 2=Tuesday ... 6=Saturday, 7=Sunday
* @param date days from 0001-01-01, on or before weekday
*/
long weekdayOnOrBefore(long date, int weekday) {
return date - mod(date - mod(weekday, 7), 7);
}
long weekdayOnOrAfter(long date, int weekday) {
return weekdayOnOrBefore(date + 6, weekday);
}
long weekdayNearest(long date, int weekday) {
return weekdayOnOrBefore(date + 3, weekday);
}
long weekdayBefore(long date, int weekday) {
return weekdayOnOrBefore(date - 1, weekday);
}
long weekdayAfter(long date, int weekday) {
return weekdayOnOrBefore(date + 7, weekday);
}
long nthWeekday(int n, int weekday,
int year, int monthOfYear, int dayOfMonth)
{
if (n > 0) {
return 7 * n + weekdayBefore
(fixedFromGJ(year, monthOfYear, dayOfMonth), weekday);
} else {
return 7 * n + weekdayAfter
(fixedFromGJ(year, monthOfYear, dayOfMonth), weekday);
}
}
long firstWeekday(int weekday, int year, int monthOfYear, int dayOfMonth) {
return nthWeekday(1, weekday, year, monthOfYear, dayOfMonth);
}
long lastWeekday(int weekday, int year, int monthOfYear, int dayOfMonth) {
return nthWeekday(-1, weekday, year, monthOfYear, dayOfMonth);
}
}