1#include <prim/seadStringUtil.h>
2#include <time/seadCalendarSpan.h>
3#include <time/seadDateUtil.h>
4
5namespace sead
6{
7namespace DateUtil
8{
9bool isLeapYear(u32 year)
10{
11#ifdef MATCHING_HACK_NX_CLANG
12 bool div100, div4;
13 return (div100 = year % 100 == 0, div4 = year % 4 == 0, !div100 & div4) | (year % 400 == 0);
14#else
15 return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
16#endif
17}
18
19CalendarTime::Week calcWeekDay(const CalendarTime::Year& year, const CalendarTime::Month& month,
20 const CalendarTime::Day& day)
21{
22 int y = year.getValue();
23 int m = month.getValueOneOrigin();
24 int d = day.getValue();
25 if (m < 3)
26 {
27 y -= 1;
28 m += 12;
29 }
30 d += y + (y / 4) - (y / 100) + (y / 400);
31 d += (26 * m + 16) / 10;
32 return CalendarTime::Week(d % 7);
33}
34
35void calcSecondToCalendarSpan(CalendarSpan* out_span, u64 sec)
36{
37 if (!out_span)
38 return;
39 out_span->setDays(sec / (3600 * 24));
40 out_span->setHours((sec % (3600 * 24)) / 3600);
41 out_span->setMinutes((sec % 3600) / 60);
42 out_span->setSeconds(sec % 60);
43}
44
45bool parseW3CDTFSubString(bool* ok, u32* value, SafeString* str, s32* str_length,
46 char* out_separator, s32 parse_length, const SafeString& separators,
47 bool allow_null_separator, u32 value_min, u32 value_max)
48{
49 if (*str_length < parse_length)
50 {
51 *ok = false;
52 return true;
53 }
54
55 const char c = str->at(idx: parse_length);
56 if (!separators.include(c) && (!allow_null_separator || c != SafeString::cNullChar))
57 {
58 *ok = false;
59 return true;
60 }
61
62 FixedSafeString<8> buffer;
63 buffer.copy(src: *str, copyLength: parse_length);
64
65 if (!StringUtil::tryParseU32(out: value, str: buffer, base: StringUtil::CardinalNumber::Base10) ||
66 *value < value_min || *value > value_max)
67 {
68 *ok = false;
69 return true;
70 }
71
72 if (c == SafeString::cNullChar)
73 {
74 *ok = true;
75 return true;
76 }
77
78 *str = str->getPart(at: parse_length + 1);
79 *str_length -= parse_length + 1;
80 if (out_separator)
81 *out_separator = c;
82 return false;
83}
84
85static bool parseW3CDTFStringImpl(u32* year, u32* month, u32* day, u32* hour, u32* minute,
86 u32* second, s32* tz_hour, s32* tz_minute,
87 const SafeString& string)
88{
89 s32 len = string.calcLength();
90 bool ok = true;
91 SafeString substr = string;
92 char separator;
93
94 if (parseW3CDTFSubString(ok: &ok, value: year, str: &substr, str_length: &len, out_separator: &separator, parse_length: 4, separators: "-", allow_null_separator: true, value_min: 0, value_max: 0xFFFFFFFF))
95 return ok;
96 if (parseW3CDTFSubString(ok: &ok, value: month, str: &substr, str_length: &len, out_separator: &separator, parse_length: 2, separators: "-", allow_null_separator: true, value_min: 1, value_max: 12))
97 return ok;
98 if (parseW3CDTFSubString(ok: &ok, value: day, str: &substr, str_length: &len, out_separator: &separator, parse_length: 2, separators: "T", allow_null_separator: true, value_min: 1, value_max: 31))
99 return ok;
100 if (parseW3CDTFSubString(ok: &ok, value: hour, str: &substr, str_length: &len, out_separator: &separator, parse_length: 2, separators: ":", allow_null_separator: false, value_min: 0, value_max: 23))
101 return ok;
102 if (parseW3CDTFSubString(ok: &ok, value: minute, str: &substr, str_length: &len, out_separator: &separator, parse_length: 2, separators: ":+-Z", allow_null_separator: true, value_min: 0, value_max: 59))
103 return ok;
104
105 if (separator == ':')
106 {
107 if (parseW3CDTFSubString(ok: &ok, value: second, str: &substr, str_length: &len, out_separator: &separator, parse_length: 2, separators: ".+-Z", allow_null_separator: true, value_min: 0, value_max: 59))
108 return ok;
109
110 if (separator == '.')
111 {
112 if (len == 0)
113 return false;
114
115 auto it = substr.tokenBegin(delimiter: "+-Z");
116 ++it;
117 auto end = substr.tokenEnd(delimiter: "+-Z");
118
119 // No timezone information
120 if (it == end)
121 {
122 *tz_hour = 0;
123 *tz_minute = 0;
124 return true;
125 }
126
127 separator = substr.at(idx: it.getIndex() - 1);
128 substr = substr.getPart(at: it.getIndex());
129 len -= it.getIndex();
130 }
131 }
132
133 if (separator != '+' && separator != '-')
134 checkLength:
135 return len == 0;
136
137 bool done;
138 u32 tz_hour_abs = 0;
139 done = parseW3CDTFSubString(ok: &ok, value: &tz_hour_abs, str: &substr, str_length: &len, out_separator: nullptr, parse_length: 2, separators: ":", allow_null_separator: false, value_min: 0, value_max: 11);
140 if (ok)
141 {
142 *tz_hour = tz_hour_abs;
143 if (separator == '-')
144 *tz_hour = -tz_hour_abs;
145 }
146 if (done)
147 return ok;
148
149 u32 tz_minute_abs = 0;
150 done = parseW3CDTFSubString(ok: &ok, value: &tz_minute_abs, str: &substr, str_length: &len, out_separator: nullptr, parse_length: 2, separators: "", allow_null_separator: true, value_min: 0, value_max: 59);
151 if (ok)
152 {
153 *tz_minute = tz_minute_abs;
154 if (separator == '-')
155 *tz_minute = -tz_minute_abs;
156
157 if (done)
158 {
159 len -= 2;
160 goto checkLength;
161 }
162 }
163
164 return false;
165}
166
167bool parseW3CDTFString(CalendarTime* out_time, CalendarSpan* time_zone, const SafeString& string)
168{
169 u32 year = 1970;
170 u32 month = 1;
171 u32 day = 1;
172 u32 hour = 0;
173 u32 minute = 0;
174 u32 second = 0;
175
176 s32 tz_hour = 0;
177 s32 tz_minute = 0;
178
179 const bool ret = parseW3CDTFStringImpl(year: &year, month: &month, day: &day, hour: &hour, minute: &minute, second: &second, tz_hour: &tz_hour,
180 tz_minute: &tz_minute, string);
181
182 if (ret)
183 {
184 out_time->setDate({year, CalendarTime::Month::makeFromValueOneOrigin(month), day});
185 out_time->setTime({hour, minute, second});
186
187 time_zone->setDays(0);
188 time_zone->setHours(tz_hour);
189 time_zone->setMinutes(tz_minute);
190 time_zone->setSeconds(0);
191 }
192
193 return ret;
194}
195} // namespace DateUtil
196} // namespace sead
197