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 }