* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
* Portions Copyright (c) 2021, openGauss Contributors
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* -------------------------------------------------------------------------
*
* gs_job_calendar.cpp
* Calendaring syntax for dbe jobs.
*
* IDENTIFICATION
* src/gausskernel/process/job/gs_job_calendar.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/dbe_scheduler.h"
* repeat_interval = frequency_clause
* [; interval=?] [; bymonth=?] [; byweekno=?]
* [; byyearday=?] [; bymonthday=?] [; byday=?]
* [; byhour=?] [; byminute=?] [; bysecond=?]
*
* frequency_clause = "FREQ" "=" frequency
* frequency = "YEARLY" | "MONTHLY" | "WEEKLY" | "DAILY" |
* "HOURLY" | "MINUTELY" | "SECONDLY"
*
* Note:
* POSIX time (tm) used in this section has following flags:
* int tm_sec Seconds [0,60]. (with leap second!!)
* int tm_min Minutes [0,59].
* int tm_hour Hour [0,23].
* int tm_mday Day of month [1,31].
* int tm_mon Month of year [0,11]. (not ISO standard!! [1,12])
* int tm_year Years since 1900.
* int tm_wday Day of week [0,6] (Sunday =0). (not ISO standard!! sun =7)
* int tm_yday Day of year [0,365]. (not ISO standard!! [1,366])
* int tm_isdst Daylight Savings flag.
*
*/
static bool IsLegalIntervalStr(const char* str, bool numeric_only = false);
static char *get_calendar_clause_val(char **tokens, const char *clause, bool numeric_only = false);
static char **tokenize_str(char *src, const char *delims, int fields);
static int validate_field_names(char **toks);
* Interpreter functions
* Interpret calendaring syntax.
*/
static bool get_calendar_freqency(Calendar calendar, char **tokens);
static void get_calendar_n_interval(Calendar calendar, char **tokens);
static char *get_calendar_bymonth_val(Calendar calendar, char **tokens);
static void get_calendar_bymonth(Calendar calendar, char **tokens);
static void get_calendar_byweekno(Calendar calendar, char **tokens);
static void get_calendar_byyearday(Calendar calendar, char **tokens);
static void get_calendar_bydate(Calendar calendar, char **tokens);
static char *get_calendar_bymonthday_val(Calendar calendar, char **tokens);
static void get_calendar_bymonthday(Calendar calendar, char **tokens);
static void get_calendar_byday(Calendar calendar, char **tokens);
static char *get_calendar_byhour_val(Calendar calendar, char **tokens);
static void get_calendar_byhour(Calendar calendar, char **tokens);
static char *get_calendar_byminute_val(Calendar calendar, char **tokens);
static void get_calendar_byminute(Calendar calendar, char **tokens);
static char *get_calendar_bysecond_val(Calendar calendar, char **tokens);
static void get_calendar_bysecond(Calendar calendar, char **tokens);
Calendar interpret_calendar_interval(char *calendar_str);
* Evaluation functions
* Calculate calendaring interval.
*/
static Interval *get_calendar_period(Calendar calendar, int num_of_period = 1);
static void copy_calendar_dates(TimestampTz *timeline, int nvals, int cnt);
static bool validate_calendar_monthday(int year, int month, int mday);
static void fastforward_calendar_period(Calendar calendar, TimestampTz *start_date, TimestampTz date_after);
static bool recheck_calendar_period(Calendar calendar, TimestampTz start_date, TimestampTz next_date);
static int timestamp_cmp_func(const void *dt1, const void *dt2);
static TimestampTz get_next_calendar_period(Calendar calendar, TimestampTz base_date);
static bool find_nearest_calendar_time(Calendar calendar, TimestampTz *timeline, TimestampTz start, int cnt,
TimestampTz *nearest);
static void prepare_calendar_period(Calendar calendar, TimestampTz base_date, TimestampTz *timeline);
static void evaluate_calendar_bymonth(Calendar calendar, TimestampTz *timeline, int *cnt);
static void evaluate_calendar_byweekno(Calendar calendar, TimestampTz *timeline, int *cnt);
static void evaluate_calendar_byyearday(Calendar calendar, TimestampTz *timeline, int *cnt);
static void evaluate_calendar_bymonthday(Calendar calendar, TimestampTz *timeline, int *cnt);
static void evaluate_calendar_byhour(Calendar calendar, TimestampTz *timeline, int *cnt);
static void evaluate_calendar_byminute(Calendar calendar, TimestampTz *timeline, int *cnt);
static void evaluate_calendar_bysecond(Calendar calendar, TimestampTz *timeline, int *cnt);
static bool evaluate_calendar_period(Calendar calendar, TimestampTz *timeline, TimestampTz *sub_timeline,
TimestampTz start_date, TimestampTz *next_date);
static TimestampTz evaluate_calendar_interval(Calendar calendar, TimestampTz start_date);
* @brief IsLegalIntervalStr
* Is interval legal?
* @param str
* @param numeric_only
* @return true legal
* @return false illegal
*/
static bool IsLegalIntervalStr(const char* str, bool numeric_only)
{
size_t NBytes = (unsigned int)strlen(str);
if (NBytes > (MAX_CALENDAR_FIELD_LEN)) {
return false;
}
if (numeric_only) {
for (size_t i = 0; i < NBytes; i++) {
if (!isdigit(str[i]) && str[i] != ',' && str[i] != ' ' && str[i] != '-') {
return false;
}
}
return true;
}
for (size_t i = 0; i < NBytes; i++) {
if (IsIllegalIntervalCharacter(str[i])) {
return false;
}
}
return true;
}
* @brief get_calendar_clause
* Get calendar clause and return its value;
* @param tokens calendar interval tokens
* @param clause clause name
* @return char* value
*/
static char *get_calendar_clause_val(char **tokens, const char *clause, bool numeric_only)
{
char *val = NULL;
for (int i = 0; i < MAX_CALENDAR_FIELDS; i += 2) {
if (tokens[i] != NULL && pg_strcasecmp(tokens[i], clause) == 0) {
val = tokens[i + 1];
break;
}
}
if (val == NULL) {
return NULL;
}
if (!IsLegalIntervalStr(val, numeric_only)) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("Invalid value string for clause \'%s\'", clause), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
return val;
}
* @brief get_calendar_freqency
* Get frequency_clause value.
* frequency_clause = "FREQ" "=" ( predefined_frequency | user_defined_frequency )
* predefined_frequency = "YEARLY" | "MONTHLY" | "WEEKLY" | "DAILY" | "HOURLY" | "MINUTELY" | "SECONDLY"
* user_defined_frequency = named_schedule
* @param calendar
* @param tokens
* @return true clause exists
* @return false clause absent
*/
static bool get_calendar_freqency(Calendar calendar, char **tokens)
{
char *val = get_calendar_clause_val(tokens, "freq", false);
if (val == NULL) {
return false;
}
if (pg_strcasecmp(val, "yearly") == 0) {
calendar->frequency = YEARLY;
} else if (pg_strcasecmp(val, "monthly") == 0) {
calendar->frequency = MONTHLY;
} else if (pg_strcasecmp(val, "weekly") == 0) {
calendar->frequency = WEEKLY;
} else if (pg_strcasecmp(val, "daily") == 0) {
calendar->frequency = DAILY;
} else if (pg_strcasecmp(val, "hourly") == 0) {
calendar->frequency = HOURLY;
} else if (pg_strcasecmp(val, "minutely") == 0) {
calendar->frequency = MINUTELY;
} else if (pg_strcasecmp(val, "secondly") == 0) {
calendar->frequency = SECONDLY;
} else {
pfree_ext(tokens);
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("Invalid frequency value \'%s\'.", val), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
return true;
}
* @brief get_calendar_n_interval
* Get interval_clause.
* interval_clause = "INTERVAL" "=" intervalnum
* intervalnum = 1 through 99
* @param calendar
* @param tokens
*/
static void get_calendar_n_interval(Calendar calendar, char **tokens)
{
calendar->interval = 1;
char *val = get_calendar_clause_val(tokens, "interval", true);
if (val == NULL) {
return;
}
int num = atoi(val);
if (num < 1 || num > MAX_CALENDAR_INTERVAL_NUM) {
pfree_ext(tokens);
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("Interval \'%d\' not in range [1, 99].", num), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
calendar->interval = num;
}
static char *get_calendar_bymonth_val(Calendar calendar, char **tokens)
{
char *val = get_calendar_clause_val(tokens, "bymonth", false);
if (val != NULL) {
return val;
}
if (calendar->frequency > MONTHLY) {
for (int i = 0; i < MONTHS_PER_YEAR; i++) {
calendar->bymonth[i] = i + 1;
}
calendar->month_len = MONTHS_PER_YEAR;
calendar->date_depth *= calendar->month_len;
} else if (calendar->frequency < MONTHLY) {
calendar->month_len = 0;
} else {
int mod = (calendar->interval >= MONTHS_PER_YEAR) ? calendar->interval % MONTHS_PER_YEAR : calendar->interval;
if (mod == 0) {
mod = MONTHS_PER_YEAR;
}
calendar->month_len = (MONTHS_PER_YEAR % mod == 0) ? MONTHS_PER_YEAR / mod : MONTHS_PER_YEAR;
calendar->date_depth *= calendar->month_len;
calendar->month_len *= -1;
calendar->bymonth[0] = mod;
}
return NULL;
}
* @brief get_calendar_bymonth
* Get bymonth_clause.
* bymonth_clause = "BYMONTH" "=" monthlist
* monthlist = month ( "," month)*
* month = numeric_month | char_month
* numeric_month = 1 | 2 | 3 ... 12
* char_month = "JAN" | "FEB" | "MAR" | "APR" | "MAY" | "JUN" | "JUL" | "AUG" | "SEP" | "OCT" | "NOV" | "DEC"
* @param calendar
* @param tokens
*/
static void get_calendar_bymonth(Calendar calendar, char **tokens)
{
const char *month_str[] = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
char *val = get_calendar_bymonth_val(calendar, tokens);
if (val == NULL) {
return;
}
char *context = NULL;
char *tok = strtok_s(val, ",", &context);
bool used[MONTHS_PER_YEAR] = {0};
while (tok != NULL) {
int month = 0;
if (isalpha(tok[0])) {
for (int j = 0; j < MONTHS_PER_YEAR; j++) {
if (pg_strcasecmp(val, month_str[j]) == 0) {
month = j + 1;
break;
}
}
if (month == 0) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("Invalid month token \'%s\'.", val), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
} else {
month = atoi(tok);
if (month < 1 || month > MONTHS_PER_YEAR) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("Interval \'%d\' not in range [1, 12].", month), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
}
tok = strtok_s(NULL, ",", &context);
if (used[month - 1]) {
continue;
}
used[month - 1] = true;
}
for (int i = 0; i < MONTHS_PER_YEAR; i++) {
if (used[i]) {
calendar->bymonth[calendar->month_len] = i + 1;
calendar->month_len++;
}
}
calendar->date_depth *= (calendar->month_len == 0) ? 1 : calendar->month_len;
calendar->byfields |= INTERVAL_BYMONTH;
}
* @brief get_calendar_byweekno
* Get byweekno_clause.
* byweekno_clause = "BYWEEKNO" "=" weeknumber_list
* weeknumber_list = weeknumber ( "," weeknumber)*
* weeknumber = [minus] weekno
* weekno = 1 through 53
* @param calendar
* @param tokens
*/
static void get_calendar_byweekno(Calendar calendar, char **tokens)
{
char *val = get_calendar_clause_val(tokens, "byweekno", true);
if (val != NULL) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("BYWEEKNO clause is currently unsupported."), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
}
* @brief get_calendar_byyearday
* Get byyearday_clause.
* byyearday_clause = "BYYEARDAY" "=" yearday_list
* yearday_list = yearday ( "," yearday)*
* yearday = [minus] yeardaynum
* yeardaynum = 1 through 366
* @param calendar
* @param tokens
*/
static void get_calendar_byyearday(Calendar calendar, char **tokens)
{
char *val = get_calendar_clause_val(tokens, "byyearday", true);
if (val != NULL) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("BYYEARDAY clause is currently unsupported."), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
}
* @brief get_calendar_bydate
* Get bydate_clause.
* bydate_clause = "BYDATE" "=" date_list
* date_list = date ( "," date)*
* date = [YYYY]MMDD [ offset | span ]
* @param calendar
* @param tokens
*/
static void get_calendar_bydate(Calendar calendar, char **tokens)
{
char *val = get_calendar_clause_val(tokens, "byweekno", true);
if (val != NULL) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("BYDATE clause is currently unsupported."), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
}
static char *get_calendar_bymonthday_val(Calendar calendar, char **tokens)
{
char *val = get_calendar_clause_val(tokens, "bymonthday", true);
if (val != NULL) {
return val;
}
if (calendar->frequency >= DAILY || calendar->frequency == WEEKLY) {
for (int i = 0; i < DAYS_PER_MONTH + 1; i++) {
calendar->bymonthday[i] = i + 1;
}
calendar->monthday_len = DAYS_PER_MONTH + 1;
calendar->date_depth *= calendar->monthday_len;
} else {
calendar->monthday_len = 0;
}
return NULL;
}
* @brief get_calendar_bymonth
* Get bymonthday_clause.
* bymonthday_clause = "BYMONTHDAY" "=" monthday_list
* monthday_list = monthday ( "," monthday)*
* monthday = [minus] monthdaynum
* monthdaynum = 1 through 31
* @param calendar
* @param tokens
*/
static void get_calendar_bymonthday(Calendar calendar, char **tokens)
{
char *val = get_calendar_bymonthday_val(calendar, tokens);
if (val == NULL) {
return;
}
char *context = NULL;
char *tok = strtok_s(val, ",", &context);
bool used_p[DAYS_PER_MONTH + 1] = {0};
bool used_n[DAYS_PER_MONTH + 1] = {0};
while (tok != NULL) {
int monthday = atoi(tok);
if (monthday < -(DAYS_PER_MONTH + 1) || monthday > (DAYS_PER_MONTH + 1) || monthday == 0) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("Invalid monthday \'%d\'.", monthday), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
tok = strtok_s(NULL, ",", &context);
if (monthday > 0 && used_p[monthday - 1]) {
continue;
}
if (monthday < 0 && used_n[(-monthday) - 1]) {
continue;
}
if (monthday > 0) {
used_p[monthday - 1] = true;
}
if (monthday < 0) {
used_n[(-monthday) - 1] = true;
}
}
for (int i = 0; i <= DAYS_PER_MONTH; i++) {
if (used_p[i]) {
calendar->bymonthday[calendar->monthday_len] = i + 1;
calendar->monthday_len++;
}
}
for (int i = DAYS_PER_MONTH; i >= 0; i--) {
if (used_n[i]) {
calendar->bymonthday[calendar->monthday_len] = -(i + 1);
calendar->monthday_len++;
}
}
calendar->date_depth *= (calendar->monthday_len == 0) ? 1 : calendar->monthday_len;
calendar->byfields |= INTERVAL_BYMONTHDAY;
}
* @brief get_calendar_byday
* Get byday_clause.
* byday_clause = "BYDAY" "=" byday_list
* byday_list = byday ( "," byday)*
* byday = [weekdaynum] day
* weekdaynum = [minus] daynum
* daynum = 1 through 53 -- yearly
* daynum = 1 through 5 -- monthly
* day = "MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT" | "SUN"
* @param calendar
* @param tokens
*/
static void get_calendar_byday(Calendar calendar, char **tokens)
{
char *val = get_calendar_clause_val(tokens, "byday", true);
if (val != NULL) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("BYDAY clause is currently unsupported."), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
}
static char *get_calendar_byhour_val(Calendar calendar, char **tokens)
{
char *val = get_calendar_clause_val(tokens, "byhour", true);
if (val != NULL) {
return val;
}
if (calendar->frequency > HOURLY) {
for (int i = 0; i < HOURS_PER_DAY; i++) {
calendar->byhour[i] = i;
}
calendar->hour_len = HOURS_PER_DAY;
calendar->time_depth *= calendar->hour_len;
} else if (calendar->frequency < HOURLY) {
calendar->hour_len = 0;
} else {
int mod = (calendar->interval >= HOURS_PER_DAY) ? calendar->interval % HOURS_PER_DAY : calendar->interval;
if (mod == 0) {
mod = HOURS_PER_DAY;
}
calendar->hour_len = (HOURS_PER_DAY % mod == 0) ? HOURS_PER_DAY / mod : HOURS_PER_DAY;
calendar->time_depth *= calendar->hour_len;
calendar->hour_len *= -1;
calendar->byhour[0] = mod;
}
return NULL;
}
* @brief get_calendar_byhour
* Get byhour_clause.
* byhour_clause = "BYHOUR" "=" hour_list
* hour_list = hour ( "," hour)*
* hour = 0 through 23
* @param calendar
* @param tokens
*/
static void get_calendar_byhour(Calendar calendar, char **tokens)
{
char *val = get_calendar_byhour_val(calendar, tokens);
if (val == NULL) {
return;
}
char *context = NULL;
char *tok = strtok_s(val, ",", &context);
bool used[HOURS_PER_DAY] = {0};
while (tok != NULL) {
int hour = atoi(tok);
if (hour < 0 || hour >= HOURS_PER_DAY) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("Invalid time \'%d\' o\' clock.", hour), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
tok = strtok_s(NULL, ",", &context);
if (used[hour]) {
continue;
}
used[hour] = true;
}
for (int i = 0; i < HOURS_PER_DAY; i++) {
if (used[i]) {
calendar->byhour[calendar->hour_len] = i;
calendar->hour_len++;
}
}
calendar->time_depth *= (calendar->hour_len == 0) ? 1 : calendar->hour_len;
calendar->byfields |= INTERVAL_BYHOUR;
}
static char *get_calendar_byminute_val(Calendar calendar, char **tokens)
{
char *val = get_calendar_clause_val(tokens, "byminute", true);
if (val != NULL) {
return val;
}
if (calendar->frequency > MINUTELY) {
for (int i = 0; i < MINS_PER_HOUR; i++) {
calendar->byminute[i] = i;
}
calendar->minute_len = MINS_PER_HOUR;
calendar->time_depth *= calendar->minute_len;
} else if (calendar->frequency < MINUTELY) {
calendar->minute_len = 0;
} else {
int mod = (calendar->interval >= MINS_PER_HOUR) ? calendar->interval % MINS_PER_HOUR : calendar->interval;
if (mod == 0) {
mod = MINS_PER_HOUR;
}
calendar->minute_len = (MINS_PER_HOUR % mod == 0) ? MINS_PER_HOUR / mod : MINS_PER_HOUR;
calendar->time_depth *= calendar->minute_len;
calendar->minute_len *= -1;
calendar->byminute[0] = mod;
}
return NULL;
}
* @brief get_calendar_byminute
* Get byminute_clause.
* byminute_clause = "BYMINUTE" "=" minute_list
* minute_list = minute ( "," minute)*
* minute = 0 through 59
* @param calendar
* @param tokens
*/
static void get_calendar_byminute(Calendar calendar, char **tokens)
{
char *val = get_calendar_byminute_val(calendar, tokens);
if (val == NULL) {
return;
}
char *context = NULL;
char *tok = strtok_s(val, ",", &context);
bool used[MINS_PER_HOUR] = {0};
while (tok != NULL) {
int minute = atoi(tok);
if (minute < 0 || minute >= MINS_PER_HOUR) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("Invalid time \'%d\' minute [0, 59].", minute), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
tok = strtok_s(NULL, ",", &context);
if (used[minute]) {
continue;
}
used[minute] = true;
}
for (int i = 0; i < MINS_PER_HOUR; i++) {
if (used[i]) {
calendar->byminute[calendar->minute_len] = i;
calendar->minute_len++;
}
}
calendar->time_depth *= (calendar->minute_len == 0) ? 1 : calendar->minute_len;
calendar->byfields |= INTERVAL_BYMINUTE;
}
static char *get_calendar_bysecond_val(Calendar calendar, char **tokens)
{
char *val = get_calendar_clause_val(tokens, "bysecond", true);
if (val != NULL) {
return val;
}
Assert(calendar->frequency <= SECONDLY);
if (calendar->frequency < SECONDLY) {
calendar->second_len = 0;
} else {
int mod = (calendar->interval >= SECS_PER_MINUTE) ? calendar->interval % SECS_PER_MINUTE : calendar->interval;
if (mod == 0) {
mod = SECS_PER_MINUTE;
}
calendar->second_len = (SECS_PER_MINUTE % mod == 0) ? SECS_PER_MINUTE / mod : SECS_PER_MINUTE;
calendar->time_depth *= calendar->second_len;
calendar->second_len *= -1;
calendar->bysecond[0] = mod;
}
return NULL;
}
* @brief get_calendar_bysecond
* Get bysecond_clause.
* bysecond_clause = "BYSECOND" "=" second_list
* second_list = second ( "," second)*
* second = 0 through 59
* @param interval
* @param tokens
*/
static void get_calendar_bysecond(Calendar calendar, char **tokens)
{
char *val = get_calendar_bysecond_val(calendar, tokens);
if (val == NULL) {
return;
}
char *context = NULL;
char *tok = strtok_s(val, ",", &context);
bool used[SECS_PER_MINUTE] = {0};
while (tok != NULL) {
int second = atoi(tok);
if (second < 0 || second >= SECS_PER_MINUTE) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("Invalid time \'%d\' seconds [0, 59].", second), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
tok = strtok_s(NULL, ",", &context);
if (used[second]) {
continue;
}
used[second] = true;
}
for (int i = 0; i < SECS_PER_MINUTE; i++) {
if (used[i]) {
calendar->bysecond[calendar->second_len] = i;
calendar->second_len++;
}
}
calendar->time_depth *= (calendar->second_len == 0) ? 1 : calendar->second_len;
calendar->byfields |= INTERVAL_BYSECOND;
}
* @brief tokenize_str
* Tokenize string with given delimiters.
* @param src source string
* @param delims delimiters
* @param fields number of tokens needed
* @return char** an array of tokens generated
*/
static char **tokenize_str(char *src, const char *delims, int fields)
{
char **tokens = (char **)palloc0(sizeof(char *) * fields);
int count = 0;
char *context = NULL;
char *tok = strtok_s(src, delims, &context);
while (tok != NULL) {
tokens[count] = tok;
tok = strtok_s(NULL, delims, &context);
count++;
if (count >= fields) {
pfree_ext(tokens);
return NULL;
}
}
return tokens;
}
static int validate_field_names(char **toks)
{
bool valid = false;
const int name_pos_step = 2;
const char *supported_fields[SUPPORTED_FIELDS] = {"freq", "interval", "bymonth", "bymonthday", "byhour",
"byminute", "bysecond"};
bool fields_used[SUPPORTED_FIELDS] = {0};
for (int i = 0; i < MAX_CALENDAR_FIELDS; i += name_pos_step) {
for (int j = 0; j < SUPPORTED_FIELDS; j++) {
if (toks[i] == NULL) {
valid = true;
break;
}
if (pg_strcasecmp(toks[i], supported_fields[j]) != 0) {
continue;
}
if (fields_used[j]) {
valid = false;
} else {
fields_used[j] = true;
valid = true;
}
break;
}
if (!valid) {
return i;
}
valid = false;
}
return -1;
}
* @brief interpret_calendar_interval
* The main interpreter of calendar interval.
* @param interval_str
* @return Calendar
*/
Calendar interpret_calendar_interval(char *calendar_str)
{
Assert(calendar_str != NULL);
bool field = false;
char **str_toks = tokenize_str(calendar_str, " =;", MAX_CALENDAR_FIELDS);
if (str_toks == NULL) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("Unable to parse calendaring string."),
errcause("Calendaring string is too long/invalid"),
erraction("Please modify the calendaring string.")));
}
int pos = validate_field_names(str_toks);
if (pos >= 0) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED),
errmsg("Fail to evaluate calendaring string."),
errdetail("Incorrect/duplicate clause name '%s'.", str_toks[pos]), errcause("N/A"),
erraction("Please modify the calendaring string.")));
}
Calendar calendar = (Calendar)palloc0(sizeof(CalendarContext));
field = get_calendar_freqency(calendar, str_toks);
if (!field || pg_strcasecmp(str_toks[0], "freq") != 0) {
* Return NULL if frequency clause is missing(or not the first),
* possibly not a calendaring syntax
*/
pfree_ext(calendar);
return NULL;
}
get_calendar_n_interval(calendar, str_toks);
calendar->date_depth = 1;
calendar->time_depth = 1;
get_calendar_bymonth(calendar, str_toks);
get_calendar_byweekno(calendar, str_toks);
get_calendar_byyearday(calendar, str_toks);
get_calendar_bydate(calendar, str_toks);
get_calendar_bymonthday(calendar, str_toks);
get_calendar_byday(calendar, str_toks);
get_calendar_byhour(calendar, str_toks);
get_calendar_byminute(calendar, str_toks);
get_calendar_bysecond(calendar, str_toks);
pfree_ext(str_toks);
return calendar;
}
* @brief get_calendar_period
* Get the calendar period in the form of Interval.
* @param calendar
* @param num_of_period
* @return Interval*
*/
static Interval *get_calendar_period(Calendar calendar, int num_of_period)
{
const char *freq_str = NULL;
if (calendar->frequency == YEARLY) {
freq_str = "years";
} else if (calendar->frequency == MONTHLY) {
freq_str = "months";
} else if (calendar->frequency == WEEKLY) {
freq_str = "weeks";
} else if (calendar->frequency == DAILY) {
freq_str = "days";
} else if (calendar->frequency == HOURLY) {
freq_str = "hours";
} else if (calendar->frequency == MINUTELY) {
freq_str = "minutes";
} else if (calendar->frequency == SECONDLY) {
freq_str = "seconds";
} else {
return NULL;
}
return calendar_construct_interval(freq_str, calendar->interval * num_of_period);
}
* @brief copy_calendar_dates
* copy batches of timestamps.
* @param dates target timestamp array
* @param nvals number of batches
* @param cnt number of existing values
* @param date_in optional, only valid when dates is empty
*/
static void copy_calendar_dates(TimestampTz *timeline, int nvals, int cnt)
{
if (nvals <= 1) {
return;
}
for (int i = 0; i < cnt; i++) {
for (int j = 1; j < nvals; j++) {
timeline[i + (j * cnt)] = timeline[i];
}
}
}
* @brief validate_calendar_monthday
* Check if a given monthday is valid.
* @param year
* @param month
* @param mday
* @return true
* @return false
*/
static bool validate_calendar_monthday(int year, int month, int mday)
{
bool month_31[MONTHS_PER_YEAR] = {1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1};
if ((!month_31[month - 1] && mday > 30) || (month_31[month - 1] && mday > 31)) {
return false;
}
if ((!isleap(year) && month == 2 && mday > 29) || (isleap(year) && month == 2 && mday > 28)) {
return false;
}
if (!isleap(year) && month == 2 && mday < -28) {
return false;
}
if (isleap(year) && month == 2 && mday < -29) {
return false;
}
if (!month_31[month - 1] && mday < -30) {
return false;
}
return true;
}
* @brief evaluate_calendar_bymonth
* Evaluate bymonth field.
* @param calendar
* @param dates
* @param cnt
*/
static void evaluate_calendar_bymonth(Calendar calendar, TimestampTz *timeline, int *cnt)
{
Assert(*cnt <= calendar->date_depth);
if (*cnt == 0) {
return;
}
Interval *interval = NULL;
int chunk = *cnt;
TimestampTz ref = truncate_calendar_date(CStringGetTextDatum("month"), calendar->ref_date);
if (calendar->month_len == 0) {
calendar->bymonth[0] = calendar->tm.tm_mon;
calendar->month_len = 1;
} else if (calendar->month_len < 0) {
int mod = calendar->bymonth[0];
int start = (calendar->tm.tm_mon - 1) - (((calendar->tm.tm_mon - 1) / mod) * mod) + 1;
calendar->month_len = 0;
while (start < MONTHS_PER_YEAR) {
calendar->bymonth[calendar->month_len] = start;
start += mod;
calendar->month_len++;
}
}
*cnt = 0;
copy_calendar_dates(timeline, calendar->month_len, chunk);
for (int i = 0; i < calendar->month_len; i++) {
interval = calendar_construct_interval("months", calendar->bymonth[i] - 1);
for (int j = i * chunk; j < (i + 1) * chunk; j++) {
timeline[*cnt] = DatumGetTimestampTz(timestamp_pl_interval(timeline[j], interval));
if (timestamp_cmp_internal(timeline[*cnt], ref) >= 0) {
(*cnt)++;
}
}
pfree_ext(interval);
}
}
static void evaluate_calendar_byweekno(Calendar calendar, TimestampTz *timeline, int *cnt)
{
return;
}
static void evaluate_calendar_byyearday(Calendar calendar, TimestampTz *timeline, int *cnt)
{
return;
}
* @brief evaluate_calendar_bymonthday
* Evaluate bymonthday field.
* @param calendar
* @param timeline
* @param cnt
*/
static void evaluate_calendar_bymonthday(Calendar calendar, TimestampTz *timeline, int *cnt)
{
Assert(*cnt <= calendar->date_depth);
if (*cnt == 0) {
return;
}
int chunk = *cnt;
Interval *interval = NULL;
TimestampTz ref = truncate_calendar_date(CStringGetTextDatum("day"), calendar->ref_date);
if (calendar->monthday_len == 0) {
calendar->bymonthday[0] = calendar->tm.tm_mday;
calendar->monthday_len = 1;
}
*cnt = 0;
int tz = 0;
fsec_t fsec;
struct pg_tm tt, *tm = &tt;
copy_calendar_dates(timeline, calendar->monthday_len, chunk);
for (int i = 0; i < calendar->monthday_len; i++) {
if (calendar->bymonthday[i] < 0) {
interval = calendar_construct_interval("months", 1);
} else {
interval = calendar_construct_interval("days", calendar->bymonthday[i] - 1);
}
for (int j = i * chunk; j < (i + 1) * chunk; j++) {
if (timestamp2tm(timeline[j], &tz, tm, &fsec, NULL, NULL) != 0) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("Cannot evaluate calendar clause."), errdetail("Invalid timeline."),
errcause("N/A"), erraction("Please modify the calendaring string.")));
}
if (!validate_calendar_monthday(tm->tm_year, tm->tm_mon, calendar->bymonthday[i])) {
continue;
}
timeline[*cnt] = DatumGetTimestampTz(timestamp_pl_interval(timeline[j], interval));
if (calendar->bymonthday[i] <= 0) {
pfree_ext(interval);
interval = calendar_construct_interval("days", -(calendar->bymonthday[i]));
timeline[*cnt] = DatumGetTimestampTz(timestamp_mi_interval(timeline[j], interval));
}
if (timestamp_cmp_internal(timeline[*cnt], ref) >= 0) {
(*cnt)++;
}
}
pfree_ext(interval);
}
}
* @brief evaluate_calendar_byhour
* Evaluate byhour field.
* @param calendar
* @param timeline
* @param cnt
*/
static void evaluate_calendar_byhour(Calendar calendar, TimestampTz *timeline, int *cnt)
{
Assert(*cnt <= calendar->time_depth);
if (*cnt == 0) {
return;
}
Interval *interval = NULL;
int chunk = *cnt;
TimestampTz ref = truncate_calendar_date(CStringGetTextDatum("hour"), calendar->ref_date);
if (calendar->hour_len == 0) {
calendar->byhour[0] = calendar->tm.tm_hour;
calendar->hour_len = 1;
} else if (calendar->hour_len < 0) {
int mod = calendar->byhour[0];
int start = calendar->tm.tm_hour - ((calendar->tm.tm_hour / mod) * mod);
calendar->hour_len = 0;
while (start < HOURS_PER_DAY) {
calendar->byhour[calendar->hour_len] = start;
start += mod;
calendar->hour_len++;
}
}
*cnt = 0;
copy_calendar_dates(timeline, calendar->hour_len, chunk);
for (int i = 0; i < calendar->hour_len; i++) {
interval = calendar_construct_interval("hours", calendar->byhour[i]);
for (int j = i * chunk; j < (i + 1) * chunk; j++) {
timeline[*cnt] = DatumGetTimestampTz(timestamp_pl_interval(timeline[j], interval));
if (timestamp_cmp_internal(timeline[*cnt], ref) >= 0) {
(*cnt)++;
}
}
pfree_ext(interval);
}
}
* @brief evaluate_calendar_byminute
* Evaluate byminunte field.
* @param calendar
* @param timeline
* @param cnt
*/
static void evaluate_calendar_byminute(Calendar calendar, TimestampTz *timeline, int *cnt)
{
Assert(*cnt <= calendar->time_depth);
if (*cnt == 0) {
return;
}
Interval *interval = NULL;
int chunk = *cnt;
TimestampTz ref = truncate_calendar_date(CStringGetTextDatum("minute"), calendar->ref_date);
if (calendar->minute_len == 0) {
calendar->byminute[0] = calendar->tm.tm_min;
calendar->minute_len = 1;
} else if (calendar->minute_len < 0) {
int mod = calendar->byminute[0];
int start = calendar->tm.tm_min - ((calendar->tm.tm_min / mod) * mod);
calendar->minute_len = 0;
while (start < MINS_PER_HOUR) {
calendar->byminute[calendar->minute_len] = start;
start += mod;
calendar->minute_len++;
}
}
*cnt = 0;
copy_calendar_dates(timeline, calendar->minute_len, chunk);
for (int i = 0; i < calendar->minute_len; i++) {
interval = calendar_construct_interval("minutes", calendar->byminute[i]);
for (int j = i * chunk; j < (i + 1) * chunk; j++) {
timeline[*cnt] = DatumGetTimestampTz(timestamp_pl_interval(timeline[j], interval));
if (timestamp_cmp_internal(timeline[*cnt], ref) >= 0) {
(*cnt)++;
}
}
pfree_ext(interval);
}
}
* @brief evaluate_calendar_bysecond
* Evaluate bysecond field.
* @param calendar
* @param timeline
* @param cnt
*/
static void evaluate_calendar_bysecond(Calendar calendar, TimestampTz *timeline, int *cnt)
{
Assert(*cnt <= calendar->time_depth);
if (*cnt == 0) {
return;
}
Interval *interval = NULL;
int chunk = *cnt;
TimestampTz ref = truncate_calendar_date(CStringGetTextDatum("second"), calendar->ref_date);
if (calendar->second_len == 0) {
calendar->bysecond[0] = calendar->tm.tm_sec;
calendar->second_len = 1;
} else if (calendar->second_len < 0) {
int mod = calendar->bysecond[0];
int start = calendar->tm.tm_sec - ((calendar->tm.tm_sec / mod) * mod);
calendar->second_len = 0;
while (start < SECS_PER_MINUTE) {
calendar->bysecond[calendar->second_len] = start;
start += mod;
calendar->second_len++;
}
}
*cnt = 0;
copy_calendar_dates(timeline, calendar->second_len, chunk);
for (int i = 0; i < calendar->second_len; i++) {
interval = calendar_construct_interval("seconds", calendar->bysecond[i]);
for (int j = i * chunk; j < (i + 1) * chunk; j++) {
timeline[*cnt] = DatumGetTimestampTz(timestamp_pl_interval(timeline[j], interval));
if (timestamp_cmp_internal(timeline[*cnt], ref) >= 0) {
(*cnt)++;
}
}
pfree_ext(interval);
}
}
* @brief fastforward_calendar_period
* Fastforward to the date right before the date after to save some computing power.
* @param calendar
* @param start_date
* @param date_after
*/
static void fastforward_calendar_period(Calendar calendar, TimestampTz *start_date, TimestampTz date_after)
{
Interval *interval = calendar_construct_interval("second", 1);
if (timestamptz_cmp_internal(*start_date, date_after) >= 0) {
calendar->ref_date = DatumGetTimestampTz(timestamp_pl_interval(*start_date, interval));
pfree_ext(interval);
return;
}
TimestampTz new_start_date;
Interval *period = get_calendar_period(calendar);
if (period == NULL) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("Cannot evaluate calendar clause."), errdetail("Broken interval clause."),
errcause("N/A"), erraction("Please modify the calendaring string.")));
}
Datum pace_datum = DirectFunctionCall2(interval_part, CStringGetTextDatum("epoch"),
PointerGetDatum(period));
int pace = (int)DatumGetFloat8(pace_datum);
pfree_ext(period);
long elapsed_sec = 0;
int elapsed_usec = 0;
TimestampDifference(*start_date, date_after, &elapsed_sec, &elapsed_usec);
int num_of_periods = (elapsed_sec / pace);
if (num_of_periods >= 1) {
Interval *ff_span = get_calendar_period(calendar, num_of_periods);
if (ff_span == NULL) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("Cannot evaluate calendar clause."), errdetail("Broken interval clause."),
errcause("N/A"), erraction("Please modify the calendaring string.")));
}
new_start_date = DatumGetTimestampTz(timestamp_pl_interval(*start_date, ff_span));
pfree_ext(ff_span);
*start_date = new_start_date;
}
calendar->ref_date = DatumGetTimestampTz(timestamp_pl_interval(date_after, interval));
pfree_ext(interval);
}
* @brief recheck_calendar_period
* Usually we need to double check if a given timestamp resonate with original frequency.
* @param calendar
* @param start_date
* @param next_date
* @return true resonate
* @return false does not resonate
*/
static bool recheck_calendar_period(Calendar calendar, TimestampTz start_date, TimestampTz next_date)
{
const char *field_str = NULL;
int multiplier = 1;
if (calendar->frequency == YEARLY) {
field_str = "year";
} else if (calendar->frequency == MONTHLY) {
field_str = "month";
} else if (calendar->frequency == WEEKLY) {
field_str = "day";
multiplier = DAYS_PER_WEEK;
} else if (calendar->frequency == DAILY) {
field_str = "day";
} else if (calendar->frequency == HOURLY) {
field_str = "hour";
} else if (calendar->frequency == MINUTELY) {
field_str = "minute";
} else if (calendar->frequency == SECONDLY) {
field_str = "second";
} else {
return false;
}
start_date = truncate_calendar_date(CStringGetTextDatum(field_str), start_date);
next_date = truncate_calendar_date(CStringGetTextDatum(field_str), next_date);
int64 elapsed = timestamp_diff_internal(cstring_to_text(field_str), start_date, next_date, true);
if (elapsed % (calendar->interval * multiplier) == 0) {
return true;
}
return false;
}
* @brief timestamp_cmp_func
* Compare func for qsort
* @param dt1
* @param dt2
* @return int
*/
static int timestamp_cmp_func(const void *dt1, const void *dt2)
{
TimestampTz *t1 = (TimestampTz *)dt1;
TimestampTz *t2 = (TimestampTz *)dt2;
return timestamp_cmp_internal(*t1, *t2);
}
* @brief find_nearest_calendar_time
* Find the nearest timestamp from all valid timestamps.
* @param calendar context
* @param timeline timestamp list
* @param start start time
* @param cnt number of valid final timestamps
* @param nearest out value
* @return true found
* @return false not found
*/
static bool find_nearest_calendar_time(Calendar calendar, TimestampTz *timeline, TimestampTz start, int cnt,
TimestampTz *nearest)
{
qsort(timeline, cnt, sizeof(TimestampTz), timestamp_cmp_func);
for (int i = 0; i < cnt; i++) {
if (timestamp_cmp_internal(timeline[i], calendar->ref_date) >= 0 &&
recheck_calendar_period(calendar, start, timeline[i])) {
*nearest = timeline[i];
return true;
}
}
return false;
}
* @brief evaluate_calendar_period
*
* @param calendar
* @param period
* @param start_date
* @param next_date
* @return true success
* @return false fail
*/
static bool evaluate_calendar_period(Calendar calendar, TimestampTz *timeline, TimestampTz *sub_timeline,
TimestampTz start_date, TimestampTz *next_date)
{
int date_cnt = 1;
evaluate_calendar_bymonth(calendar, timeline, &date_cnt);
evaluate_calendar_byweekno(calendar, timeline, &date_cnt);
evaluate_calendar_byyearday(calendar, timeline, &date_cnt);
evaluate_calendar_bymonthday(calendar, timeline, &date_cnt);
qsort(timeline, date_cnt, sizeof(TimestampTz), timestamp_cmp_func);
* Use the sorted timeline (date part) to generate matching timestamp for each day.
*/
int time_cnt;
for (int i = 0; i < date_cnt; i++) {
time_cnt = 1;
sub_timeline[0] = timeline[i];
evaluate_calendar_byhour(calendar, sub_timeline, &time_cnt);
evaluate_calendar_byminute(calendar, sub_timeline, &time_cnt);
evaluate_calendar_bysecond(calendar, sub_timeline, &time_cnt);
if (find_nearest_calendar_time(calendar, sub_timeline, start_date, time_cnt, next_date)) {
return true;
}
}
return false;
}
* @brief prepare_calendar_period
*
* @param calendar
* @param base_date
* @param timeline
*/
static void prepare_calendar_period(Calendar calendar, TimestampTz base_date, TimestampTz *timeline)
{
Assert(calendar != NULL);
Assert(calendar->frequency <= MAX_FREQ);
Assert(calendar->frequency >= YEARLY);
Assert(calendar->date_depth > 0);
Assert(calendar->time_depth > 0);
timeline[0] = truncate_calendar_date(CStringGetTextDatum("year"), TimestampTzGetDatum(base_date));
if (calendar->date_depth > MAX_CALENDAR_DATE_DEPTH || calendar->time_depth > SECS_PER_DAY) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("Cannot evaluate calendar clause."),
errdetail("The scheduler run out of attempts to find a valid date in the foreesable future."),
errcause("N/A"), erraction("Please modify the calendaring string.")));
}
fsec_t fsec;
struct pg_tm tt, *tm = &tt;
int tz;
if (timestamp2tm(base_date, &tz, tm, &fsec, NULL, NULL) != 0) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("Cannot evaluate calendar clause."), errdetail("Fail to truncate start date."),
errcause("N/A"), erraction("Please modify the calendaring string.")));
}
calendar->tm = tt;
calendar->fsec = fsec;
if (timestamp_cmp_internal(timeline[0], calendar->ref_date) > 0) {
calendar->ref_date = timeline[0];
}
}
* @brief get_next_calendar_period
* Get the next calendar period. Usually a year, but can be multiple years if interval is large.
* @param calendar
* @param base_date
* @return TimestampTz
*/
static TimestampTz get_next_calendar_period(Calendar calendar, TimestampTz base_date)
{
int span_multiplier = 1;
if (calendar->frequency == MONTHLY && calendar->interval > MONTHS_PER_YEAR) {
span_multiplier = calendar->interval / MONTHS_PER_YEAR;
}
Interval *span = calendar_construct_interval("years", span_multiplier);
base_date = DatumGetTimestampTz(timestamp_pl_interval(base_date, span));
pfree_ext(span);
return base_date;
}
* @brief evaluate_calendar_interval
* Calculate next date base on start date.
* @param calendar
* @param start_date
* @return char*
*/
static TimestampTz evaluate_calendar_interval(Calendar calendar, TimestampTz start_date)
{
if (calendar == NULL) {
return start_date;
}
int try_count = 0;
TimestampTz next_date = start_date;
TimestampTz base_date = start_date;
TimestampTz *timeline = (TimestampTz *)palloc0(calendar->date_depth * sizeof(TimestampTz));
TimestampTz *sub_timeline = (TimestampTz *)palloc0((calendar->time_depth + 1) * sizeof(TimestampTz));
while (try_count < YEARS_PER_CENTURY) {
prepare_calendar_period(calendar, base_date, timeline);
if (evaluate_calendar_period(calendar, timeline, sub_timeline, start_date, &next_date)) {
break;
}
base_date = get_next_calendar_period(calendar, base_date);
CHECK_FOR_INTERRUPTS();
try_count++;
}
pfree_ext(timeline);
pfree_ext(sub_timeline);
if (try_count >= YEARS_PER_CENTURY) {
ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("Cannot evaluate calendar clause."), errdetail("Calender clause too deep."),
errcause("N/A"), erraction("Please modify the calendaring string.")));
}
return next_date;
}
* @brief evaluate_repeat_interval
* Calculate next date base on start date and calendar string.
* @param calendar_in
* @param base_time
* @return Datum
*/
Datum evaluate_repeat_interval(Datum calendar_in, Datum start_date, Datum date_after)
{
char *calendar_str = text_to_cstring(DatumGetTextP(calendar_in));
if (strlen(calendar_str) > (size_t)MAX_CALENDAR_STR_LEN) {
pfree_ext(calendar_str);
return calendar_in;
}
TimestampTz start_date_raw = DatumGetTimestampTz(start_date);
Calendar calendar = interpret_calendar_interval(calendar_str);
start_date_raw = truncate_calendar_date(CStringGetTextDatum("second"), TimestampTzGetDatum(start_date_raw));
if (calendar != NULL) {
date_after = truncate_calendar_date(CStringGetTextDatum("second"), TimestampTzGetDatum(date_after));
fastforward_calendar_period(calendar, &start_date_raw, DatumGetTimestampTz(date_after));
}
TimestampTz next_date = evaluate_calendar_interval(calendar, start_date_raw);
pfree_ext(calendar_str);
if (calendar == NULL) {
return calendar_in;
} else {
pfree_ext(calendar);
}
return TimestampTzGetDatum(next_date);
}
* @brief evaluate_calendar_string_internal
* eval_calendar_string interface.
* @return Datum
*/
Datum evaluate_calendar_string_internal(PG_FUNCTION_ARGS)
{
Datum string = PG_GETARG_DATUM(0);
Datum start_date = PG_GETARG_DATUM(1);
Datum date_after = PG_GETARG_DATUM(2);
Datum new_next_date = evaluate_repeat_interval(string, start_date, date_after);
PG_RETURN_DATUM(new_next_date);
}