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