local-time string to epoch seconds

This commit is contained in:
John Kerl 2018-04-28 12:51:35 -07:00
parent 313824953a
commit edeedaa2fd
11 changed files with 105 additions and 60 deletions

View file

@ -298,6 +298,8 @@ static function_lookup_t FUNCTION_LOOKUP_TABLE[] = {
"Formats floating-point seconds as in\nfsec2hms(5000.25) = \"01:23:20.250000\""},
{FUNC_CLASS_TIME, "gmt2sec", 1,0, "Parses GMT timestamp as integer seconds since\nthe epoch."},
{FUNC_CLASS_TIME, "localtime2sec", 1,0, "Parses local timestamp as integer seconds since\n"
"the epoch. Consults $TZ environment variable."},
{FUNC_CLASS_TIME, "hms2fsec", 1,0,
"Recovers floating-point seconds as in\nhms2fsec(\"01:23:20.250000\") = 5000.250000"},
@ -317,8 +319,7 @@ static function_lookup_t FUNCTION_LOOKUP_TABLE[] = {
"as GMT timestamp with year-month-date, e.g. sec2gmtdate(1440768801.7) = \"2015-08-28\".\n"
"Leaves non-numbers as-is."},
{FUNC_CLASS_TIME, "sec2localtime", 1,0,
"Formats seconds since epoch (integer part)\n"
{FUNC_CLASS_TIME, "sec2localtime", 1,0, "Formats seconds since epoch (integer part)\n"
"as local timestamp, e.g. sec2localtime(1440768801.7) = \"2015-08-28T13:33:21Z\".\n"
"Consults $TZ environment variable. Leaves non-numbers as-is."},
{FUNC_CLASS_TIME, "sec2localtime", 2,0,
@ -346,6 +347,8 @@ static function_lookup_t FUNCTION_LOOKUP_TABLE[] = {
"Parses timestamp as floating-point seconds since the epoch,\n"
"e.g. strptime(\"2015-08-28T13:33:21Z\",\"%Y-%m-%dT%H:%M:%SZ\") = 1440768801.000000,\n"
"and strptime(\"2015-08-28T13:33:21.345Z\",\"%Y-%m-%dT%H:%M:%SZ\") = 1440768801.345000."},
{FUNC_CLASS_TIME, "strptime_local", 2,0,
"Like strptime, but consults $TZ environment variable to find and use local timezone."},
{FUNC_CLASS_TIME, "systime", 0,0,
"Floating-point seconds since the epoch,\n"
"e.g. 1440768801.748936." },
@ -1153,6 +1156,7 @@ static rval_evaluator_t* fmgr_alloc_evaluator_from_unary_func_name(char* fnnm, r
} else if (streq(fnnm, "fsec2dhms")) { return rval_evaluator_alloc_from_s_f_func(s_f_fsec2dhms_func, parg1);
} else if (streq(fnnm, "fsec2hms")) { return rval_evaluator_alloc_from_s_f_func(s_f_fsec2hms_func, parg1);
} else if (streq(fnnm, "gmt2sec")) { return rval_evaluator_alloc_from_i_s_func(i_s_gmt2sec_func, parg1);
} else if (streq(fnnm, "localtime2sec")) { return rval_evaluator_alloc_from_i_s_func(i_s_localtime2sec_func, parg1);
} else if (streq(fnnm, "hexfmt")) { return rval_evaluator_alloc_from_x_x_func(s_x_hexfmt_func, parg1);
} else if (streq(fnnm, "hms2fsec")) { return rval_evaluator_alloc_from_f_s_func(f_s_hms2fsec_func, parg1);
} else if (streq(fnnm, "hms2sec")) { return rval_evaluator_alloc_from_f_s_func(i_s_hms2sec_func, parg1);
@ -1231,6 +1235,7 @@ static rval_evaluator_t* fmgr_alloc_evaluator_from_binary_func_name(char* fnnm,
} else if (streq(fnnm, "strftime")) { return rval_evaluator_alloc_from_x_ns_func(s_ns_strftime_func, parg1, parg2);
} else if (streq(fnnm, "strftime_local")) { return rval_evaluator_alloc_from_x_ns_func(s_ns_strftime_local_func, parg1, parg2);
} else if (streq(fnnm, "strptime")) { return rval_evaluator_alloc_from_x_ss_func(i_ss_strptime_func, parg1, parg2);
} else if (streq(fnnm, "strptime_local")) { return rval_evaluator_alloc_from_x_ss_func(i_ss_strptime_local_func, parg1, parg2);
} else { return NULL; }
}

View file

@ -15,6 +15,7 @@ libmlr_la_SOURCES= free_flags.h \
mlrstat.h \
mlrregex.c \
mlrregex.h \
mlrtimezone.h \
mlrutil.c \
mlrutil.h \
mlrval.c \

View file

@ -41,21 +41,33 @@ char *mlr_arch_strptime(const char *s, const char *format, struct tm *ptm) {
// ----------------------------------------------------------------
// See the GNU timegm manpage -- this is what it does.
time_t mlr_arch_timegm(struct tm* ptm) {
time_t mlr_arch_timegm(struct tm* ptm, timezone_handling_t timezone_handling) {
#ifdef MLR_ON_MSYS2
// Crap, we're offering limited Windows support :(
if (timezone_handling != TIMEZONE_HANDLING_GMT) {
fprintf(stderr, "%s: Local timezone is not handled for output.\n",
MLR_GLOBALS.bargv0);
exit(1);
}
return nlnet_timegm(ptm);
#else
time_t ret;
char* tz = getenv("TZ");
mlr_arch_setenv("TZ", "GMT0");
tzset();
ret = mktime(ptm);
if (tz) {
mlr_arch_setenv("TZ", tz);
if (timezone_handling == TIMEZONE_HANDLING_GMT) {
char* tz = getenv("TZ");
mlr_arch_setenv("TZ", "GMT0");
tzset();
ret = mktime(ptm);
if (tz) {
mlr_arch_setenv("TZ", tz);
} else {
mlr_arch_unsetenv("TZ");
}
tzset();
} else {
mlr_arch_unsetenv("TZ");
ret = mktime(ptm);
}
tzset();
return ret;
#endif
}

View file

@ -3,6 +3,7 @@
#include <stdio.h>
#include <time.h>
#include "mlrtimezone.h"
// ================================================================
// Miller compiles without ifdefs on Linux, BSDs, and MacOSX -- but
@ -35,6 +36,6 @@ int mlr_arch_setenv(const char *name, const char *value);
int mlr_arch_unsetenv(const char *name);
char *mlr_arch_strptime(const char *s, const char *format, struct tm *ptm);
time_t mlr_arch_timegm(struct tm* ptm);
time_t mlr_arch_timegm(struct tm* ptm, timezone_handling_t timezone_handling);
#endif // MLR_ARCH_H

View file

@ -27,7 +27,7 @@ double get_systime() {
// formatting the seconds with a desired number of decimal places.
char* mlr_alloc_time_string_from_seconds(double seconds_since_the_epoch, char* format_string,
time_from_seconds_choice_t time_from_seconds_choice)
timezone_handling_t timezone_handling)
{
// 1. Split out the integer seconds since the epoch, which the stdlib can handle, and
@ -36,11 +36,11 @@ char* mlr_alloc_time_string_from_seconds(double seconds_since_the_epoch, char* f
double fracsec = seconds_since_the_epoch - iseconds;
struct tm tm;
switch(time_from_seconds_choice) {
case TIME_FROM_SECONDS_GMT:
switch(timezone_handling) {
case TIMEZONE_HANDLING_GMT:
tm = *gmtime(&iseconds); // No gmtime_r on Windows so just use gmtime.
break;
case TIME_FROM_SECONDS_LOCAL:
case TIMEZONE_HANDLING_LOCAL:
tm = *localtime(&iseconds); // No gmtime_r on Windows so just use gmtime.
break;
default:
@ -170,8 +170,9 @@ char* mlr_alloc_time_string_from_seconds(double seconds_since_the_epoch, char* f
// to play some tricks, inspired in part by some ideas on StackOverflow. Special shout-out
// to @tinkerware on Github for the push in the right direction! :)
double mlr_seconds_from_time_string(char* time_string, char* format_string) {
double mlr_seconds_from_time_string(char* time_string, char* format_string,
timezone_handling_t timezone_handling)
{
struct tm tm;
// 1. Just try strptime on the input as-is and return quickly if it's OK.
@ -183,7 +184,7 @@ double mlr_seconds_from_time_string(char* time_string, char* format_string) {
MLR_GLOBALS.bargv0, time_string, format_string, MLR_GLOBALS.bargv0);
exit(1);
}
return (double)mlr_arch_timegm(&tm);
return (double)mlr_arch_timegm(&tm, timezone_handling);
}
// 2. Now either there's floating-point seconds in the input, or something else is wrong.
@ -269,5 +270,5 @@ double mlr_seconds_from_time_string(char* time_string, char* format_string) {
free(elided_fraction_input);
// 8. Convert the tm to a time_t (seconds since the epoch) and then add the fractional seconds.
return mlr_arch_timegm(&tm) + fractional_seconds;
return mlr_arch_timegm(&tm, timezone_handling) + fractional_seconds;
}

View file

@ -5,11 +5,7 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
typedef enum _time_from_seconds_choice_t {
TIME_FROM_SECONDS_GMT,
TIME_FROM_SECONDS_LOCAL
} time_from_seconds_choice_t;
#include "mlrtimezone.h"
// Seconds since the epoch.
double get_systime();
@ -17,7 +13,8 @@ double get_systime();
// These use the gmtime/timegm and strftime/strptime standard-library functions, with the addition
// of support for floating-point seconds since the epoch.
char* mlr_alloc_time_string_from_seconds(double seconds_since_the_epoch, char* format,
time_from_seconds_choice_t time_from_seconds_choice);
double mlr_seconds_from_time_string(char* string, char* format);
timezone_handling_t timezone_handling);
double mlr_seconds_from_time_string(char* string, char* format,
timezone_handling_t timezone_handling);
#endif // MLRDATETIME_H

9
c/lib/mlrtimezone.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef MLRTIMEZONE_H
#define MLRTIMEZONE_H
typedef enum _timezone_handling_t {
TIMEZONE_HANDLING_GMT,
TIMEZONE_HANDLING_LOCAL
} timezone_handling_t;
#endif // MLRTIMEZONE_H

View file

@ -381,7 +381,7 @@ mv_t s_x_typeof_func(mv_t* pval1) {
// ----------------------------------------------------------------
// Precondition: psec is either int or float.
mv_t time_string_from_seconds(mv_t* psec, char* format,
time_from_seconds_choice_t time_from_seconds_choice)
timezone_handling_t timezone_handling)
{
double seconds_since_the_epoch = 0.0;
if (psec->type == MT_FLOAT) {
@ -394,14 +394,14 @@ mv_t time_string_from_seconds(mv_t* psec, char* format,
}
char* string = mlr_alloc_time_string_from_seconds(seconds_since_the_epoch, format,
time_from_seconds_choice);
timezone_handling);
return mv_from_string_with_free(string);
}
// ----------------------------------------------------------------
static mv_t sec2gmt_s_n(mv_t* pa) {
return time_string_from_seconds(pa, ISO8601_TIME_FORMAT, TIME_FROM_SECONDS_GMT);
return time_string_from_seconds(pa, ISO8601_TIME_FORMAT, TIMEZONE_HANDLING_GMT);
}
static mv_unary_func_t* sec2gmt_dispositions[MT_DIM] = {
@ -418,15 +418,15 @@ mv_t s_x_sec2gmt_func(mv_t* pval1) { return (sec2gmt_dispositions[pval1->type])(
// Precondition: val2 is already asserted int
static mv_t sec2gmt_s_ni(mv_t* pa, mv_t* pb) {
switch (pb->u.intv) {
case 1: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_1, TIME_FROM_SECONDS_GMT); break;
case 2: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_2, TIME_FROM_SECONDS_GMT); break;
case 3: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_3, TIME_FROM_SECONDS_GMT); break;
case 4: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_4, TIME_FROM_SECONDS_GMT); break;
case 5: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_5, TIME_FROM_SECONDS_GMT); break;
case 6: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_6, TIME_FROM_SECONDS_GMT); break;
case 7: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_7, TIME_FROM_SECONDS_GMT); break;
case 8: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_8, TIME_FROM_SECONDS_GMT); break;
case 9: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_9, TIME_FROM_SECONDS_GMT); break;
case 1: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_1, TIMEZONE_HANDLING_GMT); break;
case 2: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_2, TIMEZONE_HANDLING_GMT); break;
case 3: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_3, TIMEZONE_HANDLING_GMT); break;
case 4: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_4, TIMEZONE_HANDLING_GMT); break;
case 5: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_5, TIMEZONE_HANDLING_GMT); break;
case 6: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_6, TIMEZONE_HANDLING_GMT); break;
case 7: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_7, TIMEZONE_HANDLING_GMT); break;
case 8: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_8, TIMEZONE_HANDLING_GMT); break;
case 9: return time_string_from_seconds(pa, ISO8601_TIME_FORMAT_9, TIMEZONE_HANDLING_GMT); break;
default: return mv_error();
}
}
@ -444,7 +444,7 @@ mv_t s_xi_sec2gmt_func(mv_t* pval1, mv_t* pval2) { return (sec2gmtn_dispositions
// ----------------------------------------------------------------
static mv_t sec2gmtdate_s_n(mv_t* pa) {
return time_string_from_seconds(pa, ISO8601_DATE_FORMAT, TIME_FROM_SECONDS_GMT);
return time_string_from_seconds(pa, ISO8601_DATE_FORMAT, TIMEZONE_HANDLING_GMT);
}
static mv_unary_func_t* sec2gmtdate_dispositions[MT_DIM] = {
@ -461,7 +461,7 @@ mv_t s_x_sec2gmtdate_func(mv_t* pval1) { return (sec2gmtdate_dispositions[pval1-
// ----------------------------------------------------------------
static mv_t sec2localtime_s_n(mv_t* pa) {
return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT, TIME_FROM_SECONDS_LOCAL);
return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT, TIMEZONE_HANDLING_LOCAL);
}
static mv_unary_func_t* sec2localtime_dispositions[MT_DIM] = {
@ -478,15 +478,15 @@ mv_t s_x_sec2localtime_func(mv_t* pval1) { return (sec2localtime_dispositions[pv
// Precondition: val2 is already asserted int
static mv_t sec2localtime_s_ni(mv_t* pa, mv_t* pb) {
switch (pb->u.intv) {
case 1: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_1, TIME_FROM_SECONDS_LOCAL); break;
case 2: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_2, TIME_FROM_SECONDS_LOCAL); break;
case 3: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_3, TIME_FROM_SECONDS_LOCAL); break;
case 4: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_4, TIME_FROM_SECONDS_LOCAL); break;
case 5: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_5, TIME_FROM_SECONDS_LOCAL); break;
case 6: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_6, TIME_FROM_SECONDS_LOCAL); break;
case 7: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_7, TIME_FROM_SECONDS_LOCAL); break;
case 8: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_8, TIME_FROM_SECONDS_LOCAL); break;
case 9: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_9, TIME_FROM_SECONDS_LOCAL); break;
case 1: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_1, TIMEZONE_HANDLING_LOCAL); break;
case 2: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_2, TIMEZONE_HANDLING_LOCAL); break;
case 3: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_3, TIMEZONE_HANDLING_LOCAL); break;
case 4: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_4, TIMEZONE_HANDLING_LOCAL); break;
case 5: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_5, TIMEZONE_HANDLING_LOCAL); break;
case 6: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_6, TIMEZONE_HANDLING_LOCAL); break;
case 7: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_7, TIMEZONE_HANDLING_LOCAL); break;
case 8: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_8, TIMEZONE_HANDLING_LOCAL); break;
case 9: return time_string_from_seconds(pa, ISO8601_LOCAL_TIME_FORMAT_9, TIMEZONE_HANDLING_LOCAL); break;
default: return mv_error();
}
}
@ -504,7 +504,7 @@ mv_t s_xi_sec2localtime_func(mv_t* pval1, mv_t* pval2) { return (sec2localn_disp
// ----------------------------------------------------------------
static mv_t sec2localdate_s_n(mv_t* pa) {
return time_string_from_seconds(pa, ISO8601_DATE_FORMAT, TIME_FROM_SECONDS_LOCAL);
return time_string_from_seconds(pa, ISO8601_DATE_FORMAT, TIMEZONE_HANDLING_LOCAL);
}
static mv_unary_func_t* sec2localdate_dispositions[MT_DIM] = {
@ -522,35 +522,50 @@ mv_t s_x_sec2localdate_func(mv_t* pval1) { return (sec2localdate_dispositions[pv
// ----------------------------------------------------------------
mv_t s_ns_strftime_func(mv_t* pval1, mv_t* pval2) {
mv_t rv = time_string_from_seconds(pval1, pval2->u.strv, TIME_FROM_SECONDS_GMT);
mv_t rv = time_string_from_seconds(pval1, pval2->u.strv, TIMEZONE_HANDLING_GMT);
mv_free(pval2);
return rv;
}
// ----------------------------------------------------------------
mv_t s_ns_strftime_local_func(mv_t* pval1, mv_t* pval2) {
mv_t rv = time_string_from_seconds(pval1, pval2->u.strv, TIME_FROM_SECONDS_LOCAL);
mv_t rv = time_string_from_seconds(pval1, pval2->u.strv, TIMEZONE_HANDLING_LOCAL);
mv_free(pval2);
return rv;
}
// ----------------------------------------------------------------
static mv_t seconds_from_time_string(char* string, char* format) {
static mv_t seconds_from_time_string(char* string, char* format,
timezone_handling_t timezone_handling)
{
if (*string == '\0') {
return mv_empty();
} else {
return mv_from_float(mlr_seconds_from_time_string(string, format));
return mv_from_float(mlr_seconds_from_time_string(string, format, timezone_handling));
}
}
mv_t i_s_gmt2sec_func(mv_t* pval1) {
mv_t rv = seconds_from_time_string(pval1->u.strv, ISO8601_TIME_FORMAT);
mv_t rv = seconds_from_time_string(pval1->u.strv, ISO8601_TIME_FORMAT, TIMEZONE_HANDLING_GMT);
mv_free(pval1);
return rv;
}
mv_t i_s_localtime2sec_func(mv_t* pval1) {
mv_t rv = seconds_from_time_string(pval1->u.strv, ISO8601_LOCAL_TIME_FORMAT, TIMEZONE_HANDLING_LOCAL);
mv_free(pval1);
return rv;
}
mv_t i_ss_strptime_func(mv_t* pval1, mv_t* pval2) {
mv_t rv = seconds_from_time_string(pval1->u.strv, pval2->u.strv);
mv_t rv = seconds_from_time_string(pval1->u.strv, pval2->u.strv, TIMEZONE_HANDLING_GMT);
mv_free(pval1);
mv_free(pval2);
return rv;
}
mv_t i_ss_strptime_local_func(mv_t* pval1, mv_t* pval2) {
mv_t rv = seconds_from_time_string(pval1->u.strv, pval2->u.strv, TIMEZONE_HANDLING_LOCAL);
mv_free(pval1);
mv_free(pval2);
return rv;

View file

@ -217,9 +217,13 @@ mv_t s_xi_sec2localtime_func(mv_t* pval1, mv_t* pval2);
mv_t s_x_sec2localdate_func(mv_t* pval1);
mv_t i_s_gmt2sec_func(mv_t* pval1);
mv_t i_s_localtime2sec_func(mv_t* pval1);
mv_t s_ns_strftime_func(mv_t* pval1, mv_t* pval2);
mv_t s_ns_strftime_local_func(mv_t* pval1, mv_t* pval2);
mv_t i_ss_strptime_func(mv_t* pval1, mv_t* pval2);
mv_t i_ss_strptime_local_func(mv_t* pval1, mv_t* pval2);
mv_t s_i_sec2hms_func(mv_t* pval1);
mv_t s_f_fsec2hms_func(mv_t* pval1);
@ -231,7 +235,7 @@ mv_t i_s_dhms2sec_func(mv_t* pval1);
mv_t f_s_dhms2fsec_func(mv_t* pval1);
mv_t time_string_from_seconds(mv_t* psec, char* format,
time_from_seconds_choice_t time_from_seconds_choice);
timezone_handling_t timezone_handling);
// ----------------------------------------------------------------
// arg2 evaluates to string via compound expression; regexes compiled on each call

View file

@ -133,7 +133,7 @@ static sllv_t* mapper_sec2gmt_process(lrec_t* pinrec, context_t* pctx, void* pvs
} else {
mv_t mval = mv_scan_number_nullable(sval);
if (!mv_is_error(&mval)) {
mv_t stamp = time_string_from_seconds(&mval, pstate->format_string, TIME_FROM_SECONDS_GMT);
mv_t stamp = time_string_from_seconds(&mval, pstate->format_string, TIMEZONE_HANDLING_GMT);
lrec_put(pinrec, name, stamp.u.strv, FREE_ENTRY_VALUE);
}
}

View file

@ -92,7 +92,7 @@ static sllv_t* mapper_sec2gmtdate_process(lrec_t* pinrec, context_t* pctx, void*
mv_t mval = mv_scan_number_nullable(sval);
if (!mv_is_error(&mval)) {
mv_t stamp = time_string_from_seconds(&mval, ISO8601_DATE_FORMAT,
TIME_FROM_SECONDS_GMT);
TIMEZONE_HANDLING_GMT);
lrec_put(pinrec, name, stamp.u.strv, FREE_ENTRY_VALUE);
}
}