OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 80 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

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