mirror of
https://github.com/google/pebble.git
synced 2025-06-02 08:23:10 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
25
src/fw/applib/pbl_std/local.h
Normal file
25
src/fw/applib/pbl_std/local.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#define EPOCH_YEAR 1970
|
||||
#define EPOCH_WDAY 4
|
||||
#define EPOCH_YEARS_SINCE_LEAP 2
|
||||
#define EPOCH_YEARS_SINCE_CENTURY 70
|
||||
#define EPOCH_YEARS_SINCE_LEAP_CENTURY 370
|
||||
|
||||
#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
|
74
src/fw/applib/pbl_std/locale.c
Normal file
74
src/fw/applib/pbl_std/locale.c
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "locale.h"
|
||||
#include "applib/i18n.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
|
||||
void locale_init_app_locale(LocaleInfo *info) {
|
||||
strncpy(info->app_locale_strings, "en_US", ISO_LOCALE_LENGTH);
|
||||
strncpy(info->app_locale_time, "en_US", ISO_LOCALE_LENGTH);
|
||||
}
|
||||
|
||||
static void prv_update_locale(char *locale, const char *new) {
|
||||
if (new) {
|
||||
strncpy(locale, new, ISO_LOCALE_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
char *pbl_setlocale(int category, const char *locale) {
|
||||
LocaleInfo *info = app_state_get_locale_info();
|
||||
if (locale == NULL) {
|
||||
switch (category) {
|
||||
case LC_ALL:
|
||||
return info->app_locale_strings;
|
||||
case LC_TIME:
|
||||
return info->app_locale_time;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
} else if (*locale == '\0') {
|
||||
locale = app_get_system_locale();
|
||||
}
|
||||
|
||||
switch (category) {
|
||||
case LC_ALL:
|
||||
prv_update_locale(info->app_locale_strings, locale);
|
||||
prv_update_locale(info->app_locale_time, locale);
|
||||
return info->app_locale_strings;
|
||||
case LC_TIME:
|
||||
prv_update_locale(info->app_locale_time, locale);
|
||||
return info->app_locale_time;
|
||||
default:
|
||||
/* not implemented */
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct lconv pbl_lconv = {
|
||||
".", "", "", "", "", "", "", "", "", "",
|
||||
255, 255, 255, 255,
|
||||
255, 255, 255, 255,
|
||||
255, 255, 255, 255,
|
||||
255, 255
|
||||
};
|
||||
|
||||
struct lconv *pbl_localeconv_r(struct _reent *data) {
|
||||
return (struct lconv *)&pbl_lconv;
|
||||
}
|
||||
|
35
src/fw/applib/pbl_std/locale.h
Normal file
35
src/fw/applib/pbl_std/locale.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include <locale.h>
|
||||
|
||||
typedef struct {
|
||||
char sys_locale[ISO_LOCALE_LENGTH];
|
||||
char app_locale_time[ISO_LOCALE_LENGTH];
|
||||
char app_locale_strings[ISO_LOCALE_LENGTH];
|
||||
} LocaleInfo;
|
||||
|
||||
void locale_init_app_locale(LocaleInfo *info);
|
||||
|
||||
char *pbl_setlocale(int category, const char *locale);
|
||||
|
||||
struct _reent;
|
||||
|
||||
struct lconv *pbl_localeconv_r(struct _reent *data);
|
||||
|
309
src/fw/applib/pbl_std/pbl_std.c
Normal file
309
src/fw/applib/pbl_std/pbl_std.c
Normal file
|
@ -0,0 +1,309 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "applib/pbl_std/pbl_std.h"
|
||||
#include "applib/app_logging.h"
|
||||
#include "applib/applib_malloc.auto.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "process_state/worker_state/worker_state.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
|
||||
// Time
|
||||
time_t pbl_override_time(time_t *tloc) {
|
||||
time_t t = sys_get_time();
|
||||
if (tloc) {
|
||||
*tloc = t;
|
||||
}
|
||||
return (t);
|
||||
}
|
||||
|
||||
// Manually construct double to avoid requiring soft-fp
|
||||
static double prv_time_to_double(time_t time) {
|
||||
// time_t is 32bit signed int, convert it manually
|
||||
#ifndef UNITTEST
|
||||
_Static_assert(sizeof(time_t) == 4, "Conversion depends on 32bit time_t");
|
||||
#endif
|
||||
|
||||
if (time == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Construct the double in two uint32_t's then reinterpret as double
|
||||
// Using a uint64_t would pull in helper functions for 64bit shifts
|
||||
union {
|
||||
double result;
|
||||
uint32_t representation[2];
|
||||
} conv;
|
||||
|
||||
conv.representation[1] = 0;
|
||||
if (time < 0) {
|
||||
// set the sign bit
|
||||
conv.representation[1] |= (1 << 31);
|
||||
|
||||
// sign bit takes care of positive vs negative
|
||||
time *= -1;
|
||||
}
|
||||
|
||||
uint32_t significand = (uint32_t)time;
|
||||
|
||||
// In order to normalize the significand, we shift off all the leading 0s
|
||||
// plus the msb which is implicitly included
|
||||
int shift = __builtin_clz(time) + 1;
|
||||
|
||||
significand <<= shift;
|
||||
|
||||
// top 20 bits fit into the upper 32 bits of the double
|
||||
conv.representation[1] |= (significand >> 12);
|
||||
|
||||
// bottom 12 bits go into the lower 32 bits of the double
|
||||
conv.representation[0] = (significand << 20);
|
||||
|
||||
// Exponent is biased by 1023, then adjusted for the amount the significand was
|
||||
// shifted. Out of the 52 significand bits, the bottom 20 are never used.
|
||||
uint32_t exp = 1023 + 52 - shift - 20;
|
||||
// Set the exponent
|
||||
conv.representation[1] |= (exp << 20);
|
||||
|
||||
return conv.result;
|
||||
}
|
||||
|
||||
double pbl_override_difftime(time_t end, time_t beginning) {
|
||||
return prv_time_to_double(end - beginning);
|
||||
}
|
||||
|
||||
time_t pbl_override_time_legacy(time_t *tloc) {
|
||||
time_t t = sys_get_time();
|
||||
time_t legacy_time = sys_time_utc_to_local(t);
|
||||
|
||||
if (tloc) {
|
||||
*tloc = legacy_time;
|
||||
}
|
||||
return (legacy_time);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(time_t, pbl_override_mktime, struct tm *tb) {
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
syscall_assert_userspace_buffer(tb, sizeof(struct tm));
|
||||
}
|
||||
return mktime(tb);
|
||||
}
|
||||
|
||||
uint16_t time_ms(time_t *tloc, uint16_t *out_ms) {
|
||||
uint16_t t_ms;
|
||||
time_t t_s;
|
||||
sys_get_time_ms(&t_s, &t_ms);
|
||||
if (out_ms) {
|
||||
*out_ms = t_ms;
|
||||
}
|
||||
if (tloc) {
|
||||
*tloc = t_s;
|
||||
}
|
||||
return (t_ms);
|
||||
}
|
||||
|
||||
uint16_t pbl_override_time_ms_legacy(time_t *tloc, uint16_t *out_ms) {
|
||||
uint16_t t_ms;
|
||||
time_t t_s;
|
||||
sys_get_time_ms(&t_s, &t_ms);
|
||||
time_t legacy_time = sys_time_utc_to_local(t_s);
|
||||
|
||||
if (out_ms) {
|
||||
*out_ms = t_ms;
|
||||
}
|
||||
if (tloc) {
|
||||
*tloc = legacy_time;
|
||||
}
|
||||
return (t_ms);
|
||||
}
|
||||
|
||||
extern size_t localized_strftime(char* s,
|
||||
size_t maxsize, const char* format, const struct tm* tim_p, const char *locale);
|
||||
|
||||
|
||||
struct tm *pbl_override_gmtime(const time_t *timep) {
|
||||
struct tm *gmtime_tm = NULL;
|
||||
|
||||
if (pebble_task_get_current() == PebbleTask_App) {
|
||||
gmtime_tm = app_state_get_gmtime_tm();
|
||||
} else {
|
||||
gmtime_tm = worker_state_get_gmtime_tm();
|
||||
}
|
||||
|
||||
sys_gmtime_r(timep, gmtime_tm);
|
||||
return gmtime_tm;
|
||||
}
|
||||
|
||||
struct tm *pbl_override_localtime(const time_t *timep) {
|
||||
struct tm *localtime_tm = NULL;
|
||||
char *localtime_zone = NULL;
|
||||
|
||||
if (pebble_task_get_current() == PebbleTask_App) {
|
||||
localtime_tm = app_state_get_localtime_tm();
|
||||
localtime_zone = app_state_get_localtime_zone();
|
||||
} else {
|
||||
localtime_tm = worker_state_get_localtime_tm();
|
||||
localtime_zone = worker_state_get_localtime_zone();
|
||||
}
|
||||
|
||||
sys_localtime_r(timep, localtime_tm);
|
||||
// We have to work around localtime_r resetting tm_zone below
|
||||
sys_copy_timezone_abbr((char*)localtime_zone, *timep);
|
||||
strncpy(localtime_tm->tm_zone, localtime_zone, TZ_LEN);
|
||||
|
||||
return localtime_tm;
|
||||
}
|
||||
|
||||
|
||||
int pbl_strftime(char* s, size_t maxsize, const char* format, const struct tm* tim_p) {
|
||||
char *locale = app_state_get_locale_info()->app_locale_time;
|
||||
return sys_strftime(s, maxsize, format, tim_p, locale);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(int, sys_strftime, char* s, size_t maxsize, const char* format,
|
||||
const struct tm* tim_p, char *locale) {
|
||||
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
syscall_assert_userspace_buffer(s, maxsize);
|
||||
syscall_assert_userspace_buffer(format, strlen(format));
|
||||
syscall_assert_userspace_buffer(tim_p, sizeof(struct tm));
|
||||
}
|
||||
|
||||
return localized_strftime(s, maxsize, format, tim_p, locale);
|
||||
}
|
||||
|
||||
|
||||
void *pbl_memcpy(void *destination, const void *source, size_t num) {
|
||||
// In releases prior to FW 2.5 we used GCC 4.7 and newlib as our libc implementation. However,
|
||||
// in 2.5 we switched to GCC 4.8 and nano-newlib. We actually ran into bad apps that would pass
|
||||
// negative numbers as their num parameter, which newlib handled by copying nothing, but
|
||||
// newlib-nano didn't handle at all and would interpret size_t as a extremely large unsigned
|
||||
// number. Guard against this happening so apps that used to work with the old libc will work
|
||||
// with the new libc. See PBL-7873.
|
||||
if (((ptrdiff_t) num) <= 0) {
|
||||
return destination;
|
||||
}
|
||||
|
||||
return memcpy(destination, source, num);
|
||||
}
|
||||
|
||||
// Formatted printing. The ROM's libc library (newlib) is compiled without
|
||||
// floating point support in order to save space. Because of this, you can
|
||||
// get an exception and a OS reset if an app tries to use %f or any of the
|
||||
// other floating point format types in an snprintf call. This is because the
|
||||
// implementation does not advance the va_arg (pointer to arguments on the
|
||||
// stack) pointer when it sees one of these FP types and will thus end up using
|
||||
// that floating point argument as the argument for the *next* format type. If
|
||||
// the next format type is a string for example, you end up with a bus error
|
||||
// when it tries to use that argument as a pointer.
|
||||
//
|
||||
// To prevent an OS reset, we scan the format string here and bail with an
|
||||
// APP_LOG message if we detect an attempt to use floating point. We also
|
||||
// return a "floating point not supported" string in the passed in str
|
||||
// buffer.
|
||||
int pbl_snprintf(char * str, size_t n, const char * format, ...) {
|
||||
int ret;
|
||||
const char* fp_msg = "floating point not supported in snprintf";
|
||||
|
||||
|
||||
// Scan string and see if it has any floating point specifiers in it
|
||||
bool has_fp = false;
|
||||
bool end_spec;
|
||||
bool end_format=false;
|
||||
const char* fmt = format;
|
||||
char ch;
|
||||
|
||||
while (true) {
|
||||
|
||||
// Skip to next '%'
|
||||
while (*fmt != 0 && *fmt != '%')
|
||||
fmt++;
|
||||
if (*fmt == 0)
|
||||
break;
|
||||
fmt++;
|
||||
|
||||
// Skip flags, width, and precision until we find the format
|
||||
// specifier. If it's a floating point specifier, immediately bail.
|
||||
end_spec = false;
|
||||
while (!end_spec) {
|
||||
ch = *fmt++;
|
||||
switch (ch) {
|
||||
case 0:
|
||||
end_format = true;
|
||||
end_spec = true;
|
||||
break;
|
||||
|
||||
// flags, precision, and width specifiers
|
||||
case '+':
|
||||
case ' ':
|
||||
case '-':
|
||||
case '#':
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
case '.':
|
||||
break;
|
||||
|
||||
// length specifiers
|
||||
case 'h':
|
||||
case 'l':
|
||||
case 'L':
|
||||
case 'j':
|
||||
case 'z':
|
||||
case 't':
|
||||
break;
|
||||
|
||||
// floating point types
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'g':
|
||||
case 'G':
|
||||
case 'a':
|
||||
case 'A':
|
||||
has_fp = true;
|
||||
end_spec = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
end_spec = true;
|
||||
break;
|
||||
} // end of switch statement
|
||||
|
||||
} // while (!end_spec);
|
||||
|
||||
if (end_format || has_fp)
|
||||
break;
|
||||
} // while (true)
|
||||
|
||||
// Return error message if we detected floating point
|
||||
if (has_fp) {
|
||||
APP_LOG(APP_LOG_LEVEL_DEBUG, "Floating point is not supported by snprintf: "
|
||||
"it was called with format string '%s'", format);
|
||||
strncpy(str, fp_msg, n-1);
|
||||
str[n-1] = 0;
|
||||
return strlen(str);
|
||||
}
|
||||
|
||||
// Safe to process
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
ret = vsnprintf(str, n, format, args);
|
||||
va_end(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
109
src/fw/applib/pbl_std/pbl_std.h
Normal file
109
src/fw/applib/pbl_std/pbl_std.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "util/time/time.h"
|
||||
|
||||
/*
|
||||
* C Standard Library functions for consumption by 3rd party apps
|
||||
*
|
||||
*/
|
||||
|
||||
//! @addtogroup StandardC Standard C
|
||||
//! @{
|
||||
//! @addtogroup StandardTime Time
|
||||
//! @{
|
||||
|
||||
//! Obtain the number of seconds since epoch.
|
||||
//! Note that the epoch is not adjusted for Timezones and Daylight Savings.
|
||||
//! @param tloc Optionally points to an address of a time_t variable to store the time in.
|
||||
//! If you only want to use the return value, you may pass NULL into tloc instead
|
||||
//! @return The number of seconds since epoch, January 1st 1970
|
||||
time_t pbl_override_time(time_t *tloc);
|
||||
|
||||
//! Obtain the number of seconds elapsed between beginning and end represented as a double.
|
||||
//! @param end A time_t variable representing some number of seconds since epoch, January 1st 1970
|
||||
//! @param beginning A time_t variable representing some number of seconds since epoch,
|
||||
//! January 1st 1970. Note that end should be greater than beginning, but this is not enforced.
|
||||
//! @return The number of seconds elapsed between beginning and end.
|
||||
//! @note Pebble uses software floating point emulation. Including this function which returns a
|
||||
//! double will significantly increase the size of your binary. We recommend directly
|
||||
//! subtracting both timestamps to calculate a time difference.
|
||||
//! \code{.c}
|
||||
//! int difference = ts1 - ts2;
|
||||
//! \endcode
|
||||
double pbl_override_difftime(time_t end, time_t beginning);
|
||||
|
||||
//! Obtain the number of seconds since epoch.
|
||||
//! Note that the epoch is adjusted for Timezones and Daylight Savings.
|
||||
//! @param tloc Optionally points to an address of a time_t variable to store the time in.
|
||||
//! If you only want to use the return value, you may pass NULL into tloc instead
|
||||
//! @return The number of seconds since epoch, January 1st 1970
|
||||
time_t pbl_override_time_legacy(time_t *tloc);
|
||||
|
||||
//! convert the time value pointed at by clock to a struct tm which contains the time
|
||||
//! adjusted for the local timezone
|
||||
//! @param timep A pointer to an object of type time_t that contains a time value
|
||||
//! @return A pointer to a struct tm containing the broken out time value adjusted
|
||||
//! for the local timezone
|
||||
struct tm *pbl_override_localtime(const time_t *timep);
|
||||
|
||||
//! convert the time value pointed at by clock to a struct tm
|
||||
//! which contains the time expressed in Coordinated Universal Time (UTC)
|
||||
//! @param timep A pointer to an object of type time_t that contains a time value
|
||||
//! @return A pointer to a struct tm containing Coordinated Universal Time (UTC)
|
||||
struct tm *pbl_override_gmtime(const time_t *timep);
|
||||
|
||||
//! convert the broken-down time structure to a timestamp
|
||||
//! expressed in Coordinated Universal Time (UTC)
|
||||
//! @param tb A pointer to an object of type tm that contains broken-down time
|
||||
//! @return The number of seconds since epoch, January 1st 1970
|
||||
time_t pbl_override_mktime(struct tm *tb);
|
||||
|
||||
//! Returns the current local time in Unix Timestamp Format with milliseconds
|
||||
//! The time_ms() method, in contrast, returns the UTC time instead of local time.
|
||||
//! @param t_loc Optionally points to an address of a time_t variable to store the time in.
|
||||
//! If you don't need this value, you may pass NULL into t_loc instead
|
||||
//! @param out_ms Optionally points to an address of a uin16_t variable to store the ms in.
|
||||
//! If you only want to use the return value, you may pass NULL into out_ms instead
|
||||
//! @return Current milliseconds portion
|
||||
uint16_t pbl_override_time_ms_legacy(time_t *t_loc, uint16_t *out_ms);
|
||||
|
||||
//! Format the time value at tm according to fmt and place the result in a buffer s of size max
|
||||
//! @param s A preallocation char array of size max
|
||||
//! @param maxsize the size of the array s
|
||||
//! @param format a formatting string
|
||||
//! @param tm_p A pointer to a struct tm containing a broken out time value
|
||||
//! @return The number of bytes placed in the array s, not including the null byte,
|
||||
//! 0 if the value does not fit.
|
||||
int pbl_strftime(char* s, size_t maxsize, const char* format, const struct tm* tm_p);
|
||||
|
||||
//! Returns the current UTC time in Unix Timestamp Format with Milliseconds
|
||||
//! @param t_utc if provided receives current UTC Unix Time seconds portion
|
||||
//! @param out_ms if provided receives current Unix Time milliseconds portion
|
||||
//! @return Current Unix Time milliseconds portion
|
||||
uint16_t time_ms(time_t *t_utc, uint16_t *out_ms);
|
||||
|
||||
//! @} // end addtogroup StandardTime
|
||||
//! @} // end addtogroup StandardC
|
||||
|
||||
int pbl_snprintf(char * str, size_t n, const char * format, ...);
|
||||
|
||||
void *pbl_memcpy(void *destination, const void *source, size_t num);
|
443
src/fw/applib/pbl_std/strftime.c
Normal file
443
src/fw/applib/pbl_std/strftime.c
Normal file
|
@ -0,0 +1,443 @@
|
|||
/*
|
||||
* Copyright 2024 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This was bad and huge and ugly. Now it's good and small and ugly.
|
||||
|
||||
// NOTE: PBL-22056
|
||||
// Our old strftime had a bug where a negative gmtoff that wasn't at least an hour would still show
|
||||
// up as positive for %z. Obviously this is wrong, but in the interest of compatibility and code
|
||||
// size, we're keeping it.
|
||||
|
||||
// NOTE:
|
||||
// Our old strftime had support for the POSIX-2008 '+' flag. Because it takes a lot of code to
|
||||
// support, and is practically useless, we don't support it. I don't think this will break
|
||||
// anything, but it's worth noting.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
#include "time.h"
|
||||
#include "local.h"
|
||||
#include "timelocal.h"
|
||||
#include "applib/i18n.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#define INTFMT_PADSPACE (0)
|
||||
#define INTFMT_PADZERO (1)
|
||||
|
||||
// Used for wrong specifiers that want Monday as first day of the week.
|
||||
static int prv_week_of_year(const struct tm *t, bool monday_is_first_day) {
|
||||
int wday = t->tm_wday; // Week day in range 0-6 (Sun-Sat)
|
||||
if (monday_is_first_day) {
|
||||
wday = (wday + 6) % 7;
|
||||
}
|
||||
// Boost the year day up so the division gets the right result.
|
||||
return (t->tm_yday + 7 - wday) / 7;
|
||||
}
|
||||
|
||||
static int prv_full_year(int year) {
|
||||
return year + TM_YEAR_ORIGIN;
|
||||
}
|
||||
|
||||
static int prv_iso8601_base_week(const struct tm *t) {
|
||||
// Not quite the same as prv_week_of_year
|
||||
// The ISO-8601 week count is defined as the number of weeks with Thursday in it.
|
||||
// Who knows why...
|
||||
return (t->tm_yday + 10 - ((t->tm_wday + 6) % 7)) / 7;
|
||||
}
|
||||
|
||||
// Here be dragons
|
||||
static int prv_year_week_count(int year, const struct tm *t,
|
||||
int normal_compare, int leap_compare) {
|
||||
/*
|
||||
Find first wday of the year.
|
||||
|
||||
CurWday
|
||||
CurYday| 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|
||||
0 | Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|
||||
1 | Sat | Sun | Mon | Tue | Wed | Thu | Fri |
|
||||
2 | Fri | Sat | Sun | Mon | Tue | Wed | Thu |
|
||||
|
||||
wday - yday
|
||||
|
||||
6 - 0 = 6 = Sat
|
||||
6 - 2 = 4 = Thu
|
||||
0 - 2 = -2+7 = 5 = Fri
|
||||
|
||||
(wday - yday % 7)
|
||||
*/
|
||||
// First wday of the year
|
||||
int wday = (((t->tm_wday - t->tm_yday) % 7) + 7) % 7;
|
||||
|
||||
// Don't ask me, I didn't decide this.
|
||||
if (wday == normal_compare || (YEAR_IS_LEAP(year) && wday == leap_compare)) {
|
||||
return 53;
|
||||
} else {
|
||||
return 52;
|
||||
}
|
||||
}
|
||||
|
||||
static int prv_iso8601_adjust(const struct tm *t, int year) {
|
||||
const int week = prv_iso8601_base_week(t);
|
||||
if (week == 0) {
|
||||
return -1;
|
||||
} else if (week > prv_year_week_count(year, t, 4, 3)) {
|
||||
// 53 weeks if the current year started on a Thursday,
|
||||
// orrrrr Wednesday and this year is a leap year.
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int prv_iso8601_year(const struct tm *t) {
|
||||
const int year = prv_full_year(t->tm_year);
|
||||
return year + prv_iso8601_adjust(t, year);
|
||||
}
|
||||
|
||||
static int prv_iso8601_week(const struct tm *t) {
|
||||
const int year = prv_full_year(t->tm_year);
|
||||
switch (prv_iso8601_adjust(t, year)) {
|
||||
case -1:
|
||||
// 53 weeks if the current year started on a Friday,
|
||||
// orrrrrr Saturday and last year was a leap year.
|
||||
return prv_year_week_count(year - 1, t, 5, 6);
|
||||
case 1:
|
||||
return 1;
|
||||
default:
|
||||
return prv_iso8601_base_week(t);
|
||||
}
|
||||
}
|
||||
|
||||
// Sorry I made a mess, it was in the name of size.
|
||||
size_t localized_strftime(char * restrict dest_str, size_t maxsize, const char * restrict fmt,
|
||||
const struct tm * restrict t, const char *locale) {
|
||||
const struct lc_time_T *time_locale = time_locale_get();
|
||||
size_t left = maxsize;
|
||||
const int year = prv_full_year(t->tm_year);
|
||||
const int hour_12h = (t->tm_hour % 12 == 0) ? 12 : (t->tm_hour % 12);
|
||||
// Only use i18n if we're in the kernel, or the app locale is the system locale.
|
||||
const bool use_i18n = !locale ||
|
||||
(strncmp(locale, app_get_system_locale(), ISO_LOCALE_LENGTH) == 0);
|
||||
|
||||
while (left) {
|
||||
// Copy up to the next '%'
|
||||
char *ptr = strchr(fmt, '%');
|
||||
if (ptr == NULL) {
|
||||
// Get the ending \0 of the string
|
||||
// Equivalent to ptr = fmt + strlen(fmt), but a bit faster/smaller
|
||||
ptr = strchr(fmt, '\0');
|
||||
}
|
||||
size_t length = (ptrdiff_t)(ptr - fmt);
|
||||
|
||||
// Verify we have enough space for the copy
|
||||
if (left <= length) {
|
||||
goto _out_of_size;
|
||||
}
|
||||
memcpy(dest_str, fmt, length);
|
||||
dest_str += length;
|
||||
left -= length;
|
||||
|
||||
fmt = ptr + 1;
|
||||
// End of string
|
||||
if (*ptr == '\0' || *fmt == '\0') {
|
||||
break;
|
||||
}
|
||||
|
||||
char pad = '\0';
|
||||
size_t width = 0;
|
||||
|
||||
// Process flags
|
||||
// These are the only ones our old impl cared about.
|
||||
if (*fmt == '0' || *fmt == '+') {
|
||||
pad = *fmt++;
|
||||
}
|
||||
|
||||
// Process field width
|
||||
if (*fmt >= '1' && *fmt <= '9') {
|
||||
width = strtol(fmt, &ptr, 10);
|
||||
fmt = ptr;
|
||||
}
|
||||
|
||||
// Process modifiers (SU)
|
||||
if (*fmt == 'E' || *fmt == 'O') {
|
||||
// We just drop them on the floor. Uh oh spaghetti-o
|
||||
// Not like the old implementation really did them either.
|
||||
fmt++;
|
||||
}
|
||||
|
||||
// Helper macros to make goto stuff look cleaner
|
||||
#define FMT_STRCOPY(V) do { \
|
||||
i18nstr = NULL; \
|
||||
cpystr = V; \
|
||||
goto _fmt_strcopy; \
|
||||
} while (0)
|
||||
#define FMT_STRCOPY_I18N(V) do { \
|
||||
i18nstr = V; \
|
||||
goto _fmt_strcopy; \
|
||||
} while (0)
|
||||
|
||||
#define FMT_INTCOPY(V, L, F) do { \
|
||||
cpyint_val = V; \
|
||||
cpyint_len = L; \
|
||||
cpyint_flag = F; \
|
||||
goto _fmt_intcopy; \
|
||||
} while (0)
|
||||
|
||||
#define FMT_RECURSE(FMT) do { \
|
||||
i18nstr = NULL; \
|
||||
cpystr = FMT; \
|
||||
goto _fmt_recurse; \
|
||||
} while (0)
|
||||
#define FMT_RECURSE_I18N(FMT) do { \
|
||||
i18nstr = FMT; \
|
||||
goto _fmt_recurse; \
|
||||
} while (0)
|
||||
|
||||
// Terrible local state for goto hell
|
||||
const char *cpystr = NULL;
|
||||
const char *i18nstr;
|
||||
int cpyint_val;
|
||||
size_t cpyint_len;
|
||||
unsigned int cpyint_flag;
|
||||
// Process conversion specifiers
|
||||
switch (*fmt) {
|
||||
case 'a':
|
||||
FMT_STRCOPY_I18N(time_locale->wday[t->tm_wday % DAYS_PER_WEEK]);
|
||||
_fmt_strcopy:
|
||||
// old strftime doesn't use 'width' for strings
|
||||
if (!use_i18n && i18nstr) {
|
||||
cpystr = i18nstr;
|
||||
i18nstr = NULL;
|
||||
}
|
||||
if (i18nstr) {
|
||||
length = sys_i18n_get_length(i18nstr);
|
||||
} else {
|
||||
length = strlen(cpystr);
|
||||
}
|
||||
if (left <= length) {
|
||||
goto _out_of_size;
|
||||
}
|
||||
if (i18nstr) {
|
||||
sys_i18n_get_with_buffer(i18nstr, dest_str, length + 1);
|
||||
} else {
|
||||
memcpy(dest_str, cpystr, length);
|
||||
}
|
||||
dest_str += length;
|
||||
left -= length;
|
||||
break;
|
||||
case 'A':
|
||||
FMT_STRCOPY_I18N(time_locale->weekday[t->tm_wday % DAYS_PER_WEEK]);
|
||||
break;
|
||||
case 'h': // SU
|
||||
case 'b':
|
||||
FMT_STRCOPY_I18N(time_locale->mon[t->tm_mon % MONTHS_PER_YEAR]);
|
||||
break;
|
||||
case 'B':
|
||||
FMT_STRCOPY_I18N(time_locale->month[t->tm_mon % MONTHS_PER_YEAR]);
|
||||
break;
|
||||
case 'c':
|
||||
FMT_RECURSE_I18N(time_locale->c_fmt);
|
||||
_fmt_recurse:
|
||||
if (!use_i18n && i18nstr) {
|
||||
cpystr = i18nstr;
|
||||
i18nstr = NULL;
|
||||
}
|
||||
if (i18nstr) {
|
||||
cpystr = i18n_get(i18nstr, dest_str);
|
||||
}
|
||||
length = localized_strftime(dest_str, left, cpystr, t, locale);
|
||||
if (i18nstr) {
|
||||
i18n_free(i18nstr, dest_str);
|
||||
}
|
||||
if (length == 0) {
|
||||
goto _out_of_size;
|
||||
}
|
||||
dest_str += length;
|
||||
left -= length;
|
||||
break;
|
||||
case 'C': // SU
|
||||
FMT_INTCOPY(year / 100, 2, INTFMT_PADZERO);
|
||||
_fmt_intcopy:
|
||||
{
|
||||
const char *intfmt;
|
||||
if (pad != '\0' || cpyint_flag == INTFMT_PADZERO) {
|
||||
intfmt = "%0*d";
|
||||
} else {
|
||||
intfmt = "%*d";
|
||||
}
|
||||
width = MAX(width, cpyint_len);
|
||||
length = snprintf(NULL, 0, intfmt, width, cpyint_val);
|
||||
if (left <= length) {
|
||||
goto _out_of_size;
|
||||
}
|
||||
sprintf(dest_str, intfmt, width, cpyint_val);
|
||||
dest_str += length;
|
||||
left -= length;
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
FMT_INTCOPY(t->tm_mday, 2, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'D': // SU
|
||||
FMT_RECURSE("%m/%d/%y");
|
||||
break;
|
||||
case 'e': // SU
|
||||
FMT_INTCOPY(t->tm_mday, 2, INTFMT_PADSPACE);
|
||||
break;
|
||||
case 'F': // C99
|
||||
FMT_RECURSE("%Y-%m-%d");
|
||||
break;
|
||||
case 'g': // TZ
|
||||
FMT_INTCOPY(prv_iso8601_year(t) % 100, 2, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'G': // TZ
|
||||
FMT_INTCOPY(prv_iso8601_year(t), 4, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'H':
|
||||
FMT_INTCOPY(t->tm_hour, 2, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'I':
|
||||
FMT_INTCOPY(hour_12h, 2, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'j':
|
||||
FMT_INTCOPY(t->tm_yday+1, 3, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'k': // TZ
|
||||
FMT_INTCOPY(t->tm_hour, 2, INTFMT_PADSPACE);
|
||||
break;
|
||||
case 'l': // TZ
|
||||
FMT_INTCOPY(hour_12h, 2, INTFMT_PADSPACE);
|
||||
break;
|
||||
case 'm':
|
||||
FMT_INTCOPY(t->tm_mon + 1, 2, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'M':
|
||||
FMT_INTCOPY(t->tm_min, 2, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'r': // SU
|
||||
FMT_RECURSE_I18N(time_locale->r_fmt);
|
||||
break;
|
||||
case 'p':
|
||||
if (t->tm_hour < 12) {
|
||||
FMT_STRCOPY_I18N(time_locale->am_pm_upcase[0]);
|
||||
} else {
|
||||
FMT_STRCOPY_I18N(time_locale->am_pm_upcase[1]);
|
||||
}
|
||||
break;
|
||||
case 'P': // GNU
|
||||
if (t->tm_hour < 12) {
|
||||
FMT_STRCOPY_I18N(time_locale->am_pm_downcase[0]);
|
||||
} else {
|
||||
FMT_STRCOPY_I18N(time_locale->am_pm_downcase[1]);
|
||||
}
|
||||
break;
|
||||
case 'R': // SU
|
||||
FMT_RECURSE("%H:%M");
|
||||
break;
|
||||
case 'S':
|
||||
FMT_INTCOPY(t->tm_sec, 2, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'T': // SU
|
||||
FMT_RECURSE("%H:%M:%S");
|
||||
break;
|
||||
case 'u': // SU
|
||||
if (t->tm_wday == 0) {
|
||||
FMT_INTCOPY(7, 1, INTFMT_PADZERO);
|
||||
break;
|
||||
}
|
||||
// fall-thru
|
||||
case 'w':
|
||||
FMT_INTCOPY(t->tm_wday, 1, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'U':
|
||||
// Week starting on Sunday
|
||||
FMT_INTCOPY(prv_week_of_year(t, false), 2, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'V': // SU
|
||||
FMT_INTCOPY(prv_iso8601_week(t), 2, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'W':
|
||||
// Week starting on Monday, like savages
|
||||
FMT_INTCOPY(prv_week_of_year(t, true), 2, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'x':
|
||||
FMT_RECURSE_I18N(time_locale->x_fmt);
|
||||
break;
|
||||
case 'X':
|
||||
FMT_RECURSE_I18N(time_locale->X_fmt);
|
||||
break;
|
||||
case 'y':
|
||||
FMT_INTCOPY(year % 100, 2, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'Y':
|
||||
FMT_INTCOPY(year, 4, INTFMT_PADZERO);
|
||||
break;
|
||||
case 'z': {
|
||||
if (left < 5) {
|
||||
goto _out_of_size;
|
||||
}
|
||||
sprintf(dest_str, "%+.2d%02u", t->tm_gmtoff / 3600, (abs(t->tm_gmtoff) / 60) % 60);
|
||||
dest_str += 5;
|
||||
left -= 5;
|
||||
break;
|
||||
}
|
||||
case 'Z':
|
||||
FMT_STRCOPY(t->tm_zone);
|
||||
break;
|
||||
|
||||
case 'n': // SU
|
||||
*dest_str++ = '\n';
|
||||
goto _fmt_chcopy;
|
||||
case 't': // SU
|
||||
*dest_str++ = '\t';
|
||||
goto _fmt_chcopy;
|
||||
// Copy stuff
|
||||
case '%':
|
||||
*dest_str++ = '%';
|
||||
_fmt_chcopy:
|
||||
left--;
|
||||
break;
|
||||
case 's': // TZ // Old implementation didn't have it, skip it for code size.
|
||||
case '+': // TZ // Old implementation didn't have it, skip it for code size.
|
||||
default: // Old implementation just ignores invalid specifiers
|
||||
break;
|
||||
}
|
||||
fmt++;
|
||||
}
|
||||
if (left == 0) {
|
||||
goto _out_of_size;
|
||||
}
|
||||
// Finish him!!
|
||||
dest_str[0] = '\0';
|
||||
return maxsize - left;
|
||||
|
||||
_out_of_size:
|
||||
// Oops we're dead
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t strftime(char * restrict s, size_t maxsize, const char* format, const struct tm* tim_p) {
|
||||
// Pass a NULL locale because firmware strftime is always localized
|
||||
return localized_strftime(s, maxsize, format, tim_p, NULL);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue