object: add a type for storing, parsing, and stringifying date/time values
This commit is contained in:
489
object/datetime.c
Normal file
489
object/datetime.c
Normal file
@@ -0,0 +1,489 @@
|
|||||||
|
#include "datetime.h"
|
||||||
|
|
||||||
|
#include "blue/core/stream.h"
|
||||||
|
#include "blue/object/string.h"
|
||||||
|
|
||||||
|
#include <blue/object/datetime.h>
|
||||||
|
|
||||||
|
static void datetime_to_string(const struct b_object *obj, struct b_stream *out);
|
||||||
|
|
||||||
|
static struct b_object_type string_type = {
|
||||||
|
.t_name = "corelib::string",
|
||||||
|
.t_flags = B_OBJECT_FUNDAMENTAL,
|
||||||
|
.t_id = B_OBJECT_TYPE_DATETIME,
|
||||||
|
.t_instance_size = sizeof(struct b_datetime),
|
||||||
|
.t_to_string = datetime_to_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct b_datetime *b_datetime_create(void)
|
||||||
|
{
|
||||||
|
return (struct b_datetime *)b_object_type_instantiate(&string_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_leap_year(const struct b_datetime *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 *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_year >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_month_valid(const struct b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_month >= 1 && dt->dt_month <= 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_day_valid(const struct b_datetime *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 *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 *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 *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;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct b_datetime *parse_rfc3339(const char *s)
|
||||||
|
{
|
||||||
|
struct b_datetime *dt = b_datetime_create();
|
||||||
|
if (!dt) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 dt;
|
||||||
|
fail:
|
||||||
|
b_datetime_release(dt);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct b_datetime *b_datetime_parse(enum b_datetime_format format, const char *s)
|
||||||
|
{
|
||||||
|
struct b_datetime *dt = NULL;
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case B_DATETIME_FORMAT_RFC3339:
|
||||||
|
dt = parse_rfc3339(s);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dt) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validate(dt)) {
|
||||||
|
b_datetime_release(dt);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum b_status encode_rfc3339(const struct b_datetime *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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void b_datetime_to_string(
|
||||||
|
const b_datetime *dt, b_datetime_format format, struct 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool b_datetime_is_localtime(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_localtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool b_datetime_has_date(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_has_date;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool b_datetime_has_time(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_has_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
long b_datetime_year(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_year;
|
||||||
|
}
|
||||||
|
|
||||||
|
long b_datetime_month(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_month;
|
||||||
|
}
|
||||||
|
|
||||||
|
long b_datetime_day(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_day;
|
||||||
|
}
|
||||||
|
|
||||||
|
long b_datetime_hour(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
long b_datetime_minute(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_min;
|
||||||
|
}
|
||||||
|
|
||||||
|
long b_datetime_second(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
long b_datetime_subsecond(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_msec;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool b_datetime_zone_offset_is_negative(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_zone_offset_negative;
|
||||||
|
}
|
||||||
|
|
||||||
|
long b_datetime_zone_offset_hour(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_zone_offset_hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
long b_datetime_zone_offset_minute(const b_datetime *dt)
|
||||||
|
{
|
||||||
|
return dt->dt_zone_offset_minute;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void datetime_to_string(const struct b_object *obj, struct b_stream *out)
|
||||||
|
{
|
||||||
|
struct b_datetime *dt = B_DATETIME(obj);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
object/datetime.h
Normal file
17
object/datetime.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef _BLUELIB_DATETIME_H_
|
||||||
|
#define _BLUELIB_DATETIME_H_
|
||||||
|
|
||||||
|
#include "object.h"
|
||||||
|
|
||||||
|
struct b_datetime {
|
||||||
|
struct b_object dt_base;
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
49
object/include/blue/object/datetime.h
Normal file
49
object/include/blue/object/datetime.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#ifndef BLUELIB_DATETIME_H_
|
||||||
|
#define BLUELIB_DATETIME_H_
|
||||||
|
|
||||||
|
#include <blue/core/status.h>
|
||||||
|
#include <blue/object/object.h>
|
||||||
|
#include <blue/object/type.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
struct b_string;
|
||||||
|
|
||||||
|
#define B_DATETIME(p) ((b_datetime *)(p))
|
||||||
|
|
||||||
|
typedef struct b_datetime b_datetime;
|
||||||
|
|
||||||
|
typedef enum b_datetime_format {
|
||||||
|
B_DATETIME_FORMAT_RFC3339 = 1,
|
||||||
|
} b_datetime_format;
|
||||||
|
|
||||||
|
BLUE_API b_datetime *b_datetime_create(void);
|
||||||
|
BLUE_API b_datetime *b_datetime_parse(b_datetime_format format, const char *s);
|
||||||
|
BLUE_API void b_datetime_to_string(
|
||||||
|
const b_datetime *dt, b_datetime_format format, struct b_string *dest);
|
||||||
|
|
||||||
|
static inline b_datetime *b_datetime_retain(b_datetime *dt)
|
||||||
|
{
|
||||||
|
return B_DATETIME(b_retain(B_OBJECT(dt)));
|
||||||
|
}
|
||||||
|
static inline void b_datetime_release(b_datetime *dt)
|
||||||
|
{
|
||||||
|
b_release(B_OBJECT(dt));
|
||||||
|
}
|
||||||
|
|
||||||
|
BLUE_API bool b_datetime_is_localtime(const b_datetime *dt);
|
||||||
|
BLUE_API bool b_datetime_has_date(const b_datetime *dt);
|
||||||
|
BLUE_API bool b_datetime_has_time(const b_datetime *dt);
|
||||||
|
|
||||||
|
BLUE_API long b_datetime_year(const b_datetime *dt);
|
||||||
|
BLUE_API long b_datetime_month(const b_datetime *dt);
|
||||||
|
BLUE_API long b_datetime_day(const b_datetime *dt);
|
||||||
|
BLUE_API long b_datetime_hour(const b_datetime *dt);
|
||||||
|
BLUE_API long b_datetime_minute(const b_datetime *dt);
|
||||||
|
BLUE_API long b_datetime_second(const b_datetime *dt);
|
||||||
|
BLUE_API long b_datetime_subsecond(const b_datetime *dt);
|
||||||
|
|
||||||
|
BLUE_API bool b_datetime_zone_offset_is_negative(const b_datetime *dt);
|
||||||
|
BLUE_API long b_datetime_zone_offset_hour(const b_datetime *dt);
|
||||||
|
BLUE_API long b_datetime_zone_offset_minute(const b_datetime *dt);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -30,6 +30,7 @@ typedef enum b_fundamental_type_id {
|
|||||||
B_OBJECT_TYPE_PATH,
|
B_OBJECT_TYPE_PATH,
|
||||||
B_OBJECT_TYPE_FILE,
|
B_OBJECT_TYPE_FILE,
|
||||||
B_OBJECT_TYPE_DIRECTORY,
|
B_OBJECT_TYPE_DIRECTORY,
|
||||||
|
B_OBJECT_TYPE_DATETIME,
|
||||||
} b_fundamental_type_id;
|
} b_fundamental_type_id;
|
||||||
|
|
||||||
typedef enum b_object_type_flags {
|
typedef enum b_object_type_flags {
|
||||||
|
|||||||
Reference in New Issue
Block a user