org.jopendocument.dom.ODEpoch Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008 jOpenDocument, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU
* General Public License Version 3 only ("GPL").
* You may not use this file except in compliance with the License.
* You can obtain a copy of the License at http://www.gnu.org/licenses/gpl-3.0.html
* See the License for the specific language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*
*/
package org.jopendocument.dom;
import org.jopendocument.util.TimeUtils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TimeZone;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.Duration;
/**
* The null date of an OpenDocument.
*/
public final class ODEpoch {
static private final BigDecimal MS_PER_DAY = BigDecimal.valueOf(24l * 60l * 60l * 1000l);
static private final DateFormat DATE_FORMAT;
static private final ODEpoch DEFAULT_EPOCH;
static private final Map cache = new LinkedHashMap(4, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return this.size() > 16;
}
};
static {
DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
DATE_FORMAT.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC")));
try {
DEFAULT_EPOCH = new ODEpoch("1899-12-30");
} catch (ParseException e) {
// shouldn't happen since string are constants
throw new IllegalStateException(e);
}
}
static public final ODEpoch getDefaultEpoch() {
return DEFAULT_EPOCH;
}
static public final ODEpoch getInstance(String date) throws ParseException {
if (date == null || date.equals(DEFAULT_EPOCH.getDateString())) {
return DEFAULT_EPOCH;
} else {
ODEpoch res = cache.get(date);
if (res == null) {
res = new ODEpoch(date);
cache.put(date, res);
}
return res;
}
}
static private final Calendar parse(final String date) throws ParseException {
final Calendar cal = (Calendar) DATE_FORMAT.getCalendar().clone();
cal.setTime(DATE_FORMAT.parse(date));
return cal;
}
private final String dateString;
private final Calendar epochUTC;
private ODEpoch(final String date) throws ParseException {
this.dateString = date;
this.epochUTC = parse(date);
assert this.epochUTC.getTimeZone().equals(DATE_FORMAT.getTimeZone());
}
public final String getDateString() {
return this.dateString;
}
public final Calendar getCalendar() {
return (Calendar) this.epochUTC.clone();
}
private final Calendar getDate(final Duration duration) {
// If we don't use the UTC calendar, we go from 0:00 (the epoch), we add n days, we get
// to the last Sunday of March at 0:00, so far so good, but then we add say 10 hours, thus
// going through the change of offset, and arriving at 11:00.
final Calendar res = getCalendar();
duration.addTo(res);
return res;
}
public final Duration normalizeToDays(final Duration dur) {
final Duration res = dur.getYears() == 0 && dur.getMonths() == 0 ? dur : getDuration(getDays(dur));
assert res.getYears() == 0 && res.getMonths() == 0;
return res;
}
public final Duration normalizeToHours(final Duration dur) {
final Duration durationInDays = normalizeToDays(dur);
final BigInteger days = (BigInteger) durationInDays.getField(DatatypeConstants.DAYS);
final BigInteger hours = ((BigInteger) durationInDays.getField(DatatypeConstants.HOURS)).add(days.multiply(BigInteger.valueOf(24)));
return TimeUtils.getTypeFactory().newDuration(days.signum() >= 0, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO, hours, (BigInteger) durationInDays.getField(DatatypeConstants.MINUTES),
(BigDecimal) durationInDays.getField(DatatypeConstants.SECONDS));
}
public final BigDecimal getDays(final Duration duration) {
return getDays(getDate(duration));
}
public final BigDecimal getDays(final Calendar cal) {
// can't use Duration.normalizeWith() since it doesn't handle DST, i.e. going from winter to
// summer at midnight will miss a day
final long diff = TimeUtils.normalizeLocalTime(cal) - this.epochUTC.getTimeInMillis();
return BigDecimal.valueOf(diff).divide(MS_PER_DAY, MathContext.DECIMAL128);
}
public final Calendar getDate(final BigDecimal days) {
return getDate(days, Calendar.getInstance());
}
public final Calendar getDate(final BigDecimal days, final Calendar res) {
final Calendar utcCal = getDate(getDuration(days));
// can't use getTimeZone().getOffset() since we have no idea for the UTC time
return TimeUtils.copyLocalTime(utcCal, res);
}
private final static Duration getDuration(final BigDecimal days) {
final BigDecimal posDays = days.abs();
final BigInteger wholeDays = posDays.toBigInteger().abs();
final BigDecimal hours = posDays.subtract(new BigDecimal(wholeDays)).multiply(BigDecimal.valueOf(24));
final BigInteger wholeHours = hours.toBigInteger();
final BigDecimal minutes = hours.subtract(new BigDecimal(wholeHours)).multiply(BigDecimal.valueOf(60));
final BigInteger wholeMinutes = minutes.toBigInteger();
// round to 16 digits, i.e. 10^-14 seconds is more than enough
// it is required since the number coming from getDays() might have been rounded
final BigDecimal seconds = minutes.subtract(new BigDecimal(wholeMinutes)).multiply(BigDecimal.valueOf(60)).round(MathContext.DECIMAL64);
return TimeUtils.getTypeFactory().newDuration(days.signum() >= 0, BigInteger.ZERO, BigInteger.ZERO, wholeDays, wholeHours, wholeMinutes, seconds);
}
} © 2015 - 2025 Weber Informatics LLC | Privacy Policy