OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 73 | Rev 180 | Go to most recent revision | 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
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
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;
17
 
18
import java.math.BigDecimal;
19
import java.math.BigInteger;
20
import java.math.MathContext;
21
import java.text.DateFormat;
22
import java.text.ParseException;
23
import java.text.SimpleDateFormat;
24
import java.util.Calendar;
25
import java.util.LinkedHashMap;
26
import java.util.Map;
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")
25 ilm 46
    static private final Map<String, ODEpoch> cache = new LinkedHashMap<String, ODEpoch>(4, 0.75f, true) {
47
        @Override
48
        protected boolean removeEldestEntry(Map.Entry<String, ODEpoch> eldest) {
49
            return this.size() > 16;
50
        }
51
    };
52
 
53
    static {
54
        DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
55
        DATE_FORMAT.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC")));
56
        try {
57
            DEFAULT_EPOCH = new ODEpoch("1899-12-30");
58
        } catch (ParseException e) {
59
            // shouldn't happen since string are constants
60
            throw new IllegalStateException(e);
61
        }
62
    }
63
 
64
    static public final ODEpoch getDefaultEpoch() {
65
        return DEFAULT_EPOCH;
66
    }
67
 
68
    static public final ODEpoch getInstance(String date) throws ParseException {
69
        if (date == null || date.equals(DEFAULT_EPOCH.getDateString())) {
70
            return DEFAULT_EPOCH;
71
        } else {
80 ilm 72
            synchronized (cache) {
73
                ODEpoch res = cache.get(date);
74
                if (res == null) {
75
                    res = new ODEpoch(date);
76
                    cache.put(date, res);
77
                }
78
                return res;
25 ilm 79
            }
80
        }
81
    }
82
 
83
    static private final Calendar parse(final String date) throws ParseException {
80 ilm 84
        synchronized (DATE_FORMAT) {
85
            final Calendar cal = (Calendar) DATE_FORMAT.getCalendar().clone();
86
            cal.setTime(DATE_FORMAT.parse(date));
87
            return cal;
88
        }
25 ilm 89
    }
90
 
91
    private final String dateString;
92
    private final Calendar epochUTC;
93
 
94
    private ODEpoch(final String date) throws ParseException {
95
        this.dateString = date;
96
        this.epochUTC = parse(date);
97
        assert this.epochUTC.getTimeZone().equals(DATE_FORMAT.getTimeZone());
98
    }
99
 
100
    public final String getDateString() {
101
        return this.dateString;
102
    }
103
 
104
    public final Calendar getCalendar() {
105
        return (Calendar) this.epochUTC.clone();
106
    }
107
 
108
    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
110
        // 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.
112
        final Calendar res = getCalendar();
113
        duration.addTo(res);
114
        return res;
115
    }
116
 
117
    public final Duration normalizeToDays(final Duration dur) {
80 ilm 118
        // we have to convert to a date to know the duration of years and months
25 ilm 119
        final Duration res = dur.getYears() == 0 && dur.getMonths() == 0 ? dur : getDuration(getDays(dur));
120
        assert res.getYears() == 0 && res.getMonths() == 0;
121
        return res;
122
    }
123
 
124
    public final Duration normalizeToHours(final Duration dur) {
125
        final Duration durationInDays = normalizeToDays(dur);
126
        final BigInteger days = (BigInteger) durationInDays.getField(DatatypeConstants.DAYS);
73 ilm 127
        if (days == null || days.equals(BigInteger.ZERO))
128
            return durationInDays;
25 ilm 129
        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),
131
                (BigDecimal) durationInDays.getField(DatatypeConstants.SECONDS));
132
    }
133
 
134
    public final BigDecimal getDays(final Duration duration) {
135
        return getDays(getDate(duration));
136
    }
137
 
138
    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
140
        // summer at midnight will miss a day
141
        final long diff = TimeUtils.normalizeLocalTime(cal) - this.epochUTC.getTimeInMillis();
142
        return BigDecimal.valueOf(diff).divide(MS_PER_DAY, MathContext.DECIMAL128);
143
    }
144
 
145
    public final Calendar getDate(final BigDecimal days) {
80 ilm 146
        return getDate(days, ODValueType.getCalendar());
25 ilm 147
    }
148
 
149
    public final Calendar getDate(final BigDecimal days, final Calendar res) {
150
        final Calendar utcCal = getDate(getDuration(days));
151
        // can't use getTimeZone().getOffset() since we have no idea for the UTC time
152
        return TimeUtils.copyLocalTime(utcCal, res);
153
    }
154
 
155
    private final static Duration getDuration(final BigDecimal days) {
156
        final BigDecimal posDays = days.abs();
157
        final BigInteger wholeDays = posDays.toBigInteger().abs();
158
        final BigDecimal hours = posDays.subtract(new BigDecimal(wholeDays)).multiply(BigDecimal.valueOf(24));
159
        final BigInteger wholeHours = hours.toBigInteger();
160
        final BigDecimal minutes = hours.subtract(new BigDecimal(wholeHours)).multiply(BigDecimal.valueOf(60));
161
        final BigInteger wholeMinutes = minutes.toBigInteger();
162
        // 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
164
        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);
166
    }
167
}