|
28 | 28 | * |
29 | 29 | */ |
30 | 30 |
|
31 | | -#include "ruby/version.h" |
32 | 31 | #include "pg.h" |
33 | 32 | #include "pg_util.h" |
34 | 33 | #ifdef HAVE_INTTYPES_H |
@@ -621,6 +620,25 @@ static int parse_year(const char **str) { |
621 | 620 | return year; |
622 | 621 | } |
623 | 622 |
|
| 623 | +/* |
| 624 | + * Seconds since 1970-01-01 00:00:00 UTC in the proleptic Gregorian calendar, like timegm() but much faster on macOS. |
| 625 | + * Days are counted per http://howardhinnant.github.io/date_algorithms.html#days_from_civil |
| 626 | + */ |
| 627 | +static int64_t seconds_since_epoch(int year, int mon, int day, int hour, int min, int sec) |
| 628 | +{ |
| 629 | + int64_t era, days; |
| 630 | + int year_of_era, day_of_year, day_of_era; |
| 631 | + |
| 632 | + year -= mon <= 2; |
| 633 | + era = (year >= 0 ? year : year - 399) / 400; |
| 634 | + year_of_era = (int)(year - era * 400); |
| 635 | + day_of_year = (153 * (mon > 2 ? mon - 3 : mon + 9) + 2) / 5 + day - 1; |
| 636 | + day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year; |
| 637 | + days = era * 146097 + day_of_era - 719468; |
| 638 | + |
| 639 | + return days * 86400 + hour * 3600 + min * 60 + sec; |
| 640 | +} |
| 641 | + |
624 | 642 | #define TZ_NEG 1 |
625 | 643 | #define TZ_POS 2 |
626 | 644 |
|
@@ -708,71 +726,59 @@ static VALUE pg_text_dec_timestamp(t_pg_coder *conv, const char *val, int len, i |
708 | 726 | VALUE sec_value; |
709 | 727 | VALUE gmt_offset_value; |
710 | 728 | VALUE res; |
711 | | - |
712 | | -#if (RUBY_API_VERSION_MAJOR > 2 || (RUBY_API_VERSION_MAJOR == 2 && RUBY_API_VERSION_MINOR >= 3)) && defined(HAVE_TIMEGM) |
713 | | - /* Fast path for time conversion */ |
714 | | - struct tm tm; |
715 | 729 | struct timespec ts; |
716 | | - tm.tm_year = year - 1900; |
717 | | - tm.tm_mon = mon - 1; |
718 | | - tm.tm_mday = day; |
719 | | - tm.tm_hour = hour; |
720 | | - tm.tm_min = min; |
721 | | - tm.tm_sec = sec; |
722 | | - tm.tm_isdst = -1; |
| 730 | + int gmt_offset = tz_hour * 3600 + tz_min * 60 + tz_sec; |
723 | 731 |
|
724 | | - if (tz_given) { |
725 | | - /* with timezone */ |
726 | | - time_t time = timegm(&tm); |
727 | | - if (time != -1){ |
728 | | - int gmt_offset; |
| 732 | + if (tz_given == TZ_NEG) |
| 733 | + { |
| 734 | + gmt_offset = - gmt_offset; |
| 735 | + } |
729 | 736 |
|
730 | | - gmt_offset = tz_hour * 3600 + tz_min * 60 + tz_sec; |
731 | | - if (tz_given == TZ_NEG) |
732 | | - { |
733 | | - gmt_offset = - gmt_offset; |
734 | | - } |
735 | | - ts.tv_sec = time - gmt_offset; |
736 | | - ts.tv_nsec = nsec; |
737 | | - return rb_time_timespec_new(&ts, gmt_offset); |
| 737 | + /* Fast path for time conversion */ |
| 738 | + ts.tv_nsec = nsec; |
| 739 | + if (!tz_given && (conv->flags & PG_CODER_TIMESTAMP_DB_LOCAL)) { |
| 740 | + /* without timezone, interpreted as local time */ |
| 741 | +#ifndef _WIN32 |
| 742 | + /* Windows' mktime() fails on certain dates, so it takes the slow path */ |
| 743 | + struct tm tm; |
| 744 | + tm.tm_year = year - 1900; |
| 745 | + tm.tm_mon = mon - 1; |
| 746 | + tm.tm_mday = day; |
| 747 | + tm.tm_hour = hour; |
| 748 | + tm.tm_min = min; |
| 749 | + tm.tm_sec = sec; |
| 750 | + tm.tm_isdst = -1; |
| 751 | + |
| 752 | + ts.tv_sec = mktime(&tm); |
| 753 | + if (ts.tv_sec != -1) { |
| 754 | + return rb_time_timespec_new(&ts, conv->flags & PG_CODER_TIMESTAMP_APP_LOCAL ? INT_MAX : INT_MAX-1); |
738 | 755 | } |
| 756 | +#endif |
739 | 757 | } else { |
740 | | - /* without timezone */ |
741 | | - time_t time; |
742 | | - |
743 | | - if( conv->flags & PG_CODER_TIMESTAMP_DB_LOCAL ) { |
744 | | - time = mktime(&tm); |
745 | | - } else { |
746 | | - time = timegm(&tm); |
747 | | - } |
748 | | - if (time != -1){ |
749 | | - ts.tv_sec = time; |
750 | | - ts.tv_nsec = nsec; |
751 | | - return rb_time_timespec_new(&ts, conv->flags & PG_CODER_TIMESTAMP_APP_LOCAL ? INT_MAX : INT_MAX-1); |
| 758 | + /* with timezone, or without timezone interpreted as UTC */ |
| 759 | + int64_t time = seconds_since_epoch(year, mon, day, hour, min, sec) - gmt_offset; |
| 760 | + ts.tv_sec = (time_t)time; |
| 761 | + if (ts.tv_sec == time) { |
| 762 | + return rb_time_timespec_new(&ts, tz_given ? gmt_offset : conv->flags & PG_CODER_TIMESTAMP_APP_LOCAL ? INT_MAX : INT_MAX-1); |
752 | 763 | } |
753 | 764 | } |
754 | | - /* Some libc implementations fail to convert certain values, |
| 765 | + /* mktime() fails on some values with some libc implementations, |
| 766 | + * and a 32 bit time_t can't hold every PostgreSQL timestamp, |
755 | 767 | * so that we fall through to the slow path. |
756 | 768 | */ |
757 | | -#endif |
758 | | - if (nsec) { |
759 | | - int sec_numerator = sec * 1000000 + nsec / 1000; |
760 | | - int sec_denominator = 1000000; |
| 769 | + if (nsec % 1000) { |
| 770 | + sec_value = rb_funcall(Qnil, s_id_Rational, 2, |
| 771 | + LL2NUM((LONG_LONG)sec * 1000000000 + nsec), INT2NUM(1000000000)); |
| 772 | + } else if (nsec) { |
| 773 | + /* Counted in microseconds, the numerator stays a Fixnum where long is 32 bit, like on Windows */ |
761 | 774 | sec_value = rb_funcall(Qnil, s_id_Rational, 2, |
762 | | - INT2NUM(sec_numerator), INT2NUM(sec_denominator)); |
| 775 | + INT2NUM(sec * 1000000 + nsec / 1000), INT2NUM(1000000)); |
763 | 776 | } else { |
764 | 777 | sec_value = INT2NUM(sec); |
765 | 778 | } |
766 | 779 |
|
767 | 780 | if (tz_given) { |
768 | 781 | /* with timezone */ |
769 | | - int gmt_offset; |
770 | | - |
771 | | - gmt_offset = tz_hour * 3600 + tz_min * 60 + tz_sec; |
772 | | - if (tz_given == TZ_NEG) |
773 | | - { |
774 | | - gmt_offset = - gmt_offset; |
775 | | - } |
776 | 782 | gmt_offset_value = INT2NUM(gmt_offset); |
777 | 783 | } else { |
778 | 784 | /* without timezone */ |
|
0 commit comments