Files
bluelib/ds/datetime.c

593 lines
11 KiB
C
Raw Normal View History

#include <blue/core/stream.h>
#include <blue/ds/datetime.h>
#include <blue/ds/string.h>
/*** PRIVATE DATA *************************************************************/
struct b_datetime_p {
unsigned int dt_year, dt_month, dt_day;
unsigned short dt_hour, dt_min, dt_sec;
unsigned int dt_msec;
bool dt_has_date, dt_has_time, dt_localtime;
unsigned short dt_zone_offset_hour, dt_zone_offset_minute;
bool dt_zone_offset_negative;
};
/*** PRIVATE FUNCTIONS ********************************************************/
static bool is_leap_year(const struct b_datetime_p *dt)
{
if ((dt->dt_year % 400) == 0) {
return true;
}
if ((dt->dt_year % 4) == 0 && (dt->dt_year % 100) != 0) {
return true;
}
return false;
}
static bool is_year_valid(const struct b_datetime_p *dt)
{
return dt->dt_year >= 0;
}
static bool is_month_valid(const struct b_datetime_p *dt)
{
return dt->dt_month >= 1 && dt->dt_month <= 12;
}
static bool is_day_valid(const struct b_datetime_p *dt)
{
if (dt->dt_day < 1) {
return false;
}
switch (dt->dt_month) {
case 2:
return dt->dt_day <= (is_leap_year(dt) ? 29 : 28);
case 4:
case 6:
case 9:
case 11:
return dt->dt_day <= 30;
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
return dt->dt_day <= 31;
default:
return false;
}
}
static bool is_time_valid(const struct b_datetime_p *dt)
{
if (!(dt->dt_hour >= 0 && dt->dt_hour <= 23)) {
return false;
}
if (!(dt->dt_min >= 0 && dt->dt_min <= 59)) {
return false;
}
if (!(dt->dt_sec >= 0 && dt->dt_sec <= 60)) {
return false;
}
return true;
}
static bool is_zone_valid(const struct b_datetime_p *dt)
{
if (!(dt->dt_zone_offset_hour >= 0 && dt->dt_zone_offset_hour <= 23)) {
return false;
}
if (!(dt->dt_zone_offset_minute >= 0 && dt->dt_zone_offset_minute <= 59)) {
return false;
}
return true;
}
static bool validate(const struct b_datetime_p *dt)
{
if (dt->dt_has_date) {
if (!is_year_valid(dt)) {
return false;
}
if (!is_month_valid(dt)) {
return false;
}
if (!is_day_valid(dt)) {
return false;
}
}
if (dt->dt_has_time) {
if (!is_time_valid(dt)) {
return false;
}
if (!is_zone_valid(dt)) {
return false;
}
}
return true;
}
static b_datetime *parse_rfc3339(const char *s)
{
b_datetime *d = b_datetime_create();
if (!d) {
return NULL;
}
struct b_datetime_p *dt = b_object_get_private(d, B_TYPE_DATETIME);
size_t len = strlen(s);
size_t i = 0, c = 0;
bool has_date = false, has_time = false;
dt->dt_localtime = true;
if (len >= 10 && s[4] == '-' && s[7] == '-') {
has_date = true;
}
if (len >= 8 && s[2] == ':' && s[5] == ':') {
has_time = true;
}
if (len >= 19 && s[4] == '-' && s[7] == '-'
&& (s[10] == 'T' || s[10] == 't' || s[10] == ' ') && s[13] == ':'
&& s[16] == ':') {
has_date = true;
has_time = true;
}
if (!has_date && !has_time) {
goto fail;
}
if (has_date) {
for (c = 0; c < 4; c++, i++) {
if (!isdigit(s[i])) {
goto fail;
}
dt->dt_year *= 10;
dt->dt_year += (s[i] - '0');
}
if (s[i++] != '-') {
goto fail;
}
for (c = 0; c < 2; c++, i++) {
if (!isdigit(s[i])) {
goto fail;
}
dt->dt_month *= 10;
dt->dt_month += (s[i] - '0');
}
if (s[i++] != '-' || dt->dt_month > 12) {
goto fail;
}
for (c = 0; c < 2; c++, i++) {
if (!isdigit(s[i])) {
goto fail;
}
dt->dt_day *= 10;
dt->dt_day += (s[i] - '0');
}
if (dt->dt_day > 31) {
goto fail;
}
}
if ((s[i] == 'T' || s[i] == 't' || s[i] == ' ') && !has_time) {
goto fail;
}
if (has_date && has_time) {
if (s[i] != 'T' && s[i] != 't' && s[i] != ' ') {
goto fail;
}
i++;
}
if (has_time) {
for (c = 0; c < 2; c++, i++) {
if (!isdigit(s[i])) {
goto fail;
}
dt->dt_hour *= 10;
dt->dt_hour += (s[i] - '0');
}
if (s[i++] != ':') {
goto fail;
}
for (c = 0; c < 2; c++, i++) {
if (!isdigit(s[i])) {
goto fail;
}
dt->dt_min *= 10;
dt->dt_min += (s[i] - '0');
}
if (s[i++] != ':') {
goto fail;
}
for (c = 0; c < 2; c++, i++) {
if (!isdigit(s[i])) {
goto fail;
}
dt->dt_sec *= 10;
dt->dt_sec += (s[i] - '0');
}
if (s[i] == '.') {
i++;
for (c = 0; s[i]; c++, i++) {
if (!isdigit(s[i])) {
break;
}
dt->dt_msec *= 10;
dt->dt_msec += (s[i] - '0');
}
if (c == 0) {
goto fail;
}
}
if (s[i] == '+' || s[i] == '-') {
dt->dt_localtime = false;
dt->dt_zone_offset_negative = s[i] == '-';
i++;
for (c = 0; c < 2; c++, i++) {
if (!isdigit(s[i])) {
goto fail;
}
dt->dt_zone_offset_hour *= 10;
dt->dt_zone_offset_hour += (s[i] - '0');
}
if (s[i++] != ':') {
goto fail;
}
for (c = 0; c < 2; c++, i++) {
if (!isdigit(s[i])) {
goto fail;
}
dt->dt_zone_offset_minute *= 10;
dt->dt_zone_offset_minute += (s[i] - '0');
}
} else if (s[i] == 'Z' || s[i] == 'z') {
dt->dt_localtime = false;
i++;
}
}
if (s[i] != 0) {
goto fail;
}
dt->dt_has_date = has_date;
dt->dt_has_time = has_time;
return d;
fail:
b_datetime_unref(d);
return NULL;
}
static enum b_status encode_rfc3339(
const struct b_datetime_p *dt, struct b_stream *out)
{
if (dt->dt_has_date) {
b_stream_write_fmt(
out, NULL, "%04ld-%02ld-%02ld", dt->dt_year,
dt->dt_month, dt->dt_day);
}
if (dt->dt_has_date && dt->dt_has_time) {
b_stream_write_char(out, 'T');
}
if (dt->dt_has_time) {
b_stream_write_fmt(
out, NULL, "%02ld:%02ld:%02ld", dt->dt_hour, dt->dt_min,
dt->dt_sec);
if (dt->dt_msec > 0) {
b_stream_write_fmt(out, NULL, ".%04ld", dt->dt_msec);
}
if (!dt->dt_localtime) {
if (dt->dt_zone_offset_hour == 0
&& dt->dt_zone_offset_minute == 0) {
b_stream_write_char(out, 'Z');
} else {
b_stream_write_fmt(
out, NULL, "%c%02ld:%02ld",
dt->dt_zone_offset_negative ? '-' : '+',
dt->dt_zone_offset_hour,
dt->dt_zone_offset_minute);
}
}
}
return B_SUCCESS;
}
static void datetime_to_string(
const struct b_datetime_p *dt, b_datetime_format format, b_string *dest)
{
struct b_stream *out;
b_string_open_stream(dest, &out);
switch (format) {
case B_DATETIME_FORMAT_RFC3339:
encode_rfc3339(dt, out);
break;
default:
break;
}
b_stream_close(out);
}
static bool datetime_is_localtime(const struct b_datetime_p *dt)
{
return dt->dt_localtime;
}
static bool datetime_has_date(const struct b_datetime_p *dt)
{
return dt->dt_has_date;
}
static bool datetime_has_time(const struct b_datetime_p *dt)
{
return dt->dt_has_time;
}
static long datetime_year(const struct b_datetime_p *dt)
{
return dt->dt_year;
}
static long datetime_month(const struct b_datetime_p *dt)
{
return dt->dt_month;
}
static long datetime_day(const struct b_datetime_p *dt)
{
return dt->dt_day;
}
static long datetime_hour(const struct b_datetime_p *dt)
{
return dt->dt_hour;
}
static long datetime_minute(const struct b_datetime_p *dt)
{
return dt->dt_min;
}
static long datetime_second(const struct b_datetime_p *dt)
{
return dt->dt_sec;
}
static long datetime_subsecond(const struct b_datetime_p *dt)
{
return dt->dt_msec;
}
static bool datetime_zone_offset_is_negative(const struct b_datetime_p *dt)
{
return dt->dt_zone_offset_negative;
}
static long datetime_zone_offset_hour(const struct b_datetime_p *dt)
{
return dt->dt_zone_offset_hour;
}
static long datetime_zone_offset_minute(const struct b_datetime_p *dt)
{
return dt->dt_zone_offset_minute;
}
/*** PUBLIC FUNCTIONS *********************************************************/
b_datetime *b_datetime_parse(enum b_datetime_format format, const char *s)
{
b_datetime *dt = NULL;
switch (format) {
case B_DATETIME_FORMAT_RFC3339:
dt = parse_rfc3339(s);
break;
default:
return NULL;
}
if (!dt) {
return NULL;
}
struct b_datetime_p *p = b_object_get_private(dt, B_TYPE_DATETIME);
if (!validate(p)) {
b_datetime_unref(dt);
return NULL;
}
return dt;
}
void b_datetime_to_string(
const b_datetime *dt, b_datetime_format format, b_string *dest)
{
B_CLASS_DISPATCH_STATIC(
B_TYPE_DATETIME, datetime_to_string, dt, format, dest);
}
bool b_datetime_is_localtime(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_is_localtime, dt);
}
bool b_datetime_has_date(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_has_date, dt);
}
bool b_datetime_has_time(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_has_time, dt);
}
long b_datetime_year(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_year, dt);
}
long b_datetime_month(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_month, dt);
}
long b_datetime_day(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_day, dt);
}
long b_datetime_hour(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_hour, dt);
}
long b_datetime_minute(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_minute, dt);
}
long b_datetime_second(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_second, dt);
}
long b_datetime_subsecond(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_subsecond, dt);
}
bool b_datetime_zone_offset_is_negative(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(
B_TYPE_DATETIME, datetime_zone_offset_is_negative, dt);
}
long b_datetime_zone_offset_hour(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_zone_offset_hour, dt);
}
long b_datetime_zone_offset_minute(const b_datetime *dt)
{
B_CLASS_DISPATCH_STATIC_0(B_TYPE_DATETIME, datetime_zone_offset_minute, dt);
}
/*** VIRTUAL FUNCTIONS ********************************************************/
static void datetime_init(b_object *obj, void *priv)
{
struct b_datetime_p *dt = priv;
}
static void datetime_fini(b_object *obj, void *priv)
{
struct b_datetime_p *dt = priv;
}
static void _datetime_to_string(const b_object *obj, struct b_stream *out)
{
struct b_datetime_p *dt = b_object_get_private(obj, B_TYPE_DATETIME);
if (dt->dt_has_date) {
b_stream_write_fmt(
out, NULL, "%04ld-%02ld-%02ld", dt->dt_year,
dt->dt_month, dt->dt_day);
}
if (dt->dt_has_date && dt->dt_has_time) {
b_stream_write_char(out, ' ');
}
if (dt->dt_has_time) {
b_stream_write_fmt(
out, NULL, "%02ld:%02ld:%02ld", dt->dt_hour, dt->dt_min,
dt->dt_sec);
if (dt->dt_msec > 0) {
b_stream_write_fmt(out, NULL, ".%04ld", dt->dt_msec);
}
if (!dt->dt_localtime) {
b_stream_write_fmt(
out, NULL, " %c%02ld:%02ld",
dt->dt_zone_offset_negative ? '-' : '+',
dt->dt_zone_offset_hour,
dt->dt_zone_offset_minute);
}
}
}
/*** CLASS DEFINITION *********************************************************/
B_TYPE_CLASS_DEFINITION_BEGIN(b_datetime)
B_TYPE_CLASS_INTERFACE_BEGIN(b_object, B_TYPE_OBJECT)
B_INTERFACE_ENTRY(to_string) = _datetime_to_string;
B_TYPE_CLASS_INTERFACE_END(b_object, B_TYPE_OBJECT)
B_TYPE_CLASS_DEFINITION_END(b_datetime)
B_TYPE_DEFINITION_BEGIN(b_datetime)
B_TYPE_ID(0x06a6030b, 0x1e3c, 0x4be2, 0xbd23, 0xf34f4a8e68be);
B_TYPE_CLASS(b_datetime_class);
B_TYPE_INSTANCE_INIT(datetime_init);
B_TYPE_INSTANCE_INIT(datetime_fini);
B_TYPE_DEFINITION_END(b_datetime)