001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer.geoimage;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.text.ParseException;
007import java.util.Objects;
008
009/**
010 * Timezone in hours.<p>
011 * TODO: should probably be replaced by {@link java.util.TimeZone}.
012 * @since 11914 (extracted from {@link CorrelateGpxWithImages})
013 */
014public final class Timezone {
015
016    static final Timezone ZERO = new Timezone(0.0);
017    private final double timezone;
018
019    Timezone(double hours) {
020        this.timezone = hours;
021    }
022
023    /**
024     * Returns the timezone in hours.
025     * @return the timezone in hours
026     */
027    public double getHours() {
028        return timezone;
029    }
030
031    String formatTimezone() {
032        StringBuilder ret = new StringBuilder();
033
034        double timezone = this.timezone;
035        if (timezone < 0) {
036            ret.append('-');
037            timezone = -timezone;
038        } else {
039            ret.append('+');
040        }
041        ret.append((long) timezone).append(':');
042        int minutes = (int) ((timezone % 1) * 60);
043        if (minutes < 10) {
044            ret.append('0');
045        }
046        ret.append(minutes);
047
048        return ret.toString();
049    }
050
051    static Timezone parseTimezone(String timezone) throws ParseException {
052
053        if (timezone.isEmpty())
054            return ZERO;
055
056        String error = tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM");
057
058        char sgnTimezone = '+';
059        StringBuilder hTimezone = new StringBuilder();
060        StringBuilder mTimezone = new StringBuilder();
061        int state = 1; // 1=start/sign, 2=hours, 3=minutes.
062        for (int i = 0; i < timezone.length(); i++) {
063            char c = timezone.charAt(i);
064            switch (c) {
065                case ' ':
066                    if (state != 2 || hTimezone.length() != 0)
067                        throw new ParseException(error, i);
068                    break;
069                case '+':
070                case '-':
071                    if (state == 1) {
072                        sgnTimezone = c;
073                        state = 2;
074                    } else
075                        throw new ParseException(error, i);
076                    break;
077                case ':':
078                case '.':
079                    if (state == 2) {
080                        state = 3;
081                    } else
082                        throw new ParseException(error, i);
083                    break;
084                case '0':
085                case '1':
086                case '2':
087                case '3':
088                case '4':
089                case '5':
090                case '6':
091                case '7':
092                case '8':
093                case '9':
094                    switch (state) {
095                        case 1:
096                        case 2:
097                            state = 2;
098                            hTimezone.append(c);
099                            break;
100                        case 3:
101                            mTimezone.append(c);
102                            break;
103                        default:
104                            throw new ParseException(error, i);
105                    }
106                    break;
107                default:
108                    throw new ParseException(error, i);
109            }
110        }
111
112        int h = 0;
113        int m = 0;
114        try {
115            h = Integer.parseInt(hTimezone.toString());
116            if (mTimezone.length() > 0) {
117                m = Integer.parseInt(mTimezone.toString());
118            }
119        } catch (NumberFormatException nfe) {
120            // Invalid timezone
121            throw (ParseException) new ParseException(error, 0).initCause(nfe);
122        }
123
124        if (h > 12 || m > 59)
125            throw new ParseException(error, 0);
126        else
127            return new Timezone((h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1));
128    }
129
130    @Override
131    public boolean equals(Object o) {
132        if (this == o) return true;
133        if (!(o instanceof Timezone)) return false;
134        Timezone timezone1 = (Timezone) o;
135        return Double.compare(timezone1.timezone, timezone) == 0;
136    }
137
138    @Override
139    public int hashCode() {
140        return Objects.hash(timezone);
141    }
142}