#include #include #include /*** PRIVATE DATA *************************************************************/ struct fx_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 fx_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 fx_datetime_p *dt) { return dt->dt_year >= 0; } static bool is_month_valid(const struct fx_datetime_p *dt) { return dt->dt_month >= 1 && dt->dt_month <= 12; } static bool is_day_valid(const struct fx_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 fx_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 fx_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 fx_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 fx_datetime *parse_rfc3339(const char *s) { fx_datetime *d = fx_datetime_create(); if (!d) { return NULL; } struct fx_datetime_p *dt = fx_object_get_private(d, FX_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: fx_datetime_unref(d); return NULL; } static enum fx_status encode_rfc3339(const struct fx_datetime_p *dt, fx_stream *out) { if (dt->dt_has_date) { fx_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) { fx_stream_write_char(out, 'T'); } if (dt->dt_has_time) { fx_stream_write_fmt( out, NULL, "%02ld:%02ld:%02ld", dt->dt_hour, dt->dt_min, dt->dt_sec); if (dt->dt_msec > 0) { fx_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) { fx_stream_write_char(out, 'Z'); } else { fx_stream_write_fmt( out, NULL, "%c%02ld:%02ld", dt->dt_zone_offset_negative ? '-' : '+', dt->dt_zone_offset_hour, dt->dt_zone_offset_minute); } } } return FX_SUCCESS; } static void datetime_to_string( const struct fx_datetime_p *dt, fx_datetime_format format, fx_stream *dest) { switch (format) { case FX_DATETIME_FORMAT_RFC3339: encode_rfc3339(dt, dest); break; default: break; } } static bool datetime_is_localtime(const struct fx_datetime_p *dt) { return dt->dt_localtime; } static bool datetime_has_date(const struct fx_datetime_p *dt) { return dt->dt_has_date; } static bool datetime_has_time(const struct fx_datetime_p *dt) { return dt->dt_has_time; } static long datetime_year(const struct fx_datetime_p *dt) { return dt->dt_year; } static long datetime_month(const struct fx_datetime_p *dt) { return dt->dt_month; } static long datetime_day(const struct fx_datetime_p *dt) { return dt->dt_day; } static long datetime_hour(const struct fx_datetime_p *dt) { return dt->dt_hour; } static long datetime_minute(const struct fx_datetime_p *dt) { return dt->dt_min; } static long datetime_second(const struct fx_datetime_p *dt) { return dt->dt_sec; } static long datetime_subsecond(const struct fx_datetime_p *dt) { return dt->dt_msec; } static bool datetime_zone_offset_is_negative(const struct fx_datetime_p *dt) { return dt->dt_zone_offset_negative; } static long datetime_zone_offset_hour(const struct fx_datetime_p *dt) { return dt->dt_zone_offset_hour; } static long datetime_zone_offset_minute(const struct fx_datetime_p *dt) { return dt->dt_zone_offset_minute; } /*** PUBLIC FUNCTIONS *********************************************************/ fx_datetime *fx_datetime_parse(enum fx_datetime_format format, const char *s) { fx_datetime *dt = NULL; switch (format) { case FX_DATETIME_FORMAT_RFC3339: dt = parse_rfc3339(s); break; default: return NULL; } if (!dt) { return NULL; } struct fx_datetime_p *p = fx_object_get_private(dt, FX_TYPE_DATETIME); if (!validate(p)) { fx_datetime_unref(dt); return NULL; } return dt; } void fx_datetime_to_string( const fx_datetime *dt, fx_datetime_format format, fx_stream *dest) { FX_CLASS_DISPATCH_STATIC( FX_TYPE_DATETIME, datetime_to_string, dt, format, dest); } bool fx_datetime_is_localtime(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_is_localtime, dt); } bool fx_datetime_has_date(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_has_date, dt); } bool fx_datetime_has_time(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_has_time, dt); } long fx_datetime_year(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_year, dt); } long fx_datetime_month(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_month, dt); } long fx_datetime_day(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_day, dt); } long fx_datetime_hour(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_hour, dt); } long fx_datetime_minute(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_minute, dt); } long fx_datetime_second(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_second, dt); } long fx_datetime_subsecond(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_subsecond, dt); } bool fx_datetime_zone_offset_is_negative(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0( FX_TYPE_DATETIME, datetime_zone_offset_is_negative, dt); } long fx_datetime_zone_offset_hour(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_zone_offset_hour, dt); } long fx_datetime_zone_offset_minute(const fx_datetime *dt) { FX_CLASS_DISPATCH_STATIC_0(FX_TYPE_DATETIME, datetime_zone_offset_minute, dt); } /*** VIRTUAL FUNCTIONS ********************************************************/ static void datetime_init(fx_object *obj, void *priv) { struct fx_datetime_p *dt = priv; } static void datetime_fini(fx_object *obj, void *priv) { struct fx_datetime_p *dt = priv; } static void _datetime_to_string(const fx_object *obj, fx_stream *out) { struct fx_datetime_p *dt = fx_object_get_private(obj, FX_TYPE_DATETIME); if (dt->dt_has_date) { fx_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) { fx_stream_write_char(out, ' '); } if (dt->dt_has_time) { fx_stream_write_fmt( out, NULL, "%02ld:%02ld:%02ld", dt->dt_hour, dt->dt_min, dt->dt_sec); if (dt->dt_msec > 0) { fx_stream_write_fmt(out, NULL, ".%04ld", dt->dt_msec); } if (!dt->dt_localtime) { fx_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 *********************************************************/ FX_TYPE_CLASS_DEFINITION_BEGIN(fx_datetime) FX_TYPE_CLASS_INTERFACE_BEGIN(fx_object, FX_TYPE_OBJECT) FX_INTERFACE_ENTRY(to_string) = _datetime_to_string; FX_TYPE_CLASS_INTERFACE_END(fx_object, FX_TYPE_OBJECT) FX_TYPE_CLASS_DEFINITION_END(fx_datetime) FX_TYPE_DEFINITION_BEGIN(fx_datetime) FX_TYPE_ID(0x06a6030b, 0x1e3c, 0x4be2, 0xbd23, 0xf34f4a8e68be); FX_TYPE_CLASS(fx_datetime_class); FX_TYPE_INSTANCE_PRIVATE(struct fx_datetime_p); FX_TYPE_INSTANCE_INIT(datetime_init); FX_TYPE_INSTANCE_FINI(datetime_fini); FX_TYPE_DEFINITION_END(fx_datetime)