1 // Copyright 2019 yuzu emulator team
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4
5 #include <climits>
6
7 #include "common/assert.h"
8 #include "common/logging/log.h"
9 #include "core/file_sys/content_archive.h"
10 #include "core/file_sys/nca_metadata.h"
11 #include "core/file_sys/registered_cache.h"
12 #include "core/file_sys/romfs.h"
13 #include "core/file_sys/system_archive/system_archive.h"
14 #include "core/hle/service/time/time_zone_manager.h"
15
16 namespace Service::Time::TimeZone {
17
18 static constexpr s32 epoch_year{1970};
19 static constexpr s32 year_base{1900};
20 static constexpr s32 epoch_week_day{4};
21 static constexpr s32 seconds_per_minute{60};
22 static constexpr s32 minutes_per_hour{60};
23 static constexpr s32 hours_per_day{24};
24 static constexpr s32 days_per_week{7};
25 static constexpr s32 days_per_normal_year{365};
26 static constexpr s32 days_per_leap_year{366};
27 static constexpr s32 months_per_year{12};
28 static constexpr s32 seconds_per_hour{seconds_per_minute * minutes_per_hour};
29 static constexpr s32 seconds_per_day{seconds_per_hour * hours_per_day};
30 static constexpr s32 years_per_repeat{400};
31 static constexpr s64 average_seconds_per_year{31556952};
32 static constexpr s64 seconds_per_repeat{years_per_repeat * average_seconds_per_year};
33
34 struct Rule {
35 enum class Type : u32 { JulianDay, DayOfYear, MonthNthDayOfWeek };
36 Type rule_type{};
37 s32 day{};
38 s32 week{};
39 s32 month{};
40 s32 transition_time{};
41 };
42
43 struct CalendarTimeInternal {
44 s64 year{};
45 s8 month{};
46 s8 day{};
47 s8 hour{};
48 s8 minute{};
49 s8 second{};
CompareService::Time::TimeZone::CalendarTimeInternal50 int Compare(const CalendarTimeInternal& other) const {
51 if (year != other.year) {
52 if (year < other.year) {
53 return -1;
54 }
55 return 1;
56 }
57 if (month != other.month) {
58 return month - other.month;
59 }
60 if (day != other.day) {
61 return day - other.day;
62 }
63 if (hour != other.hour) {
64 return hour - other.hour;
65 }
66 if (minute != other.minute) {
67 return minute - other.minute;
68 }
69 if (second != other.second) {
70 return second - other.second;
71 }
72 return {};
73 }
74 };
75
76 template <typename TResult, typename TOperand>
SafeAdd(TResult & result,TOperand op)77 static bool SafeAdd(TResult& result, TOperand op) {
78 result = result + op;
79 return true;
80 }
81
82 template <typename TResult, typename TUnit, typename TBase>
SafeNormalize(TResult & result,TUnit & unit,TBase base)83 static bool SafeNormalize(TResult& result, TUnit& unit, TBase base) {
84 TUnit delta{};
85 if (unit >= 0) {
86 delta = unit / base;
87 } else {
88 delta = -1 - (-1 - unit) / base;
89 }
90 unit -= delta * base;
91 return SafeAdd(result, delta);
92 }
93
94 template <typename T>
IsLeapYear(T year)95 static constexpr bool IsLeapYear(T year) {
96 return ((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0);
97 }
98
99 template <typename T>
GetYearLengthInDays(T year)100 static constexpr T GetYearLengthInDays(T year) {
101 return IsLeapYear(year) ? days_per_leap_year : days_per_normal_year;
102 }
103
GetLeapDaysFromYearPositive(s64 year)104 static constexpr s64 GetLeapDaysFromYearPositive(s64 year) {
105 return year / 4 - year / 100 + year / years_per_repeat;
106 }
107
GetLeapDaysFromYear(s64 year)108 static constexpr s64 GetLeapDaysFromYear(s64 year) {
109 if (year < 0) {
110 return -1 - GetLeapDaysFromYearPositive(-1 - year);
111 } else {
112 return GetLeapDaysFromYearPositive(year);
113 }
114 }
115
GetMonthLength(bool is_leap_year,int month)116 static constexpr int GetMonthLength(bool is_leap_year, int month) {
117 constexpr std::array<int, 12> month_lengths{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
118 constexpr std::array<int, 12> month_lengths_leap{31, 29, 31, 30, 31, 30,
119 31, 31, 30, 31, 30, 31};
120 return is_leap_year ? month_lengths_leap[month] : month_lengths[month];
121 }
122
IsDigit(char value)123 static constexpr bool IsDigit(char value) {
124 return value >= '0' && value <= '9';
125 }
126
GetQZName(const char * name,int offset,char delimiter)127 static constexpr int GetQZName(const char* name, int offset, char delimiter) {
128 while (name[offset] != '\0' && name[offset] != delimiter) {
129 offset++;
130 }
131 return offset;
132 }
133
GetTZName(const char * name,int offset)134 static constexpr int GetTZName(const char* name, int offset) {
135 for (char value{name[offset]};
136 value != '\0' && !IsDigit(value) && value != ',' && value != '-' && value != '+';
137 offset++) {
138 value = name[offset];
139 }
140 return offset;
141 }
142
GetInteger(const char * name,int & offset,int & value,int min,int max)143 static constexpr bool GetInteger(const char* name, int& offset, int& value, int min, int max) {
144 value = 0;
145 char temp{name[offset]};
146 if (!IsDigit(temp)) {
147 return {};
148 }
149 do {
150 value = value * 10 + (temp - '0');
151 if (value > max) {
152 return {};
153 }
154 temp = name[offset];
155 } while (IsDigit(temp));
156
157 return value >= min;
158 }
159
GetSeconds(const char * name,int & offset,int & seconds)160 static constexpr bool GetSeconds(const char* name, int& offset, int& seconds) {
161 seconds = 0;
162 int value{};
163 if (!GetInteger(name, offset, value, 0, hours_per_day * days_per_week - 1)) {
164 return {};
165 }
166 seconds = value * seconds_per_hour;
167
168 if (name[offset] == ':') {
169 offset++;
170 if (!GetInteger(name, offset, value, 0, minutes_per_hour - 1)) {
171 return {};
172 }
173 seconds += value * seconds_per_minute;
174 if (name[offset] == ':') {
175 offset++;
176 if (!GetInteger(name, offset, value, 0, seconds_per_minute)) {
177 return {};
178 }
179 seconds += value;
180 }
181 }
182 return true;
183 }
184
GetOffset(const char * name,int & offset,int & value)185 static constexpr bool GetOffset(const char* name, int& offset, int& value) {
186 bool is_negative{};
187 if (name[offset] == '-') {
188 is_negative = true;
189 offset++;
190 } else if (name[offset] == '+') {
191 offset++;
192 }
193 if (!GetSeconds(name, offset, value)) {
194 return {};
195 }
196 if (is_negative) {
197 value = -value;
198 }
199 return true;
200 }
201
GetRule(const char * name,int & position,Rule & rule)202 static constexpr bool GetRule(const char* name, int& position, Rule& rule) {
203 bool is_valid{};
204 if (name[position] == 'J') {
205 position++;
206 rule.rule_type = Rule::Type::JulianDay;
207 is_valid = GetInteger(name, position, rule.day, 1, days_per_normal_year);
208 } else if (name[position] == 'M') {
209 position++;
210 rule.rule_type = Rule::Type::MonthNthDayOfWeek;
211 is_valid = GetInteger(name, position, rule.month, 1, months_per_year);
212 if (!is_valid) {
213 return {};
214 }
215 if (name[position++] != '.') {
216 return {};
217 }
218 is_valid = GetInteger(name, position, rule.week, 1, 5);
219 if (!is_valid) {
220 return {};
221 }
222 if (name[position++] != '.') {
223 return {};
224 }
225 is_valid = GetInteger(name, position, rule.day, 0, days_per_week - 1);
226 } else if (isdigit(name[position])) {
227 rule.rule_type = Rule::Type::DayOfYear;
228 is_valid = GetInteger(name, position, rule.day, 0, days_per_leap_year - 1);
229 } else {
230 return {};
231 }
232 if (!is_valid) {
233 return {};
234 }
235 if (name[position] == '/') {
236 position++;
237 return GetOffset(name, position, rule.transition_time);
238 } else {
239 rule.transition_time = 2 * seconds_per_hour;
240 }
241 return true;
242 }
243
TransitionTime(int year,Rule rule,int offset)244 static constexpr int TransitionTime(int year, Rule rule, int offset) {
245 int value{};
246 switch (rule.rule_type) {
247 case Rule::Type::JulianDay:
248 value = (rule.day - 1) * seconds_per_day;
249 if (IsLeapYear(year) && rule.day >= 60) {
250 value += seconds_per_day;
251 }
252 break;
253 case Rule::Type::DayOfYear:
254 value = rule.day * seconds_per_day;
255 break;
256 case Rule::Type::MonthNthDayOfWeek: {
257 // Use Zeller's Congruence (https://en.wikipedia.org/wiki/Zeller%27s_congruence) to
258 // calculate the day of the week for any Julian or Gregorian calendar date.
259 const int m1{(rule.month + 9) % 12 + 1};
260 const int yy0{(rule.month <= 2) ? (year - 1) : year};
261 const int yy1{yy0 / 100};
262 const int yy2{yy0 % 100};
263 int day_of_week{((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7};
264
265 if (day_of_week < 0) {
266 day_of_week += days_per_week;
267 }
268 int day{rule.day - day_of_week};
269 if (day < 0) {
270 day += days_per_week;
271 }
272 for (int i{1}; i < rule.week; i++) {
273 if (day + days_per_week >= GetMonthLength(IsLeapYear(year), rule.month - 1)) {
274 break;
275 }
276 day += days_per_week;
277 }
278
279 value = day * seconds_per_day;
280 for (int index{}; index < rule.month - 1; ++index) {
281 value += GetMonthLength(IsLeapYear(year), index) * seconds_per_day;
282 }
283 break;
284 }
285 default:
286 UNREACHABLE();
287 }
288 return value + rule.transition_time + offset;
289 }
290
ParsePosixName(const char * name,TimeZoneRule & rule)291 static bool ParsePosixName(const char* name, TimeZoneRule& rule) {
292 constexpr char default_rule[]{",M4.1.0,M10.5.0"};
293 const char* std_name{name};
294 int std_len{};
295 int offset{};
296 int std_offset{};
297
298 if (name[offset] == '<') {
299 offset++;
300 std_name = name + offset;
301 const int std_name_offset{offset};
302 offset = GetQZName(name, offset, '>');
303 if (name[offset] != '>') {
304 return {};
305 }
306 std_len = offset - std_name_offset;
307 offset++;
308 } else {
309 offset = GetTZName(name, offset);
310 std_len = offset;
311 }
312 if (std_len == 0) {
313 return {};
314 }
315 if (!GetOffset(name, offset, std_offset)) {
316 return {};
317 }
318
319 int char_count{std_len + 1};
320 int dest_len{};
321 int dest_offset{};
322 const char* dest_name{name + offset};
323 if (rule.chars.size() < std::size_t(char_count)) {
324 return {};
325 }
326
327 if (name[offset] != '\0') {
328 if (name[offset] == '<') {
329 dest_name = name + (++offset);
330 const int dest_name_offset{offset};
331 offset = GetQZName(name, offset, '>');
332 if (name[offset] != '>') {
333 return {};
334 }
335 dest_len = offset - dest_name_offset;
336 offset++;
337 } else {
338 dest_name = name + (offset);
339 offset = GetTZName(name, offset);
340 dest_len = offset;
341 }
342 if (dest_len == 0) {
343 return {};
344 }
345 char_count += dest_len + 1;
346 if (rule.chars.size() < std::size_t(char_count)) {
347 return {};
348 }
349 if (name[offset] != '\0' && name[offset] != ',' && name[offset] != ';') {
350 if (!GetOffset(name, offset, dest_offset)) {
351 return {};
352 }
353 } else {
354 dest_offset = std_offset - seconds_per_hour;
355 }
356 if (name[offset] == '\0') {
357 name = default_rule;
358 offset = 0;
359 }
360 if (name[offset] == ',' || name[offset] == ';') {
361 offset++;
362
363 Rule start{};
364 if (!GetRule(name, offset, start)) {
365 return {};
366 }
367 if (name[offset++] != ',') {
368 return {};
369 }
370
371 Rule end{};
372 if (!GetRule(name, offset, end)) {
373 return {};
374 }
375 if (name[offset] != '\0') {
376 return {};
377 }
378
379 rule.type_count = 2;
380 rule.ttis[0].gmt_offset = -dest_offset;
381 rule.ttis[0].is_dst = true;
382 rule.ttis[0].abbreviation_list_index = std_len + 1;
383 rule.ttis[1].gmt_offset = -std_offset;
384 rule.ttis[1].is_dst = false;
385 rule.ttis[1].abbreviation_list_index = 0;
386 rule.default_type = 0;
387
388 s64 jan_first{};
389 int time_count{};
390 int jan_offset{};
391 int year_beginning{epoch_year};
392 do {
393 const int year_seconds{GetYearLengthInDays(year_beginning - 1) * seconds_per_day};
394 year_beginning--;
395 if (!SafeAdd(jan_first, -year_seconds)) {
396 jan_offset = -year_seconds;
397 break;
398 }
399 } while (epoch_year - years_per_repeat / 2 < year_beginning);
400
401 int year_limit{year_beginning + years_per_repeat + 1};
402 int year{};
403 for (year = year_beginning; year < year_limit; year++) {
404 int start_time{TransitionTime(year, start, std_offset)};
405 int end_time{TransitionTime(year, end, dest_offset)};
406 const int year_seconds{GetYearLengthInDays(year) * seconds_per_day};
407 const bool is_reversed{end_time < start_time};
408 if (is_reversed) {
409 int swap{start_time};
410 start_time = end_time;
411 end_time = swap;
412 }
413
414 if (is_reversed ||
415 (start_time < end_time &&
416 (end_time - start_time < (year_seconds + (std_offset - dest_offset))))) {
417 if (rule.ats.size() - 2 < std::size_t(time_count)) {
418 break;
419 }
420
421 rule.ats[time_count] = jan_first;
422 if (SafeAdd(rule.ats[time_count], jan_offset + start_time)) {
423 rule.types[time_count++] = is_reversed ? 1 : 0;
424 } else if (jan_offset != 0) {
425 rule.default_type = is_reversed ? 1 : 0;
426 }
427
428 rule.ats[time_count] = jan_first;
429 if (SafeAdd(rule.ats[time_count], jan_offset + end_time)) {
430 rule.types[time_count++] = is_reversed ? 0 : 1;
431 year_limit = year + years_per_repeat + 1;
432 } else if (jan_offset != 0) {
433 rule.default_type = is_reversed ? 0 : 1;
434 }
435 }
436 if (!SafeAdd(jan_first, jan_offset + year_seconds)) {
437 break;
438 }
439 jan_offset = 0;
440 }
441 rule.time_count = time_count;
442 if (time_count == 0) {
443 rule.type_count = 1;
444 } else if (years_per_repeat < year - year_beginning) {
445 rule.go_back = true;
446 rule.go_ahead = true;
447 }
448 } else {
449 if (name[offset] == '\0') {
450 return {};
451 }
452
453 s64 their_std_offset{};
454 for (int index{}; index < rule.time_count; ++index) {
455 const s8 type{rule.types[index]};
456 if (rule.ttis[type].is_standard_time_daylight) {
457 their_std_offset = -rule.ttis[type].gmt_offset;
458 }
459 }
460
461 s64 their_offset{their_std_offset};
462 for (int index{}; index < rule.time_count; ++index) {
463 const s8 type{rule.types[index]};
464 rule.types[index] = rule.ttis[type].is_dst ? 1 : 0;
465 if (!rule.ttis[type].is_gmt) {
466 if (!rule.ttis[type].is_standard_time_daylight) {
467 rule.ats[index] += dest_offset - their_std_offset;
468 } else {
469 rule.ats[index] += std_offset - their_std_offset;
470 }
471 }
472 their_offset = -rule.ttis[type].gmt_offset;
473 if (!rule.ttis[type].is_dst) {
474 their_std_offset = their_offset;
475 }
476 }
477 rule.ttis[0].gmt_offset = -std_offset;
478 rule.ttis[0].is_dst = false;
479 rule.ttis[0].abbreviation_list_index = 0;
480 rule.ttis[1].gmt_offset = -dest_offset;
481 rule.ttis[1].is_dst = true;
482 rule.ttis[1].abbreviation_list_index = std_len + 1;
483 rule.type_count = 2;
484 rule.default_type = 0;
485 }
486 } else {
487 // Default is standard time
488 rule.type_count = 1;
489 rule.time_count = 0;
490 rule.default_type = 0;
491 rule.ttis[0].gmt_offset = -std_offset;
492 rule.ttis[0].is_dst = false;
493 rule.ttis[0].abbreviation_list_index = 0;
494 }
495
496 rule.char_count = char_count;
497 for (int index{}; index < std_len; ++index) {
498 rule.chars[index] = std_name[index];
499 }
500
501 rule.chars[std_len++] = '\0';
502 if (dest_len != 0) {
503 for (int index{}; index < dest_len; ++index) {
504 rule.chars[std_len + index] = dest_name[index];
505 }
506 rule.chars[std_len + dest_len] = '\0';
507 }
508
509 return true;
510 }
511
ParseTimeZoneBinary(TimeZoneRule & time_zone_rule,FileSys::VirtualFile & vfs_file)512 static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFile& vfs_file) {
513 TzifHeader header{};
514 if (vfs_file->ReadObject<TzifHeader>(&header) != sizeof(TzifHeader)) {
515 return {};
516 }
517
518 constexpr s32 time_zone_max_leaps{50};
519 constexpr s32 time_zone_max_chars{50};
520 if (!(0 <= header.leap_count && header.leap_count < time_zone_max_leaps &&
521 0 < header.type_count && header.type_count < s32(time_zone_rule.ttis.size()) &&
522 0 <= header.time_count && header.time_count < s32(time_zone_rule.ats.size()) &&
523 0 <= header.char_count && header.char_count < time_zone_max_chars &&
524 (header.ttis_std_count == header.type_count || header.ttis_std_count == 0) &&
525 (header.ttis_gmt_count == header.type_count || header.ttis_gmt_count == 0))) {
526 return {};
527 }
528 time_zone_rule.time_count = header.time_count;
529 time_zone_rule.type_count = header.type_count;
530 time_zone_rule.char_count = header.char_count;
531
532 int time_count{};
533 u64 read_offset = sizeof(TzifHeader);
534 for (int index{}; index < time_zone_rule.time_count; ++index) {
535 s64_be at{};
536 vfs_file->ReadObject<s64_be>(&at, read_offset);
537 time_zone_rule.types[index] = 1;
538 if (time_count != 0 && at <= time_zone_rule.ats[time_count - 1]) {
539 if (at < time_zone_rule.ats[time_count - 1]) {
540 return {};
541 }
542 time_zone_rule.types[index - 1] = 0;
543 time_count--;
544 }
545 time_zone_rule.ats[time_count++] = at;
546 read_offset += sizeof(s64_be);
547 }
548 time_count = 0;
549 for (int index{}; index < time_zone_rule.time_count; ++index) {
550 const u8 type{*vfs_file->ReadByte(read_offset)};
551 read_offset += sizeof(u8);
552 if (time_zone_rule.time_count <= type) {
553 return {};
554 }
555 if (time_zone_rule.types[index] != 0) {
556 time_zone_rule.types[time_count++] = type;
557 }
558 }
559 time_zone_rule.time_count = time_count;
560 for (int index{}; index < time_zone_rule.type_count; ++index) {
561 TimeTypeInfo& ttis{time_zone_rule.ttis[index]};
562 u32_be gmt_offset{};
563 vfs_file->ReadObject<u32_be>(&gmt_offset, read_offset);
564 read_offset += sizeof(u32_be);
565 ttis.gmt_offset = gmt_offset;
566
567 const u8 dst{*vfs_file->ReadByte(read_offset)};
568 read_offset += sizeof(u8);
569 if (dst >= 2) {
570 return {};
571 }
572 ttis.is_dst = dst != 0;
573
574 const s32 abbreviation_list_index{*vfs_file->ReadByte(read_offset)};
575 read_offset += sizeof(u8);
576 if (abbreviation_list_index >= time_zone_rule.char_count) {
577 return {};
578 }
579 ttis.abbreviation_list_index = abbreviation_list_index;
580 }
581
582 vfs_file->ReadArray(time_zone_rule.chars.data(), time_zone_rule.char_count, read_offset);
583 time_zone_rule.chars[time_zone_rule.char_count] = '\0';
584 read_offset += time_zone_rule.char_count;
585 for (int index{}; index < time_zone_rule.type_count; ++index) {
586 if (header.ttis_std_count == 0) {
587 time_zone_rule.ttis[index].is_standard_time_daylight = false;
588 } else {
589 const u8 dst{*vfs_file->ReadByte(read_offset)};
590 read_offset += sizeof(u8);
591 if (dst >= 2) {
592 return {};
593 }
594 time_zone_rule.ttis[index].is_standard_time_daylight = dst != 0;
595 }
596 }
597
598 for (int index{}; index < time_zone_rule.type_count; ++index) {
599 if (header.ttis_std_count == 0) {
600 time_zone_rule.ttis[index].is_gmt = false;
601 } else {
602 const u8 dst{*vfs_file->ReadByte(read_offset)};
603 read_offset += sizeof(u8);
604 if (dst >= 2) {
605 return {};
606 }
607 time_zone_rule.ttis[index].is_gmt = dst != 0;
608 }
609 }
610
611 const u64 position{(read_offset - sizeof(TzifHeader))};
612 const s64 bytes_read = s64(vfs_file->GetSize() - sizeof(TzifHeader) - position);
613 if (bytes_read < 0) {
614 return {};
615 }
616 constexpr s32 time_zone_name_max{255};
617 if (bytes_read > (time_zone_name_max + 1)) {
618 return {};
619 }
620
621 std::array<char, time_zone_name_max + 1> temp_name{};
622 vfs_file->ReadArray(temp_name.data(), bytes_read, read_offset);
623 if (bytes_read > 2 && temp_name[0] == '\n' && temp_name[bytes_read - 1] == '\n' &&
624 std::size_t(time_zone_rule.type_count) + 2 <= time_zone_rule.ttis.size()) {
625 temp_name[bytes_read - 1] = '\0';
626
627 std::array<char, time_zone_name_max> name{};
628 std::memcpy(name.data(), temp_name.data() + 1, std::size_t(bytes_read - 1));
629
630 TimeZoneRule temp_rule;
631 if (ParsePosixName(name.data(), temp_rule)) {
632 UNIMPLEMENTED();
633 }
634 }
635 if (time_zone_rule.type_count == 0) {
636 return {};
637 }
638 if (time_zone_rule.time_count > 1) {
639 UNIMPLEMENTED();
640 }
641
642 s32 default_type{};
643
644 for (default_type = 0; default_type < time_zone_rule.time_count; default_type++) {
645 if (time_zone_rule.types[default_type] == 0) {
646 break;
647 }
648 }
649
650 default_type = default_type < time_zone_rule.time_count ? -1 : 0;
651 if (default_type < 0 && time_zone_rule.time_count > 0 &&
652 time_zone_rule.ttis[time_zone_rule.types[0]].is_dst) {
653 default_type = time_zone_rule.types[0];
654 while (--default_type >= 0) {
655 if (!time_zone_rule.ttis[default_type].is_dst) {
656 break;
657 }
658 }
659 }
660 if (default_type < 0) {
661 default_type = 0;
662 while (time_zone_rule.ttis[default_type].is_dst) {
663 if (++default_type >= time_zone_rule.type_count) {
664 default_type = 0;
665 break;
666 }
667 }
668 }
669 time_zone_rule.default_type = default_type;
670 return true;
671 }
672
CreateCalendarTime(s64 time,int gmt_offset,CalendarTimeInternal & calendar_time,CalendarAdditionalInfo & calendar_additional_info)673 static ResultCode CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInternal& calendar_time,
674 CalendarAdditionalInfo& calendar_additional_info) {
675 s64 year{epoch_year};
676 s64 time_days{time / seconds_per_day};
677 s64 remaining_seconds{time % seconds_per_day};
678 while (time_days < 0 || time_days >= GetYearLengthInDays(year)) {
679 s64 delta = time_days / days_per_leap_year;
680 if (!delta) {
681 delta = time_days < 0 ? -1 : 1;
682 }
683 s64 new_year{year};
684 if (!SafeAdd(new_year, delta)) {
685 return ERROR_OUT_OF_RANGE;
686 }
687 time_days -= (new_year - year) * days_per_normal_year;
688 time_days -= GetLeapDaysFromYear(new_year - 1) - GetLeapDaysFromYear(year - 1);
689 year = new_year;
690 }
691
692 s64 day_of_year{time_days};
693 remaining_seconds += gmt_offset;
694 while (remaining_seconds < 0) {
695 remaining_seconds += seconds_per_day;
696 day_of_year--;
697 }
698
699 while (remaining_seconds >= seconds_per_day) {
700 remaining_seconds -= seconds_per_day;
701 day_of_year++;
702 }
703
704 while (day_of_year < 0) {
705 if (!SafeAdd(year, -1)) {
706 return ERROR_OUT_OF_RANGE;
707 }
708 day_of_year += GetYearLengthInDays(year);
709 }
710
711 while (day_of_year >= GetYearLengthInDays(year)) {
712 day_of_year -= GetYearLengthInDays(year);
713 if (!SafeAdd(year, 1)) {
714 return ERROR_OUT_OF_RANGE;
715 }
716 }
717
718 calendar_time.year = year;
719 calendar_additional_info.day_of_year = static_cast<u32>(day_of_year);
720 s64 day_of_week{
721 (epoch_week_day +
722 ((year - epoch_year) % days_per_week) * (days_per_normal_year % days_per_week) +
723 GetLeapDaysFromYear(year - 1) - GetLeapDaysFromYear(epoch_year - 1) + day_of_year) %
724 days_per_week};
725 if (day_of_week < 0) {
726 day_of_week += days_per_week;
727 }
728
729 calendar_additional_info.day_of_week = static_cast<u32>(day_of_week);
730 calendar_time.hour = static_cast<s8>((remaining_seconds / seconds_per_hour) % seconds_per_hour);
731 remaining_seconds %= seconds_per_hour;
732 calendar_time.minute = static_cast<s8>(remaining_seconds / seconds_per_minute);
733 calendar_time.second = static_cast<s8>(remaining_seconds % seconds_per_minute);
734
735 for (calendar_time.month = 0;
736 day_of_year >= GetMonthLength(IsLeapYear(year), calendar_time.month);
737 ++calendar_time.month) {
738 day_of_year -= GetMonthLength(IsLeapYear(year), calendar_time.month);
739 }
740
741 calendar_time.day = static_cast<s8>(day_of_year + 1);
742 calendar_additional_info.is_dst = false;
743 calendar_additional_info.gmt_offset = gmt_offset;
744
745 return RESULT_SUCCESS;
746 }
747
ToCalendarTimeInternal(const TimeZoneRule & rules,s64 time,CalendarTimeInternal & calendar_time,CalendarAdditionalInfo & calendar_additional_info)748 static ResultCode ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time,
749 CalendarTimeInternal& calendar_time,
750 CalendarAdditionalInfo& calendar_additional_info) {
751 if ((rules.go_ahead && time < rules.ats[0]) ||
752 (rules.go_back && time > rules.ats[rules.time_count - 1])) {
753 s64 seconds{};
754 if (time < rules.ats[0]) {
755 seconds = rules.ats[0] - time;
756 } else {
757 seconds = time - rules.ats[rules.time_count - 1];
758 }
759 seconds--;
760
761 const s64 years{(seconds / seconds_per_repeat + 1) * years_per_repeat};
762 seconds = years * average_seconds_per_year;
763
764 s64 new_time{time};
765 if (time < rules.ats[0]) {
766 new_time += seconds;
767 } else {
768 new_time -= seconds;
769 }
770 if (new_time < rules.ats[0] && new_time > rules.ats[rules.time_count - 1]) {
771 return ERROR_TIME_NOT_FOUND;
772 }
773 if (const ResultCode result{
774 ToCalendarTimeInternal(rules, new_time, calendar_time, calendar_additional_info)};
775 result != RESULT_SUCCESS) {
776 return result;
777 }
778 if (time < rules.ats[0]) {
779 calendar_time.year -= years;
780 } else {
781 calendar_time.year += years;
782 }
783
784 return RESULT_SUCCESS;
785 }
786
787 s32 tti_index{};
788 if (rules.time_count == 0 || time < rules.ats[0]) {
789 tti_index = rules.default_type;
790 } else {
791 s32 low{1};
792 s32 high{rules.time_count};
793 while (low < high) {
794 s32 mid{(low + high) >> 1};
795 if (time < rules.ats[mid]) {
796 high = mid;
797 } else {
798 low = mid + 1;
799 }
800 }
801 tti_index = rules.types[low - 1];
802 }
803
804 if (const ResultCode result{CreateCalendarTime(time, rules.ttis[tti_index].gmt_offset,
805 calendar_time, calendar_additional_info)};
806 result != RESULT_SUCCESS) {
807 return result;
808 }
809
810 calendar_additional_info.is_dst = rules.ttis[tti_index].is_dst;
811 const char* time_zone{&rules.chars[rules.ttis[tti_index].abbreviation_list_index]};
812 for (int index{}; time_zone[index] != '\0'; ++index) {
813 calendar_additional_info.timezone_name[index] = time_zone[index];
814 }
815 return RESULT_SUCCESS;
816 }
817
ToCalendarTimeImpl(const TimeZoneRule & rules,s64 time,CalendarInfo & calendar)818 static ResultCode ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) {
819 CalendarTimeInternal calendar_time{};
820 const ResultCode result{
821 ToCalendarTimeInternal(rules, time, calendar_time, calendar.additiona_info)};
822 calendar.time.year = static_cast<s16>(calendar_time.year);
823
824 // Internal impl. uses 0-indexed month
825 calendar.time.month = static_cast<s8>(calendar_time.month + 1);
826
827 calendar.time.day = calendar_time.day;
828 calendar.time.hour = calendar_time.hour;
829 calendar.time.minute = calendar_time.minute;
830 calendar.time.second = calendar_time.second;
831 return result;
832 }
833
834 TimeZoneManager::TimeZoneManager() = default;
835 TimeZoneManager::~TimeZoneManager() = default;
836
ToCalendarTime(const TimeZoneRule & rules,s64 time,CalendarInfo & calendar) const837 ResultCode TimeZoneManager::ToCalendarTime(const TimeZoneRule& rules, s64 time,
838 CalendarInfo& calendar) const {
839 return ToCalendarTimeImpl(rules, time, calendar);
840 }
841
SetDeviceLocationNameWithTimeZoneRule(const std::string & location_name,FileSys::VirtualFile & vfs_file)842 ResultCode TimeZoneManager::SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name,
843 FileSys::VirtualFile& vfs_file) {
844 TimeZoneRule rule{};
845 if (ParseTimeZoneBinary(rule, vfs_file)) {
846 device_location_name = location_name;
847 time_zone_rule = rule;
848 return RESULT_SUCCESS;
849 }
850 return ERROR_TIME_ZONE_CONVERSION_FAILED;
851 }
852
SetUpdatedTime(const Clock::SteadyClockTimePoint & value)853 ResultCode TimeZoneManager::SetUpdatedTime(const Clock::SteadyClockTimePoint& value) {
854 time_zone_update_time_point = value;
855 return RESULT_SUCCESS;
856 }
857
ToCalendarTimeWithMyRules(s64 time,CalendarInfo & calendar) const858 ResultCode TimeZoneManager::ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const {
859 if (is_initialized) {
860 return ToCalendarTime(time_zone_rule, time, calendar);
861 } else {
862 return ERROR_UNINITIALIZED_CLOCK;
863 }
864 }
865
ParseTimeZoneRuleBinary(TimeZoneRule & rules,FileSys::VirtualFile & vfs_file) const866 ResultCode TimeZoneManager::ParseTimeZoneRuleBinary(TimeZoneRule& rules,
867 FileSys::VirtualFile& vfs_file) const {
868 if (!ParseTimeZoneBinary(rules, vfs_file)) {
869 return ERROR_TIME_ZONE_CONVERSION_FAILED;
870 }
871 return RESULT_SUCCESS;
872 }
873
ToPosixTime(const TimeZoneRule & rules,const CalendarTime & calendar_time,s64 & posix_time) const874 ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules,
875 const CalendarTime& calendar_time, s64& posix_time) const {
876 posix_time = 0;
877
878 CalendarTimeInternal internal_time{
879 .year = calendar_time.year,
880 // Internal impl. uses 0-indexed month
881 .month = static_cast<s8>(calendar_time.month - 1),
882 .day = calendar_time.day,
883 .hour = calendar_time.hour,
884 .minute = calendar_time.minute,
885 .second = calendar_time.second,
886 };
887
888 s32 hour{internal_time.hour};
889 s32 minute{internal_time.minute};
890 if (!SafeNormalize(hour, minute, minutes_per_hour)) {
891 return ERROR_OVERFLOW;
892 }
893 internal_time.minute = static_cast<s8>(minute);
894
895 s32 day{internal_time.day};
896 if (!SafeNormalize(day, hour, hours_per_day)) {
897 return ERROR_OVERFLOW;
898 }
899 internal_time.day = static_cast<s8>(day);
900 internal_time.hour = static_cast<s8>(hour);
901
902 s64 year{internal_time.year};
903 s64 month{internal_time.month};
904 if (!SafeNormalize(year, month, months_per_year)) {
905 return ERROR_OVERFLOW;
906 }
907 internal_time.month = static_cast<s8>(month);
908
909 if (!SafeAdd(year, year_base)) {
910 return ERROR_OVERFLOW;
911 }
912
913 while (day <= 0) {
914 if (!SafeAdd(year, -1)) {
915 return ERROR_OVERFLOW;
916 }
917 s64 temp_year{year};
918 if (1 < internal_time.month) {
919 ++temp_year;
920 }
921 day += static_cast<s32>(GetYearLengthInDays(temp_year));
922 }
923
924 while (day > days_per_leap_year) {
925 s64 temp_year{year};
926 if (1 < internal_time.month) {
927 temp_year++;
928 }
929 day -= static_cast<s32>(GetYearLengthInDays(temp_year));
930 if (!SafeAdd(year, 1)) {
931 return ERROR_OVERFLOW;
932 }
933 }
934
935 while (true) {
936 const s32 month_length{GetMonthLength(IsLeapYear(year), internal_time.month)};
937 if (day <= month_length) {
938 break;
939 }
940 day -= month_length;
941 internal_time.month++;
942 if (internal_time.month >= months_per_year) {
943 internal_time.month = 0;
944 if (!SafeAdd(year, 1)) {
945 return ERROR_OVERFLOW;
946 }
947 }
948 }
949 internal_time.day = static_cast<s8>(day);
950
951 if (!SafeAdd(year, -year_base)) {
952 return ERROR_OVERFLOW;
953 }
954 internal_time.year = year;
955
956 s32 saved_seconds{};
957 if (internal_time.second >= 0 && internal_time.second < seconds_per_minute) {
958 saved_seconds = 0;
959 } else if (year + year_base < epoch_year) {
960 s32 second{internal_time.second};
961 if (!SafeAdd(second, 1 - seconds_per_minute)) {
962 return ERROR_OVERFLOW;
963 }
964 saved_seconds = second;
965 internal_time.second = 1 - seconds_per_minute;
966 } else {
967 saved_seconds = internal_time.second;
968 internal_time.second = 0;
969 }
970
971 s64 low{LLONG_MIN};
972 s64 high{LLONG_MAX};
973 while (true) {
974 s64 pivot{low / 2 + high / 2};
975 if (pivot < low) {
976 pivot = low;
977 } else if (pivot > high) {
978 pivot = high;
979 }
980 s32 direction{};
981 CalendarTimeInternal candidate_calendar_time{};
982 CalendarAdditionalInfo unused{};
983 if (ToCalendarTimeInternal(rules, pivot, candidate_calendar_time, unused) !=
984 RESULT_SUCCESS) {
985 if (pivot > 0) {
986 direction = 1;
987 } else {
988 direction = -1;
989 }
990 } else {
991 direction = candidate_calendar_time.Compare(internal_time);
992 }
993 if (!direction) {
994 const s64 time_result{pivot + saved_seconds};
995 if ((time_result < pivot) != (saved_seconds < 0)) {
996 return ERROR_OVERFLOW;
997 }
998 posix_time = time_result;
999 break;
1000 } else {
1001 if (pivot == low) {
1002 if (pivot == LLONG_MAX) {
1003 return ERROR_TIME_NOT_FOUND;
1004 }
1005 pivot++;
1006 low++;
1007 } else if (pivot == high) {
1008 if (pivot == LLONG_MIN) {
1009 return ERROR_TIME_NOT_FOUND;
1010 }
1011 pivot--;
1012 high--;
1013 }
1014 if (low > high) {
1015 return ERROR_TIME_NOT_FOUND;
1016 }
1017 if (direction > 0) {
1018 high = pivot;
1019 } else {
1020 low = pivot;
1021 }
1022 }
1023 }
1024 return RESULT_SUCCESS;
1025 }
1026
ToPosixTimeWithMyRule(const CalendarTime & calendar_time,s64 & posix_time) const1027 ResultCode TimeZoneManager::ToPosixTimeWithMyRule(const CalendarTime& calendar_time,
1028 s64& posix_time) const {
1029 if (is_initialized) {
1030 return ToPosixTime(time_zone_rule, calendar_time, posix_time);
1031 }
1032 posix_time = 0;
1033 return ERROR_UNINITIALIZED_CLOCK;
1034 }
1035
GetDeviceLocationName(LocationName & value) const1036 ResultCode TimeZoneManager::GetDeviceLocationName(LocationName& value) const {
1037 if (!is_initialized) {
1038 return ERROR_UNINITIALIZED_CLOCK;
1039 }
1040 std::memcpy(value.data(), device_location_name.c_str(), device_location_name.size());
1041 return RESULT_SUCCESS;
1042 }
1043
1044 } // namespace Service::Time::TimeZone
1045