001package net.filebot.web;
002
003import static net.filebot.Logging.*;
004
005import java.io.Serializable;
006import java.time.Instant;
007import java.time.LocalDate;
008import java.time.ZoneOffset;
009import java.time.chrono.ChronoLocalDate;
010import java.time.format.DateTimeFormatter;
011import java.time.temporal.ChronoUnit;
012import java.util.Locale;
013import java.util.Objects;
014import java.util.regex.Matcher;
015import java.util.regex.Pattern;
016
017public class SimpleDate implements Serializable, Comparable<Object> {
018
019        protected int year;
020        protected int month;
021        protected int day;
022
023        public SimpleDate() {
024                // used by deserializer
025        }
026
027        public SimpleDate(int year, int month, int day) {
028                this.year = year;
029                this.month = month;
030                this.day = day;
031        }
032
033        public SimpleDate(LocalDate date) {
034                this.year = date.getYear();
035                this.month = date.getMonthValue();
036                this.day = date.getDayOfMonth();
037        }
038
039        public SimpleDate(int year) {
040                this(year, 1, 1);
041        }
042
043        public int getYear() {
044                return year;
045        }
046
047        public int getMonth() {
048                return month;
049        }
050
051        public int getDay() {
052                return day;
053        }
054
055        @Override
056        public boolean equals(Object obj) {
057                if (obj instanceof SimpleDate) {
058                        SimpleDate other = (SimpleDate) obj;
059                        return year == other.year && month == other.month && day == other.day;
060                } else if (obj instanceof CharSequence) {
061                        return toString().equals(obj.toString());
062                }
063                return super.equals(obj);
064        }
065
066        @Override
067        public int compareTo(Object other) {
068                if (other instanceof SimpleDate) {
069                        return compareTo((SimpleDate) other);
070                }
071                if (other instanceof ChronoLocalDate) {
072                        return compareTo((ChronoLocalDate) other);
073                }
074                if (other instanceof CharSequence) {
075                        SimpleDate otherDate = parse(other.toString());
076                        if (otherDate != null) {
077                                return compareTo(otherDate);
078                        }
079                }
080                throw new IllegalArgumentException("Bad Date: " + other);
081        }
082
083        public int compareTo(SimpleDate other) {
084                return toLocalDate().compareTo(other.toLocalDate());
085        }
086
087        public int compareTo(ChronoLocalDate other) {
088                return toLocalDate().compareTo(other);
089        }
090
091        public int compareTo(Instant other) {
092                return toInstant().compareTo(other);
093        }
094
095        public SimpleDate plus(int days) {
096                return new SimpleDate(toLocalDate().plusDays(days));
097        }
098
099        public SimpleDate minus(int days) {
100                return new SimpleDate(toLocalDate().minusDays(days));
101        }
102
103        public long minus(SimpleDate other) {
104                return getEpochDay() - other.getEpochDay();
105        }
106
107        @Override
108        public int hashCode() {
109                return Objects.hash(year, month, day);
110        }
111
112        @Override
113        public SimpleDate clone() {
114                return new SimpleDate(year, month, day);
115        }
116
117        public String format(String pattern) {
118                return format(pattern, Locale.US);
119        }
120
121        public String format(String pattern, Locale locale) {
122                return DateTimeFormatter.ofPattern(pattern, locale).format(toLocalDate());
123        }
124
125        public LocalDate toLocalDate() {
126                try {
127                        return LocalDate.of(year, month, day);
128                } catch (Exception e) {
129                        throw new IllegalStateException("Bad Date: " + this, e);
130                }
131        }
132
133        public Instant toInstant() {
134                return toLocalDate().atStartOfDay(ZoneOffset.UTC).toInstant();
135        }
136
137        public long getEpochDay() {
138                return ChronoUnit.DAYS.between(Instant.EPOCH, toInstant());
139        }
140
141        public long getTimeStamp() {
142                return toInstant().toEpochMilli();
143        }
144
145        @Override
146        public String toString() {
147                return String.format(Locale.ROOT, "%04d-%02d-%02d", year, month, day);
148        }
149
150        public static SimpleDate now() {
151                return new SimpleDate(LocalDate.now());
152        }
153
154        public static SimpleDate from(Instant instant) {
155                return instant == null ? null : new SimpleDate(instant.atOffset(ZoneOffset.UTC).toLocalDate());
156        }
157
158        public static SimpleDate of(int year, int month, int day) {
159                return new SimpleDate(LocalDate.of(year, month, day));
160        }
161
162        public static SimpleDate parse(String date) {
163                if (date == null || date.isEmpty()) {
164                        return null;
165                }
166
167                Matcher m = DATE_FORMAT.matcher(date);
168                if (m.matches()) {
169                        int year = Integer.parseInt(m.group(1));
170                        int month = Integer.parseInt(m.group(2));
171                        int day = Integer.parseInt(m.group(3));
172
173                        try {
174                                return new SimpleDate(LocalDate.of(year, month, day));
175                        } catch (Exception e) {
176                                debug.finest(message("Bad Date", date));
177                        }
178                }
179
180                return null;
181        }
182
183        public static final Pattern DATE_FORMAT = Pattern.compile("(\\d{4})\\D(\\d{1,2})\\D(\\d{1,2})");
184
185}