Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed leap year handling by reworking upb_mktime() -> upb_timegm(). #6695

Merged
merged 1 commit into from
Sep 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 23 additions & 41 deletions ruby/ext/google/protobuf_c/upb.c
Original file line number Diff line number Diff line change
Expand Up @@ -10117,46 +10117,28 @@ static void start_timestamp_zone(upb_json_parser *p, const char *ptr) {
capture_begin(p, ptr);
}

#define EPOCH_YEAR 1970
#define TM_YEAR_BASE 1900

static bool isleap(int year) {
return (year % 4) == 0 && (year % 100 != 0 || (year % 400) == 0);
}

const unsigned short int __mon_yday[2][13] = {
/* Normal years. */
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
/* Leap years. */
{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
};

int64_t epoch(int year, int yday, int hour, int min, int sec) {
int64_t years = year - EPOCH_YEAR;

int64_t leap_days = years / 4 - years / 100 + years / 400;

int64_t days = years * 365 + yday + leap_days;
int64_t hours = days * 24 + hour;
int64_t mins = hours * 60 + min;
int64_t secs = mins * 60 + sec;
return secs;
}


static int64_t upb_mktime(const struct tm *tp) {
int sec = tp->tm_sec;
int min = tp->tm_min;
int hour = tp->tm_hour;
int mday = tp->tm_mday;
int mon = tp->tm_mon;
int year = tp->tm_year + TM_YEAR_BASE;

/* Calculate day of year from year, month, and day of month. */
int mon_yday = ((__mon_yday[isleap(year)][mon]) - 1);
int yday = mon_yday + mday;

return epoch(year, yday, hour, min, sec);
/* epoch_days(1970, 1, 1) == 1970-01-01 == 0. */
static int epoch_days(int year, int month, int day) {
static const uint16_t month_yday[12] = {0, 31, 59, 90, 120, 151,
181, 212, 243, 273, 304, 334};
int febs_since_0 = month > 2 ? year + 1 : year;
int leap_days_since_0 = div_round_up(febs_since_0, 4) -
div_round_up(febs_since_0, 100) +
div_round_up(febs_since_0, 400);
int days_since_0 =
365 * year + month_yday[month - 1] + (day - 1) + leap_days_since_0;

/* Convert from 0-epoch (0001-01-01 BC) to Unix Epoch (1970-01-01 AD).
* Since the "BC" system does not have a year zero, 1 BC == year zero. */
return days_since_0 - 719528;
}

static int64_t upb_timegm(const struct tm *tp) {
int64_t ret = epoch_days(tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday);
ret = (ret * 24) + tp->tm_hour;
ret = (ret * 60) + tp->tm_min;
ret = (ret * 60) + tp->tm_sec;
return ret;
}

static bool end_timestamp_zone(upb_json_parser *p, const char *ptr) {
Expand Down Expand Up @@ -10186,7 +10168,7 @@ static bool end_timestamp_zone(upb_json_parser *p, const char *ptr) {
}

/* Normalize tm */
seconds = upb_mktime(&p->tm);
seconds = upb_timegm(&p->tm);

/* Check timestamp boundary */
if (seconds < -62135596800) {
Expand Down
12 changes: 12 additions & 0 deletions ruby/tests/common_tests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,18 @@ def test_converts_time
assert_raise(Google::Protobuf::TypeError) { m.timestamp = 2.4 }
assert_raise(Google::Protobuf::TypeError) { m.timestamp = '4' }
assert_raise(Google::Protobuf::TypeError) { m.timestamp = proto_module::TimeMessage.new }

def test_time(year, month, day)
str = ("\"%04d-%02d-%02dT00:00:00.000+00:00\"" % [year, month, day])
t = Google::Protobuf::Timestamp.decode_json(str)
time = Time.new(year, month, day, 0, 0, 0, "+00:00")
assert_equal t.seconds, time.to_i
end

(1970..2010).each do |year|
test_time(year, 2, 28)
test_time(year, 3, 01)
end
end

def test_converts_duration
Expand Down