1 /*
2  * Copyright (c) 2021 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "date_time_format_impl.h"
17 #include <cstring>
18 #include "date_time_data.h"
19 #include "i18n_pattern.h"
20 
21 using namespace OHOS::I18N;
22 using namespace std;
23 
24 /**
25  * construct a DateTimeFormat object with request pattern and locale.
26  * now we only support patterns defined in AvailableDateTimeFormatPatterns.
27  * locale, locale information to retrieve datetime resource form icu data.
28  */
DateTimeFormatImpl(AvailableDateTimeFormatPattern requestPattern,const LocaleInfo & locale)29 DateTimeFormatImpl::DateTimeFormatImpl(AvailableDateTimeFormatPattern requestPattern, const LocaleInfo &locale)
30 {
31     fLocale = locale;
32     this->requestPattern = requestPattern;
33 }
34 
GetWeekName(const int32_t & index,DateTimeDataType type) const35 std::string DateTimeFormatImpl::GetWeekName(const int32_t &index, DateTimeDataType type) const
36 {
37     return (data == nullptr) ? "" : data->GetDayName(index, type);
38 }
39 
GetMonthName(const int32_t & index,DateTimeDataType type) const40 std::string DateTimeFormatImpl::GetMonthName(const int32_t &index, DateTimeDataType type) const
41 {
42     return (data == nullptr) ? "" : data->GetMonthName(index, type);
43 }
44 
GetAmPmMarker(const int32_t & index,DateTimeDataType type) const45 std::string DateTimeFormatImpl::GetAmPmMarker(const int32_t &index, DateTimeDataType type) const
46 {
47     return (data == nullptr) ? "" : data->GetAmPmMarker(index, type);
48 }
49 
AddSeconds(const string & hmPattern) const50 string DateTimeFormatImpl::AddSeconds(const string &hmPattern) const
51 {
52     uint32_t size = hmPattern.size();
53     if (size == 0 || data == nullptr) {
54         return "";
55     }
56     int32_t i = hmPattern.size() - 1;
57     string out;
58     out.reserve(DECIMAL_COUNT); // allocate ten more bytes
59     while (i >= 0) {
60         if (hmPattern[i] == 'm') {
61             break;
62         }
63         --i;
64     }
65     out.append(hmPattern.substr(0, i + 1));
66     out.append(1, data->GetTimeSeparator());
67     out.append("ss");
68     out.append(hmPattern.substr(i + 1, hmPattern.size() - i - 1));
69     return out;
70 }
71 
~DateTimeFormatImpl()72 DateTimeFormatImpl::~DateTimeFormatImpl()
73 {
74     FreeResource();
75 }
76 
Init(const DataResource & resource)77 bool DateTimeFormatImpl::Init(const DataResource &resource)
78 {
79     char *formatAbbreviatedMonthNames = resource.GetString(DataResourceType::GREGORIAN_FORMAT_ABBR_MONTH);
80     char *formatWideMonthNames = resource.GetString(DataResourceType::GREGORIAN_FORMAT_WIDE_MONTH);
81     char *standaloneAbbreviatedMonthNames =
82         resource.GetString(DataResourceType::GREGORIAN_STANDALONE_ABBR_MONTH);
83     char *standaloneWideMonthNames = resource.GetString(DataResourceType::GREGORIAN_STANDALONE_WIDE_MONTH);
84     char *formatAbbreviatedDayNames = resource.GetString(DataResourceType::GREGORIAN_FORMAT_ABBR_DAY);
85     char *formatWideDayNames = resource.GetString(DataResourceType::GREGORIAN_FORMAT_WIDE_DAY);
86     char *standaloneAbbreviatedDayNames = resource.GetString(DataResourceType::GREGORIAN_STANDALONE_ABBR_DAY);
87     char *standaloneWideDayNames = resource.GetString(DataResourceType::GREGORIAN_STANDALONE_WIDE_DAY);
88     char *amPmMarkers = resource.GetString(DataResourceType::GREGORIAN_AM_PMS);
89     char *timePatterns = resource.GetString(DataResourceType::GREGORIAN_TIME_PATTERNS);
90     char *datePatterns = resource.GetString(DataResourceType::GREGORIAN_DATE_PATTERNS);
91     char *timeSeparator = resource.GetString(DataResourceType::TIME_SEPARATOR);
92     char *defaultHour = resource.GetString(DataResourceType::DEFAULT_HOUR);
93     char *hourMinuteSecondPatterns = resource.GetString(DataResourceType::GREGORIAN_HOUR_MINUTE_SECOND_PATTERN);
94     char *fullMediumShortPatterns = resource.GetString(DataResourceType::GREGORIAN_FULL_MEDIUM_SHORT_PATTERN);
95     char *elapsedPatterns = resource.GetString(DataResourceType::ELAPSED_PATERNS);
96     char sepAndHour[SEP_HOUR_SIZE];
97     if ((timeSeparator == nullptr) || (defaultHour == nullptr) ||
98         (strlen(timeSeparator) < 1) || (strlen(defaultHour) < 1)) {
99         return false;
100     }
101     sepAndHour[0] = timeSeparator[0];
102     sepAndHour[1] = defaultHour[0];
103     data = new(nothrow) DateTimeData(amPmMarkers, sepAndHour, 2); // 2 is length of sepAndHour
104     if (data == nullptr) {
105         return false;
106     }
107     data->SetMonthNamesData(formatAbbreviatedMonthNames, formatWideMonthNames,
108         standaloneAbbreviatedMonthNames, standaloneWideMonthNames);
109     data->SetDayNamesData(formatAbbreviatedDayNames, formatWideDayNames,
110         standaloneAbbreviatedDayNames, standaloneWideDayNames);
111     data->SetPatternsData(datePatterns, timePatterns, hourMinuteSecondPatterns,
112         fullMediumShortPatterns, elapsedPatterns);
113     fPattern = GetStringFromPattern(requestPattern, data);
114     return true;
115 }
116 
FreeResource()117 void DateTimeFormatImpl::FreeResource()
118 {
119     if (data != nullptr) {
120         delete data;
121         data = nullptr;
122     }
123     if (numberFormat != nullptr) {
124         delete numberFormat;
125         numberFormat = nullptr;
126     }
127 }
128 
ApplyPattern(const AvailableDateTimeFormatPattern & requestPattern)129 void DateTimeFormatImpl::ApplyPattern(const AvailableDateTimeFormatPattern &requestPattern)
130 {
131     this->requestPattern = requestPattern;
132     fPattern = GetStringFromPattern(requestPattern, data);
133 }
134 
GetLocale()135 LocaleInfo DateTimeFormatImpl::GetLocale()
136 {
137     return fLocale;
138 }
139 
140 /**
141  * parse a time (represent by the seconds elapsed from UTC 1970, January 1 00:00:00) to its text format.
142  * cal, seconds from from UTC 1970, January 1 00:00:00
143  * zoneInfoOffest, string representation of offset such as "+01:45"
144  * appendTo, output of this method.
145  */
Format(const time_t & cal,const string & zoneInfo,string & appendTo,I18nStatus & status) const146 void DateTimeFormatImpl::Format(const time_t &cal, const string &zoneInfo, string &appendTo,
147     I18nStatus &status) const
148 {
149     const time_t adjust = cal + ParseZoneInfo(zoneInfo);
150     struct tm tmStruct = {0};
151     tm *tmPtr = &tmStruct;
152     gmtime_r(&adjust, tmPtr);
153     const tm time = *tmPtr;
154     Format(time, this->fPattern, appendTo, status);
155 }
156 
Format(const struct tm & time,const string & pattern,string & appendTo,I18nStatus & status) const157 void DateTimeFormatImpl::Format(const struct tm &time, const string &pattern, string &appendTo,
158     I18nStatus &status) const
159 {
160     bool inQuote = false;
161     char pre = '\0';
162     uint32_t count = 0;
163     for (size_t i = 0; i < pattern.size(); ++i) {
164         char current = pattern.at(i);
165         if ((current != pre) && (count != 0)) {
166             Process(time, appendTo, pre, count, status);
167             count = 0;
168         }
169         if (current == QUOTE) {
170             if ((i + 1 < pattern.size()) && pattern[i + 1] == QUOTE) {
171                 appendTo.append(1, QUOTE);
172                 ++i;
173             } else {
174                 inQuote = !inQuote;
175             }
176         } else if (!inQuote && (((current >= 'a') && (current <= 'z')) || ((current >= 'A') && (current <= 'Z')))) {
177             pre = current;
178             ++count;
179         } else {
180             appendTo.append(1, current);
181         }
182     }
183 
184     if (count != 0) {
185         Process(time, appendTo, pre, count, status);
186     }
187 }
188 
189 /**
190  * parse zoneInfo string such as “+1:45” to 1 hour 45 minutes = 3600 * 1 + 45 * 60 seconds
191  */
ParseZoneInfo(const string & zoneInfo) const192 int32_t DateTimeFormatImpl::ParseZoneInfo(const string &zoneInfo) const
193 {
194     int32_t ret = 0;
195     uint32_t size = zoneInfo.size();
196     if (size == 0) {
197         return ret;
198     }
199     bool sign = true;
200     uint32_t index = 0;
201     if (zoneInfo[index] == '+') {
202         ++index;
203     } else if (zoneInfo[index] == '-') {
204         ++index;
205         sign = false;
206     }
207 
208     uint32_t hour = 0;
209     uint32_t minute = 0;
210     bool isHour = true;
211     uint32_t temp = 0;
212     while (index < size) {
213         char cur = zoneInfo[index];
214         if (isdigit(cur)) {
215             temp *= DECIMAL_COUNT; // convert string to its decimal format
216             temp += (cur - '0');
217             ++index;
218         } else if (cur == ':' && isHour) {
219             hour = temp;
220             temp = 0;
221             ++index;
222             isHour = false;
223         } else {
224             return 0;
225         }
226     }
227     if (!isHour && (temp != 0)) {
228         minute = temp;
229     }
230     ret = SECONDS_IN_HOUR * hour + minute * SECONDS_IN_MINUTE;
231 
232     if (!sign) {
233         return -ret;
234     }
235     return ret;
236 }
237 
238 /**
239  * convert a UTC seconds to its string format
240  */
Process(const tm & time,string & appendTo,char pre,uint32_t count,I18nStatus & status) const241 void DateTimeFormatImpl::Process(const tm &time, string &appendTo, char pre, uint32_t count, I18nStatus &status) const
242 {
243     if ((status != I18nStatus::ISUCCESS) || !data) {
244         return;
245     }
246     if (IsTimeChar(pre)) {
247         ProcessTime(time, appendTo, pre, count, status);
248         return;
249     }
250     switch (pre) {
251         case 'L': {
252             int32_t standaloneMonth = time.tm_mon;
253             if (count == WIDE_COUNT) {
254                 appendTo.append(data->GetMonthName(standaloneMonth, DateTimeDataType::STANDALONE_WIDE));
255             } else if (count == ABB_COUNT) {
256                 appendTo.append(data->GetMonthName(standaloneMonth, DateTimeDataType::STANDALONE_ABBR));
257             } else {
258                 ZeroPadding(appendTo, count, MAX_COUNT, standaloneMonth + 1);
259             }
260             break;
261         }
262         case 'M': {
263             int32_t month = time.tm_mon;
264             if (count == WIDE_COUNT) {
265                 appendTo.append(data->GetMonthName(month, DateTimeDataType::FORMAT_WIDE));
266             } else if (count == ABB_COUNT) {
267                 appendTo.append(data->GetMonthName(month, DateTimeDataType::FORMAT_ABBR));
268             } else {
269                 ZeroPadding(appendTo, count, MAX_COUNT, month + 1);
270             }
271             break;
272         }
273         default: {
274             ProcessWeekDayYear(time, appendTo, pre, count, status);
275         }
276     }
277 }
278 
ProcessWeekDayYear(const tm & time,string & appendTo,char pre,uint32_t count,I18nStatus & status) const279 void DateTimeFormatImpl::ProcessWeekDayYear(const tm &time, string &appendTo, char pre,
280     uint32_t count, I18nStatus &status) const
281 {
282     switch (pre) {
283         case 'c': {
284             int32_t standaloneWeekDay = time.tm_wday;
285             if (count == WIDE_COUNT) {
286                 appendTo.append(data->GetDayName(standaloneWeekDay, DateTimeDataType::STANDALONE_WIDE));
287             } else if (count == ABB_COUNT) {
288                 appendTo.append(data->GetDayName(standaloneWeekDay, DateTimeDataType::STANDALONE_ABBR));
289             } else {
290                 ZeroPadding(appendTo, count, MAX_COUNT, standaloneWeekDay);
291             }
292             break;
293         }
294         // case 'c':
295         case 'e':
296         case 'E': {
297             int32_t weekDay = time.tm_wday;
298             if (count == WIDE_COUNT) {
299                 appendTo.append(data->GetDayName(weekDay, DateTimeDataType::FORMAT_WIDE));
300             } else if (count == ABB_COUNT) {
301                 appendTo.append(data->GetDayName(weekDay, DateTimeDataType::FORMAT_ABBR));
302             } else {
303                 ZeroPadding(appendTo, count, MAX_COUNT, weekDay);
304             }
305             break;
306         }
307         case 'd': {
308             int32_t day = time.tm_mday;
309             ZeroPadding(appendTo, count, MAX_COUNT, day);
310             break;
311         }
312         case 'y': {
313             int32_t year = time.tm_year + YEAR_START;
314             if (count == SHORT_YEAR_FORMAT_COUNT) {
315                 int adjustYear = year - (year / ONE_HUNDRED_YEAR) * ONE_HUNDRED_YEAR;
316                 ZeroPadding(appendTo, count, MAX_COUNT, adjustYear);
317             } else {
318                 appendTo.append(FormatYear(year));
319             }
320             break;
321         }
322         default: {
323             return;
324         }
325     }
326 }
327 
IsTimeChar(char ch) const328 bool DateTimeFormatImpl::IsTimeChar(char ch) const
329 {
330     string timeCharacters = "ahHkKms:";
331     return (timeCharacters.find(ch) != string::npos) ? true : false;
332 }
333 
ProcessTime(const tm & time,string & appendTo,char pre,uint32_t count,I18nStatus & status) const334 void DateTimeFormatImpl::ProcessTime(const tm &time, string &appendTo, char pre,
335     uint32_t count, I18nStatus &status) const
336 {
337     switch (pre) {
338         case 'a': {
339             int32_t index = (time.tm_hour < LENGTH_HOUR) ? 0 : 1;
340             string amText = data->GetAmPmMarker(index, DateTimeDataType::FORMAT_ABBR);
341             appendTo.append(amText);
342             break;
343         }
344         // deal with hour.
345         // input is in the range of 0, 23. And final representation is in the range of 1, 12
346         case 'h': {
347             int32_t hour = (time.tm_hour == 0) ? LENGTH_HOUR : time.tm_hour;
348             int32_t index = (hour > LENGTH_HOUR) ? (hour - LENGTH_HOUR) : hour;
349             ZeroPadding(appendTo, count, MAX_COUNT, index);
350             break;
351         }
352         case 'H': {
353             int32_t hour = time.tm_hour;
354             ZeroPadding(appendTo, count, MAX_COUNT, hour);
355             break;
356         }
357         case 'K': {
358             int32_t hour = time.tm_hour;
359             int32_t index = (hour >= LENGTH_HOUR) ? (hour - LENGTH_HOUR) : hour;
360             ZeroPadding(appendTo, count, MAX_COUNT, index);
361             break;
362         }
363         case 'k': {
364             int32_t hour = time.tm_hour + 1;
365             ZeroPadding(appendTo, count, MAX_COUNT, hour);
366             break;
367         }
368         case 'm': {
369             int32_t minute = time.tm_min;
370             ZeroPadding(appendTo, count, MAX_COUNT, minute);
371             break;
372         }
373         case 's': {
374             int32_t second = time.tm_sec;
375             ZeroPadding(appendTo, count, MAX_COUNT, second);
376             break;
377         }
378         case ':': {
379             appendTo.append(1, data->GetTimeSeparator());
380         }
381         default:
382             return;
383     }
384 }
385 
386 /**
387  * Padding numbers with 0, minValue is the requested minimal length of the output number
388  */
ZeroPadding(string & appendTo,uint32_t minValue,uint32_t maxValue,int32_t value) const389 void DateTimeFormatImpl::ZeroPadding(string &appendTo, uint32_t minValue, uint32_t maxValue, int32_t value) const
390 {
391     // value should >= 0
392     if (value < 0) {
393         return;
394     }
395     uint32_t adjustValue = (minValue < maxValue) ? minValue : maxValue;
396     uint32_t count = GetLength(value);
397     string temp = "";
398     while (count < adjustValue) {
399         temp += GetZero();
400         ++count;
401     }
402     temp += FormatNumber(value);
403     appendTo.append(temp);
404 }
405 
FormatNumber(int32_t value) const406 string DateTimeFormatImpl::FormatNumber(int32_t value) const
407 {
408     int status = 0;
409     return numberFormat ? numberFormat->Format(value, status) : to_string(value);
410 }
411 
GetZero() const412 string DateTimeFormatImpl::GetZero() const
413 {
414     int status = 0;
415     return numberFormat ? numberFormat->Format(0, status) : "0";
416 }
417 
GetLength(int32_t value) const418 uint32_t DateTimeFormatImpl::GetLength(int32_t value) const
419 {
420     if (value < DECIMAL_COUNT) {
421         return 1;
422     }
423     uint32_t count = 0;
424     uint32_t temp = value;
425     while (temp) {
426         ++count;
427         temp = temp / DECIMAL_COUNT;
428     }
429     return count;
430 }
431 
FormatYear(int32_t value) const432 string DateTimeFormatImpl::FormatYear(int32_t value) const
433 {
434     int status = 0;
435     return numberFormat ? numberFormat->FormatNoGroup(value, status) : to_string(value);
436 }
437 
Get12HourTimeWithoutAmpm(const time_t & cal,const std::string & zoneInfo,std::string & appendTo,I18nStatus & status) const438 int8_t DateTimeFormatImpl::Get12HourTimeWithoutAmpm(const time_t &cal, const std::string &zoneInfo,
439     std::string &appendTo, I18nStatus &status) const
440 {
441     if (requestPattern != HOUR12_MINUTE && requestPattern != HOUR12_MINUTE_SECOND) {
442         status = IERROR;
443         return 0;
444     }
445     int8_t ret = 0;
446     char *pattern = GetNoAmPmPattern(fPattern, ret);
447     if (pattern == nullptr) {
448         status = IERROR;
449         return 0;
450     }
451     const time_t adjust = cal + ParseZoneInfo(zoneInfo);
452     struct tm tmStruct = { 0 };
453     tm *tmPtr = &tmStruct;
454     gmtime_r(&adjust, tmPtr);
455     const tm time = *tmPtr;
456     string tempPattern(pattern);
457     I18nFree(static_cast<void *>(pattern));
458     Format(time, tempPattern, appendTo, status);
459     return ret;
460 }
461 
GetNoAmPmPattern(const string & patternString,int8_t & ret) const462 char *DateTimeFormatImpl::GetNoAmPmPattern(const string &patternString, int8_t &ret) const
463 {
464     size_t len = patternString.size();
465     char *pattern = nullptr;
466     if ((len > 1) && (patternString[0] == 'a')) {
467         ret = 1;
468         if (patternString[1] == ' ') {
469             pattern = static_cast<char*>(I18nMalloc(len - 1));
470             if (pattern == nullptr) {
471                 return nullptr;
472             }
473             for (size_t i = 0; i < len - AM_PM_MAX_LENGTH; ++i) {
474                 pattern[i] = patternString[i + AM_PM_MAX_LENGTH];
475             }
476             pattern[len - AM_PM_MAX_LENGTH] = '\0';
477         } else {
478             pattern = static_cast<char*>(I18nMalloc(len));
479             if (pattern == nullptr) {
480                 return nullptr;
481             }
482             for (size_t i = 0; i < len - 1; ++i) {
483                 pattern[i] = patternString[i + 1];
484             }
485             pattern[len - 1] = '\0';
486         }
487     } else if ((len > 1) && (patternString[len - 1] == 'a')) {
488         ret = -1;
489         if (patternString[len - AM_PM_MAX_LENGTH] == ' ') {
490             pattern = static_cast<char*>(I18nMalloc(len - 1));
491             if (pattern == nullptr) {
492                 return nullptr;
493             }
494             for (size_t i = 0; i < len - AM_PM_MAX_LENGTH; ++i) {
495                 pattern[i] = patternString[i];
496             }
497             pattern[len - AM_PM_MAX_LENGTH] = '\0';
498         } else {
499             pattern = static_cast<char*>(I18nMalloc(len));
500             if (pattern == nullptr) {
501                 return nullptr;
502             }
503             for (size_t i = 0; i < len - 1; ++i) {
504                 pattern[i] = patternString[i];
505             }
506             pattern[len - 1] = '\0';
507         }
508     }
509     return pattern;
510 }
511 
FormatElapsedDuration(int32_t milliseconds,ElapsedPatternType type,I18nStatus & status) const512 std::string DateTimeFormatImpl::FormatElapsedDuration(int32_t milliseconds, ElapsedPatternType type,
513     I18nStatus &status) const
514 {
515     if (milliseconds < 0) {
516         status = IERROR;
517         return "";
518     }
519     string pattern = GetStringFromElapsedPattern(type, data);
520     int32_t mil = milliseconds % SECOND_IN_MILLIS / CONSTANT_TIME_NUMBER;
521     int32_t sec = milliseconds % MINUTE_IN_MILLIS / SECOND_IN_MILLIS;
522     int32_t min;
523     if ((type == ELAPSED_MINUTE_SECOND) || (type == ELAPSED_MINUTE_SECOND_MILLISECOND)) {
524         min = milliseconds / MINUTE_IN_MILLIS;
525     } else {
526         min = milliseconds % HOUR_IN_MILLIS / MINUTE_IN_MILLIS;
527     }
528     int32_t hour = milliseconds / HOUR_IN_MILLIS;
529     const struct ElapsedTime time = { hour, min, sec, mil };
530     bool inQuote = false;
531     char pre = '\0';
532     uint32_t count = 0;
533     string ret;
534     for (size_t i = 0; i < pattern.size(); ++i) {
535         char current = pattern.at(i);
536         if ((current != pre) && (count != 0)) {
537             FormatElapsed(time, pre, count, ret, status);
538             count = 0;
539         }
540         if (current == QUOTE) {
541             if ((i + 1 < pattern.size()) && pattern[i + 1] == QUOTE) {
542                 ret.append(1, QUOTE);
543                 ++i;
544             } else {
545                 inQuote = !inQuote;
546             }
547         } else if (!inQuote && (((current >= 'a') && (current <= 'z')) || ((current >= 'A') && (current <= 'Z')))) {
548             pre = current;
549             ++count;
550         } else {
551             ret.append(1, current);
552         }
553     }
554     if (count != 0) {
555         FormatElapsed(time, pre, count, ret, status);
556     }
557     return ret;
558 }
559 
FormatElapsed(const struct ElapsedTime & time,char pre,uint32_t count,string & appendTo,I18nStatus & status) const560 void DateTimeFormatImpl::FormatElapsed(const struct ElapsedTime &time, char pre, uint32_t count,
561     string &appendTo, I18nStatus &status) const
562 {
563     switch (pre) {
564         case 'H': {
565             ZeroPadding(appendTo, count, MAX_COUNT, time.hours);
566             return;
567         }
568         case 'm': {
569             ZeroPadding(appendTo, count, MAX_COUNT, time.minutes);
570             return;
571         }
572         case 's': {
573             ZeroPadding(appendTo, count, MAX_COUNT, time.seconds);
574             return;
575         }
576         case 'S': {
577             ZeroPadding(appendTo, count, MAX_COUNT, time.milliseconds);
578             return;
579         }
580         default: {
581             status = IERROR;
582             return;
583         }
584     }
585 }
586 
GetTimeSeparator()587 std::string DateTimeFormatImpl::GetTimeSeparator()
588 {
589     std::string ret = "";
590     if (data == nullptr) {
591         return ret;
592     }
593     ret.append(1, data->GetTimeSeparator());
594     return ret;
595 }