OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 25 | Rev 80 | 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
 
32
/**
33
 * The null date of an OpenDocument.
34
 */
35
public final class ODEpoch {
36
 
37
    static private final BigDecimal MS_PER_DAY = BigDecimal.valueOf(24l * 60l * 60l * 1000l);
38
    static private final DateFormat DATE_FORMAT;
39
    static private final ODEpoch DEFAULT_EPOCH;
40
    static private final Map<String, ODEpoch> cache = new LinkedHashMap<String, ODEpoch>(4, 0.75f, true) {
41
        @Override
42
        protected boolean removeEldestEntry(Map.Entry<String, ODEpoch> eldest) {
43
            return this.size() > 16;
44
        }
45
    };
46
 
47
    static {
48
        DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
49
        DATE_FORMAT.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC")));
50
        try {
51
            DEFAULT_EPOCH = new ODEpoch("1899-12-30");
52
        } catch (ParseException e) {
53
            // shouldn't happen since string are constants
54
            throw new IllegalStateException(e);
55
        }
56
    }
57
 
58
    static public final ODEpoch getDefaultEpoch() {
59
        return DEFAULT_EPOCH;
60
    }
61
 
62
    static public final ODEpoch getInstance(String date) throws ParseException {
63
        if (date == null || date.equals(DEFAULT_EPOCH.getDateString())) {
64
            return DEFAULT_EPOCH;
65
        } else {
66
            ODEpoch res = cache.get(date);
67
            if (res == null) {
68
                res = new ODEpoch(date);
69
                cache.put(date, res);
70
            }
71
            return res;
72
        }
73
    }
74
 
75
    static private final Calendar parse(final String date) throws ParseException {
76
        final Calendar cal = (Calendar) DATE_FORMAT.getCalendar().clone();
77
        cal.setTime(DATE_FORMAT.parse(date));
78
        return cal;
79
    }
80
 
81
    private final String dateString;
82
    private final Calendar epochUTC;
83
 
84
    private ODEpoch(final String date) throws ParseException {
85
        this.dateString = date;
86
        this.epochUTC = parse(date);
87
        assert this.epochUTC.getTimeZone().equals(DATE_FORMAT.getTimeZone());
88
    }
89
 
90
    public final String getDateString() {
91
        return this.dateString;
92
    }
93
 
94
    public final Calendar getCalendar() {
95
        return (Calendar) this.epochUTC.clone();
96
    }
97
 
98
    private final Calendar getDate(final Duration duration) {
99
        // If we don't use the UTC calendar, we go from 0:00 (the epoch), we add n days, we get
100
        // to the last Sunday of March at 0:00, so far so good, but then we add say 10 hours, thus
101
        // going through the change of offset, and arriving at 11:00.
102
        final Calendar res = getCalendar();
103
        duration.addTo(res);
104
        return res;
105
    }
106
 
107
    public final Duration normalizeToDays(final Duration dur) {
108
        final Duration res = dur.getYears() == 0 && dur.getMonths() == 0 ? dur : getDuration(getDays(dur));
109
        assert res.getYears() == 0 && res.getMonths() == 0;
110
        return res;
111
    }
112
 
113
    public final Duration normalizeToHours(final Duration dur) {
114
        final Duration durationInDays = normalizeToDays(dur);
115
        final BigInteger days = (BigInteger) durationInDays.getField(DatatypeConstants.DAYS);
73 ilm 116
        if (days == null || days.equals(BigInteger.ZERO))
117
            return durationInDays;
25 ilm 118
        final BigInteger hours = ((BigInteger) durationInDays.getField(DatatypeConstants.HOURS)).add(days.multiply(BigInteger.valueOf(24)));
119
        return TimeUtils.getTypeFactory().newDuration(days.signum() >= 0, BigInteger.ZERO, BigInteger.ZERO, BigInteger.ZERO, hours, (BigInteger) durationInDays.getField(DatatypeConstants.MINUTES),
120
                (BigDecimal) durationInDays.getField(DatatypeConstants.SECONDS));
121
    }
122
 
123
    public final BigDecimal getDays(final Duration duration) {
124
        return getDays(getDate(duration));
125
    }
126
 
127
    public final BigDecimal getDays(final Calendar cal) {
128
        // can't use Duration.normalizeWith() since it doesn't handle DST, i.e. going from winter to
129
        // summer at midnight will miss a day
130
        final long diff = TimeUtils.normalizeLocalTime(cal) - this.epochUTC.getTimeInMillis();
131
        return BigDecimal.valueOf(diff).divide(MS_PER_DAY, MathContext.DECIMAL128);
132
    }
133
 
134
    public final Calendar getDate(final BigDecimal days) {
135
        return getDate(days, Calendar.getInstance());
136
    }
137
 
138
    public final Calendar getDate(final BigDecimal days, final Calendar res) {
139
        final Calendar utcCal = getDate(getDuration(days));
140
        // can't use getTimeZone().getOffset() since we have no idea for the UTC time
141
        return TimeUtils.copyLocalTime(utcCal, res);
142
    }
143
 
144
    private final static Duration getDuration(final BigDecimal days) {
145
        final BigDecimal posDays = days.abs();
146
        final BigInteger wholeDays = posDays.toBigInteger().abs();
147
        final BigDecimal hours = posDays.subtract(new BigDecimal(wholeDays)).multiply(BigDecimal.valueOf(24));
148
        final BigInteger wholeHours = hours.toBigInteger();
149
        final BigDecimal minutes = hours.subtract(new BigDecimal(wholeHours)).multiply(BigDecimal.valueOf(60));
150
        final BigInteger wholeMinutes = minutes.toBigInteger();
151
        // round to 16 digits, i.e. 10^-14 seconds is more than enough
152
        // it is required since the number coming from getDays() might have been rounded
153
        final BigDecimal seconds = minutes.subtract(new BigDecimal(wholeMinutes)).multiply(BigDecimal.valueOf(60)).round(MathContext.DECIMAL64);
154
        return TimeUtils.getTypeFactory().newDuration(days.signum() >= 0, BigInteger.ZERO, BigInteger.ZERO, wholeDays, wholeHours, wholeMinutes, seconds);
155
    }
156
}