1+ package com .relogiclabs .json .schema .time ;
2+
3+ import com .relogiclabs .json .schema .exception .InvalidDateTimeException ;
4+ import lombok .Getter ;
5+ import lombok .RequiredArgsConstructor ;
6+
7+ import java .time .LocalDate ;
8+ import java .util .Arrays ;
9+ import java .util .HashMap ;
10+ import java .util .Map ;
11+
12+ import static com .relogiclabs .json .schema .internal .util .StringHelper .concat ;
13+ import static com .relogiclabs .json .schema .message .ErrorCode .DCNF01 ;
14+ import static com .relogiclabs .json .schema .message .ErrorCode .DDAY03 ;
15+ import static com .relogiclabs .json .schema .message .ErrorCode .DDAY04 ;
16+ import static com .relogiclabs .json .schema .message .ErrorCode .DERA02 ;
17+ import static com .relogiclabs .json .schema .message .ErrorCode .DHUR03 ;
18+ import static com .relogiclabs .json .schema .message .ErrorCode .DHUR04 ;
19+ import static com .relogiclabs .json .schema .message .ErrorCode .DHUR05 ;
20+ import static com .relogiclabs .json .schema .message .ErrorCode .DHUR06 ;
21+ import static com .relogiclabs .json .schema .message .ErrorCode .DINV01 ;
22+ import static com .relogiclabs .json .schema .message .ErrorCode .DMIN03 ;
23+ import static com .relogiclabs .json .schema .message .ErrorCode .DMON05 ;
24+ import static com .relogiclabs .json .schema .message .ErrorCode .DSEC03 ;
25+ import static com .relogiclabs .json .schema .message .ErrorCode .DTAP02 ;
26+ import static com .relogiclabs .json .schema .message .ErrorCode .DUTC04 ;
27+ import static com .relogiclabs .json .schema .message .ErrorCode .DUTC05 ;
28+ import static com .relogiclabs .json .schema .message .ErrorCode .DWKD03 ;
29+ import static com .relogiclabs .json .schema .message .ErrorCode .DYAR03 ;
30+ import static java .time .DayOfWeek .FRIDAY ;
31+ import static java .time .DayOfWeek .MONDAY ;
32+ import static java .time .DayOfWeek .SATURDAY ;
33+ import static java .time .DayOfWeek .SUNDAY ;
34+ import static java .time .DayOfWeek .THURSDAY ;
35+ import static java .time .DayOfWeek .TUESDAY ;
36+ import static java .time .DayOfWeek .WEDNESDAY ;
37+ import static org .apache .commons .lang3 .StringUtils .removeEnd ;
38+
39+ @ RequiredArgsConstructor
40+ public class DateTimeContext {
41+ private static final int PIVOT_YEAR = 50 ;
42+ private static final int [] DAYS_IN_MONTH = { 0 , 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 };
43+ private static final Map <String , Integer > MONTHS = new HashMap <>();
44+ private static final Map <String , Integer > WEEKDAYS = new HashMap <>();
45+
46+
47+ static {
48+ addMonth ("january" , "jan" , 1 );
49+ addMonth ("february" , "feb" , 2 );
50+ addMonth ("march" , "mar" , 3 );
51+ addMonth ("april" , "apr" , 4 );
52+ addMonth ("may" , "may" , 5 );
53+ addMonth ("june" , "jun" , 6 );
54+ addMonth ("july" , "jul" , 7 );
55+ addMonth ("august" , "aug" , 8 );
56+ addMonth ("september" , "sep" , 9 );
57+ addMonth ("october" , "oct" , 10 );
58+ addMonth ("november" , "nov" , 11 );
59+ addMonth ("december" , "dec" , 12 );
60+
61+ addWeekday ("sunday" , "sun" , SUNDAY .getValue ());
62+ addWeekday ("monday" , "mon" , MONDAY .getValue ());
63+ addWeekday ("tuesday" , "tue" , TUESDAY .getValue ());
64+ addWeekday ("wednesday" , "wed" , WEDNESDAY .getValue ());
65+ addWeekday ("thursday" , "thu" , THURSDAY .getValue ());
66+ addWeekday ("friday" , "fri" , FRIDAY .getValue ());
67+ addWeekday ("saturday" , "sat" , SATURDAY .getValue ());
68+ }
69+
70+ private static void addMonth (String key1 , String key2 , int value ) {
71+ MONTHS .put (key1 , value );
72+ MONTHS .put (key2 , value );
73+ }
74+
75+ private static void addWeekday (String key1 , String key2 , int value ) {
76+ WEEKDAYS .put (key1 , value );
77+ WEEKDAYS .put (key2 , value );
78+ }
79+
80+ private static final int UNSET = -100 ;
81+
82+ private int era = UNSET ;
83+ private int year = UNSET ;
84+ private int month = UNSET ;
85+ private int weekday = UNSET ;
86+ private int day = UNSET ;
87+ private int amPm = UNSET ;
88+ private int hour = UNSET ;
89+ private int minute = UNSET ;
90+ private int second = UNSET ;
91+ private int fraction = UNSET ;
92+ private int utcOffsetHour = UNSET ;
93+ private int utcOffsetMinute = UNSET ;
94+
95+ @ Getter
96+ public final DateTimeType type ;
97+
98+ public void setEra (String era ) {
99+ var eraNum = switch (era .toUpperCase ()) {
100+ case "BC" -> 1 ;
101+ case "AD" -> 2 ;
102+ default -> throw new InvalidDateTimeException (DERA02 ,
103+ concat ("Invalid " , type , " era input" ));
104+ };
105+ this .era = checkField (this .era , eraNum );
106+ }
107+
108+ public void setYear (int year , int digitNum ) {
109+ if (year < 1 || year > 9999 ) throw new InvalidDateTimeException (DYAR03 ,
110+ concat ("Invalid " , type , " year out of range" ));
111+ year = digitNum <= 2 ? toFourDigitYear (year ) : year ;
112+ this .year = checkField (this .year , year );
113+ }
114+
115+ public void setMonth (String month ) {
116+ var monthNum = MONTHS .get (month .toLowerCase ());
117+ this .month = checkField (this .month , monthNum );
118+ }
119+
120+ public void setMonth (int month ) {
121+ if (month < 1 || month > 12 ) throw new InvalidDateTimeException (DMON05 ,
122+ concat ("Invalid " , type , " month out of range" ));
123+ this .month = checkField (this .month , month );
124+ }
125+
126+ public void setWeekday (String weekday ) {
127+ var dayOfWeek = WEEKDAYS .get (weekday .toLowerCase ());
128+ this .weekday = checkField (this .weekday , dayOfWeek );
129+ }
130+
131+ public void setDay (int day ) {
132+ if (day < 1 || day > 31 ) throw new InvalidDateTimeException (DDAY04 ,
133+ concat ("Invalid " , type , " day out of range" ));
134+ this .day = checkField (this .day , day );
135+ }
136+
137+ public void setAmPm (String amPm ) {
138+ var amPmNum = switch (amPm .toLowerCase ()) {
139+ case "am" -> 1 ;
140+ case "pm" -> 2 ;
141+ default -> throw new InvalidDateTimeException (DTAP02 ,
142+ concat ("Invalid " , type , " hour AM/PM input" ));
143+ };
144+ if (hour != UNSET && (hour < 1 || hour > 12 ))
145+ throw new InvalidDateTimeException (DHUR03 ,
146+ concat ("Invalid " , type , " hour AM/PM out of range" ));
147+ this .amPm = checkField (this .amPm , amPmNum );
148+ }
149+
150+ public void setHour (int hour ) {
151+ if (amPm != UNSET && (this .hour < 1 || this .hour > 12 ))
152+ throw new InvalidDateTimeException (DHUR04 ,
153+ concat ("Invalid " , type , " hour AM/PM out of range" ));
154+ if (hour < 0 || hour > 23 )
155+ throw new InvalidDateTimeException (DHUR06 ,
156+ concat ("Invalid " , type , " hour out of range" ));
157+ this .hour = checkField (this .hour , hour );
158+ }
159+
160+ public void setMinute (int minute ) {
161+ if (minute < 0 || minute > 59 ) throw new InvalidDateTimeException (DMIN03 ,
162+ concat ("Invalid " , type , " minute out of range" ));
163+ this .minute = checkField (this .minute , minute );
164+ }
165+
166+ public void setSecond (int second ) {
167+ if (second < 0 || second > 59 ) throw new InvalidDateTimeException (DSEC03 ,
168+ concat ("Invalid " , type , " second out of range" ));
169+ this .second = checkField (this .second , second );
170+ }
171+
172+ public void setFraction (int fraction ) {
173+ this .fraction = checkField (this .fraction , fraction );
174+ }
175+
176+ public void setUtcOffset (int hour , int minute ) {
177+ if (hour < -12 || hour > 12 ) throw new InvalidDateTimeException (DUTC04 ,
178+ concat ("Invalid " , type , " UTC offset hour out of range" ));
179+ if (minute < 0 || minute > 59 ) throw new InvalidDateTimeException (DUTC05 ,
180+ concat ("Invalid " , type , " UTC offset minute out of range" ));
181+ utcOffsetHour = checkField (utcOffsetHour , hour );
182+ utcOffsetMinute = checkField (utcOffsetMinute , minute );
183+ }
184+
185+ private int checkField (int current , int newValue ) {
186+ if (current != UNSET && current != newValue )
187+ throw new InvalidDateTimeException (DCNF01 ,
188+ concat ("Conflicting " , type , " segments input" ));
189+ return newValue ;
190+ }
191+
192+ private static boolean isAllSet (int ... values ) {
193+ return Arrays .stream (values ).allMatch (v -> v != UNSET );
194+ }
195+
196+ public void validate () {
197+ try {
198+ LocalDate date ;
199+ if (isAllSet (year , month , day )) {
200+ DAYS_IN_MONTH [2 ] = isLeapYear (year )? 29 : 28 ;
201+ if (day < 1 || day > DAYS_IN_MONTH [month ])
202+ throw new InvalidDateTimeException (DDAY03 ,
203+ concat ("Invalid " , type , " day out of range" ));
204+ date = LocalDate .of (year , month , day );
205+ if (weekday != UNSET && date .getDayOfWeek ().getValue () != weekday )
206+ throw new InvalidDateTimeException (DWKD03 , concat ("Invalid " ,
207+ type , " weekday input" ));
208+ }
209+ if (isAllSet (year , month )) date = LocalDate .of (year , month , 1 );
210+ if (isAllSet (year )) date = LocalDate .of (year , 1 , 1 );
211+ } catch (InvalidDateTimeException e ) {
212+ throw e ;
213+ } catch (Exception e ) {
214+ throw new InvalidDateTimeException (DINV01 ,
215+ concat ("Invalid " , type , " year, month or day out of range" , e ));
216+ }
217+ if (isAllSet (hour , amPm )) convertTo24Hour ();
218+ if (hour != UNSET && (hour < 0 || hour > 23 ))
219+ throw new InvalidDateTimeException (DHUR05 , concat ("Invalid " ,
220+ type , " hour out of range" ));
221+ }
222+
223+ private void convertTo24Hour () {
224+ if (amPm == 2 && hour != 12 ) hour += 12 ;
225+ else if (amPm == 1 && hour == 12 ) hour = 0 ;
226+ }
227+
228+ private static boolean isLeapYear (int year ) {
229+ return (year % 4 == 0 && year % 100 != 0 ) || (year % 400 == 0 );
230+ }
231+
232+ private static int toFourDigitYear (int year ) {
233+ var century = LocalDate .now ().getYear () / 100 * 100 ;
234+ return year < PIVOT_YEAR ? century + year : century - 100 + year ;
235+ }
236+
237+ @ Override
238+ public String toString () {
239+ var builder = new StringBuilder ("{" );
240+ if (era != UNSET ) append (builder , "Era: " , era );
241+ if (year != UNSET ) append (builder , "Year: " , year );
242+ if (month != UNSET ) append (builder , "Month: " , month );
243+ if (weekday != UNSET ) append (builder , "Weekday: " , weekday );
244+ if (day != UNSET ) append (builder , "Day: " , day );
245+ if (amPm != UNSET ) append (builder , "AM/PM: " , amPm );
246+ if (hour != UNSET ) append (builder , "Hour: " , hour );
247+ if (minute != UNSET ) append (builder , "Minute: " , minute );
248+ if (second != UNSET ) append (builder , "Second: " , second );
249+ if (fraction != UNSET ) append (builder , "Fraction: " , fraction );
250+ if (utcOffsetHour != UNSET ) append (builder , "UTC Offset Hour: " , utcOffsetHour );
251+ if (utcOffsetMinute != UNSET ) append (builder , "UTC Offset Minute: " , utcOffsetMinute );
252+ return removeEnd (builder .toString (), ", " ) + "}" ;
253+ }
254+
255+ private void append (StringBuilder builder , String label , int value ) {
256+ builder .append (label ).append (value ).append (", " );
257+ }
258+ }
0 commit comments