OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
25 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
185 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
25 ilm 5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.openoffice;
15
 
16
import org.openconcerto.utils.TimeUtils;
180 ilm 17
import org.openconcerto.utils.cache.LRUMap;
18
import org.openconcerto.utils.cc.CachedTransformer;
25 ilm 19
 
20
import java.math.BigDecimal;
21
import java.math.BigInteger;
22
import java.math.MathContext;
23
import java.text.DateFormat;
24
import java.text.ParseException;
25
import java.text.SimpleDateFormat;
26
import java.util.Calendar;
27
import java.util.TimeZone;
28
 
29
import javax.xml.datatype.DatatypeConstants;
30
import javax.xml.datatype.Duration;
31
 
80 ilm 32
import net.jcip.annotations.GuardedBy;
33
import net.jcip.annotations.Immutable;
34
 
25 ilm 35
/**
36
 * The null date of an OpenDocument.
37
 */
80 ilm 38
@Immutable
25 ilm 39
public final class ODEpoch {
40
 
41
    static private final BigDecimal MS_PER_DAY = BigDecimal.valueOf(24l * 60l * 60l * 1000l);
80 ilm 42
    @GuardedBy("DATE_FORMAT")
25 ilm 43
    static private final DateFormat DATE_FORMAT;
44
    static private final ODEpoch DEFAULT_EPOCH;
80 ilm 45
    @GuardedBy("cache")
180 ilm 46
    static private final CachedTransformer<String, ODEpoch, ParseException> cache = new CachedTransformer<>(new LRUMap<>(16, 4), ODEpoch::new);
25 ilm 47
 
48
    static {
49
        DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
50
        DATE_FORMAT.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC")));
51
        try {
52
            DEFAULT_EPOCH = new ODEpoch("1899-12-30");
53
        } catch (ParseException e) {
54
            // shouldn't happen since string are constants
55
            throw new IllegalStateException(e);
56
        }
57
    }
58
 
59
    static public final ODEpoch getDefaultEpoch() {
60
        return DEFAULT_EPOCH;
61
    }
62
 
63
    static public final ODEpoch getInstance(String date) throws ParseException {
64
        if (date == null || date.equals(DEFAULT_EPOCH.getDateString())) {
65
            return DEFAULT_EPOCH;
66
        } else {
80 ilm 67
            synchronized (cache) {
180 ilm 68
                return cache.transformChecked(date);
25 ilm 69
            }
70
        }
71
    }
72
 
180 ilm 73
    public static final BigDecimal getDays(final java.time.Duration d) {
74
        return getDays(d.toMillis());
75
    }
76
 
77
    public static final BigDecimal getDays(final long millis) {
78
        return BigDecimal.valueOf(millis).divide(MS_PER_DAY, MathContext.DECIMAL128);
79
    }
80
 
25 ilm 81
    static private final Calendar parse(final String date) throws ParseException {
80 ilm 82
        synchronized (DATE_FORMAT) {
83
            final Calendar cal = (Calendar) DATE_FORMAT.getCalendar().clone();
84
            cal.setTime(DATE_FORMAT.parse(date));
85
            return cal;
86
        }
25 ilm 87
    }
88
 
89
    private final String dateString;
90
    private final Calendar epochUTC;
91
 
92
    private ODEpoch(final String date) throws ParseException {
93
        this.dateString = date;
94
        this.epochUTC = parse(date);
95
        assert this.epochUTC.getTimeZone().equals(DATE_FORMAT.getTimeZone());
96
    }
97
 
98
    public final String getDateString() {
99
        return this.dateString;
100
    }
101
 
102
    public final Calendar getCalendar() {
103
        return (Calendar) this.epochUTC.clone();
104
    }
105
 
106
    private final Calendar getDate(final Duration duration) {
107
        // If we don't use the UTC calendar, we go from 0:00 (the epoch), we add n days, we get
108
        // to the last Sunday of March at 0:00, so far so good, but then we add say 10 hours, thus
109
        // going through the change of offset, and arriving at 11:00.
110
        final Calendar res = getCalendar();
111
        duration.addTo(res);
112
        return res;
113
    }
114
 
115
    public final Duration normalizeToDays(final Duration dur) {
80 ilm 116
        // we have to convert to a date to know the duration of years and months
25 ilm 117
        final Duration res = dur.getYears() == 0 && dur.getMonths() == 0 ? dur : getDuration(getDays(dur));
118
        assert res.getYears() == 0 && res.getMonths() == 0;
119
        return res;
120
    }
121
 
122
    public final Duration normalizeToHours(final Duration dur) {
123
        final Duration durationInDays = normalizeToDays(dur);
124
        final BigInteger days = (BigInteger) durationInDays.getField(DatatypeConstants.DAYS);
73 ilm 125
        if (days == null || days.equals(BigInteger.ZERO))
126
            return durationInDays;
25 ilm 127
        final BigInteger hours = ((BigInteger) durationInDays.getField(DatatypeConstants.HOURS)).add(days.multiply(BigInteger.valueOf(24)));
128
        return TimeUtils.getTypeFactory().newDuration(days.signum() >= 0, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO, hours, (BigInteger) durationInDays.getField(DatatypeConstants.MINUTES),
129
                (BigDecimal) durationInDays.getField(DatatypeConstants.SECONDS));
130
    }
131
 
132
    public final BigDecimal getDays(final Duration duration) {
133
        return getDays(getDate(duration));
134
    }
135
 
136
    public final BigDecimal getDays(final Calendar cal) {
137
        // can't use Duration.normalizeWith() since it doesn't handle DST, i.e. going from winter to
138
        // summer at midnight will miss a day
139
        final long diff = TimeUtils.normalizeLocalTime(cal) - this.epochUTC.getTimeInMillis();
180 ilm 140
        return getDays(diff);
25 ilm 141
    }
142
 
143
    public final Calendar getDate(final BigDecimal days) {
80 ilm 144
        return getDate(days, ODValueType.getCalendar());
25 ilm 145
    }
146
 
147
    public final Calendar getDate(final BigDecimal days, final Calendar res) {
148
        final Calendar utcCal = getDate(getDuration(days));
149
        // can't use getTimeZone().getOffset() since we have no idea for the UTC time
150
        return TimeUtils.copyLocalTime(utcCal, res);
151
    }
152
 
153
    private final static Duration getDuration(final BigDecimal days) {
154
        final BigDecimal posDays = days.abs();
155
        final BigInteger wholeDays = posDays.toBigInteger().abs();
156
        final BigDecimal hours = posDays.subtract(new BigDecimal(wholeDays)).multiply(BigDecimal.valueOf(24));
157
        final BigInteger wholeHours = hours.toBigInteger();
158
        final BigDecimal minutes = hours.subtract(new BigDecimal(wholeHours)).multiply(BigDecimal.valueOf(60));
159
        final BigInteger wholeMinutes = minutes.toBigInteger();
160
        // round to 16 digits, i.e. 10^-14 seconds is more than enough
161
        // it is required since the number coming from getDays() might have been rounded
162
        final BigDecimal seconds = minutes.subtract(new BigDecimal(wholeMinutes)).multiply(BigDecimal.valueOf(60)).round(MathContext.DECIMAL64);
163
        return TimeUtils.getTypeFactory().newDuration(days.signum() >= 0, BigInteger.ZERO, BigInteger.ZERO, wholeDays, wholeHours, wholeMinutes, seconds);
164
    }
165
}