blob: 695605b3cc77f0c8d61dcc3e33bd76ea048bb96f [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "./epoch_time_point.h"
// The first row is for non-leap years
static int days_in_a_month[2][12] = {{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
bool is_leap_year(int yy) {
if ((yy % 4) != 0) {
// not divisible by 4
return false;
}
// yy = 4x
if ((yy % 400) == 0) {
// yy = 400x
return true;
}
// yy = 4x, return true if yy != 100x
return ((yy % 100) != 0);
}
bool is_last_day_of_month(const EpochTimePoint& tp) {
int matrix_index = is_leap_year(tp.TmYear()) ? 1 : 0;
return (tp.TmMday() == days_in_a_month[matrix_index][tp.TmMon()]);
}
bool did_days_overflow(arrow_vendored::date::year_month_day ymd) {
int year = static_cast<int>(ymd.year());
int month = static_cast<unsigned int>(ymd.month());
int days = static_cast<unsigned int>(ymd.day());
int matrix_index = is_leap_year(year) ? 1 : 0;
return days > days_in_a_month[matrix_index][month - 1];
}
int last_possible_day_in_month(int year, int month) {
int matrix_index = is_leap_year(year) ? 1 : 0;
return days_in_a_month[matrix_index][month - 1];
}
extern "C" {
#include <time.h>
#include "./time_constants.h"
#include "./types.h"
#define TIMESTAMP_DIFF_FIXED_UNITS(TYPE, NAME, FROM_MILLIS) \
FORCE_INLINE \
gdv_int32 NAME##_##TYPE##_##TYPE(gdv_##TYPE start_millis, gdv_##TYPE end_millis) { \
return static_cast<int32_t>(FROM_MILLIS(end_millis - start_millis)); \
}
#define SIGN_ADJUST_DIFF(is_positive, diff) ((is_positive) ? (diff) : -(diff))
#define MONTHS_TO_TIMEUNIT(diff, num_months) (diff) / (num_months)
// Assuming end_millis > start_millis, the algorithm to find the diff in months is:
// diff_in_months = year_diff * 12 + month_diff
// This is approximately correct, except when the last month has not fully elapsed
//
// a) If end_day > start_day, return diff_in_months e.g. diff(2015-09-10, 2017-03-31)
// b) If end_day < start_day, return diff_in_months - 1 e.g. diff(2015-09-30, 2017-03-10)
// c) If end_day = start_day, check for millis e.g. diff(2017-03-10, 2015-03-10)
// Need to check if end_millis_in_day > start_millis_in_day
// c1) If end_millis_in_day >= start_millis_in_day, return diff_in_months
// c2) else return diff_in_months - 1
#define TIMESTAMP_DIFF_MONTH_UNITS(TYPE, NAME, N_MONTHS) \
FORCE_INLINE \
gdv_int32 NAME##_##TYPE##_##TYPE(gdv_##TYPE start_millis, gdv_##TYPE end_millis) { \
gdv_int32 diff; \
bool is_positive = (end_millis > start_millis); \
if (!is_positive) { \
/* if end_millis < start_millis, swap and multiply by -1 at the end */ \
gdv_##TYPE tmp = start_millis; \
start_millis = end_millis; \
end_millis = tmp; \
} \
EpochTimePoint start_tm(start_millis); \
EpochTimePoint end_tm(end_millis); \
gdv_int32 months_diff; \
months_diff = static_cast<gdv_int32>(12 * (end_tm.TmYear() - start_tm.TmYear()) + \
(end_tm.TmMon() - start_tm.TmMon())); \
if (end_tm.TmMday() > start_tm.TmMday()) { \
/* case a */ \
diff = MONTHS_TO_TIMEUNIT(months_diff, N_MONTHS); \
return SIGN_ADJUST_DIFF(is_positive, diff); \
} \
if (end_tm.TmMday() < start_tm.TmMday()) { \
/* case b */ \
months_diff += (is_last_day_of_month(end_tm) ? 1 : 0); \
diff = MONTHS_TO_TIMEUNIT(months_diff - 1, N_MONTHS); \
return SIGN_ADJUST_DIFF(is_positive, diff); \
} \
gdv_int32 end_day_millis = \
static_cast<gdv_int32>(end_tm.TmHour() * MILLIS_IN_HOUR + \
end_tm.TmMin() * MILLIS_IN_MIN + end_tm.TmSec()); \
gdv_int32 start_day_millis = \
static_cast<gdv_int32>(start_tm.TmHour() * MILLIS_IN_HOUR + \
start_tm.TmMin() * MILLIS_IN_MIN + start_tm.TmSec()); \
if (end_day_millis >= start_day_millis) { \
/* case c1 */ \
diff = MONTHS_TO_TIMEUNIT(months_diff, N_MONTHS); \
return SIGN_ADJUST_DIFF(is_positive, diff); \
} \
/* case c2 */ \
diff = MONTHS_TO_TIMEUNIT(months_diff - 1, N_MONTHS); \
return SIGN_ADJUST_DIFF(is_positive, diff); \
}
#define TIMESTAMP_DIFF(TYPE) \
TIMESTAMP_DIFF_FIXED_UNITS(TYPE, timestampdiffSecond, MILLIS_TO_SEC) \
TIMESTAMP_DIFF_FIXED_UNITS(TYPE, timestampdiffMinute, MILLIS_TO_MINS) \
TIMESTAMP_DIFF_FIXED_UNITS(TYPE, timestampdiffHour, MILLIS_TO_HOUR) \
TIMESTAMP_DIFF_FIXED_UNITS(TYPE, timestampdiffDay, MILLIS_TO_DAY) \
TIMESTAMP_DIFF_FIXED_UNITS(TYPE, timestampdiffWeek, MILLIS_TO_WEEK) \
TIMESTAMP_DIFF_MONTH_UNITS(TYPE, timestampdiffMonth, 1) \
TIMESTAMP_DIFF_MONTH_UNITS(TYPE, timestampdiffQuarter, 3) \
TIMESTAMP_DIFF_MONTH_UNITS(TYPE, timestampdiffYear, 12)
TIMESTAMP_DIFF(timestamp)
#define ADD_INT32_TO_TIMESTAMP_FIXED_UNITS(TYPE, NAME, TO_MILLIS) \
FORCE_INLINE \
gdv_##TYPE NAME##_int32_##TYPE(gdv_int32 count, gdv_##TYPE millis) { \
return millis + TO_MILLIS * static_cast<gdv_##TYPE>(count); \
}
// Documentation of mktime suggests that it handles
// TmMon() being negative, and also TmMon() being >= 12 by
// adjusting TmYear() accordingly
//
// Using gmtime_r() and timegm() instead of localtime_r() and mktime()
// since the input millis are since epoch
#define ADD_INT32_TO_TIMESTAMP_MONTH_UNITS(TYPE, NAME, N_MONTHS) \
FORCE_INLINE \
gdv_##TYPE NAME##_int32_##TYPE(gdv_int32 count, gdv_##TYPE millis) { \
EpochTimePoint tp(millis); \
return tp.AddMonths(static_cast<int>(count * N_MONTHS)).MillisSinceEpoch(); \
}
// TODO: Handle overflow while converting gdv_int64 to millis
#define ADD_INT64_TO_TIMESTAMP_FIXED_UNITS(TYPE, NAME, TO_MILLIS) \
FORCE_INLINE \
gdv_##TYPE NAME##_int64_##TYPE(gdv_int64 count, gdv_##TYPE millis) { \
return millis + TO_MILLIS * static_cast<gdv_##TYPE>(count); \
}
#define ADD_INT64_TO_TIMESTAMP_MONTH_UNITS(TYPE, NAME, N_MONTHS) \
FORCE_INLINE \
gdv_##TYPE NAME##_int64_##TYPE(gdv_int64 count, gdv_##TYPE millis) { \
EpochTimePoint tp(millis); \
return tp.AddMonths(static_cast<int>(count * N_MONTHS)).MillisSinceEpoch(); \
}
#define ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(TYPE, NAME, TO_MILLIS) \
FORCE_INLINE \
gdv_##TYPE NAME##_##TYPE##_int32(gdv_##TYPE millis, gdv_int32 count) { \
return millis + TO_MILLIS * static_cast<gdv_##TYPE>(count); \
}
#define ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(TYPE, NAME, TO_MILLIS) \
FORCE_INLINE \
gdv_##TYPE NAME##_##TYPE##_int64(gdv_##TYPE millis, gdv_int64 count) { \
return millis + TO_MILLIS * static_cast<gdv_##TYPE>(count); \
}
#define ADD_TIMESTAMP_TO_INT32_MONTH_UNITS(TYPE, NAME, N_MONTHS) \
FORCE_INLINE \
gdv_##TYPE NAME##_##TYPE##_int32(gdv_##TYPE millis, gdv_int32 count) { \
EpochTimePoint tp(millis); \
return tp.AddMonths(static_cast<int>(count * N_MONTHS)).MillisSinceEpoch(); \
}
#define ADD_TIMESTAMP_TO_INT64_MONTH_UNITS(TYPE, NAME, N_MONTHS) \
FORCE_INLINE \
gdv_##TYPE NAME##_##TYPE##_int64(gdv_##TYPE millis, gdv_int64 count) { \
EpochTimePoint tp(millis); \
return tp.AddMonths(static_cast<int>(count * N_MONTHS)).MillisSinceEpoch(); \
}
#define ADD_TIMESTAMP_INT32_FIXEDUNITS(TYPE, NAME, TO_MILLIS) \
ADD_INT32_TO_TIMESTAMP_FIXED_UNITS(TYPE, NAME, TO_MILLIS) \
ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(TYPE, NAME, TO_MILLIS)
#define ADD_TIMESTAMP_INT32_MONTHUNITS(TYPE, NAME, N_MONTHS) \
ADD_INT32_TO_TIMESTAMP_MONTH_UNITS(TYPE, NAME, N_MONTHS) \
ADD_TIMESTAMP_TO_INT32_MONTH_UNITS(TYPE, NAME, N_MONTHS)
#define TIMESTAMP_ADD_INT32(TYPE) \
ADD_TIMESTAMP_INT32_FIXEDUNITS(TYPE, timestampaddSecond, MILLIS_IN_SEC) \
ADD_TIMESTAMP_INT32_FIXEDUNITS(TYPE, timestampaddMinute, MILLIS_IN_MIN) \
ADD_TIMESTAMP_INT32_FIXEDUNITS(TYPE, timestampaddHour, MILLIS_IN_HOUR) \
ADD_TIMESTAMP_INT32_FIXEDUNITS(TYPE, timestampaddDay, MILLIS_IN_DAY) \
ADD_TIMESTAMP_INT32_FIXEDUNITS(TYPE, timestampaddWeek, MILLIS_IN_WEEK) \
ADD_TIMESTAMP_INT32_MONTHUNITS(TYPE, timestampaddMonth, 1) \
ADD_TIMESTAMP_INT32_MONTHUNITS(TYPE, timestampaddQuarter, 3) \
ADD_TIMESTAMP_INT32_MONTHUNITS(TYPE, timestampaddYear, 12)
#define ADD_TIMESTAMP_INT64_FIXEDUNITS(TYPE, NAME, TO_MILLIS) \
ADD_INT64_TO_TIMESTAMP_FIXED_UNITS(TYPE, NAME, TO_MILLIS) \
ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(TYPE, NAME, TO_MILLIS)
#define ADD_TIMESTAMP_INT64_MONTHUNITS(TYPE, NAME, N_MONTHS) \
ADD_INT64_TO_TIMESTAMP_MONTH_UNITS(TYPE, NAME, N_MONTHS) \
ADD_TIMESTAMP_TO_INT64_MONTH_UNITS(TYPE, NAME, N_MONTHS)
#define TIMESTAMP_ADD_INT64(TYPE) \
ADD_TIMESTAMP_INT64_FIXEDUNITS(TYPE, timestampaddSecond, MILLIS_IN_SEC) \
ADD_TIMESTAMP_INT64_FIXEDUNITS(TYPE, timestampaddMinute, MILLIS_IN_MIN) \
ADD_TIMESTAMP_INT64_FIXEDUNITS(TYPE, timestampaddHour, MILLIS_IN_HOUR) \
ADD_TIMESTAMP_INT64_FIXEDUNITS(TYPE, timestampaddDay, MILLIS_IN_DAY) \
ADD_TIMESTAMP_INT64_FIXEDUNITS(TYPE, timestampaddWeek, MILLIS_IN_WEEK) \
ADD_TIMESTAMP_INT64_MONTHUNITS(TYPE, timestampaddMonth, 1) \
ADD_TIMESTAMP_INT64_MONTHUNITS(TYPE, timestampaddQuarter, 3) \
ADD_TIMESTAMP_INT64_MONTHUNITS(TYPE, timestampaddYear, 12)
#define TIMESTAMP_ADD_INT(TYPE) \
TIMESTAMP_ADD_INT32(TYPE) \
TIMESTAMP_ADD_INT64(TYPE)
TIMESTAMP_ADD_INT(date64)
TIMESTAMP_ADD_INT(timestamp)
// add gdv_int32 to timestamp
ADD_INT32_TO_TIMESTAMP_FIXED_UNITS(date64, date_add, MILLIS_IN_DAY)
ADD_INT32_TO_TIMESTAMP_FIXED_UNITS(date64, add, MILLIS_IN_DAY)
ADD_INT32_TO_TIMESTAMP_FIXED_UNITS(timestamp, date_add, MILLIS_IN_DAY)
ADD_INT32_TO_TIMESTAMP_FIXED_UNITS(timestamp, add, MILLIS_IN_DAY)
// add gdv_int64 to timestamp
ADD_INT64_TO_TIMESTAMP_FIXED_UNITS(date64, date_add, MILLIS_IN_DAY)
ADD_INT64_TO_TIMESTAMP_FIXED_UNITS(date64, add, MILLIS_IN_DAY)
ADD_INT64_TO_TIMESTAMP_FIXED_UNITS(timestamp, date_add, MILLIS_IN_DAY)
ADD_INT64_TO_TIMESTAMP_FIXED_UNITS(timestamp, add, MILLIS_IN_DAY)
// date_sub, subtract, date_diff on gdv_int32
ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(date64, date_sub, -1 * MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(date64, subtract, -1 * MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(date64, date_diff, -1 * MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(timestamp, date_sub, -1 * MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(timestamp, subtract, -1 * MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(timestamp, date_diff, -1 * MILLIS_IN_DAY)
// date_sub, subtract, date_diff on gdv_int64
ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(date64, date_sub, -1 * MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(date64, subtract, -1 * MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(date64, date_diff, -1 * MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(timestamp, date_sub, -1 * MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(timestamp, subtract, -1 * MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(timestamp, date_diff, -1 * MILLIS_IN_DAY)
// add timestamp to gdv_int32
ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(date64, date_add, MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(date64, add, MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(timestamp, date_add, MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT32_FIXED_UNITS(timestamp, add, MILLIS_IN_DAY)
// add timestamp to gdv_int64
ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(date64, date_add, MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(date64, add, MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(timestamp, date_add, MILLIS_IN_DAY)
ADD_TIMESTAMP_TO_INT64_FIXED_UNITS(timestamp, add, MILLIS_IN_DAY)
} // extern "C"