diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f55c34ce..a94af6b2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: uses: ruby/actions/.github/workflows/ruby_versions.yml@master with: engine: cruby - min_version: 2.6 + min_version: 3.3 build: if: ${{ startsWith(github.repository, 'ruby/') || github.event_name != 'schedule' }} @@ -40,7 +40,5 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: true # 'bundle install' and cache - - run: choco install gperf - if: ${{ matrix.ruby == 'mswin' }} - name: Run test - run: bundle exec rake compile test + run: bundle exec rake test diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml index 9e0d465e..a428e12b 100644 --- a/.github/workflows/update.yml +++ b/.github/workflows/update.yml @@ -12,14 +12,13 @@ jobs: steps: - uses: actions/checkout@v6 - name: Set up Ruby - run: sudo apt-get --no-install-recommends -q -y install ruby-dev gperf + run: sudo apt-get --no-install-recommends -q -y install ruby-dev - name: Install dependencies run: sudo gem install --no-document nokogiri - name: Update zonetab working-directory: ext/date run: | make -f prereq.mk update-zonetab - make -f prereq.mk zonetab.h env: top_srcdir: ../.. srcdir: . @@ -31,7 +30,7 @@ jobs: echo "diff=true" >> $GITHUB_OUTPUT - name: Commit run: | - git commit --message="Update zonetab.h at $(date +%F)" ext/date + git commit --message="Update zonetab at $(date +%F)" ext/date lib/date/zonetab.rb git pull --ff-only origin ${GITHUB_REF#refs/heads/} git push origin ${GITHUB_REF#refs/heads/} env: diff --git a/Rakefile b/Rakefile index 933384c0..20d08f72 100644 --- a/Rakefile +++ b/Rakefile @@ -1,30 +1,14 @@ require "bundler/gem_tasks" require "rake/testtask" -require "shellwords" -require "rake/extensiontask" - -extask = Rake::ExtensionTask.new("date") do |ext| - ext.name = "date_core" - ext.lib_dir.sub!(%r[(?=/|\z)], "/#{RUBY_VERSION}/#{ext.platform}") -end +# Pure Ruby — no compilation needed Rake::TestTask.new(:test) do |t| - t.libs << extask.lib_dir + t.libs << "lib" t.libs << "test/lib" t.ruby_opts << "-rhelper" t.test_files = FileList['test/**/test_*.rb'] end -task compile: "ext/date/zonetab.h" -file "ext/date/zonetab.h" => "ext/date/zonetab.list" do |t| - dir, hdr = File.split(t.name) - make_program_name = - ENV['MAKE'] || ENV['make'] || - RbConfig::CONFIG['configure_args'][/with-make-prog\=\K\w+/] || - (/mswin/ =~ RUBY_PLATFORM ? 'nmake' : 'make') - make_program = Shellwords.split(make_program_name) - sh(*make_program, "-f", "prereq.mk", "top_srcdir=.."+"/.."*dir.count("/"), - hdr, chdir: dir) -end +task :compile # no-op, kept for compatibility task :default => [:compile, :test] diff --git a/date.gemspec b/date.gemspec index cb439bd0..d9fed315 100644 --- a/date.gemspec +++ b/date.gemspec @@ -1,31 +1,19 @@ # frozen_string_literal: true -version = File.foreach(File.expand_path("../lib/date.rb", __FILE__)).find do |line| - /^\s*VERSION\s*=\s*["'](.*)["']/ =~ line and break $1 -end +require_relative "lib/date/version" Gem::Specification.new do |s| s.name = "date" - s.version = version + s.version = Date::VERSION s.summary = "The official date library for Ruby." s.description = "The official date library for Ruby." - if Gem::Platform === s.platform and s.platform =~ 'java' or RUBY_ENGINE == 'jruby' - s.platform = 'java' - # No files shipped, no require path, no-op for now on JRuby - else - s.require_path = %w{lib} + s.require_path = %w{lib} - s.files = [ - "README.md", "COPYING", "BSDL", - "lib/date.rb", "ext/date/date_core.c", "ext/date/date_parse.c", "ext/date/date_strftime.c", - "ext/date/date_strptime.c", "ext/date/date_tmx.h", "ext/date/extconf.rb", "ext/date/prereq.mk", - "ext/date/zonetab.h", "ext/date/zonetab.list" - ] - s.extensions = "ext/date/extconf.rb" - end + s.files = Dir["README.md", "COPYING", "BSDL", "lib/**/*.rb", + "ext/date/prereq.mk", "ext/date/zonetab.list"] - s.required_ruby_version = ">= 2.6.0" + s.required_ruby_version = ">= 3.3.0" s.authors = ["Tadayoshi Funaba"] s.email = [nil] diff --git a/ext/date/date_core.c b/ext/date/date_core.c deleted file mode 100644 index 72d697c8..00000000 --- a/ext/date/date_core.c +++ /dev/null @@ -1,10258 +0,0 @@ -/* - date_core.c: Coded by Tadayoshi Funaba 2010-2014 -*/ - -#include "ruby.h" -#include "ruby/encoding.h" -#include "ruby/util.h" -#include -#include -#if defined(HAVE_SYS_TIME_H) -#include -#endif - -#undef NDEBUG -#define NDEBUG -#include - -#ifdef RUBY_EXTCONF_H -#include RUBY_EXTCONF_H -#endif - -#define USE_PACK - -static ID id_cmp, id_le_p, id_ge_p, id_eqeq_p; -static VALUE cDate, cDateTime; -static VALUE eDateError; -static VALUE half_days_in_day, day_in_nanoseconds; -static double positive_inf, negative_inf; - -/* used by deconstruct_keys */ -static VALUE sym_year, sym_month, sym_day, sym_yday, sym_wday; -static VALUE sym_hour, sym_min, sym_sec, sym_sec_fraction, sym_zone; - -#define f_boolcast(x) ((x) ? Qtrue : Qfalse) - -#define f_abs(x) rb_funcall(x, rb_intern("abs"), 0) -#define f_negate(x) rb_funcall(x, rb_intern("-@"), 0) -#define f_add(x,y) rb_funcall(x, '+', 1, y) -#define f_sub(x,y) rb_funcall(x, '-', 1, y) -#define f_mul(x,y) rb_funcall(x, '*', 1, y) -#define f_div(x,y) rb_funcall(x, '/', 1, y) -#define f_quo(x,y) rb_funcall(x, rb_intern("quo"), 1, y) -#define f_idiv(x,y) rb_funcall(x, rb_intern("div"), 1, y) -#define f_mod(x,y) rb_funcall(x, '%', 1, y) -#define f_remainder(x,y) rb_funcall(x, rb_intern("remainder"), 1, y) -#define f_expt(x,y) rb_funcall(x, rb_intern("**"), 1, y) -#define f_floor(x) rb_funcall(x, rb_intern("floor"), 0) -#define f_ceil(x) rb_funcall(x, rb_intern("ceil"), 0) -#define f_truncate(x) rb_funcall(x, rb_intern("truncate"), 0) -#define f_round(x) rb_funcall(x, rb_intern("round"), 0) - -#define f_to_i(x) rb_funcall(x, rb_intern("to_i"), 0) -#define f_to_r(x) rb_funcall(x, rb_intern("to_r"), 0) -#define f_to_s(x) rb_funcall(x, rb_intern("to_s"), 0) -#define f_inspect(x) rb_funcall(x, rb_intern("inspect"), 0) - -#define f_add3(x,y,z) f_add(f_add(x, y), z) -#define f_sub3(x,y,z) f_sub(f_sub(x, y), z) - -#define f_frozen_ary(...) rb_ary_freeze(rb_ary_new3(__VA_ARGS__)) - -static VALUE date_initialize(int argc, VALUE *argv, VALUE self); -static VALUE datetime_initialize(int argc, VALUE *argv, VALUE self); - -#define RETURN_FALSE_UNLESS_NUMERIC(obj) if(!RTEST(rb_obj_is_kind_of((obj), rb_cNumeric))) return Qfalse -inline static void -check_numeric(VALUE obj, const char* field) -{ - if(!RTEST(rb_obj_is_kind_of(obj, rb_cNumeric))) { - rb_raise(rb_eTypeError, "invalid %s (not numeric)", field); - } -} - -inline static int -f_cmp(VALUE x, VALUE y) -{ - if (FIXNUM_P(x) && FIXNUM_P(y)) { - long c = FIX2LONG(x) - FIX2LONG(y); - if (c > 0) - return 1; - else if (c < 0) - return -1; - return 0; - } - return rb_cmpint(rb_funcallv(x, id_cmp, 1, &y), x, y); -} - -inline static VALUE -f_lt_p(VALUE x, VALUE y) -{ - if (FIXNUM_P(x) && FIXNUM_P(y)) - return f_boolcast(FIX2LONG(x) < FIX2LONG(y)); - return rb_funcall(x, '<', 1, y); -} - -inline static VALUE -f_gt_p(VALUE x, VALUE y) -{ - if (FIXNUM_P(x) && FIXNUM_P(y)) - return f_boolcast(FIX2LONG(x) > FIX2LONG(y)); - return rb_funcall(x, '>', 1, y); -} - -inline static VALUE -f_le_p(VALUE x, VALUE y) -{ - if (FIXNUM_P(x) && FIXNUM_P(y)) - return f_boolcast(FIX2LONG(x) <= FIX2LONG(y)); - return rb_funcall(x, id_le_p, 1, y); -} - -inline static VALUE -f_ge_p(VALUE x, VALUE y) -{ - if (FIXNUM_P(x) && FIXNUM_P(y)) - return f_boolcast(FIX2LONG(x) >= FIX2LONG(y)); - return rb_funcall(x, id_ge_p, 1, y); -} - -inline static VALUE -f_eqeq_p(VALUE x, VALUE y) -{ - if (FIXNUM_P(x) && FIXNUM_P(y)) - return f_boolcast(FIX2LONG(x) == FIX2LONG(y)); - return rb_funcall(x, id_eqeq_p, 1, y); -} - -inline static VALUE -f_zero_p(VALUE x) -{ - switch (TYPE(x)) { - case T_FIXNUM: - return f_boolcast(FIX2LONG(x) == 0); - case T_BIGNUM: - return Qfalse; - case T_RATIONAL: - { - VALUE num = rb_rational_num(x); - return f_boolcast(FIXNUM_P(num) && FIX2LONG(num) == 0); - } - } - return rb_funcall(x, id_eqeq_p, 1, INT2FIX(0)); -} - -#define f_nonzero_p(x) (!f_zero_p(x)) - -inline static VALUE -f_negative_p(VALUE x) -{ - if (FIXNUM_P(x)) - return f_boolcast(FIX2LONG(x) < 0); - return rb_funcall(x, '<', 1, INT2FIX(0)); -} - -#define f_positive_p(x) (!f_negative_p(x)) - -#define f_ajd(x) rb_funcall(x, rb_intern("ajd"), 0) -#define f_jd(x) rb_funcall(x, rb_intern("jd"), 0) -#define f_year(x) rb_funcall(x, rb_intern("year"), 0) -#define f_mon(x) rb_funcall(x, rb_intern("mon"), 0) -#define f_mday(x) rb_funcall(x, rb_intern("mday"), 0) -#define f_wday(x) rb_funcall(x, rb_intern("wday"), 0) -#define f_hour(x) rb_funcall(x, rb_intern("hour"), 0) -#define f_min(x) rb_funcall(x, rb_intern("min"), 0) -#define f_sec(x) rb_funcall(x, rb_intern("sec"), 0) - -/* copied from time.c */ -#define NDIV(x,y) (-(-((x)+1)/(y))-1) -#define NMOD(x,y) ((y)-(-((x)+1)%(y))-1) -#define DIV(n,d) ((n)<0 ? NDIV((n),(d)) : (n)/(d)) -#define MOD(n,d) ((n)<0 ? NMOD((n),(d)) : (n)%(d)) - -#define HAVE_JD (1 << 0) -#define HAVE_DF (1 << 1) -#define HAVE_CIVIL (1 << 2) -#define HAVE_TIME (1 << 3) -#define COMPLEX_DAT (1 << 7) - -#define have_jd_p(x) ((x)->flags & HAVE_JD) -#define have_df_p(x) ((x)->flags & HAVE_DF) -#define have_civil_p(x) ((x)->flags & HAVE_CIVIL) -#define have_time_p(x) ((x)->flags & HAVE_TIME) -#define complex_dat_p(x) ((x)->flags & COMPLEX_DAT) -#define simple_dat_p(x) (!complex_dat_p(x)) - -#define ITALY 2299161 /* 1582-10-15 */ -#define ENGLAND 2361222 /* 1752-09-14 */ -#define JULIAN positive_inf -#define GREGORIAN negative_inf -#define DEFAULT_SG ITALY - -#define UNIX_EPOCH_IN_CJD INT2FIX(2440588) /* 1970-01-01 */ - -#define MINUTE_IN_SECONDS 60 -#define HOUR_IN_SECONDS 3600 -#define DAY_IN_SECONDS 86400 -#define SECOND_IN_MILLISECONDS 1000 -#define SECOND_IN_NANOSECONDS 1000000000 - -#define JC_PERIOD0 1461 /* 365.25 * 4 */ -#define GC_PERIOD0 146097 /* 365.2425 * 400 */ -#define CM_PERIOD0 71149239 /* (lcm 7 1461 146097) */ -#define CM_PERIOD (0xfffffff / CM_PERIOD0 * CM_PERIOD0) -#define CM_PERIOD_JCY (CM_PERIOD / JC_PERIOD0 * 4) -#define CM_PERIOD_GCY (CM_PERIOD / GC_PERIOD0 * 400) - -#define REFORM_BEGIN_YEAR 1582 -#define REFORM_END_YEAR 1930 -#define REFORM_BEGIN_JD 2298874 /* ns 1582-01-01 */ -#define REFORM_END_JD 2426355 /* os 1930-12-31 */ - -#ifdef USE_PACK -#define SEC_WIDTH 6 -#define MIN_WIDTH 6 -#define HOUR_WIDTH 5 -#define MDAY_WIDTH 5 -#define MON_WIDTH 4 - -#define SEC_SHIFT 0 -#define MIN_SHIFT SEC_WIDTH -#define HOUR_SHIFT (MIN_WIDTH + SEC_WIDTH) -#define MDAY_SHIFT (HOUR_WIDTH + MIN_WIDTH + SEC_WIDTH) -#define MON_SHIFT (MDAY_WIDTH + HOUR_WIDTH + MIN_WIDTH + SEC_WIDTH) - -#define PK_MASK(x) ((1 << (x)) - 1) - -#define EX_SEC(x) (((x) >> SEC_SHIFT) & PK_MASK(SEC_WIDTH)) -#define EX_MIN(x) (((x) >> MIN_SHIFT) & PK_MASK(MIN_WIDTH)) -#define EX_HOUR(x) (((x) >> HOUR_SHIFT) & PK_MASK(HOUR_WIDTH)) -#define EX_MDAY(x) (((x) >> MDAY_SHIFT) & PK_MASK(MDAY_WIDTH)) -#define EX_MON(x) (((x) >> MON_SHIFT) & PK_MASK(MON_WIDTH)) - -#define PACK5(m,d,h,min,s) \ - (((m) << MON_SHIFT) | ((d) << MDAY_SHIFT) |\ - ((h) << HOUR_SHIFT) | ((min) << MIN_SHIFT) | ((s) << SEC_SHIFT)) - -#define PACK2(m,d) \ - (((m) << MON_SHIFT) | ((d) << MDAY_SHIFT)) -#endif - -#ifdef HAVE_FLOAT_H -#include -#endif - -#if defined(FLT_RADIX) && defined(FLT_MANT_DIG) && FLT_RADIX == 2 && FLT_MANT_DIG > 22 -#define date_sg_t float -#else -#define date_sg_t double -#endif - -#define JULIAN_EPOCH_DATE "-4712-01-01" -#define JULIAN_EPOCH_DATETIME JULIAN_EPOCH_DATE "T00:00:00+00:00" -#define JULIAN_EPOCH_DATETIME_RFC3339 "Mon, 1 Jan -4712 00:00:00 +0000" -#define JULIAN_EPOCH_DATETIME_HTTPDATE "Mon, 01 Jan -4712 00:00:00 GMT" - -/* A set of nth, jd, df and sf denote ajd + 1/2. Each ajd begin at - * noon of GMT (assume equal to UTC). However, this begins at - * midnight. - */ - -struct SimpleDateData -{ - unsigned flags; - int jd; /* as utc */ - VALUE nth; /* not always canonicalized */ - date_sg_t sg; /* 2298874..2426355 or -/+oo -- at most 22 bits */ - /* decoded as utc=local */ - int year; /* truncated */ -#ifndef USE_PACK - int mon; - int mday; - /* hour is zero */ - /* min is zero */ - /* sec is zero */ -#else - /* packed civil */ - unsigned pc; -#endif -}; - -struct ComplexDateData -{ - unsigned flags; - int jd; /* as utc */ - VALUE nth; /* not always canonicalized */ - date_sg_t sg; /* 2298874..2426355 or -/+oo -- at most 22 bits */ - /* decoded as local */ - int year; /* truncated */ -#ifndef USE_PACK - int mon; - int mday; - int hour; - int min; - int sec; -#else - /* packed civil */ - unsigned pc; -#endif - int df; /* as utc, in secs */ - int of; /* in secs */ - VALUE sf; /* in nano secs */ -}; - -union DateData { - unsigned flags; - struct SimpleDateData s; - struct ComplexDateData c; -}; - -#define get_d1(x)\ - union DateData *dat;\ - TypedData_Get_Struct(x, union DateData, &d_lite_type, dat); - -#define get_d1a(x)\ - union DateData *adat;\ - TypedData_Get_Struct(x, union DateData, &d_lite_type, adat); - -#define get_d1b(x)\ - union DateData *bdat;\ - TypedData_Get_Struct(x, union DateData, &d_lite_type, bdat); - -#define get_d2(x,y)\ - union DateData *adat, *bdat;\ - TypedData_Get_Struct(x, union DateData, &d_lite_type, adat);\ - TypedData_Get_Struct(y, union DateData, &d_lite_type, bdat); - -inline static VALUE -canon(VALUE x) -{ - if (RB_TYPE_P(x, T_RATIONAL)) { - VALUE den = rb_rational_den(x); - if (FIXNUM_P(den) && FIX2LONG(den) == 1) - return rb_rational_num(x); - } - return x; -} - -#ifndef USE_PACK -#define set_to_simple(obj, x, _nth, _jd ,_sg, _year, _mon, _mday, _flags) \ -do {\ - RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth)); \ - (x)->jd = _jd;\ - (x)->sg = (date_sg_t)(_sg);\ - (x)->year = _year;\ - (x)->mon = _mon;\ - (x)->mday = _mday;\ - (x)->flags = (_flags) & ~COMPLEX_DAT;\ -} while (0) -#else -#define set_to_simple(obj, x, _nth, _jd ,_sg, _year, _mon, _mday, _flags) \ -do {\ - RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth)); \ - (x)->jd = _jd;\ - (x)->sg = (date_sg_t)(_sg);\ - (x)->year = _year;\ - (x)->pc = PACK2(_mon, _mday);\ - (x)->flags = (_flags) & ~COMPLEX_DAT;\ -} while (0) -#endif - -#ifndef USE_PACK -#define set_to_complex(obj, x, _nth, _jd ,_df, _sf, _of, _sg,\ -_year, _mon, _mday, _hour, _min, _sec, _flags) \ -do {\ - RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth));\ - (x)->jd = _jd;\ - (x)->df = _df;\ - RB_OBJ_WRITE((obj), &(x)->sf, canon(_sf));\ - (x)->of = _of;\ - (x)->sg = (date_sg_t)(_sg);\ - (x)->year = _year;\ - (x)->mon = _mon;\ - (x)->mday = _mday;\ - (x)->hour = _hour;\ - (x)->min = _min;\ - (x)->sec = _sec;\ - (x)->flags = (_flags) | COMPLEX_DAT;\ -} while (0) -#else -#define set_to_complex(obj, x, _nth, _jd ,_df, _sf, _of, _sg,\ -_year, _mon, _mday, _hour, _min, _sec, _flags) \ -do {\ - RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth));\ - (x)->jd = _jd;\ - (x)->df = _df;\ - RB_OBJ_WRITE((obj), &(x)->sf, canon(_sf));\ - (x)->of = _of;\ - (x)->sg = (date_sg_t)(_sg);\ - (x)->year = _year;\ - (x)->pc = PACK5(_mon, _mday, _hour, _min, _sec);\ - (x)->flags = (_flags) | COMPLEX_DAT;\ -} while (0) -#endif - -#ifndef USE_PACK -#define copy_simple_to_complex(obj, x, y) \ -do {\ - RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ - (x)->jd = (y)->jd;\ - (x)->df = 0;\ - (x)->sf = INT2FIX(0);\ - (x)->of = 0;\ - (x)->sg = (date_sg_t)((y)->sg);\ - (x)->year = (y)->year;\ - (x)->mon = (y)->mon;\ - (x)->mday = (y)->mday;\ - (x)->hour = 0;\ - (x)->min = 0;\ - (x)->sec = 0;\ - (x)->flags = (y)->flags;\ -} while (0) -#else -#define copy_simple_to_complex(obj, x, y) \ -do {\ - RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ - (x)->jd = (y)->jd;\ - (x)->df = 0;\ - RB_OBJ_WRITE((obj), &(x)->sf, INT2FIX(0));\ - (x)->of = 0;\ - (x)->sg = (date_sg_t)((y)->sg);\ - (x)->year = (y)->year;\ - (x)->pc = PACK5(EX_MON((y)->pc), EX_MDAY((y)->pc), 0, 0, 0);\ - (x)->flags = (y)->flags;\ -} while (0) -#endif - -#ifndef USE_PACK -#define copy_complex_to_simple(obj, x, y) \ -do {\ - RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ - (x)->jd = (y)->jd;\ - (x)->sg = (date_sg_t)((y)->sg);\ - (x)->year = (y)->year;\ - (x)->mon = (y)->mon;\ - (x)->mday = (y)->mday;\ - (x)->flags = (y)->flags;\ -} while (0) -#else -#define copy_complex_to_simple(obj, x, y) \ -do {\ - RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ - (x)->jd = (y)->jd;\ - (x)->sg = (date_sg_t)((y)->sg);\ - (x)->year = (y)->year;\ - (x)->pc = PACK2(EX_MON((y)->pc), EX_MDAY((y)->pc));\ - (x)->flags = (y)->flags;\ -} while (0) -#endif - -/* base */ - -static int c_valid_civil_p(int, int, int, double, - int *, int *, int *, int *); - -/* Check if using pure Gregorian calendar (sg == -Infinity) */ -#define c_gregorian_only_p(sg) (isinf(sg) && (sg) < 0) - -/* - * Fast path macros for pure Gregorian calendar. - * Sets *rjd to the JD value, *ns to 1 (new style), and returns. - */ -#define GREGORIAN_JD_FAST_PATH_RET(sg, jd_expr, rjd, ns) \ - if (c_gregorian_only_p(sg)) { \ - *(rjd) = (jd_expr); \ - *(ns) = 1; \ - return 1; \ - } - -#define GREGORIAN_JD_FAST_PATH(sg, jd_expr, rjd, ns) \ - if (c_gregorian_only_p(sg)) { \ - *(rjd) = (jd_expr); \ - *(ns) = 1; \ - return; \ - } - -/* Forward declarations for Neri-Schneider optimized functions */ -static int c_gregorian_civil_to_jd(int y, int m, int d); -static void c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd); -static int c_gregorian_fdoy(int y); -static int c_gregorian_ldoy(int y); -static int c_gregorian_ldom_jd(int y, int m); -static int ns_jd_in_range(int jd); - -static int -c_find_fdoy(int y, double sg, int *rjd, int *ns) -{ - int d, rm, rd; - - GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_fdoy(y), rjd, ns); - - /* Keep existing loop for Julian/reform period */ - for (d = 1; d < 31; d++) - if (c_valid_civil_p(y, 1, d, sg, &rm, &rd, rjd, ns)) - return 1; - return 0; -} - -static int -c_find_ldoy(int y, double sg, int *rjd, int *ns) -{ - int i, rm, rd; - - GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_ldoy(y), rjd, ns); - - /* Keep existing loop for Julian/reform period */ - for (i = 0; i < 30; i++) - if (c_valid_civil_p(y, 12, 31 - i, sg, &rm, &rd, rjd, ns)) - return 1; - return 0; -} - -#ifndef NDEBUG -/* :nodoc: */ -static int -c_find_fdom(int y, int m, double sg, int *rjd, int *ns) -{ - int d, rm, rd; - - for (d = 1; d < 31; d++) - if (c_valid_civil_p(y, m, d, sg, &rm, &rd, rjd, ns)) - return 1; - return 0; -} -#endif - -static int -c_find_ldom(int y, int m, double sg, int *rjd, int *ns) -{ - int i, rm, rd; - - GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_ldom_jd(y, m), rjd, ns); - - /* Keep existing loop for Julian/reform period */ - for (i = 0; i < 30; i++) - if (c_valid_civil_p(y, m, 31 - i, sg, &rm, &rd, rjd, ns)) - return 1; - return 0; -} - -static void -c_civil_to_jd(int y, int m, int d, double sg, int *rjd, int *ns) -{ - int jd; - - GREGORIAN_JD_FAST_PATH(sg, c_gregorian_civil_to_jd(y, m, d), rjd, ns); - - /* Calculate Gregorian JD using optimized algorithm */ - jd = c_gregorian_civil_to_jd(y, m, d); - - if (jd < sg) { - /* Before Gregorian switchover - use Julian calendar */ - int y2 = y, m2 = m; - if (m2 <= 2) { - y2 -= 1; - m2 += 12; - } - jd = (int)(floor(365.25 * (y2 + 4716)) + - floor(30.6001 * (m2 + 1)) + - d - 1524); - *ns = 0; - } - else { - *ns = 1; - } - - *rjd = jd; -} - -static void -c_jd_to_civil(int jd, double sg, int *ry, int *rm, int *rdom) -{ - /* Fast path: pure Gregorian or date after switchover, within safe range */ - if ((c_gregorian_only_p(sg) || jd >= sg) && ns_jd_in_range(jd)) { - c_gregorian_jd_to_civil(jd, ry, rm, rdom); - return; - } - - /* Original algorithm for Julian calendar or extreme dates */ - { - double x, a, b, c, d, e, y, m, dom; - - if (jd < sg) - a = jd; - else { - x = floor((jd - 1867216.25) / 36524.25); - a = jd + 1 + x - floor(x / 4.0); - } - b = a + 1524; - c = floor((b - 122.1) / 365.25); - d = floor(365.25 * c); - e = floor((b - d) / 30.6001); - dom = b - d - floor(30.6001 * e); - if (e <= 13) { - m = e - 1; - y = c - 4716; - } - else { - m = e - 13; - y = c - 4715; - } - - *ry = (int)y; - *rm = (int)m; - *rdom = (int)dom; - } -} - -static void -c_ordinal_to_jd(int y, int d, double sg, int *rjd, int *ns) -{ - int ns2; - - c_find_fdoy(y, sg, rjd, &ns2); - *rjd += d - 1; - *ns = (*rjd < sg) ? 0 : 1; -} - -static void -c_jd_to_ordinal(int jd, double sg, int *ry, int *rd) -{ - int rm2, rd2, rjd, ns; - - c_jd_to_civil(jd, sg, ry, &rm2, &rd2); - c_find_fdoy(*ry, sg, &rjd, &ns); - *rd = (jd - rjd) + 1; -} - -static void -c_commercial_to_jd(int y, int w, int d, double sg, int *rjd, int *ns) -{ - int rjd2, ns2; - - c_find_fdoy(y, sg, &rjd2, &ns2); - rjd2 += 3; - *rjd = - (rjd2 - MOD((rjd2 - 1) + 1, 7)) + - 7 * (w - 1) + - (d - 1); - *ns = (*rjd < sg) ? 0 : 1; -} - -static void -c_jd_to_commercial(int jd, double sg, int *ry, int *rw, int *rd) -{ - int ry2, rm2, rd2, a, rjd2, ns2; - - c_jd_to_civil(jd - 3, sg, &ry2, &rm2, &rd2); - a = ry2; - c_commercial_to_jd(a + 1, 1, 1, sg, &rjd2, &ns2); - if (jd >= rjd2) - *ry = a + 1; - else { - c_commercial_to_jd(a, 1, 1, sg, &rjd2, &ns2); - *ry = a; - } - *rw = 1 + DIV(jd - rjd2, 7); - *rd = MOD(jd + 1, 7); - if (*rd == 0) - *rd = 7; -} - -static void -c_weeknum_to_jd(int y, int w, int d, int f, double sg, int *rjd, int *ns) -{ - int rjd2, ns2; - - c_find_fdoy(y, sg, &rjd2, &ns2); - rjd2 += 6; - *rjd = (rjd2 - MOD(((rjd2 - f) + 1), 7) - 7) + 7 * w + d; - *ns = (*rjd < sg) ? 0 : 1; -} - -static void -c_jd_to_weeknum(int jd, int f, double sg, int *ry, int *rw, int *rd) -{ - int rm, rd2, rjd, ns, j; - - c_jd_to_civil(jd, sg, ry, &rm, &rd2); - c_find_fdoy(*ry, sg, &rjd, &ns); - rjd += 6; - j = jd - (rjd - MOD((rjd - f) + 1, 7)) + 7; - *rw = (int)DIV(j, 7); - *rd = (int)MOD(j, 7); -} - -#ifndef NDEBUG -/* :nodoc: */ -static void -c_nth_kday_to_jd(int y, int m, int n, int k, double sg, int *rjd, int *ns) -{ - int rjd2, ns2; - - if (n > 0) { - c_find_fdom(y, m, sg, &rjd2, &ns2); - rjd2 -= 1; - } - else { - c_find_ldom(y, m, sg, &rjd2, &ns2); - rjd2 += 7; - } - *rjd = (rjd2 - MOD((rjd2 - k) + 1, 7)) + 7 * n; - *ns = (*rjd < sg) ? 0 : 1; -} -#endif - -inline static int -c_jd_to_wday(int jd) -{ - return MOD(jd + 1, 7); -} - -#ifndef NDEBUG -/* :nodoc: */ -static void -c_jd_to_nth_kday(int jd, double sg, int *ry, int *rm, int *rn, int *rk) -{ - int rd, rjd, ns2; - - c_jd_to_civil(jd, sg, ry, rm, &rd); - c_find_fdom(*ry, *rm, sg, &rjd, &ns2); - *rn = DIV(jd - rjd, 7) + 1; - *rk = c_jd_to_wday(jd); -} -#endif - -static int -c_valid_ordinal_p(int y, int d, double sg, - int *rd, int *rjd, int *ns) -{ - int ry2, rd2; - - if (d < 0) { - int rjd2, ns2; - - if (!c_find_ldoy(y, sg, &rjd2, &ns2)) - return 0; - c_jd_to_ordinal(rjd2 + d + 1, sg, &ry2, &rd2); - if (ry2 != y) - return 0; - d = rd2; - } - c_ordinal_to_jd(y, d, sg, rjd, ns); - c_jd_to_ordinal(*rjd, sg, &ry2, &rd2); - if (ry2 != y || rd2 != d) - return 0; - return 1; -} - -static const int monthtab[2][13] = { - { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, - { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } -}; - -inline static int -c_julian_leap_p(int y) -{ - return MOD(y, 4) == 0; -} - -inline static int -c_gregorian_leap_p(int y) -{ - return (MOD(y, 4) == 0 && y % 100 != 0) || MOD(y, 400) == 0; -} - -static int -c_julian_last_day_of_month(int y, int m) -{ - assert(m >= 1 && m <= 12); - return monthtab[c_julian_leap_p(y) ? 1 : 0][m]; -} - -static int -c_gregorian_last_day_of_month(int y, int m) -{ - assert(m >= 1 && m <= 12); - return monthtab[c_gregorian_leap_p(y) ? 1 : 0][m]; -} - -/* - * Neri-Schneider algorithm for optimized Gregorian date conversion. - * Reference: Neri & Schneider, "Euclidean Affine Functions and Applications - * to Calendar Algorithms", Software: Practice and Experience, 2023. - * https://arxiv.org/abs/2102.06959 - * - * This algorithm provides ~2-3x speedup over traditional floating-point - * implementations by using pure integer arithmetic with multiplication - * and bit-shifts instead of expensive division operations. - */ - -/* JDN of March 1, Year 0 in proleptic Gregorian calendar */ -#define NS_EPOCH 1721120 - -/* Days in a 4-year cycle (3 normal years + 1 leap year) */ -#define NS_DAYS_IN_4_YEARS 1461 - -/* Days in a 400-year Gregorian cycle (97 leap years in 400 years) */ -#define NS_DAYS_IN_400_YEARS 146097 - -/* Years per century */ -#define NS_YEARS_PER_CENTURY 100 - -/* - * Multiplier for extracting year within century using fixed-point arithmetic. - * This is ceil(2^32 / NS_DAYS_IN_4_YEARS) for the Euclidean affine function. - */ -#define NS_YEAR_MULTIPLIER 2939745 - -/* - * Coefficients for month calculation from day-of-year. - * Maps day-of-year to month using: month = (NS_MONTH_COEFF * doy + NS_MONTH_OFFSET) >> 16 - */ -#define NS_MONTH_COEFF 2141 -#define NS_MONTH_OFFSET 197913 - -/* - * Coefficients for civil date to JDN month contribution. - * Maps month to accumulated days: days = (NS_CIVIL_MONTH_COEFF * m - NS_CIVIL_MONTH_OFFSET) / 32 - */ -#define NS_CIVIL_MONTH_COEFF 979 -#define NS_CIVIL_MONTH_OFFSET 2919 -#define NS_CIVIL_MONTH_DIVISOR 32 - -/* Days from March 1 to December 31 (for Jan/Feb year adjustment) */ -#define NS_DAYS_BEFORE_NEW_YEAR 306 - -/* - * Safe bounds for Neri-Schneider algorithm to avoid integer overflow. - * These correspond to approximately years -1,000,000 to +1,000,000. - */ -#define NS_JD_MIN -364000000 -#define NS_JD_MAX 538000000 - -inline static int -ns_jd_in_range(int jd) -{ - return jd >= NS_JD_MIN && jd <= NS_JD_MAX; -} - -/* Optimized: Gregorian date -> Julian Day Number */ -static int -c_gregorian_civil_to_jd(int y, int m, int d) -{ - /* Shift epoch to March 1 of year 0 (Jan/Feb belong to previous year) */ - int j = (m < 3) ? 1 : 0; - int y0 = y - j; - int m0 = j ? m + 12 : m; - int d0 = d - 1; - - /* Calculate year contribution with leap year correction */ - int q1 = DIV(y0, NS_YEARS_PER_CENTURY); - int yc = DIV(NS_DAYS_IN_4_YEARS * y0, 4) - q1 + DIV(q1, 4); - - /* Calculate month contribution using integer arithmetic */ - int mc = (NS_CIVIL_MONTH_COEFF * m0 - NS_CIVIL_MONTH_OFFSET) / NS_CIVIL_MONTH_DIVISOR; - - /* Combine and add epoch offset to get JDN */ - return yc + mc + d0 + NS_EPOCH; -} - -/* Optimized: Julian Day Number -> Gregorian date */ -static void -c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd) -{ - int r0, n1, q1, r1, n2, q2, r2, n3, q3, r3, y0, j; - uint64_t u2; - - /* Convert JDN to rata die (March 1, Year 0 epoch) */ - r0 = jd - NS_EPOCH; - - /* Extract century and day within 400-year cycle */ - /* Use Euclidean (floor) division for negative values */ - n1 = 4 * r0 + 3; - q1 = DIV(n1, NS_DAYS_IN_400_YEARS); - r1 = MOD(n1, NS_DAYS_IN_400_YEARS) / 4; - - /* Calculate year within century and day of year */ - n2 = 4 * r1 + 3; - /* Use 64-bit arithmetic to avoid overflow */ - u2 = (uint64_t)NS_YEAR_MULTIPLIER * (uint64_t)n2; - q2 = (int)(u2 >> 32); - r2 = (int)((uint32_t)u2 / NS_YEAR_MULTIPLIER / 4); - - /* Calculate month and day using integer arithmetic */ - n3 = NS_MONTH_COEFF * r2 + NS_MONTH_OFFSET; - q3 = n3 >> 16; - r3 = (n3 & 0xFFFF) / NS_MONTH_COEFF; - - /* Combine century and year */ - y0 = NS_YEARS_PER_CENTURY * q1 + q2; - - /* Adjust for January/February (shift from fiscal year) */ - j = (r2 >= NS_DAYS_BEFORE_NEW_YEAR) ? 1 : 0; - - *ry = y0 + j; - *rm = j ? q3 - 12 : q3; - *rd = r3 + 1; -} - -/* O(1) first day of year for Gregorian calendar */ -inline static int -c_gregorian_fdoy(int y) -{ - return c_gregorian_civil_to_jd(y, 1, 1); -} - -/* O(1) last day of year for Gregorian calendar */ -inline static int -c_gregorian_ldoy(int y) -{ - return c_gregorian_civil_to_jd(y, 12, 31); -} - -/* O(1) last day of month (JDN) for Gregorian calendar */ -inline static int -c_gregorian_ldom_jd(int y, int m) -{ - return c_gregorian_civil_to_jd(y, m, c_gregorian_last_day_of_month(y, m)); -} - -static int -c_valid_julian_p(int y, int m, int d, int *rm, int *rd) -{ - int last; - - if (m < 0) - m += 13; - if (m < 1 || m > 12) - return 0; - last = c_julian_last_day_of_month(y, m); - if (d < 0) - d = last + d + 1; - if (d < 1 || d > last) - return 0; - *rm = m; - *rd = d; - return 1; -} - -static int -c_valid_gregorian_p(int y, int m, int d, int *rm, int *rd) -{ - int last; - - if (m < 0) - m += 13; - if (m < 1 || m > 12) - return 0; - last = c_gregorian_last_day_of_month(y, m); - if (d < 0) - d = last + d + 1; - if (d < 1 || d > last) - return 0; - *rm = m; - *rd = d; - return 1; -} - -static int -c_valid_civil_p(int y, int m, int d, double sg, - int *rm, int *rd, int *rjd, int *ns) -{ - int ry; - - if (m < 0) - m += 13; - if (m < 1 || m > 12) - return 0; - if (d < 0) { - if (!c_find_ldom(y, m, sg, rjd, ns)) - return 0; - c_jd_to_civil(*rjd + d + 1, sg, &ry, rm, rd); - if (ry != y || *rm != m) - return 0; - d = *rd; - } - c_civil_to_jd(y, m, d, sg, rjd, ns); - c_jd_to_civil(*rjd, sg, &ry, rm, rd); - if (ry != y || *rm != m || *rd != d) - return 0; - return 1; -} - -static int -c_valid_commercial_p(int y, int w, int d, double sg, - int *rw, int *rd, int *rjd, int *ns) -{ - int ns2, ry2, rw2, rd2; - - if (d < 0) - d += 8; - if (w < 0) { - int rjd2; - - c_commercial_to_jd(y + 1, 1, 1, sg, &rjd2, &ns2); - c_jd_to_commercial(rjd2 + w * 7, sg, &ry2, &rw2, &rd2); - if (ry2 != y) - return 0; - w = rw2; - } - c_commercial_to_jd(y, w, d, sg, rjd, ns); - c_jd_to_commercial(*rjd, sg, &ry2, rw, rd); - if (y != ry2 || w != *rw || d != *rd) - return 0; - return 1; -} - -static int -c_valid_weeknum_p(int y, int w, int d, int f, double sg, - int *rw, int *rd, int *rjd, int *ns) -{ - int ns2, ry2, rw2, rd2; - - if (d < 0) - d += 7; - if (w < 0) { - int rjd2; - - c_weeknum_to_jd(y + 1, 1, f, f, sg, &rjd2, &ns2); - c_jd_to_weeknum(rjd2 + w * 7, f, sg, &ry2, &rw2, &rd2); - if (ry2 != y) - return 0; - w = rw2; - } - c_weeknum_to_jd(y, w, d, f, sg, rjd, ns); - c_jd_to_weeknum(*rjd, f, sg, &ry2, rw, rd); - if (y != ry2 || w != *rw || d != *rd) - return 0; - return 1; -} - -#ifndef NDEBUG -/* :nodoc: */ -static int -c_valid_nth_kday_p(int y, int m, int n, int k, double sg, - int *rm, int *rn, int *rk, int *rjd, int *ns) -{ - int ns2, ry2, rm2, rn2, rk2; - - if (k < 0) - k += 7; - if (n < 0) { - int t, ny, nm, rjd2; - - t = y * 12 + m; - ny = DIV(t, 12); - nm = MOD(t, 12) + 1; - - c_nth_kday_to_jd(ny, nm, 1, k, sg, &rjd2, &ns2); - c_jd_to_nth_kday(rjd2 + n * 7, sg, &ry2, &rm2, &rn2, &rk2); - if (ry2 != y || rm2 != m) - return 0; - n = rn2; - } - c_nth_kday_to_jd(y, m, n, k, sg, rjd, ns); - c_jd_to_nth_kday(*rjd, sg, &ry2, rm, rn, rk); - if (y != ry2 || m != *rm || n != *rn || k != *rk) - return 0; - return 1; -} -#endif - -static int -c_valid_time_p(int h, int min, int s, int *rh, int *rmin, int *rs) -{ - if (h < 0) - h += 24; - if (min < 0) - min += 60; - if (s < 0) - s += 60; - *rh = h; - *rmin = min; - *rs = s; - return !(h < 0 || h > 24 || - min < 0 || min > 59 || - s < 0 || s > 59 || - (h == 24 && (min > 0 || s > 0))); -} - -inline static int -c_valid_start_p(double sg) -{ - if (isnan(sg)) - return 0; - if (isinf(sg)) - return 1; - if (sg < REFORM_BEGIN_JD || sg > REFORM_END_JD) - return 0; - return 1; -} - -inline static int -df_local_to_utc(int df, int of) -{ - df -= of; - if (df < 0) - df += DAY_IN_SECONDS; - else if (df >= DAY_IN_SECONDS) - df -= DAY_IN_SECONDS; - return df; -} - -inline static int -df_utc_to_local(int df, int of) -{ - df += of; - if (df < 0) - df += DAY_IN_SECONDS; - else if (df >= DAY_IN_SECONDS) - df -= DAY_IN_SECONDS; - return df; -} - -inline static int -jd_local_to_utc(int jd, int df, int of) -{ - df -= of; - if (df < 0) - jd -= 1; - else if (df >= DAY_IN_SECONDS) - jd += 1; - return jd; -} - -inline static int -jd_utc_to_local(int jd, int df, int of) -{ - df += of; - if (df < 0) - jd -= 1; - else if (df >= DAY_IN_SECONDS) - jd += 1; - return jd; -} - -inline static int -time_to_df(int h, int min, int s) -{ - return h * HOUR_IN_SECONDS + min * MINUTE_IN_SECONDS + s; -} - -inline static void -df_to_time(int df, int *h, int *min, int *s) -{ - *h = df / HOUR_IN_SECONDS; - df %= HOUR_IN_SECONDS; - *min = df / MINUTE_IN_SECONDS; - *s = df % MINUTE_IN_SECONDS; -} - -static VALUE -sec_to_day(VALUE s) -{ - if (FIXNUM_P(s)) - return rb_rational_new2(s, INT2FIX(DAY_IN_SECONDS)); - return f_quo(s, INT2FIX(DAY_IN_SECONDS)); -} - -inline static VALUE -isec_to_day(int s) -{ - return sec_to_day(INT2FIX(s)); -} - -static VALUE -ns_to_day(VALUE n) -{ - if (FIXNUM_P(n)) - return rb_rational_new2(n, day_in_nanoseconds); - return f_quo(n, day_in_nanoseconds); -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -ms_to_sec(VALUE m) -{ - if (FIXNUM_P(m)) - return rb_rational_new2(m, INT2FIX(SECOND_IN_MILLISECONDS)); - return f_quo(m, INT2FIX(SECOND_IN_MILLISECONDS)); -} -#endif - -static VALUE -ns_to_sec(VALUE n) -{ - if (FIXNUM_P(n)) - return rb_rational_new2(n, INT2FIX(SECOND_IN_NANOSECONDS)); - return f_quo(n, INT2FIX(SECOND_IN_NANOSECONDS)); -} - -#ifndef NDEBUG -/* :nodoc: */ -inline static VALUE -ins_to_day(int n) -{ - return ns_to_day(INT2FIX(n)); -} -#endif - -static int -safe_mul_p(VALUE x, long m) -{ - long ix; - - if (!FIXNUM_P(x)) - return 0; - ix = FIX2LONG(x); - if (ix < 0) { - if (ix <= (FIXNUM_MIN / m)) - return 0; - } - else { - if (ix >= (FIXNUM_MAX / m)) - return 0; - } - return 1; -} - -static VALUE -day_to_sec(VALUE d) -{ - if (safe_mul_p(d, DAY_IN_SECONDS)) - return LONG2FIX(FIX2LONG(d) * DAY_IN_SECONDS); - return f_mul(d, INT2FIX(DAY_IN_SECONDS)); -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -day_to_ns(VALUE d) -{ - return f_mul(d, day_in_nanoseconds); -} -#endif - -static VALUE -sec_to_ms(VALUE s) -{ - if (safe_mul_p(s, SECOND_IN_MILLISECONDS)) - return LONG2FIX(FIX2LONG(s) * SECOND_IN_MILLISECONDS); - return f_mul(s, INT2FIX(SECOND_IN_MILLISECONDS)); -} - -static VALUE -sec_to_ns(VALUE s) -{ - if (safe_mul_p(s, SECOND_IN_NANOSECONDS)) - return LONG2FIX(FIX2LONG(s) * SECOND_IN_NANOSECONDS); - return f_mul(s, INT2FIX(SECOND_IN_NANOSECONDS)); -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -isec_to_ns(int s) -{ - return sec_to_ns(INT2FIX(s)); -} -#endif - -static VALUE -div_day(VALUE d, VALUE *f) -{ - if (f) - *f = f_mod(d, INT2FIX(1)); - return f_floor(d); -} - -static VALUE -div_df(VALUE d, VALUE *f) -{ - VALUE s = day_to_sec(d); - - if (f) - *f = f_mod(s, INT2FIX(1)); - return f_floor(s); -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -div_sf(VALUE s, VALUE *f) -{ - VALUE n = sec_to_ns(s); - - if (f) - *f = f_mod(n, INT2FIX(1)); - return f_floor(n); -} -#endif - -static void -decode_day(VALUE d, VALUE *jd, VALUE *df, VALUE *sf) -{ - VALUE f; - - *jd = div_day(d, &f); - *df = div_df(f, &f); - *sf = sec_to_ns(f); -} - -inline static double -s_virtual_sg(union DateData *x) -{ - if (isinf(x->s.sg)) - return x->s.sg; - if (f_zero_p(x->s.nth)) - return x->s.sg; - else if (f_negative_p(x->s.nth)) - return positive_inf; - return negative_inf; -} - -inline static double -c_virtual_sg(union DateData *x) -{ - if (isinf(x->c.sg)) - return x->c.sg; - if (f_zero_p(x->c.nth)) - return x->c.sg; - else if (f_negative_p(x->c.nth)) - return positive_inf; - return negative_inf; -} - -inline static double -m_virtual_sg(union DateData *x) -{ - if (simple_dat_p(x)) - return s_virtual_sg(x); - else - return c_virtual_sg(x); -} - -#define canonicalize_jd(_nth, _jd) \ -do {\ - if (_jd < 0) {\ - _nth = f_sub(_nth, INT2FIX(1));\ - _jd += CM_PERIOD;\ - }\ - if (_jd >= CM_PERIOD) {\ - _nth = f_add(_nth, INT2FIX(1));\ - _jd -= CM_PERIOD;\ - }\ -} while (0) - -inline static void -canonicalize_s_jd(VALUE obj, union DateData *x) -{ - int j = x->s.jd; - VALUE nth = x->s.nth; - assert(have_jd_p(x)); - canonicalize_jd(nth, x->s.jd); - RB_OBJ_WRITE(obj, &x->s.nth, nth); - if (x->s.jd != j) - x->flags &= ~HAVE_CIVIL; -} - -inline static void -get_s_jd(union DateData *x) -{ - assert(simple_dat_p(x)); - if (!have_jd_p(x)) { - int jd, ns; - - assert(have_civil_p(x)); -#ifndef USE_PACK - c_civil_to_jd(x->s.year, x->s.mon, x->s.mday, - s_virtual_sg(x), &jd, &ns); -#else - c_civil_to_jd(x->s.year, EX_MON(x->s.pc), EX_MDAY(x->s.pc), - s_virtual_sg(x), &jd, &ns); -#endif - x->s.jd = jd; - x->s.flags |= HAVE_JD; - } -} - -inline static void -get_s_civil(union DateData *x) -{ - assert(simple_dat_p(x)); - if (!have_civil_p(x)) { - int y, m, d; - - assert(have_jd_p(x)); - c_jd_to_civil(x->s.jd, s_virtual_sg(x), &y, &m, &d); - x->s.year = y; -#ifndef USE_PACK - x->s.mon = m; - x->s.mday = d; -#else - x->s.pc = PACK2(m, d); -#endif - x->s.flags |= HAVE_CIVIL; - } -} - -inline static void -get_c_df(union DateData *x) -{ - assert(complex_dat_p(x)); - if (!have_df_p(x)) { - assert(have_time_p(x)); -#ifndef USE_PACK - x->c.df = df_local_to_utc(time_to_df(x->c.hour, x->c.min, x->c.sec), - x->c.of); -#else - x->c.df = df_local_to_utc(time_to_df(EX_HOUR(x->c.pc), - EX_MIN(x->c.pc), - EX_SEC(x->c.pc)), - x->c.of); -#endif - x->c.flags |= HAVE_DF; - } -} - -inline static void -get_c_time(union DateData *x) -{ - assert(complex_dat_p(x)); - if (!have_time_p(x)) { -#ifndef USE_PACK - int r; - assert(have_df_p(x)); - r = df_utc_to_local(x->c.df, x->c.of); - df_to_time(r, &x->c.hour, &x->c.min, &x->c.sec); - x->c.flags |= HAVE_TIME; -#else - int r, m, d, h, min, s; - - assert(have_df_p(x)); - m = EX_MON(x->c.pc); - d = EX_MDAY(x->c.pc); - r = df_utc_to_local(x->c.df, x->c.of); - df_to_time(r, &h, &min, &s); - x->c.pc = PACK5(m, d, h, min, s); - x->c.flags |= HAVE_TIME; -#endif - } -} - -inline static void -canonicalize_c_jd(VALUE obj, union DateData *x) -{ - int j = x->c.jd; - VALUE nth = x->c.nth; - assert(have_jd_p(x)); - canonicalize_jd(nth, x->c.jd); - RB_OBJ_WRITE(obj, &x->c.nth, nth); - if (x->c.jd != j) - x->flags &= ~HAVE_CIVIL; -} - -inline static void -get_c_jd(union DateData *x) -{ - assert(complex_dat_p(x)); - if (!have_jd_p(x)) { - int jd, ns; - - assert(have_civil_p(x)); -#ifndef USE_PACK - c_civil_to_jd(x->c.year, x->c.mon, x->c.mday, - c_virtual_sg(x), &jd, &ns); -#else - c_civil_to_jd(x->c.year, EX_MON(x->c.pc), EX_MDAY(x->c.pc), - c_virtual_sg(x), &jd, &ns); -#endif - - get_c_time(x); -#ifndef USE_PACK - x->c.jd = jd_local_to_utc(jd, - time_to_df(x->c.hour, x->c.min, x->c.sec), - x->c.of); -#else - x->c.jd = jd_local_to_utc(jd, - time_to_df(EX_HOUR(x->c.pc), - EX_MIN(x->c.pc), - EX_SEC(x->c.pc)), - x->c.of); -#endif - x->c.flags |= HAVE_JD; - } -} - -inline static void -get_c_civil(union DateData *x) -{ - assert(complex_dat_p(x)); - if (!have_civil_p(x)) { -#ifndef USE_PACK - int jd, y, m, d; -#else - int jd, y, m, d, h, min, s; -#endif - - assert(have_jd_p(x)); - get_c_df(x); - jd = jd_utc_to_local(x->c.jd, x->c.df, x->c.of); - c_jd_to_civil(jd, c_virtual_sg(x), &y, &m, &d); - x->c.year = y; -#ifndef USE_PACK - x->c.mon = m; - x->c.mday = d; -#else - h = EX_HOUR(x->c.pc); - min = EX_MIN(x->c.pc); - s = EX_SEC(x->c.pc); - x->c.pc = PACK5(m, d, h, min, s); -#endif - x->c.flags |= HAVE_CIVIL; - } -} - -inline static int -local_jd(union DateData *x) -{ - assert(complex_dat_p(x)); - assert(have_jd_p(x)); - assert(have_df_p(x)); - return jd_utc_to_local(x->c.jd, x->c.df, x->c.of); -} - -inline static int -local_df(union DateData *x) -{ - assert(complex_dat_p(x)); - assert(have_df_p(x)); - return df_utc_to_local(x->c.df, x->c.of); -} - -static void -decode_year(VALUE y, double style, - VALUE *nth, int *ry) -{ - int period; - VALUE t; - - period = (style < 0) ? - CM_PERIOD_GCY : - CM_PERIOD_JCY; - if (FIXNUM_P(y)) { - long iy, it, inth; - - iy = FIX2LONG(y); - if (iy >= (FIXNUM_MAX - 4712)) - goto big; - it = iy + 4712; /* shift */ - inth = DIV(it, ((long)period)); - *nth = LONG2FIX(inth); - if (inth) - it = MOD(it, ((long)period)); - *ry = (int)it - 4712; /* unshift */ - return; - } - big: - t = f_add(y, INT2FIX(4712)); /* shift */ - *nth = f_idiv(t, INT2FIX(period)); - if (f_nonzero_p(*nth)) - t = f_mod(t, INT2FIX(period)); - *ry = FIX2INT(t) - 4712; /* unshift */ -} - -static void -encode_year(VALUE nth, int y, double style, - VALUE *ry) -{ - int period; - VALUE t; - - period = (style < 0) ? - CM_PERIOD_GCY : - CM_PERIOD_JCY; - if (f_zero_p(nth)) - *ry = INT2FIX(y); - else { - t = f_mul(INT2FIX(period), nth); - t = f_add(t, INT2FIX(y)); - *ry = t; - } -} - -static void -decode_jd(VALUE jd, VALUE *nth, int *rjd) -{ - *nth = f_idiv(jd, INT2FIX(CM_PERIOD)); - if (f_zero_p(*nth)) { - *rjd = FIX2INT(jd); - return; - } - *rjd = FIX2INT(f_mod(jd, INT2FIX(CM_PERIOD))); -} - -static void -encode_jd(VALUE nth, int jd, VALUE *rjd) -{ - if (f_zero_p(nth)) { - *rjd = INT2FIX(jd); - return; - } - *rjd = f_add(f_mul(INT2FIX(CM_PERIOD), nth), INT2FIX(jd)); -} - -inline static double -guess_style(VALUE y, double sg) /* -/+oo or zero */ -{ - double style = 0; - - if (isinf(sg)) - style = sg; - else if (!FIXNUM_P(y)) - style = f_positive_p(y) ? negative_inf : positive_inf; - else { - long iy = FIX2LONG(y); - - assert(FIXNUM_P(y)); - if (iy < REFORM_BEGIN_YEAR) - style = positive_inf; - else if (iy > REFORM_END_YEAR) - style = negative_inf; - } - return style; -} - -inline static void -m_canonicalize_jd(VALUE obj, union DateData *x) -{ - if (simple_dat_p(x)) { - get_s_jd(x); - canonicalize_s_jd(obj, x); - } - else { - get_c_jd(x); - canonicalize_c_jd(obj, x); - } -} - -inline static VALUE -m_nth(union DateData *x) -{ - if (simple_dat_p(x)) - return x->s.nth; - else { - get_c_civil(x); - return x->c.nth; - } -} - -inline static int -m_jd(union DateData *x) -{ - if (simple_dat_p(x)) { - get_s_jd(x); - return x->s.jd; - } - else { - get_c_jd(x); - return x->c.jd; - } -} - -static VALUE -m_real_jd(union DateData *x) -{ - VALUE nth, rjd; - int jd; - - nth = m_nth(x); - jd = m_jd(x); - - encode_jd(nth, jd, &rjd); - return rjd; -} - -static int -m_local_jd(union DateData *x) -{ - if (simple_dat_p(x)) { - get_s_jd(x); - return x->s.jd; - } - else { - get_c_jd(x); - get_c_df(x); - return local_jd(x); - } -} - -static VALUE -m_real_local_jd(union DateData *x) -{ - VALUE nth, rjd; - int jd; - - nth = m_nth(x); - jd = m_local_jd(x); - - encode_jd(nth, jd, &rjd); - return rjd; -} - -inline static int -m_df(union DateData *x) -{ - if (simple_dat_p(x)) - return 0; - else { - get_c_df(x); - return x->c.df; - } -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -m_df_in_day(union DateData *x) -{ - return isec_to_day(m_df(x)); -} -#endif - -static int -m_local_df(union DateData *x) -{ - if (simple_dat_p(x)) - return 0; - else { - get_c_df(x); - return local_df(x); - } -} - -#ifndef NDEBUG -static VALUE -m_local_df_in_day(union DateData *x) -{ - return isec_to_day(m_local_df(x)); -} -#endif - -inline static VALUE -m_sf(union DateData *x) -{ - if (simple_dat_p(x)) - return INT2FIX(0); - else - return x->c.sf; -} - -#ifndef NDEBUG -static VALUE -m_sf_in_day(union DateData *x) -{ - return ns_to_day(m_sf(x)); -} -#endif - -static VALUE -m_sf_in_sec(union DateData *x) -{ - return ns_to_sec(m_sf(x)); -} - -static VALUE -m_fr(union DateData *x) -{ - if (simple_dat_p(x)) - return INT2FIX(0); - else { - int df; - VALUE sf, fr; - - df = m_local_df(x); - sf = m_sf(x); - fr = isec_to_day(df); - if (f_nonzero_p(sf)) - fr = f_add(fr, ns_to_day(sf)); - return fr; - } -} - -#define HALF_DAYS_IN_SECONDS (DAY_IN_SECONDS / 2) - -static VALUE -m_ajd(union DateData *x) -{ - VALUE r, sf; - int df; - - if (simple_dat_p(x)) { - r = m_real_jd(x); - if (FIXNUM_P(r) && FIX2LONG(r) <= (FIXNUM_MAX / 2) && FIX2LONG(r) >= (FIXNUM_MIN + 1) / 2) { - long ir = FIX2LONG(r); - ir = ir * 2 - 1; - return rb_rational_new2(LONG2FIX(ir), INT2FIX(2)); - } - else - return rb_rational_new2(f_sub(f_mul(r, - INT2FIX(2)), - INT2FIX(1)), - INT2FIX(2)); - } - - r = m_real_jd(x); - df = m_df(x); - df -= HALF_DAYS_IN_SECONDS; - if (df) - r = f_add(r, isec_to_day(df)); - sf = m_sf(x); - if (f_nonzero_p(sf)) - r = f_add(r, ns_to_day(sf)); - - return r; -} - -static VALUE -m_amjd(union DateData *x) -{ - VALUE r, sf; - int df; - - r = m_real_jd(x); - if (FIXNUM_P(r) && FIX2LONG(r) >= (FIXNUM_MIN + 2400001)) { - long ir = FIX2LONG(r); - ir -= 2400001; - r = rb_rational_new1(LONG2FIX(ir)); - } - else - r = rb_rational_new1(f_sub(m_real_jd(x), - INT2FIX(2400001))); - - if (simple_dat_p(x)) - return r; - - df = m_df(x); - if (df) - r = f_add(r, isec_to_day(df)); - sf = m_sf(x); - if (f_nonzero_p(sf)) - r = f_add(r, ns_to_day(sf)); - - return r; -} - -inline static int -m_of(union DateData *x) -{ - if (simple_dat_p(x)) - return 0; - else { - get_c_jd(x); - return x->c.of; - } -} - -static VALUE -m_of_in_day(union DateData *x) -{ - return isec_to_day(m_of(x)); -} - -inline static double -m_sg(union DateData *x) -{ - if (simple_dat_p(x)) - return x->s.sg; - else { - get_c_jd(x); - return x->c.sg; - } -} - -static int -m_julian_p(union DateData *x) -{ - int jd; - double sg; - - if (simple_dat_p(x)) { - get_s_jd(x); - jd = x->s.jd; - sg = s_virtual_sg(x); - } - else { - get_c_jd(x); - jd = x->c.jd; - sg = c_virtual_sg(x); - } - if (isinf(sg)) - return sg == positive_inf; - return jd < sg; -} - -inline static int -m_gregorian_p(union DateData *x) -{ - return !m_julian_p(x); -} - -inline static int -m_proleptic_julian_p(union DateData *x) -{ - double sg; - - sg = m_sg(x); - if (isinf(sg) && sg > 0) - return 1; - return 0; -} - -inline static int -m_proleptic_gregorian_p(union DateData *x) -{ - double sg; - - sg = m_sg(x); - if (isinf(sg) && sg < 0) - return 1; - return 0; -} - -inline static int -m_year(union DateData *x) -{ - if (simple_dat_p(x)) { - get_s_civil(x); - return x->s.year; - } - else { - get_c_civil(x); - return x->c.year; - } -} - -static VALUE -m_real_year(union DateData *x) -{ - VALUE nth, ry; - int year; - - nth = m_nth(x); - year = m_year(x); - - if (f_zero_p(nth)) - return INT2FIX(year); - - encode_year(nth, year, - m_gregorian_p(x) ? -1 : +1, - &ry); - return ry; -} - -inline static int -m_mon(union DateData *x) -{ - if (simple_dat_p(x)) { - get_s_civil(x); -#ifndef USE_PACK - return x->s.mon; -#else - return EX_MON(x->s.pc); -#endif - } - else { - get_c_civil(x); -#ifndef USE_PACK - return x->c.mon; -#else - return EX_MON(x->c.pc); -#endif - } -} - -inline static int -m_mday(union DateData *x) -{ - if (simple_dat_p(x)) { - get_s_civil(x); -#ifndef USE_PACK - return x->s.mday; -#else - return EX_MDAY(x->s.pc); -#endif - } - else { - get_c_civil(x); -#ifndef USE_PACK - return x->c.mday; -#else - return EX_MDAY(x->c.pc); -#endif - } -} - -static const int yeartab[2][13] = { - { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }, - { 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 } -}; - -static int -c_julian_to_yday(int y, int m, int d) -{ - assert(m >= 1 && m <= 12); - return yeartab[c_julian_leap_p(y) ? 1 : 0][m] + d; -} - -static int -c_gregorian_to_yday(int y, int m, int d) -{ - assert(m >= 1 && m <= 12); - return yeartab[c_gregorian_leap_p(y) ? 1 : 0][m] + d; -} - -static int -m_yday(union DateData *x) -{ - int jd, ry, rd; - double sg; - - jd = m_local_jd(x); - sg = m_virtual_sg(x); /* !=m_sg() */ - - if (m_proleptic_gregorian_p(x) || - (jd - sg) > 366) - return c_gregorian_to_yday(m_year(x), m_mon(x), m_mday(x)); - if (m_proleptic_julian_p(x)) - return c_julian_to_yday(m_year(x), m_mon(x), m_mday(x)); - c_jd_to_ordinal(jd, sg, &ry, &rd); - return rd; -} - -static int -m_wday(union DateData *x) -{ - return c_jd_to_wday(m_local_jd(x)); -} - -static int -m_cwyear(union DateData *x) -{ - int ry, rw, rd; - - c_jd_to_commercial(m_local_jd(x), m_virtual_sg(x), /* !=m_sg() */ - &ry, &rw, &rd); - return ry; -} - -static VALUE -m_real_cwyear(union DateData *x) -{ - VALUE nth, ry; - int year; - - nth = m_nth(x); - year = m_cwyear(x); - - if (f_zero_p(nth)) - return INT2FIX(year); - - encode_year(nth, year, - m_gregorian_p(x) ? -1 : +1, - &ry); - return ry; -} - -static int -m_cweek(union DateData *x) -{ - int ry, rw, rd; - - c_jd_to_commercial(m_local_jd(x), m_virtual_sg(x), /* !=m_sg() */ - &ry, &rw, &rd); - return rw; -} - -static int -m_cwday(union DateData *x) -{ - int w; - - w = m_wday(x); - if (w == 0) - w = 7; - return w; -} - -static int -m_wnumx(union DateData *x, int f) -{ - int ry, rw, rd; - - c_jd_to_weeknum(m_local_jd(x), f, m_virtual_sg(x), /* !=m_sg() */ - &ry, &rw, &rd); - return rw; -} - -static int -m_wnum0(union DateData *x) -{ - return m_wnumx(x, 0); -} - -static int -m_wnum1(union DateData *x) -{ - return m_wnumx(x, 1); -} - -inline static int -m_hour(union DateData *x) -{ - if (simple_dat_p(x)) - return 0; - else { - get_c_time(x); -#ifndef USE_PACK - return x->c.hour; -#else - return EX_HOUR(x->c.pc); -#endif - } -} - -inline static int -m_min(union DateData *x) -{ - if (simple_dat_p(x)) - return 0; - else { - get_c_time(x); -#ifndef USE_PACK - return x->c.min; -#else - return EX_MIN(x->c.pc); -#endif - } -} - -inline static int -m_sec(union DateData *x) -{ - if (simple_dat_p(x)) - return 0; - else { - get_c_time(x); -#ifndef USE_PACK - return x->c.sec; -#else - return EX_SEC(x->c.pc); -#endif - } -} - -#define decode_offset(of,s,h,m)\ -do {\ - int a;\ - s = (of < 0) ? '-' : '+';\ - a = (of < 0) ? -of : of;\ - h = a / HOUR_IN_SECONDS;\ - m = a % HOUR_IN_SECONDS / MINUTE_IN_SECONDS;\ -} while (0) - -static VALUE -of2str(int of) -{ - int s, h, m; - - decode_offset(of, s, h, m); - return rb_enc_sprintf(rb_usascii_encoding(), "%c%02d:%02d", s, h, m); -} - -static VALUE -m_zone(union DateData *x) -{ - if (simple_dat_p(x)) - return rb_usascii_str_new2("+00:00"); - return of2str(m_of(x)); -} - -inline static VALUE -f_kind_of_p(VALUE x, VALUE c) -{ - return rb_obj_is_kind_of(x, c); -} - -inline static VALUE -k_date_p(VALUE x) -{ - return f_kind_of_p(x, cDate); -} - -inline static VALUE -k_numeric_p(VALUE x) -{ - return f_kind_of_p(x, rb_cNumeric); -} - -inline static VALUE -k_rational_p(VALUE x) -{ - return f_kind_of_p(x, rb_cRational); -} - -static inline void -expect_numeric(VALUE x) -{ - if (!k_numeric_p(x)) - rb_raise(rb_eTypeError, "expected numeric"); -} - -#ifndef NDEBUG -/* :nodoc: */ -static void -civil_to_jd(VALUE y, int m, int d, double sg, - VALUE *nth, int *ry, - int *rjd, - int *ns) -{ - double style = guess_style(y, sg); - - if (style == 0) { - int jd; - - c_civil_to_jd(FIX2INT(y), m, d, sg, &jd, ns); - decode_jd(INT2FIX(jd), nth, rjd); - if (f_zero_p(*nth)) - *ry = FIX2INT(y); - else { - VALUE nth2; - decode_year(y, *ns ? -1 : +1, &nth2, ry); - } - } - else { - decode_year(y, style, nth, ry); - c_civil_to_jd(*ry, m, d, style, rjd, ns); - } -} - -static void -jd_to_civil(VALUE jd, double sg, - VALUE *nth, int *rjd, - int *ry, int *rm, int *rd) -{ - decode_jd(jd, nth, rjd); - c_jd_to_civil(*rjd, sg, ry, rm, rd); -} - -static void -ordinal_to_jd(VALUE y, int d, double sg, - VALUE *nth, int *ry, - int *rjd, - int *ns) -{ - double style = guess_style(y, sg); - - if (style == 0) { - int jd; - - c_ordinal_to_jd(FIX2INT(y), d, sg, &jd, ns); - decode_jd(INT2FIX(jd), nth, rjd); - if (f_zero_p(*nth)) - *ry = FIX2INT(y); - else { - VALUE nth2; - decode_year(y, *ns ? -1 : +1, &nth2, ry); - } - } - else { - decode_year(y, style, nth, ry); - c_ordinal_to_jd(*ry, d, style, rjd, ns); - } -} - -static void -jd_to_ordinal(VALUE jd, double sg, - VALUE *nth, int *rjd, - int *ry, int *rd) -{ - decode_jd(jd, nth, rjd); - c_jd_to_ordinal(*rjd, sg, ry, rd); -} - -static void -commercial_to_jd(VALUE y, int w, int d, double sg, - VALUE *nth, int *ry, - int *rjd, - int *ns) -{ - double style = guess_style(y, sg); - - if (style == 0) { - int jd; - - c_commercial_to_jd(FIX2INT(y), w, d, sg, &jd, ns); - decode_jd(INT2FIX(jd), nth, rjd); - if (f_zero_p(*nth)) - *ry = FIX2INT(y); - else { - VALUE nth2; - decode_year(y, *ns ? -1 : +1, &nth2, ry); - } - } - else { - decode_year(y, style, nth, ry); - c_commercial_to_jd(*ry, w, d, style, rjd, ns); - } -} - -static void -jd_to_commercial(VALUE jd, double sg, - VALUE *nth, int *rjd, - int *ry, int *rw, int *rd) -{ - decode_jd(jd, nth, rjd); - c_jd_to_commercial(*rjd, sg, ry, rw, rd); -} - -static void -weeknum_to_jd(VALUE y, int w, int d, int f, double sg, - VALUE *nth, int *ry, - int *rjd, - int *ns) -{ - double style = guess_style(y, sg); - - if (style == 0) { - int jd; - - c_weeknum_to_jd(FIX2INT(y), w, d, f, sg, &jd, ns); - decode_jd(INT2FIX(jd), nth, rjd); - if (f_zero_p(*nth)) - *ry = FIX2INT(y); - else { - VALUE nth2; - decode_year(y, *ns ? -1 : +1, &nth2, ry); - } - } - else { - decode_year(y, style, nth, ry); - c_weeknum_to_jd(*ry, w, d, f, style, rjd, ns); - } -} - -static void -jd_to_weeknum(VALUE jd, int f, double sg, - VALUE *nth, int *rjd, - int *ry, int *rw, int *rd) -{ - decode_jd(jd, nth, rjd); - c_jd_to_weeknum(*rjd, f, sg, ry, rw, rd); -} - -static void -nth_kday_to_jd(VALUE y, int m, int n, int k, double sg, - VALUE *nth, int *ry, - int *rjd, - int *ns) -{ - double style = guess_style(y, sg); - - if (style == 0) { - int jd; - - c_nth_kday_to_jd(FIX2INT(y), m, n, k, sg, &jd, ns); - decode_jd(INT2FIX(jd), nth, rjd); - if (f_zero_p(*nth)) - *ry = FIX2INT(y); - else { - VALUE nth2; - decode_year(y, *ns ? -1 : +1, &nth2, ry); - } - } - else { - decode_year(y, style, nth, ry); - c_nth_kday_to_jd(*ry, m, n, k, style, rjd, ns); - } -} - -static void -jd_to_nth_kday(VALUE jd, double sg, - VALUE *nth, int *rjd, - int *ry, int *rm, int *rn, int *rk) -{ - decode_jd(jd, nth, rjd); - c_jd_to_nth_kday(*rjd, sg, ry, rm, rn, rk); -} -#endif - -static int -valid_ordinal_p(VALUE y, int d, double sg, - VALUE *nth, int *ry, - int *rd, int *rjd, - int *ns) -{ - double style = guess_style(y, sg); - int r; - - if (style == 0) { - int jd; - - r = c_valid_ordinal_p(FIX2INT(y), d, sg, rd, &jd, ns); - if (!r) - return 0; - decode_jd(INT2FIX(jd), nth, rjd); - if (f_zero_p(*nth)) - *ry = FIX2INT(y); - else { - VALUE nth2; - decode_year(y, *ns ? -1 : +1, &nth2, ry); - } - } - else { - decode_year(y, style, nth, ry); - r = c_valid_ordinal_p(*ry, d, style, rd, rjd, ns); - } - return r; -} - -static int -valid_gregorian_p(VALUE y, int m, int d, - VALUE *nth, int *ry, - int *rm, int *rd) -{ - decode_year(y, -1, nth, ry); - return c_valid_gregorian_p(*ry, m, d, rm, rd); -} - -static int -valid_civil_p(VALUE y, int m, int d, double sg, - VALUE *nth, int *ry, - int *rm, int *rd, int *rjd, - int *ns) -{ - double style = guess_style(y, sg); - int r; - - if (style == 0) { - int jd; - - r = c_valid_civil_p(FIX2INT(y), m, d, sg, rm, rd, &jd, ns); - if (!r) - return 0; - decode_jd(INT2FIX(jd), nth, rjd); - if (f_zero_p(*nth)) - *ry = FIX2INT(y); - else { - VALUE nth2; - decode_year(y, *ns ? -1 : +1, &nth2, ry); - } - } - else { - decode_year(y, style, nth, ry); - if (style < 0) - r = c_valid_gregorian_p(*ry, m, d, rm, rd); - else - r = c_valid_julian_p(*ry, m, d, rm, rd); - if (!r) - return 0; - c_civil_to_jd(*ry, *rm, *rd, style, rjd, ns); - } - return r; -} - -static int -valid_commercial_p(VALUE y, int w, int d, double sg, - VALUE *nth, int *ry, - int *rw, int *rd, int *rjd, - int *ns) -{ - double style = guess_style(y, sg); - int r; - - if (style == 0) { - int jd; - - r = c_valid_commercial_p(FIX2INT(y), w, d, sg, rw, rd, &jd, ns); - if (!r) - return 0; - decode_jd(INT2FIX(jd), nth, rjd); - if (f_zero_p(*nth)) - *ry = FIX2INT(y); - else { - VALUE nth2; - decode_year(y, *ns ? -1 : +1, &nth2, ry); - } - } - else { - decode_year(y, style, nth, ry); - r = c_valid_commercial_p(*ry, w, d, style, rw, rd, rjd, ns); - } - return r; -} - -static int -valid_weeknum_p(VALUE y, int w, int d, int f, double sg, - VALUE *nth, int *ry, - int *rw, int *rd, int *rjd, - int *ns) -{ - double style = guess_style(y, sg); - int r; - - if (style == 0) { - int jd; - - r = c_valid_weeknum_p(FIX2INT(y), w, d, f, sg, rw, rd, &jd, ns); - if (!r) - return 0; - decode_jd(INT2FIX(jd), nth, rjd); - if (f_zero_p(*nth)) - *ry = FIX2INT(y); - else { - VALUE nth2; - decode_year(y, *ns ? -1 : +1, &nth2, ry); - } - } - else { - decode_year(y, style, nth, ry); - r = c_valid_weeknum_p(*ry, w, d, f, style, rw, rd, rjd, ns); - } - return r; -} - -#ifndef NDEBUG -/* :nodoc: */ -static int -valid_nth_kday_p(VALUE y, int m, int n, int k, double sg, - VALUE *nth, int *ry, - int *rm, int *rn, int *rk, int *rjd, - int *ns) -{ - double style = guess_style(y, sg); - int r; - - if (style == 0) { - int jd; - - r = c_valid_nth_kday_p(FIX2INT(y), m, n, k, sg, rm, rn, rk, &jd, ns); - if (!r) - return 0; - decode_jd(INT2FIX(jd), nth, rjd); - if (f_zero_p(*nth)) - *ry = FIX2INT(y); - else { - VALUE nth2; - decode_year(y, *ns ? -1 : +1, &nth2, ry); - } - } - else { - decode_year(y, style, nth, ry); - r = c_valid_nth_kday_p(*ry, m, n, k, style, rm, rn, rk, rjd, ns); - } - return r; -} -#endif - -VALUE date_zone_to_diff(VALUE); - -static int -offset_to_sec(VALUE vof, int *rof) -{ - int try_rational = 1; - - again: - switch (TYPE(vof)) { - case T_FIXNUM: - { - long n; - - n = FIX2LONG(vof); - if (n != -1 && n != 0 && n != 1) - return 0; - *rof = (int)n * DAY_IN_SECONDS; - return 1; - } - case T_FLOAT: - { - double n; - - n = RFLOAT_VALUE(vof) * DAY_IN_SECONDS; - if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) - return 0; - *rof = (int)round(n); - if (*rof != n) - rb_warning("fraction of offset is ignored"); - return 1; - } - default: - expect_numeric(vof); - vof = f_to_r(vof); - if (!k_rational_p(vof)) { - if (!try_rational) Check_Type(vof, T_RATIONAL); - try_rational = 0; - goto again; - } - /* fall through */ - case T_RATIONAL: - { - VALUE vs, vn, vd; - long n; - - vs = day_to_sec(vof); - - if (!k_rational_p(vs)) { - vn = vs; - goto rounded; - } - vn = rb_rational_num(vs); - vd = rb_rational_den(vs); - - if (FIXNUM_P(vn) && FIXNUM_P(vd) && (FIX2LONG(vd) == 1)) - n = FIX2LONG(vn); - else { - vn = f_round(vs); - if (!f_eqeq_p(vn, vs)) - rb_warning("fraction of offset is ignored"); - rounded: - if (!FIXNUM_P(vn)) - return 0; - n = FIX2LONG(vn); - if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) - return 0; - } - *rof = (int)n; - return 1; - } - case T_STRING: - { - VALUE vs = date_zone_to_diff(vof); - long n; - - if (!FIXNUM_P(vs)) - return 0; - n = FIX2LONG(vs); - if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) - return 0; - *rof = (int)n; - return 1; - } - } - return 0; -} - -/* date */ - -#define valid_sg(sg) \ -do {\ - if (!c_valid_start_p(sg)) {\ - sg = 0;\ - rb_warning("invalid start is ignored");\ - }\ -} while (0) - -static VALUE -valid_jd_sub(int argc, VALUE *argv, VALUE klass, int need_jd) -{ - double sg = NUM2DBL(argv[1]); - valid_sg(sg); - return argv[0]; -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -date_s__valid_jd_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vjd, vsg; - VALUE argv2[2]; - - rb_scan_args(argc, argv, "11", &vjd, &vsg); - - argv2[0] = vjd; - if (argc < 2) - argv2[1] = DBL2NUM(GREGORIAN); - else - argv2[1] = vsg; - - return valid_jd_sub(2, argv2, klass, 1); -} -#endif - -/* - * call-seq: - * Date.valid_jd?(jd, start = Date::ITALY) -> true - * - * Implemented for compatibility; - * returns +true+ unless +jd+ is invalid (i.e., not a Numeric). - * - * Date.valid_jd?(2451944) # => true - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - * Related: Date.jd. - */ -static VALUE -date_s_valid_jd_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vjd, vsg; - VALUE argv2[2]; - - rb_scan_args(argc, argv, "11", &vjd, &vsg); - - RETURN_FALSE_UNLESS_NUMERIC(vjd); - argv2[0] = vjd; - if (argc < 2) - argv2[1] = INT2FIX(DEFAULT_SG); - else - argv2[1] = vsg; - - if (NIL_P(valid_jd_sub(2, argv2, klass, 0))) - return Qfalse; - return Qtrue; -} - -static VALUE -valid_civil_sub(int argc, VALUE *argv, VALUE klass, int need_jd) -{ - VALUE nth, y; - int m, d, ry, rm, rd; - double sg; - - y = argv[0]; - m = NUM2INT(argv[1]); - d = NUM2INT(argv[2]); - sg = NUM2DBL(argv[3]); - - valid_sg(sg); - - if (!need_jd && (guess_style(y, sg) < 0)) { - if (!valid_gregorian_p(y, m, d, - &nth, &ry, - &rm, &rd)) - return Qnil; - return INT2FIX(0); /* dummy */ - } - else { - int rjd, ns; - VALUE rjd2; - - if (!valid_civil_p(y, m, d, sg, - &nth, &ry, - &rm, &rd, &rjd, - &ns)) - return Qnil; - if (!need_jd) - return INT2FIX(0); /* dummy */ - encode_jd(nth, rjd, &rjd2); - return rjd2; - } -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -date_s__valid_civil_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vm, vd, vsg; - VALUE argv2[4]; - - rb_scan_args(argc, argv, "31", &vy, &vm, &vd, &vsg); - - argv2[0] = vy; - argv2[1] = vm; - argv2[2] = vd; - if (argc < 4) - argv2[3] = DBL2NUM(GREGORIAN); - else - argv2[3] = vsg; - - return valid_civil_sub(4, argv2, klass, 1); -} -#endif - -/* - * call-seq: - * Date.valid_civil?(year, month, mday, start = Date::ITALY) -> true or false - * - * Returns +true+ if the arguments define a valid ordinal date, - * +false+ otherwise: - * - * Date.valid_date?(2001, 2, 3) # => true - * Date.valid_date?(2001, 2, 29) # => false - * Date.valid_date?(2001, 2, -1) # => true - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - * Related: Date.jd, Date.new. - */ -static VALUE -date_s_valid_civil_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vm, vd, vsg; - VALUE argv2[4]; - - rb_scan_args(argc, argv, "31", &vy, &vm, &vd, &vsg); - - RETURN_FALSE_UNLESS_NUMERIC(vy); - RETURN_FALSE_UNLESS_NUMERIC(vm); - RETURN_FALSE_UNLESS_NUMERIC(vd); - argv2[0] = vy; - argv2[1] = vm; - argv2[2] = vd; - if (argc < 4) - argv2[3] = INT2FIX(DEFAULT_SG); - else - argv2[3] = vsg; - - if (NIL_P(valid_civil_sub(4, argv2, klass, 0))) - return Qfalse; - return Qtrue; -} - -static VALUE -valid_ordinal_sub(int argc, VALUE *argv, VALUE klass, int need_jd) -{ - VALUE nth, y; - int d, ry, rd; - double sg; - - y = argv[0]; - d = NUM2INT(argv[1]); - sg = NUM2DBL(argv[2]); - - valid_sg(sg); - - { - int rjd, ns; - VALUE rjd2; - - if (!valid_ordinal_p(y, d, sg, - &nth, &ry, - &rd, &rjd, - &ns)) - return Qnil; - if (!need_jd) - return INT2FIX(0); /* dummy */ - encode_jd(nth, rjd, &rjd2); - return rjd2; - } -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -date_s__valid_ordinal_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vd, vsg; - VALUE argv2[3]; - - rb_scan_args(argc, argv, "21", &vy, &vd, &vsg); - - argv2[0] = vy; - argv2[1] = vd; - if (argc < 3) - argv2[2] = DBL2NUM(GREGORIAN); - else - argv2[2] = vsg; - - return valid_ordinal_sub(3, argv2, klass, 1); -} -#endif - -/* - * call-seq: - * Date.valid_ordinal?(year, yday, start = Date::ITALY) -> true or false - * - * Returns +true+ if the arguments define a valid ordinal date, - * +false+ otherwise: - * - * Date.valid_ordinal?(2001, 34) # => true - * Date.valid_ordinal?(2001, 366) # => false - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - * Related: Date.jd, Date.ordinal. - */ -static VALUE -date_s_valid_ordinal_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vd, vsg; - VALUE argv2[3]; - - rb_scan_args(argc, argv, "21", &vy, &vd, &vsg); - - RETURN_FALSE_UNLESS_NUMERIC(vy); - RETURN_FALSE_UNLESS_NUMERIC(vd); - argv2[0] = vy; - argv2[1] = vd; - if (argc < 3) - argv2[2] = INT2FIX(DEFAULT_SG); - else - argv2[2] = vsg; - - if (NIL_P(valid_ordinal_sub(3, argv2, klass, 0))) - return Qfalse; - return Qtrue; -} - -static VALUE -valid_commercial_sub(int argc, VALUE *argv, VALUE klass, int need_jd) -{ - VALUE nth, y; - int w, d, ry, rw, rd; - double sg; - - y = argv[0]; - w = NUM2INT(argv[1]); - d = NUM2INT(argv[2]); - sg = NUM2DBL(argv[3]); - - valid_sg(sg); - - { - int rjd, ns; - VALUE rjd2; - - if (!valid_commercial_p(y, w, d, sg, - &nth, &ry, - &rw, &rd, &rjd, - &ns)) - return Qnil; - if (!need_jd) - return INT2FIX(0); /* dummy */ - encode_jd(nth, rjd, &rjd2); - return rjd2; - } -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -date_s__valid_commercial_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vw, vd, vsg; - VALUE argv2[4]; - - rb_scan_args(argc, argv, "31", &vy, &vw, &vd, &vsg); - - argv2[0] = vy; - argv2[1] = vw; - argv2[2] = vd; - if (argc < 4) - argv2[3] = DBL2NUM(GREGORIAN); - else - argv2[3] = vsg; - - return valid_commercial_sub(4, argv2, klass, 1); -} -#endif - -/* - * call-seq: - * Date.valid_commercial?(cwyear, cweek, cwday, start = Date::ITALY) -> true or false - * - * Returns +true+ if the arguments define a valid commercial date, - * +false+ otherwise: - * - * Date.valid_commercial?(2001, 5, 6) # => true - * Date.valid_commercial?(2001, 5, 8) # => false - * - * See Date.commercial. - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - * Related: Date.jd, Date.commercial. - */ -static VALUE -date_s_valid_commercial_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vw, vd, vsg; - VALUE argv2[4]; - - rb_scan_args(argc, argv, "31", &vy, &vw, &vd, &vsg); - - RETURN_FALSE_UNLESS_NUMERIC(vy); - RETURN_FALSE_UNLESS_NUMERIC(vw); - RETURN_FALSE_UNLESS_NUMERIC(vd); - argv2[0] = vy; - argv2[1] = vw; - argv2[2] = vd; - if (argc < 4) - argv2[3] = INT2FIX(DEFAULT_SG); - else - argv2[3] = vsg; - - if (NIL_P(valid_commercial_sub(4, argv2, klass, 0))) - return Qfalse; - return Qtrue; -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -valid_weeknum_sub(int argc, VALUE *argv, VALUE klass, int need_jd) -{ - VALUE nth, y; - int w, d, f, ry, rw, rd; - double sg; - - y = argv[0]; - w = NUM2INT(argv[1]); - d = NUM2INT(argv[2]); - f = NUM2INT(argv[3]); - sg = NUM2DBL(argv[4]); - - valid_sg(sg); - - { - int rjd, ns; - VALUE rjd2; - - if (!valid_weeknum_p(y, w, d, f, sg, - &nth, &ry, - &rw, &rd, &rjd, - &ns)) - return Qnil; - if (!need_jd) - return INT2FIX(0); /* dummy */ - encode_jd(nth, rjd, &rjd2); - return rjd2; - } -} - -/* :nodoc: */ -static VALUE -date_s__valid_weeknum_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vw, vd, vf, vsg; - VALUE argv2[5]; - - rb_scan_args(argc, argv, "41", &vy, &vw, &vd, &vf, &vsg); - - argv2[0] = vy; - argv2[1] = vw; - argv2[2] = vd; - argv2[3] = vf; - if (argc < 5) - argv2[4] = DBL2NUM(GREGORIAN); - else - argv2[4] = vsg; - - return valid_weeknum_sub(5, argv2, klass, 1); -} - -/* :nodoc: */ -static VALUE -date_s_valid_weeknum_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vw, vd, vf, vsg; - VALUE argv2[5]; - - rb_scan_args(argc, argv, "41", &vy, &vw, &vd, &vf, &vsg); - - argv2[0] = vy; - argv2[1] = vw; - argv2[2] = vd; - argv2[3] = vf; - if (argc < 5) - argv2[4] = INT2FIX(DEFAULT_SG); - else - argv2[4] = vsg; - - if (NIL_P(valid_weeknum_sub(5, argv2, klass, 0))) - return Qfalse; - return Qtrue; -} - -static VALUE -valid_nth_kday_sub(int argc, VALUE *argv, VALUE klass, int need_jd) -{ - VALUE nth, y; - int m, n, k, ry, rm, rn, rk; - double sg; - - y = argv[0]; - m = NUM2INT(argv[1]); - n = NUM2INT(argv[2]); - k = NUM2INT(argv[3]); - sg = NUM2DBL(argv[4]); - - { - int rjd, ns; - VALUE rjd2; - - if (!valid_nth_kday_p(y, m, n, k, sg, - &nth, &ry, - &rm, &rn, &rk, &rjd, - &ns)) - return Qnil; - if (!need_jd) - return INT2FIX(0); /* dummy */ - encode_jd(nth, rjd, &rjd2); - return rjd2; - } -} - -/* :nodoc: */ -static VALUE -date_s__valid_nth_kday_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vm, vn, vk, vsg; - VALUE argv2[5]; - - rb_scan_args(argc, argv, "41", &vy, &vm, &vn, &vk, &vsg); - - argv2[0] = vy; - argv2[1] = vm; - argv2[2] = vn; - argv2[3] = vk; - if (argc < 5) - argv2[4] = DBL2NUM(GREGORIAN); - else - argv2[4] = vsg; - - return valid_nth_kday_sub(5, argv2, klass, 1); -} - -/* :nodoc: */ -static VALUE -date_s_valid_nth_kday_p(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vm, vn, vk, vsg; - VALUE argv2[5]; - - rb_scan_args(argc, argv, "41", &vy, &vm, &vn, &vk, &vsg); - - argv2[0] = vy; - argv2[1] = vm; - argv2[2] = vn; - argv2[3] = vk; - if (argc < 5) - argv2[4] = INT2FIX(DEFAULT_SG); - else - argv2[4] = vsg; - - if (NIL_P(valid_nth_kday_sub(5, argv2, klass, 0))) - return Qfalse; - return Qtrue; -} - -/* :nodoc: */ -static VALUE -date_s_zone_to_diff(VALUE klass, VALUE str) -{ - return date_zone_to_diff(str); -} -#endif - -/* - * call-seq: - * Date.julian_leap?(year) -> true or false - * - * Returns +true+ if the given year is a leap year - * in the {proleptic Julian calendar}[https://en.wikipedia.org/wiki/Proleptic_Julian_calendar], +false+ otherwise: - * - * Date.julian_leap?(1900) # => true - * Date.julian_leap?(1901) # => false - * - * Related: Date.gregorian_leap?. - */ -static VALUE -date_s_julian_leap_p(VALUE klass, VALUE y) -{ - VALUE nth; - int ry; - - check_numeric(y, "year"); - decode_year(y, +1, &nth, &ry); - return f_boolcast(c_julian_leap_p(ry)); -} - -/* - * call-seq: - * Date.gregorian_leap?(year) -> true or false - * - * Returns +true+ if the given year is a leap year - * in the {proleptic Gregorian calendar}[https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar], +false+ otherwise: - * - * Date.gregorian_leap?(2000) # => true - * Date.gregorian_leap?(2001) # => false - * - * Related: Date.julian_leap?. - */ -static VALUE -date_s_gregorian_leap_p(VALUE klass, VALUE y) -{ - VALUE nth; - int ry; - - check_numeric(y, "year"); - decode_year(y, -1, &nth, &ry); - return f_boolcast(c_gregorian_leap_p(ry)); -} - -static void -d_lite_gc_mark(void *ptr) -{ - union DateData *dat = ptr; - if (simple_dat_p(dat)) - rb_gc_mark(dat->s.nth); - else { - rb_gc_mark(dat->c.nth); - rb_gc_mark(dat->c.sf); - } -} - -static size_t -d_lite_memsize(const void *ptr) -{ - const union DateData *dat = ptr; - return complex_dat_p(dat) ? sizeof(struct ComplexDateData) : sizeof(struct SimpleDateData); -} - -#ifndef HAVE_RB_EXT_RACTOR_SAFE -# define RUBY_TYPED_FROZEN_SHAREABLE 0 -#endif - -static const rb_data_type_t d_lite_type = { - "Date", - {d_lite_gc_mark, RUBY_TYPED_DEFAULT_FREE, d_lite_memsize,}, - 0, 0, - RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED|RUBY_TYPED_FROZEN_SHAREABLE, -}; - -inline static VALUE -d_simple_new_internal(VALUE klass, - VALUE nth, int jd, - double sg, - int y, int m, int d, - unsigned flags) -{ - struct SimpleDateData *dat; - VALUE obj; - - obj = TypedData_Make_Struct(klass, struct SimpleDateData, - &d_lite_type, dat); - set_to_simple(obj, dat, nth, jd, sg, y, m, d, flags); - - assert(have_jd_p(dat) || have_civil_p(dat)); - - return obj; -} - -inline static VALUE -d_complex_new_internal(VALUE klass, - VALUE nth, int jd, - int df, VALUE sf, - int of, double sg, - int y, int m, int d, - int h, int min, int s, - unsigned flags) -{ - struct ComplexDateData *dat; - VALUE obj; - - obj = TypedData_Make_Struct(klass, struct ComplexDateData, - &d_lite_type, dat); - set_to_complex(obj, dat, nth, jd, df, sf, of, sg, - y, m, d, h, min, s, flags); - - assert(have_jd_p(dat) || have_civil_p(dat)); - assert(have_df_p(dat) || have_time_p(dat)); - - return obj; -} - -static VALUE -d_lite_s_alloc_simple(VALUE klass) -{ - return d_simple_new_internal(klass, - INT2FIX(0), 0, - DEFAULT_SG, - 0, 0, 0, - HAVE_JD); -} - -static VALUE -d_lite_s_alloc_complex(VALUE klass) -{ - return d_complex_new_internal(klass, - INT2FIX(0), 0, - 0, INT2FIX(0), - 0, DEFAULT_SG, - 0, 0, 0, - 0, 0, 0, - HAVE_JD | HAVE_DF); -} - -static VALUE -d_lite_s_alloc(VALUE klass) -{ - return d_lite_s_alloc_complex(klass); -} - -static void -old_to_new(VALUE ajd, VALUE of, VALUE sg, - VALUE *rnth, int *rjd, int *rdf, VALUE *rsf, - int *rof, double *rsg) -{ - VALUE jd, df, sf, of2, t; - - decode_day(f_add(ajd, half_days_in_day), - &jd, &df, &sf); - t = day_to_sec(of); - of2 = f_round(t); - - if (!f_eqeq_p(of2, t)) - rb_warning("fraction of offset is ignored"); - - decode_jd(jd, rnth, rjd); - - *rdf = NUM2INT(df); - *rsf = sf; - *rof = NUM2INT(of2); - *rsg = NUM2DBL(sg); - - if (*rdf < 0 || *rdf >= DAY_IN_SECONDS) - rb_raise(eDateError, "invalid day fraction"); - - if (f_lt_p(*rsf, INT2FIX(0)) || - f_ge_p(*rsf, INT2FIX(SECOND_IN_NANOSECONDS))) - - if (*rof < -DAY_IN_SECONDS || *rof > DAY_IN_SECONDS) { - *rof = 0; - rb_warning("invalid offset is ignored"); - } - - if (!c_valid_start_p(*rsg)) { - *rsg = DEFAULT_SG; - rb_warning("invalid start is ignored"); - } -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -date_s_new_bang(int argc, VALUE *argv, VALUE klass) -{ - VALUE ajd, of, sg, nth, sf; - int jd, df, rof; - double rsg; - - rb_scan_args(argc, argv, "03", &ajd, &of, &sg); - - switch (argc) { - case 0: - ajd = INT2FIX(0); - case 1: - of = INT2FIX(0); - case 2: - sg = INT2FIX(DEFAULT_SG); - } - - old_to_new(ajd, of, sg, - &nth, &jd, &df, &sf, &rof, &rsg); - - if (!df && f_zero_p(sf) && !rof) - return d_simple_new_internal(klass, - nth, jd, - rsg, - 0, 0, 0, - HAVE_JD); - else - return d_complex_new_internal(klass, - nth, jd, - df, sf, - rof, rsg, - 0, 0, 0, - 0, 0, 0, - HAVE_JD | HAVE_DF); -} -#endif - -inline static int -wholenum_p(VALUE x) -{ - if (FIXNUM_P(x)) - return 1; - switch (TYPE(x)) { - case T_BIGNUM: - return 1; - case T_FLOAT: - { - double d = RFLOAT_VALUE(x); - return round(d) == d; - } - break; - case T_RATIONAL: - { - VALUE den = rb_rational_den(x); - return FIXNUM_P(den) && FIX2LONG(den) == 1; - } - break; - } - return 0; -} - -inline static VALUE -to_integer(VALUE x) -{ - if (RB_INTEGER_TYPE_P(x)) - return x; - return f_to_i(x); -} - -inline static VALUE -d_trunc(VALUE d, VALUE *fr) -{ - VALUE rd; - - if (wholenum_p(d)) { - rd = to_integer(d); - *fr = INT2FIX(0); - } - else { - rd = f_idiv(d, INT2FIX(1)); - *fr = f_mod(d, INT2FIX(1)); - } - return rd; -} - -#define jd_trunc d_trunc -#define k_trunc d_trunc - -inline static VALUE -h_trunc(VALUE h, VALUE *fr) -{ - VALUE rh; - - if (wholenum_p(h)) { - rh = to_integer(h); - *fr = INT2FIX(0); - } - else { - rh = f_idiv(h, INT2FIX(1)); - *fr = f_mod(h, INT2FIX(1)); - *fr = f_quo(*fr, INT2FIX(24)); - } - return rh; -} - -inline static VALUE -min_trunc(VALUE min, VALUE *fr) -{ - VALUE rmin; - - if (wholenum_p(min)) { - rmin = to_integer(min); - *fr = INT2FIX(0); - } - else { - rmin = f_idiv(min, INT2FIX(1)); - *fr = f_mod(min, INT2FIX(1)); - *fr = f_quo(*fr, INT2FIX(1440)); - } - return rmin; -} - -inline static VALUE -s_trunc(VALUE s, VALUE *fr) -{ - VALUE rs; - - if (wholenum_p(s)) { - rs = to_integer(s); - *fr = INT2FIX(0); - } - else { - rs = f_idiv(s, INT2FIX(1)); - *fr = f_mod(s, INT2FIX(1)); - *fr = f_quo(*fr, INT2FIX(86400)); - } - return rs; -} - -#define num2num_with_frac(s,n) \ -do {\ - s = s##_trunc(v##s, &fr);\ - if (f_nonzero_p(fr)) {\ - if (argc > n)\ - rb_raise(eDateError, "invalid fraction");\ - fr2 = fr;\ - }\ -} while (0) - -#define num2int_with_frac(s,n) \ -do {\ - s = NUM2INT(s##_trunc(v##s, &fr));\ - if (f_nonzero_p(fr)) {\ - if (argc > n)\ - rb_raise(eDateError, "invalid fraction");\ - fr2 = fr;\ - }\ -} while (0) - -#define canon24oc() \ -do {\ - if (rh == 24) {\ - rh = 0;\ - fr2 = f_add(fr2, INT2FIX(1));\ - }\ -} while (0) - -#define add_frac() \ -do {\ - if (f_nonzero_p(fr2))\ - ret = d_lite_plus(ret, fr2);\ -} while (0) - -#define val2sg(vsg,dsg) \ -do {\ - dsg = NUM2DBL(vsg);\ - if (!c_valid_start_p(dsg)) {\ - dsg = DEFAULT_SG;\ - rb_warning("invalid start is ignored");\ - }\ -} while (0) - -static VALUE d_lite_plus(VALUE, VALUE); - -/* - * call-seq: - * Date.jd(jd = 0, start = Date::ITALY) -> date - * - * Returns a new \Date object formed from the arguments: - * - * Date.jd(2451944).to_s # => "2001-02-03" - * Date.jd(2451945).to_s # => "2001-02-04" - * Date.jd(0).to_s # => "-4712-01-01" - * - * The returned date is: - * - * - Gregorian, if the argument is greater than or equal to +start+: - * - * Date::ITALY # => 2299161 - * Date.jd(Date::ITALY).gregorian? # => true - * Date.jd(Date::ITALY + 1).gregorian? # => true - * - * - Julian, otherwise - * - * Date.jd(Date::ITALY - 1).julian? # => true - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - * Related: Date.new. - */ -static VALUE -date_s_jd(int argc, VALUE *argv, VALUE klass) -{ - VALUE vjd, vsg, jd, fr, fr2, ret; - double sg; - - rb_scan_args(argc, argv, "02", &vjd, &vsg); - - jd = INT2FIX(0); - fr2 = INT2FIX(0); - sg = DEFAULT_SG; - - switch (argc) { - case 2: - val2sg(vsg, sg); - case 1: - check_numeric(vjd, "jd"); - num2num_with_frac(jd, positive_inf); - } - - { - VALUE nth; - int rjd; - - decode_jd(jd, &nth, &rjd); - ret = d_simple_new_internal(klass, - nth, rjd, - sg, - 0, 0, 0, - HAVE_JD); - } - add_frac(); - return ret; -} - -/* - * call-seq: - * Date.ordinal(year = -4712, yday = 1, start = Date::ITALY) -> date - * - * Returns a new \Date object formed fom the arguments. - * - * With no arguments, returns the date for January 1, -4712: - * - * Date.ordinal.to_s # => "-4712-01-01" - * - * With argument +year+, returns the date for January 1 of that year: - * - * Date.ordinal(2001).to_s # => "2001-01-01" - * Date.ordinal(-2001).to_s # => "-2001-01-01" - * - * With positive argument +yday+ == +n+, - * returns the date for the +nth+ day of the given year: - * - * Date.ordinal(2001, 14).to_s # => "2001-01-14" - * - * With negative argument +yday+, counts backward from the end of the year: - * - * Date.ordinal(2001, -14).to_s # => "2001-12-18" - * - * Raises an exception if +yday+ is zero or out of range. - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - * Related: Date.jd, Date.new. - */ -static VALUE -date_s_ordinal(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vd, vsg, y, fr, fr2, ret; - int d; - double sg; - - rb_scan_args(argc, argv, "03", &vy, &vd, &vsg); - - y = INT2FIX(-4712); - d = 1; - fr2 = INT2FIX(0); - sg = DEFAULT_SG; - - switch (argc) { - case 3: - val2sg(vsg, sg); - case 2: - check_numeric(vd, "yday"); - num2int_with_frac(d, positive_inf); - case 1: - check_numeric(vy, "year"); - y = vy; - } - - { - VALUE nth; - int ry, rd, rjd, ns; - - if (!valid_ordinal_p(y, d, sg, - &nth, &ry, - &rd, &rjd, - &ns)) - rb_raise(eDateError, "invalid date"); - - ret = d_simple_new_internal(klass, - nth, rjd, - sg, - 0, 0, 0, - HAVE_JD); - } - add_frac(); - return ret; -} - -/* - * Same as Date.new. - */ -static VALUE -date_s_civil(int argc, VALUE *argv, VALUE klass) -{ - return date_initialize(argc, argv, d_lite_s_alloc_simple(klass)); -} - -/* - * call-seq: - * Date.new(year = -4712, month = 1, mday = 1, start = Date::ITALY) -> date - * - * Returns a new \Date object constructed from the given arguments: - * - * Date.new(2022).to_s # => "2022-01-01" - * Date.new(2022, 2).to_s # => "2022-02-01" - * Date.new(2022, 2, 4).to_s # => "2022-02-04" - * - * Argument +month+ should be in range (1..12) or range (-12..-1); - * when the argument is negative, counts backward from the end of the year: - * - * Date.new(2022, -11, 4).to_s # => "2022-02-04" - * - * Argument +mday+ should be in range (1..n) or range (-n..-1) - * where +n+ is the number of days in the month; - * when the argument is negative, counts backward from the end of the month. - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - * Related: Date.jd. - */ -static VALUE -date_initialize(int argc, VALUE *argv, VALUE self) -{ - VALUE vy, vm, vd, vsg, y, fr, fr2, ret; - int m, d; - double sg; - struct SimpleDateData *dat = rb_check_typeddata(self, &d_lite_type); - - if (!simple_dat_p(dat)) { - rb_raise(rb_eTypeError, "Date expected"); - } - - rb_scan_args(argc, argv, "04", &vy, &vm, &vd, &vsg); - - y = INT2FIX(-4712); - m = 1; - d = 1; - fr2 = INT2FIX(0); - sg = DEFAULT_SG; - - switch (argc) { - case 4: - val2sg(vsg, sg); - case 3: - check_numeric(vd, "day"); - num2int_with_frac(d, positive_inf); - case 2: - check_numeric(vm, "month"); - m = NUM2INT(vm); - case 1: - check_numeric(vy, "year"); - y = vy; - } - - if (guess_style(y, sg) < 0) { - VALUE nth; - int ry, rm, rd; - - if (!valid_gregorian_p(y, m, d, - &nth, &ry, - &rm, &rd)) - rb_raise(eDateError, "invalid date"); - - set_to_simple(self, dat, nth, 0, sg, ry, rm, rd, HAVE_CIVIL); - } - else { - VALUE nth; - int ry, rm, rd, rjd, ns; - - if (!valid_civil_p(y, m, d, sg, - &nth, &ry, - &rm, &rd, &rjd, - &ns)) - rb_raise(eDateError, "invalid date"); - - set_to_simple(self, dat, nth, rjd, sg, ry, rm, rd, HAVE_JD | HAVE_CIVIL); - } - ret = self; - add_frac(); - return ret; -} - -/* - * call-seq: - * Date.commercial(cwyear = -4712, cweek = 1, cwday = 1, start = Date::ITALY) -> date - * - * Returns a new \Date object constructed from the arguments. - * - * Argument +cwyear+ gives the year, and should be an integer. - * - * Argument +cweek+ gives the index of the week within the year, - * and should be in range (1..53) or (-53..-1); - * in some years, 53 or -53 will be out-of-range; - * if negative, counts backward from the end of the year: - * - * Date.commercial(2022, 1, 1).to_s # => "2022-01-03" - * Date.commercial(2022, 52, 1).to_s # => "2022-12-26" - * - * Argument +cwday+ gives the indes of the weekday within the week, - * and should be in range (1..7) or (-7..-1); - * 1 or -7 is Monday; - * if negative, counts backward from the end of the week: - * - * Date.commercial(2022, 1, 1).to_s # => "2022-01-03" - * Date.commercial(2022, 1, -7).to_s # => "2022-01-03" - * - * When +cweek+ is 1: - * - * - If January 1 is a Friday, Saturday, or Sunday, - * the first week begins in the week after: - * - * Date::ABBR_DAYNAMES[Date.new(2023, 1, 1).wday] # => "Sun" - * Date.commercial(2023, 1, 1).to_s # => "2023-01-02" - Date.commercial(2023, 1, 7).to_s # => "2023-01-08" - * - * - Otherwise, the first week is the week of January 1, - * which may mean some of the days fall on the year before: - * - * Date::ABBR_DAYNAMES[Date.new(2020, 1, 1).wday] # => "Wed" - * Date.commercial(2020, 1, 1).to_s # => "2019-12-30" - Date.commercial(2020, 1, 7).to_s # => "2020-01-05" - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - * Related: Date.jd, Date.new, Date.ordinal. - */ -static VALUE -date_s_commercial(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vw, vd, vsg, y, fr, fr2, ret; - int w, d; - double sg; - - rb_scan_args(argc, argv, "04", &vy, &vw, &vd, &vsg); - - y = INT2FIX(-4712); - w = 1; - d = 1; - fr2 = INT2FIX(0); - sg = DEFAULT_SG; - - switch (argc) { - case 4: - val2sg(vsg, sg); - case 3: - check_numeric(vd, "cwday"); - num2int_with_frac(d, positive_inf); - case 2: - check_numeric(vw, "cweek"); - w = NUM2INT(vw); - case 1: - check_numeric(vy, "year"); - y = vy; - } - - { - VALUE nth; - int ry, rw, rd, rjd, ns; - - if (!valid_commercial_p(y, w, d, sg, - &nth, &ry, - &rw, &rd, &rjd, - &ns)) - rb_raise(eDateError, "invalid date"); - - ret = d_simple_new_internal(klass, - nth, rjd, - sg, - 0, 0, 0, - HAVE_JD); - } - add_frac(); - return ret; -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -date_s_weeknum(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vw, vd, vf, vsg, y, fr, fr2, ret; - int w, d, f; - double sg; - - rb_scan_args(argc, argv, "05", &vy, &vw, &vd, &vf, &vsg); - - y = INT2FIX(-4712); - w = 0; - d = 1; - f = 0; - fr2 = INT2FIX(0); - sg = DEFAULT_SG; - - switch (argc) { - case 5: - val2sg(vsg, sg); - case 4: - f = NUM2INT(vf); - case 3: - num2int_with_frac(d, positive_inf); - case 2: - w = NUM2INT(vw); - case 1: - y = vy; - } - - { - VALUE nth; - int ry, rw, rd, rjd, ns; - - if (!valid_weeknum_p(y, w, d, f, sg, - &nth, &ry, - &rw, &rd, &rjd, - &ns)) - rb_raise(eDateError, "invalid date"); - - ret = d_simple_new_internal(klass, - nth, rjd, - sg, - 0, 0, 0, - HAVE_JD); - } - add_frac(); - return ret; -} - -/* :nodoc: */ -static VALUE -date_s_nth_kday(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vm, vn, vk, vsg, y, fr, fr2, ret; - int m, n, k; - double sg; - - rb_scan_args(argc, argv, "05", &vy, &vm, &vn, &vk, &vsg); - - y = INT2FIX(-4712); - m = 1; - n = 1; - k = 1; - fr2 = INT2FIX(0); - sg = DEFAULT_SG; - - switch (argc) { - case 5: - val2sg(vsg, sg); - case 4: - num2int_with_frac(k, positive_inf); - case 3: - n = NUM2INT(vn); - case 2: - m = NUM2INT(vm); - case 1: - y = vy; - } - - { - VALUE nth; - int ry, rm, rn, rk, rjd, ns; - - if (!valid_nth_kday_p(y, m, n, k, sg, - &nth, &ry, - &rm, &rn, &rk, &rjd, - &ns)) - rb_raise(eDateError, "invalid date"); - - ret = d_simple_new_internal(klass, - nth, rjd, - sg, - 0, 0, 0, - HAVE_JD); - } - add_frac(); - return ret; -} -#endif - -#if !defined(HAVE_GMTIME_R) -static struct tm* -gmtime_r(const time_t *t, struct tm *tm) -{ - auto struct tm *tmp = gmtime(t); - if (tmp) - *tm = *tmp; - return tmp; -} - -static struct tm* -localtime_r(const time_t *t, struct tm *tm) -{ - auto struct tm *tmp = localtime(t); - if (tmp) - *tm = *tmp; - return tmp; -} -#endif - -static void set_sg(union DateData *, double); - -/* - * call-seq: - * Date.today(start = Date::ITALY) -> date - * - * Returns a new \Date object constructed from the present date: - * - * Date.today.to_s # => "2022-07-06" - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - */ -static VALUE -date_s_today(int argc, VALUE *argv, VALUE klass) -{ - VALUE vsg, nth, ret; - double sg; - time_t t; - struct tm tm; - int y, ry, m, d; - - rb_scan_args(argc, argv, "01", &vsg); - - if (argc < 1) - sg = DEFAULT_SG; - else - val2sg(vsg, sg); - - if (time(&t) == -1) - rb_sys_fail("time"); - tzset(); - if (!localtime_r(&t, &tm)) - rb_sys_fail("localtime"); - - y = tm.tm_year + 1900; - m = tm.tm_mon + 1; - d = tm.tm_mday; - - decode_year(INT2FIX(y), -1, &nth, &ry); - - ret = d_simple_new_internal(klass, - nth, 0, - GREGORIAN, - ry, m, d, - HAVE_CIVIL); - { - get_d1(ret); - set_sg(dat, sg); - } - return ret; -} - -#define set_hash0(k,v) rb_hash_aset(hash, k, v) -#define ref_hash0(k) rb_hash_aref(hash, k) -#define del_hash0(k) rb_hash_delete(hash, k) - -#define sym(x) ID2SYM(rb_intern(x"")) - -#define set_hash(k,v) set_hash0(sym(k), v) -#define ref_hash(k) ref_hash0(sym(k)) -#define del_hash(k) del_hash0(sym(k)) - -static VALUE -rt_rewrite_frags(VALUE hash) -{ - VALUE seconds; - - seconds = del_hash("seconds"); - if (!NIL_P(seconds)) { - VALUE offset, d, h, min, s, fr; - - offset = ref_hash("offset"); - if (!NIL_P(offset)) - seconds = f_add(seconds, offset); - - d = f_idiv(seconds, INT2FIX(DAY_IN_SECONDS)); - fr = f_mod(seconds, INT2FIX(DAY_IN_SECONDS)); - - h = f_idiv(fr, INT2FIX(HOUR_IN_SECONDS)); - fr = f_mod(fr, INT2FIX(HOUR_IN_SECONDS)); - - min = f_idiv(fr, INT2FIX(MINUTE_IN_SECONDS)); - fr = f_mod(fr, INT2FIX(MINUTE_IN_SECONDS)); - - s = f_idiv(fr, INT2FIX(1)); - fr = f_mod(fr, INT2FIX(1)); - - set_hash("jd", f_add(UNIX_EPOCH_IN_CJD, d)); - set_hash("hour", h); - set_hash("min", min); - set_hash("sec", s); - set_hash("sec_fraction", fr); - } - return hash; -} - -static VALUE d_lite_year(VALUE); -static VALUE d_lite_wday(VALUE); -static VALUE d_lite_jd(VALUE); - -static VALUE -rt_complete_frags(VALUE klass, VALUE hash) -{ - static VALUE tab = Qnil; - long e; - VALUE k, a, d; - - if (NIL_P(tab)) { - tab = f_frozen_ary(11, - f_frozen_ary(2, - sym("time"), - f_frozen_ary(3, - sym("hour"), - sym("min"), - sym("sec"))), - f_frozen_ary(2, - Qnil, - f_frozen_ary(1, - sym("jd"))), - f_frozen_ary(2, - sym("ordinal"), - f_frozen_ary(5, - sym("year"), - sym("yday"), - sym("hour"), - sym("min"), - sym("sec"))), - f_frozen_ary(2, - sym("civil"), - f_frozen_ary(6, - sym("year"), - sym("mon"), - sym("mday"), - sym("hour"), - sym("min"), - sym("sec"))), - f_frozen_ary(2, - sym("commercial"), - f_frozen_ary(6, - sym("cwyear"), - sym("cweek"), - sym("cwday"), - sym("hour"), - sym("min"), - sym("sec"))), - f_frozen_ary(2, - sym("wday"), - f_frozen_ary(4, - sym("wday"), - sym("hour"), - sym("min"), - sym("sec"))), - f_frozen_ary(2, - sym("wnum0"), - f_frozen_ary(6, - sym("year"), - sym("wnum0"), - sym("wday"), - sym("hour"), - sym("min"), - sym("sec"))), - f_frozen_ary(2, - sym("wnum1"), - f_frozen_ary(6, - sym("year"), - sym("wnum1"), - sym("wday"), - sym("hour"), - sym("min"), - sym("sec"))), - f_frozen_ary(2, - Qnil, - f_frozen_ary(6, - sym("cwyear"), - sym("cweek"), - sym("wday"), - sym("hour"), - sym("min"), - sym("sec"))), - f_frozen_ary(2, - Qnil, - f_frozen_ary(6, - sym("year"), - sym("wnum0"), - sym("cwday"), - sym("hour"), - sym("min"), - sym("sec"))), - f_frozen_ary(2, - Qnil, - f_frozen_ary(6, - sym("year"), - sym("wnum1"), - sym("cwday"), - sym("hour"), - sym("min"), - sym("sec")))); - rb_gc_register_mark_object(tab); - } - - k = a = Qnil; - - { - long i, eno = 0; - VALUE t = Qnil; - - e = 0; - for (i = 0; i < RARRAY_LEN(tab); i++) { - VALUE x, a; - - x = RARRAY_AREF(tab, i); - a = RARRAY_AREF(x, 1); - - { - long j, n = 0; - - for (j = 0; j < RARRAY_LEN(a); j++) - if (!NIL_P(ref_hash0(RARRAY_AREF(a, j)))) - n++; - if (n > eno) { - eno = n; - t = x; - } - } - } - if (eno > 0) { - k = RARRAY_AREF(t, 0); - a = RARRAY_AREF(t, 1); - } - e = eno; - } - - d = Qnil; - - if (!NIL_P(k) && (RARRAY_LEN(a) > e)) { - if (k == sym("ordinal")) { - if (NIL_P(ref_hash("year"))) { - if (NIL_P(d)) - d = date_s_today(0, (VALUE *)0, cDate); - set_hash("year", d_lite_year(d)); - } - if (NIL_P(ref_hash("yday"))) - set_hash("yday", INT2FIX(1)); - } - else if (k == sym("civil")) { - long i; - - for (i = 0; i < RARRAY_LEN(a); i++) { - VALUE e = RARRAY_AREF(a, i); - - if (!NIL_P(ref_hash0(e))) - break; - if (NIL_P(d)) - d = date_s_today(0, (VALUE *)0, cDate); - set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); - } - if (NIL_P(ref_hash("mon"))) - set_hash("mon", INT2FIX(1)); - if (NIL_P(ref_hash("mday"))) - set_hash("mday", INT2FIX(1)); - } - else if (k == sym("commercial")) { - long i; - - for (i = 0; i < RARRAY_LEN(a); i++) { - VALUE e = RARRAY_AREF(a, i); - - if (!NIL_P(ref_hash0(e))) - break; - if (NIL_P(d)) - d = date_s_today(0, (VALUE *)0, cDate); - set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); - } - if (NIL_P(ref_hash("cweek"))) - set_hash("cweek", INT2FIX(1)); - if (NIL_P(ref_hash("cwday"))) - set_hash("cwday", INT2FIX(1)); - } - else if (k == sym("wday")) { - if (NIL_P(d)) - d = date_s_today(0, (VALUE *)0, cDate); - set_hash("jd", d_lite_jd(f_add(f_sub(d, - d_lite_wday(d)), - ref_hash("wday")))); - } - else if (k == sym("wnum0")) { - long i; - - for (i = 0; i < RARRAY_LEN(a); i++) { - VALUE e = RARRAY_AREF(a, i); - - if (!NIL_P(ref_hash0(e))) - break; - if (NIL_P(d)) - d = date_s_today(0, (VALUE *)0, cDate); - set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); - } - if (NIL_P(ref_hash("wnum0"))) - set_hash("wnum0", INT2FIX(0)); - if (NIL_P(ref_hash("wday"))) - set_hash("wday", INT2FIX(0)); - } - else if (k == sym("wnum1")) { - long i; - - for (i = 0; i < RARRAY_LEN(a); i++) { - VALUE e = RARRAY_AREF(a, i); - - if (!NIL_P(ref_hash0(e))) - break; - if (NIL_P(d)) - d = date_s_today(0, (VALUE *)0, cDate); - set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); - } - if (NIL_P(ref_hash("wnum1"))) - set_hash("wnum1", INT2FIX(0)); - if (NIL_P(ref_hash("wday"))) - set_hash("wday", INT2FIX(1)); - } - } - - if (k == sym("time")) { - if (f_le_p(klass, cDateTime)) { - if (NIL_P(d)) - d = date_s_today(0, (VALUE *)0, cDate); - if (NIL_P(ref_hash("jd"))) - set_hash("jd", d_lite_jd(d)); - } - } - - if (NIL_P(ref_hash("hour"))) - set_hash("hour", INT2FIX(0)); - if (NIL_P(ref_hash("min"))) - set_hash("min", INT2FIX(0)); - if (NIL_P(ref_hash("sec"))) - set_hash("sec", INT2FIX(0)); - else if (f_gt_p(ref_hash("sec"), INT2FIX(59))) - set_hash("sec", INT2FIX(59)); - - return hash; -} - -static VALUE -rt__valid_jd_p(VALUE jd, VALUE sg) -{ - return jd; -} - -static VALUE -rt__valid_ordinal_p(VALUE y, VALUE d, VALUE sg) -{ - VALUE nth, rjd2; - int ry, rd, rjd, ns; - - if (!valid_ordinal_p(y, NUM2INT(d), NUM2DBL(sg), - &nth, &ry, - &rd, &rjd, - &ns)) - return Qnil; - encode_jd(nth, rjd, &rjd2); - return rjd2; -} - -static VALUE -rt__valid_civil_p(VALUE y, VALUE m, VALUE d, VALUE sg) -{ - VALUE nth, rjd2; - int ry, rm, rd, rjd, ns; - - if (!valid_civil_p(y, NUM2INT(m), NUM2INT(d), NUM2DBL(sg), - &nth, &ry, - &rm, &rd, &rjd, - &ns)) - return Qnil; - encode_jd(nth, rjd, &rjd2); - return rjd2; -} - -static VALUE -rt__valid_commercial_p(VALUE y, VALUE w, VALUE d, VALUE sg) -{ - VALUE nth, rjd2; - int ry, rw, rd, rjd, ns; - - if (!valid_commercial_p(y, NUM2INT(w), NUM2INT(d), NUM2DBL(sg), - &nth, &ry, - &rw, &rd, &rjd, - &ns)) - return Qnil; - encode_jd(nth, rjd, &rjd2); - return rjd2; -} - -static VALUE -rt__valid_weeknum_p(VALUE y, VALUE w, VALUE d, VALUE f, VALUE sg) -{ - VALUE nth, rjd2; - int ry, rw, rd, rjd, ns; - - if (!valid_weeknum_p(y, NUM2INT(w), NUM2INT(d), NUM2INT(f), NUM2DBL(sg), - &nth, &ry, - &rw, &rd, &rjd, - &ns)) - return Qnil; - encode_jd(nth, rjd, &rjd2); - return rjd2; -} - -static VALUE -rt__valid_date_frags_p(VALUE hash, VALUE sg) -{ - { - VALUE vjd; - - if (!NIL_P(vjd = ref_hash("jd"))) { - VALUE jd = rt__valid_jd_p(vjd, sg); - if (!NIL_P(jd)) - return jd; - } - } - - { - VALUE year, yday; - - if (!NIL_P(yday = ref_hash("yday")) && - !NIL_P(year = ref_hash("year"))) { - VALUE jd = rt__valid_ordinal_p(year, yday, sg); - if (!NIL_P(jd)) - return jd; - } - } - - { - VALUE year, mon, mday; - - if (!NIL_P(mday = ref_hash("mday")) && - !NIL_P(mon = ref_hash("mon")) && - !NIL_P(year = ref_hash("year"))) { - VALUE jd = rt__valid_civil_p(year, mon, mday, sg); - if (!NIL_P(jd)) - return jd; - } - } - - { - VALUE year, week, wday; - - wday = ref_hash("cwday"); - if (NIL_P(wday)) { - wday = ref_hash("wday"); - if (!NIL_P(wday)) - if (f_zero_p(wday)) - wday = INT2FIX(7); - } - - if (!NIL_P(wday) && - !NIL_P(week = ref_hash("cweek")) && - !NIL_P(year = ref_hash("cwyear"))) { - VALUE jd = rt__valid_commercial_p(year, week, wday, sg); - if (!NIL_P(jd)) - return jd; - } - } - - { - VALUE year, week, wday; - - wday = ref_hash("wday"); - if (NIL_P(wday)) { - wday = ref_hash("cwday"); - if (!NIL_P(wday)) - if (f_eqeq_p(wday, INT2FIX(7))) - wday = INT2FIX(0); - } - - if (!NIL_P(wday) && - !NIL_P(week = ref_hash("wnum0")) && - !NIL_P(year = ref_hash("year"))) { - VALUE jd = rt__valid_weeknum_p(year, week, wday, INT2FIX(0), sg); - if (!NIL_P(jd)) - return jd; - } - } - - { - VALUE year, week, wday; - - wday = ref_hash("wday"); - if (NIL_P(wday)) - wday = ref_hash("cwday"); - if (!NIL_P(wday)) - wday = f_mod(f_sub(wday, INT2FIX(1)), - INT2FIX(7)); - - if (!NIL_P(wday) && - !NIL_P(week = ref_hash("wnum1")) && - !NIL_P(year = ref_hash("year"))) { - VALUE jd = rt__valid_weeknum_p(year, week, wday, INT2FIX(1), sg); - if (!NIL_P(jd)) - return jd; - } - } - return Qnil; -} - -static VALUE -d_new_by_frags(VALUE klass, VALUE hash, VALUE sg) -{ - VALUE jd; - - if (!c_valid_start_p(NUM2DBL(sg))) { - sg = INT2FIX(DEFAULT_SG); - rb_warning("invalid start is ignored"); - } - - if (NIL_P(hash)) - rb_raise(eDateError, "invalid date"); - - if (NIL_P(ref_hash("jd")) && - NIL_P(ref_hash("yday")) && - !NIL_P(ref_hash("year")) && - !NIL_P(ref_hash("mon")) && - !NIL_P(ref_hash("mday"))) - jd = rt__valid_civil_p(ref_hash("year"), - ref_hash("mon"), - ref_hash("mday"), sg); - else { - hash = rt_rewrite_frags(hash); - hash = rt_complete_frags(klass, hash); - jd = rt__valid_date_frags_p(hash, sg); - } - - if (NIL_P(jd)) - rb_raise(eDateError, "invalid date"); - { - VALUE nth; - int rjd; - - decode_jd(jd, &nth, &rjd); - return d_simple_new_internal(klass, - nth, rjd, - NUM2DBL(sg), - 0, 0, 0, - HAVE_JD); - } -} - -VALUE date__strptime(const char *str, size_t slen, - const char *fmt, size_t flen, VALUE hash); - -static VALUE -date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, - const char *default_fmt) -{ - VALUE vstr, vfmt, hash; - const char *str, *fmt; - size_t slen, flen; - - rb_scan_args(argc, argv, "11", &vstr, &vfmt); - - StringValue(vstr); - if (argc > 1) StringValue(vfmt); - if (!rb_enc_str_asciicompat_p(vstr)) - rb_raise(rb_eArgError, - "string should have ASCII compatible encoding"); - str = RSTRING_PTR(vstr); - slen = RSTRING_LEN(vstr); - if (argc < 2) { - fmt = default_fmt; - flen = strlen(default_fmt); - } - else { - if (!rb_enc_str_asciicompat_p(vfmt)) - rb_raise(rb_eArgError, - "format should have ASCII compatible encoding"); - fmt = RSTRING_PTR(vfmt); - flen = RSTRING_LEN(vfmt); - } - hash = rb_hash_new(); - if (NIL_P(date__strptime(str, slen, fmt, flen, hash))) - return Qnil; - - { - VALUE zone = ref_hash("zone"); - VALUE left = ref_hash("leftover"); - - if (!NIL_P(zone)) { - rb_enc_copy(zone, vstr); - set_hash("zone", zone); - } - if (!NIL_P(left)) { - rb_enc_copy(left, vstr); - set_hash("leftover", left); - } - } - - return hash; -} - -/* - * call-seq: - * Date._strptime(string, format = '%F') -> hash - * - * Returns a hash of values parsed from +string+ - * according to the given +format+: - * - * Date._strptime('2001-02-03', '%Y-%m-%d') # => {:year=>2001, :mon=>2, :mday=>3} - * - * For other formats, see - * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. - * (Unlike Date.strftime, does not support flags and width.) - * - * See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. - * - * Related: Date.strptime (returns a \Date object). - */ -static VALUE -date_s__strptime(int argc, VALUE *argv, VALUE klass) -{ - return date_s__strptime_internal(argc, argv, klass, "%F"); -} - -/* - * call-seq: - * Date.strptime(string = '-4712-01-01', format = '%F', start = Date::ITALY) -> date - * - * Returns a new \Date object with values parsed from +string+, - * according to the given +format+: - * - * Date.strptime('2001-02-03', '%Y-%m-%d') # => # - * Date.strptime('03-02-2001', '%d-%m-%Y') # => # - * Date.strptime('2001-034', '%Y-%j') # => # - * Date.strptime('2001-W05-6', '%G-W%V-%u') # => # - * Date.strptime('2001 04 6', '%Y %U %w') # => # - * Date.strptime('2001 05 6', '%Y %W %u') # => # - * Date.strptime('sat3feb01', '%a%d%b%y') # => # - * - * For other formats, see - * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. - * (Unlike Date.strftime, does not support flags and width.) - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - * See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. - * - * Related: Date._strptime (returns a hash). - */ -static VALUE -date_s_strptime(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, fmt, sg; - - rb_scan_args(argc, argv, "03", &str, &fmt, &sg); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATE); - case 1: - fmt = rb_str_new2("%F"); - case 2: - sg = INT2FIX(DEFAULT_SG); - } - - { - VALUE argv2[2], hash; - - argv2[0] = str; - argv2[1] = fmt; - hash = date_s__strptime(2, argv2, klass); - return d_new_by_frags(klass, hash, sg); - } -} - -VALUE date__parse(VALUE str, VALUE comp); - -static size_t -get_limit(VALUE opt) -{ - if (!NIL_P(opt)) { - VALUE limit = rb_hash_aref(opt, ID2SYM(rb_intern("limit"))); - if (NIL_P(limit)) return SIZE_MAX; - return NUM2SIZET(limit); - } - return 128; -} - -#ifndef HAVE_RB_CATEGORY_WARN -#define rb_category_warn(category, fmt) rb_warn(fmt) -#endif - -static VALUE -check_limit(VALUE str, VALUE opt) -{ - size_t slen, limit; - StringValue(str); - slen = RSTRING_LEN(str); - limit = get_limit(opt); - if (slen > limit) { - rb_raise(rb_eArgError, - "string length (%"PRI_SIZE_PREFIX"u) exceeds the limit %"PRI_SIZE_PREFIX"u", slen, limit); - } - return str; -} - -static VALUE -date_s__parse_internal(int argc, VALUE *argv, VALUE klass) -{ - VALUE vstr, vcomp, hash, opt; - - argc = rb_scan_args(argc, argv, "11:", &vstr, &vcomp, &opt); - vstr = check_limit(vstr, opt); - if (!rb_enc_str_asciicompat_p(vstr)) - rb_raise(rb_eArgError, - "string should have ASCII compatible encoding"); - if (argc < 2) - vcomp = Qtrue; - - hash = date__parse(vstr, vcomp); - - return hash; -} - -/* - * call-seq: - * Date._parse(string, comp = true, limit: 128) -> hash - * - * Note: - * This method recognizes many forms in +string+, - * but it is not a validator. - * For formats, see - * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] - * - * If +string+ does not specify a valid date, - * the result is unpredictable; - * consider using Date._strptime instead. - * - * Returns a hash of values parsed from +string+: - * - * Date._parse('2001-02-03') # => {:year=>2001, :mon=>2, :mday=>3} - * - * If +comp+ is +true+ and the given year is in the range (0..99), - * the current century is supplied; - * otherwise, the year is taken as given: - * - * Date._parse('01-02-03', true) # => {:year=>2001, :mon=>2, :mday=>3} - * Date._parse('01-02-03', false) # => {:year=>1, :mon=>2, :mday=>3} - * - * See argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date.parse(returns a \Date object). - */ -static VALUE -date_s__parse(int argc, VALUE *argv, VALUE klass) -{ - return date_s__parse_internal(argc, argv, klass); -} - -/* - * call-seq: - * Date.parse(string = '-4712-01-01', comp = true, start = Date::ITALY, limit: 128) -> date - * - * Note: - * This method recognizes many forms in +string+, - * but it is not a validator. - * For formats, see - * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] - * If +string+ does not specify a valid date, - * the result is unpredictable; - * consider using Date._strptime instead. - * - * Returns a new \Date object with values parsed from +string+: - * - * Date.parse('2001-02-03') # => # - * Date.parse('20010203') # => # - * Date.parse('3rd Feb 2001') # => # - * - * If +comp+ is +true+ and the given year is in the range (0..99), - * the current century is supplied; - * otherwise, the year is taken as given: - * - * Date.parse('01-02-03', true) # => # - * Date.parse('01-02-03', false) # => # - * - * See: - * - * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - Argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date._parse (returns a hash). - */ -static VALUE -date_s_parse(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, comp, sg, opt; - - argc = rb_scan_args(argc, argv, "03:", &str, &comp, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATE); - case 1: - comp = Qtrue; - case 2: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 2; - VALUE argv2[3], hash; - argv2[0] = str; - argv2[1] = comp; - if (!NIL_P(opt)) argv2[argc2++] = opt; - hash = date_s__parse(argc2, argv2, klass); - return d_new_by_frags(klass, hash, sg); - } -} - -VALUE date__iso8601(VALUE); -VALUE date__rfc3339(VALUE); -VALUE date__xmlschema(VALUE); -VALUE date__rfc2822(VALUE); -VALUE date__httpdate(VALUE); -VALUE date__jisx0301(VALUE); - -/* - * call-seq: - * Date._iso8601(string, limit: 128) -> hash - * - * Returns a hash of values parsed from +string+, which should contain - * an {ISO 8601 formatted date}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications]: - * - * d = Date.new(2001, 2, 3) - * s = d.iso8601 # => "2001-02-03" - * Date._iso8601(s) # => {:mday=>3, :year=>2001, :mon=>2} - * - * See argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date.iso8601 (returns a \Date object). - */ -static VALUE -date_s__iso8601(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, opt; - - rb_scan_args(argc, argv, "1:", &str, &opt); - if (!NIL_P(str)) str = check_limit(str, opt); - - return date__iso8601(str); -} - -/* - * call-seq: - * Date.iso8601(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date - * - * Returns a new \Date object with values parsed from +string+, - * which should contain - * an {ISO 8601 formatted date}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications]: - * - * d = Date.new(2001, 2, 3) - * s = d.iso8601 # => "2001-02-03" - * Date.iso8601(s) # => # - * - * See: - * - * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - Argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date._iso8601 (returns a hash). - */ -static VALUE -date_s_iso8601(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATE); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - if (!NIL_P(opt)) argv2[argc2++] = opt; - hash = date_s__iso8601(argc2, argv2, klass); - return d_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * Date._rfc3339(string, limit: 128) -> hash - * - * Returns a hash of values parsed from +string+, which should be a valid - * {RFC 3339 format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+3339+Format]: - * - * d = Date.new(2001, 2, 3) - * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" - * Date._rfc3339(s) - * # => {:year=>2001, :mon=>2, :mday=>3, :hour=>0, :min=>0, :sec=>0, :zone=>"+00:00", :offset=>0} - * - * See argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date.rfc3339 (returns a \Date object). - */ -static VALUE -date_s__rfc3339(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, opt; - - rb_scan_args(argc, argv, "1:", &str, &opt); - if (!NIL_P(str)) str = check_limit(str, opt); - - return date__rfc3339(str); -} - -/* - * call-seq: - * Date.rfc3339(string = '-4712-01-01T00:00:00+00:00', start = Date::ITALY, limit: 128) -> date - * - * Returns a new \Date object with values parsed from +string+, - * which should be a valid - * {RFC 3339 format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+3339+Format]: - * - * d = Date.new(2001, 2, 3) - * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" - * Date.rfc3339(s) # => # - * - * See: - * - * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - Argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date._rfc3339 (returns a hash). - */ -static VALUE -date_s_rfc3339(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATETIME); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - if (!NIL_P(opt)) argv2[argc2++] = opt; - hash = date_s__rfc3339(argc2, argv2, klass); - return d_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * Date._xmlschema(string, limit: 128) -> hash - * - * Returns a hash of values parsed from +string+, which should be a valid - * XML date format: - * - * d = Date.new(2001, 2, 3) - * s = d.xmlschema # => "2001-02-03" - * Date._xmlschema(s) # => {:year=>2001, :mon=>2, :mday=>3} - * - * See argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date.xmlschema (returns a \Date object). - */ -static VALUE -date_s__xmlschema(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, opt; - - rb_scan_args(argc, argv, "1:", &str, &opt); - if (!NIL_P(str)) str = check_limit(str, opt); - - return date__xmlschema(str); -} - -/* - * call-seq: - * Date.xmlschema(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date - * - * Returns a new \Date object with values parsed from +string+, - * which should be a valid XML date format: - * - * d = Date.new(2001, 2, 3) - * s = d.xmlschema # => "2001-02-03" - * Date.xmlschema(s) # => # - * - * See: - * - * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - Argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date._xmlschema (returns a hash). - */ -static VALUE -date_s_xmlschema(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATE); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - if (!NIL_P(opt)) argv2[argc2++] = opt; - hash = date_s__xmlschema(argc2, argv2, klass); - return d_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * Date._rfc2822(string, limit: 128) -> hash - * - * Returns a hash of values parsed from +string+, which should be a valid - * {RFC 2822 date format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+2822+Format]: - * - * d = Date.new(2001, 2, 3) - * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" - * Date._rfc2822(s) - * # => {:wday=>6, :mday=>3, :mon=>2, :year=>2001, :hour=>0, :min=>0, :sec=>0, :zone=>"+0000", :offset=>0} - * - * See argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date.rfc2822 (returns a \Date object). - */ -static VALUE -date_s__rfc2822(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, opt; - - rb_scan_args(argc, argv, "1:", &str, &opt); - if (!NIL_P(str)) str = check_limit(str, opt); - - return date__rfc2822(str); -} - -/* - * call-seq: - * Date.rfc2822(string = 'Mon, 1 Jan -4712 00:00:00 +0000', start = Date::ITALY, limit: 128) -> date - * - * Returns a new \Date object with values parsed from +string+, - * which should be a valid - * {RFC 2822 date format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+2822+Format]: - * - * d = Date.new(2001, 2, 3) - * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" - * Date.rfc2822(s) # => # - * - * See: - * - * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - Argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date._rfc2822 (returns a hash). - */ -static VALUE -date_s_rfc2822(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATETIME_RFC3339); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - if (!NIL_P(opt)) argv2[argc2++] = opt; - hash = date_s__rfc2822(argc2, argv2, klass); - return d_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * Date._httpdate(string, limit: 128) -> hash - * - * Returns a hash of values parsed from +string+, which should be a valid - * {HTTP date format}[rdoc-ref:language/strftime_formatting.rdoc@HTTP+Format]: - * - * d = Date.new(2001, 2, 3) - * s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" - * Date._httpdate(s) - * # => {:wday=>6, :mday=>3, :mon=>2, :year=>2001, :hour=>0, :min=>0, :sec=>0, :zone=>"GMT", :offset=>0} - * - * Related: Date.httpdate (returns a \Date object). - */ -static VALUE -date_s__httpdate(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, opt; - - rb_scan_args(argc, argv, "1:", &str, &opt); - if (!NIL_P(str)) str = check_limit(str, opt); - - return date__httpdate(str); -} - -/* - * call-seq: - * Date.httpdate(string = 'Mon, 01 Jan -4712 00:00:00 GMT', start = Date::ITALY, limit: 128) -> date - * - * Returns a new \Date object with values parsed from +string+, - * which should be a valid - * {HTTP date format}[rdoc-ref:language/strftime_formatting.rdoc@HTTP+Format]: - * - * d = Date.new(2001, 2, 3) - s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" - Date.httpdate(s) # => # - * - * See: - * - * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - Argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date._httpdate (returns a hash). - */ -static VALUE -date_s_httpdate(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATETIME_HTTPDATE); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - if (!NIL_P(opt)) argv2[argc2++] = opt; - hash = date_s__httpdate(argc2, argv2, klass); - return d_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * Date._jisx0301(string, limit: 128) -> hash - * - * Returns a hash of values parsed from +string+, which should be a valid - * {JIS X 0301 date format}[rdoc-ref:language/strftime_formatting.rdoc@JIS+X+0301+Format]: - * - * d = Date.new(2001, 2, 3) - * s = d.jisx0301 # => "H13.02.03" - * Date._jisx0301(s) # => {:year=>2001, :mon=>2, :mday=>3} - * - * See argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date.jisx0301 (returns a \Date object). - */ -static VALUE -date_s__jisx0301(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, opt; - - rb_scan_args(argc, argv, "1:", &str, &opt); - if (!NIL_P(str)) str = check_limit(str, opt); - - return date__jisx0301(str); -} - -/* - * call-seq: - * Date.jisx0301(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date - * - * Returns a new \Date object with values parsed from +string+, - * which should be a valid {JIS X 0301 format}[rdoc-ref:language/strftime_formatting.rdoc@JIS+X+0301+Format]: - * - * d = Date.new(2001, 2, 3) - * s = d.jisx0301 # => "H13.02.03" - * Date.jisx0301(s) # => # - * - * For no-era year, legacy format, Heisei is assumed. - * - * Date.jisx0301('13.02.03') # => # - * - * See: - * - * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - Argument {limit}[rdoc-ref:Date@Argument+limit]. - * - * Related: Date._jisx0301 (returns a hash). - */ -static VALUE -date_s_jisx0301(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATE); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - if (!NIL_P(opt)) argv2[argc2++] = opt; - hash = date_s__jisx0301(argc2, argv2, klass); - return d_new_by_frags(klass, hash, sg); - } -} - -static VALUE -dup_obj(VALUE self) -{ - get_d1a(self); - - if (simple_dat_p(adat)) { - VALUE new = d_lite_s_alloc_simple(rb_obj_class(self)); - { - get_d1b(new); - bdat->s = adat->s; - RB_OBJ_WRITTEN(new, Qundef, bdat->s.nth); - return new; - } - } - else { - VALUE new = d_lite_s_alloc_complex(rb_obj_class(self)); - { - get_d1b(new); - bdat->c = adat->c; - RB_OBJ_WRITTEN(new, Qundef, bdat->c.nth); - RB_OBJ_WRITTEN(new, Qundef, bdat->c.sf); - return new; - } - } -} - -static VALUE -dup_obj_as_complex(VALUE self) -{ - get_d1a(self); - - if (simple_dat_p(adat)) { - VALUE new = d_lite_s_alloc_complex(rb_obj_class(self)); - { - get_d1b(new); - copy_simple_to_complex(new, &bdat->c, &adat->s); - bdat->c.flags |= HAVE_DF | COMPLEX_DAT; - return new; - } - } - else { - VALUE new = d_lite_s_alloc_complex(rb_obj_class(self)); - { - get_d1b(new); - bdat->c = adat->c; - RB_OBJ_WRITTEN(new, Qundef, bdat->c.nth); - RB_OBJ_WRITTEN(new, Qundef, bdat->c.sf); - return new; - } - } -} - -#define val2off(vof,iof) \ -do {\ - if (!offset_to_sec(vof, &iof)) {\ - iof = 0;\ - rb_warning("invalid offset is ignored");\ - }\ -} while (0) - -#if 0 -static VALUE -d_lite_initialize(int argc, VALUE *argv, VALUE self) -{ - VALUE jd, vjd, vdf, sf, vsf, vof, vsg; - int df, of; - double sg; - - rb_check_frozen(self); - - rb_scan_args(argc, argv, "05", &vjd, &vdf, &vsf, &vof, &vsg); - - jd = INT2FIX(0); - df = 0; - sf = INT2FIX(0); - of = 0; - sg = DEFAULT_SG; - - switch (argc) { - case 5: - val2sg(vsg, sg); - case 4: - val2off(vof, of); - case 3: - sf = vsf; - if (f_lt_p(sf, INT2FIX(0)) || - f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) - rb_raise(eDateError, "invalid second fraction"); - case 2: - df = NUM2INT(vdf); - if (df < 0 || df >= DAY_IN_SECONDS) - rb_raise(eDateError, "invalid day fraction"); - case 1: - jd = vjd; - } - - { - VALUE nth; - int rjd; - - get_d1(self); - - decode_jd(jd, &nth, &rjd); - if (!df && f_zero_p(sf) && !of) { - set_to_simple(self, &dat->s, nth, rjd, sg, 0, 0, 0, HAVE_JD); - } - else { - if (!complex_dat_p(dat)) - rb_raise(rb_eArgError, - "cannot load complex into simple"); - - set_to_complex(self, &dat->c, nth, rjd, df, sf, of, sg, - 0, 0, 0, 0, 0, 0, HAVE_JD | HAVE_DF); - } - } - return self; -} -#endif - -/* :nodoc: */ -static VALUE -d_lite_initialize_copy(VALUE copy, VALUE date) -{ - rb_check_frozen(copy); - - if (copy == date) - return copy; - { - get_d2(copy, date); - if (simple_dat_p(bdat)) { - if (simple_dat_p(adat)) { - adat->s = bdat->s; - } - else { - adat->c.flags = bdat->s.flags | COMPLEX_DAT; - adat->c.nth = bdat->s.nth; - adat->c.jd = bdat->s.jd; - adat->c.df = 0; - adat->c.sf = INT2FIX(0); - adat->c.of = 0; - adat->c.sg = bdat->s.sg; - adat->c.year = bdat->s.year; -#ifndef USE_PACK - adat->c.mon = bdat->s.mon; - adat->c.mday = bdat->s.mday; - adat->c.hour = bdat->s.hour; - adat->c.min = bdat->s.min; - adat->c.sec = bdat->s.sec; -#else - adat->c.pc = bdat->s.pc; -#endif - } - } - else { - if (!complex_dat_p(adat)) - rb_raise(rb_eArgError, - "cannot load complex into simple"); - - adat->c = bdat->c; - } - } - return copy; -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -d_lite_fill(VALUE self) -{ - get_d1(self); - - if (simple_dat_p(dat)) { - get_s_jd(dat); - get_s_civil(dat); - } - else { - get_c_jd(dat); - get_c_civil(dat); - get_c_df(dat); - get_c_time(dat); - } - return self; -} -#endif - -/* - * call-seq: - * d.ajd -> rational - * - * Returns the astronomical Julian day number. This is a fractional - * number, which is not adjusted by the offset. - * - * DateTime.new(2001,2,3,4,5,6,'+7').ajd #=> (11769328217/4800) - * DateTime.new(2001,2,2,14,5,6,'-7').ajd #=> (11769328217/4800) - */ -static VALUE -d_lite_ajd(VALUE self) -{ - get_d1(self); - return m_ajd(dat); -} - -/* - * call-seq: - * d.amjd -> rational - * - * Returns the astronomical modified Julian day number. This is - * a fractional number, which is not adjusted by the offset. - * - * DateTime.new(2001,2,3,4,5,6,'+7').amjd #=> (249325817/4800) - * DateTime.new(2001,2,2,14,5,6,'-7').amjd #=> (249325817/4800) - */ -static VALUE -d_lite_amjd(VALUE self) -{ - get_d1(self); - return m_amjd(dat); -} - -/* - * call-seq: - * d.jd -> integer - * - * Returns the Julian day number. This is a whole number, which is - * adjusted by the offset as the local time. - * - * DateTime.new(2001,2,3,4,5,6,'+7').jd #=> 2451944 - * DateTime.new(2001,2,3,4,5,6,'-7').jd #=> 2451944 - */ -static VALUE -d_lite_jd(VALUE self) -{ - get_d1(self); - return m_real_local_jd(dat); -} - -/* - * call-seq: - * d.mjd -> integer - * - * Returns the modified Julian day number. This is a whole number, - * which is adjusted by the offset as the local time. - * - * DateTime.new(2001,2,3,4,5,6,'+7').mjd #=> 51943 - * DateTime.new(2001,2,3,4,5,6,'-7').mjd #=> 51943 - */ -static VALUE -d_lite_mjd(VALUE self) -{ - get_d1(self); - return f_sub(m_real_local_jd(dat), INT2FIX(2400001)); -} - -/* - * call-seq: - * ld -> integer - * - * Returns the - * {Lilian day number}[https://en.wikipedia.org/wiki/Lilian_date], - * which is the number of days since the beginning of the Gregorian - * calendar, October 15, 1582. - * - * Date.new(2001, 2, 3).ld # => 152784 - * - */ -static VALUE -d_lite_ld(VALUE self) -{ - get_d1(self); - return f_sub(m_real_local_jd(dat), INT2FIX(2299160)); -} - -/* - * call-seq: - * year -> integer - * - * Returns the year: - * - * Date.new(2001, 2, 3).year # => 2001 - * (Date.new(1, 1, 1) - 1).year # => 0 - * - */ -static VALUE -d_lite_year(VALUE self) -{ - get_d1(self); - return m_real_year(dat); -} - -/* - * call-seq: - * yday -> integer - * - * Returns the day of the year, in range (1..366): - * - * Date.new(2001, 2, 3).yday # => 34 - * - */ -static VALUE -d_lite_yday(VALUE self) -{ - get_d1(self); - return INT2FIX(m_yday(dat)); -} - -/* - * call-seq: - * mon -> integer - * - * Returns the month in range (1..12): - * - * Date.new(2001, 2, 3).mon # => 2 - * - */ -static VALUE -d_lite_mon(VALUE self) -{ - get_d1(self); - return INT2FIX(m_mon(dat)); -} - -/* - * call-seq: - * mday -> integer - * - * Returns the day of the month in range (1..31): - * - * Date.new(2001, 2, 3).mday # => 3 - * - */ -static VALUE -d_lite_mday(VALUE self) -{ - get_d1(self); - return INT2FIX(m_mday(dat)); -} - -/* - * call-seq: - * day_fraction -> rational - * - * Returns the fractional part of the day in range (Rational(0, 1)...Rational(1, 1)): - * - * DateTime.new(2001,2,3,12).day_fraction # => (1/2) - * - */ -static VALUE -d_lite_day_fraction(VALUE self) -{ - get_d1(self); - if (simple_dat_p(dat)) - return INT2FIX(0); - return m_fr(dat); -} - -/* - * call-seq: - * cwyear -> integer - * - * Returns commercial-date year for +self+ - * (see Date.commercial): - * - * Date.new(2001, 2, 3).cwyear # => 2001 - * Date.new(2000, 1, 1).cwyear # => 1999 - * - */ -static VALUE -d_lite_cwyear(VALUE self) -{ - get_d1(self); - return m_real_cwyear(dat); -} - -/* - * call-seq: - * cweek -> integer - * - * Returns commercial-date week index for +self+ - * (see Date.commercial): - * - * Date.new(2001, 2, 3).cweek # => 5 - * - */ -static VALUE -d_lite_cweek(VALUE self) -{ - get_d1(self); - return INT2FIX(m_cweek(dat)); -} - -/* - * call-seq: - * cwday -> integer - * - * Returns the commercial-date weekday index for +self+ - * (see Date.commercial); - * 1 is Monday: - * - * Date.new(2001, 2, 3).cwday # => 6 - * - */ -static VALUE -d_lite_cwday(VALUE self) -{ - get_d1(self); - return INT2FIX(m_cwday(dat)); -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -d_lite_wnum0(VALUE self) -{ - get_d1(self); - return INT2FIX(m_wnum0(dat)); -} - -/* :nodoc: */ -static VALUE -d_lite_wnum1(VALUE self) -{ - get_d1(self); - return INT2FIX(m_wnum1(dat)); -} -#endif - -/* - * call-seq: - * wday -> integer - * - * Returns the day of week in range (0..6); Sunday is 0: - * - * Date.new(2001, 2, 3).wday # => 6 - * - */ -static VALUE -d_lite_wday(VALUE self) -{ - get_d1(self); - return INT2FIX(m_wday(dat)); -} - -/* - * call-seq: - * sunday? -> true or false - * - * Returns +true+ if +self+ is a Sunday, +false+ otherwise. - */ -static VALUE -d_lite_sunday_p(VALUE self) -{ - get_d1(self); - return f_boolcast(m_wday(dat) == 0); -} - -/* - * call-seq: - * monday? -> true or false - * - * Returns +true+ if +self+ is a Monday, +false+ otherwise. - */ -static VALUE -d_lite_monday_p(VALUE self) -{ - get_d1(self); - return f_boolcast(m_wday(dat) == 1); -} - -/* - * call-seq: - * tuesday? -> true or false - * - * Returns +true+ if +self+ is a Tuesday, +false+ otherwise. - */ -static VALUE -d_lite_tuesday_p(VALUE self) -{ - get_d1(self); - return f_boolcast(m_wday(dat) == 2); -} - -/* - * call-seq: - * wednesday? -> true or false - * - * Returns +true+ if +self+ is a Wednesday, +false+ otherwise. - */ -static VALUE -d_lite_wednesday_p(VALUE self) -{ - get_d1(self); - return f_boolcast(m_wday(dat) == 3); -} - -/* - * call-seq: - * thursday? -> true or false - * - * Returns +true+ if +self+ is a Thursday, +false+ otherwise. - */ -static VALUE -d_lite_thursday_p(VALUE self) -{ - get_d1(self); - return f_boolcast(m_wday(dat) == 4); -} - -/* - * call-seq: - * friday? -> true or false - * - * Returns +true+ if +self+ is a Friday, +false+ otherwise. - */ -static VALUE -d_lite_friday_p(VALUE self) -{ - get_d1(self); - return f_boolcast(m_wday(dat) == 5); -} - -/* - * call-seq: - * saturday? -> true or false - * - * Returns +true+ if +self+ is a Saturday, +false+ otherwise. - */ -static VALUE -d_lite_saturday_p(VALUE self) -{ - get_d1(self); - return f_boolcast(m_wday(dat) == 6); -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -d_lite_nth_kday_p(VALUE self, VALUE n, VALUE k) -{ - int rjd, ns; - - get_d1(self); - - if (NUM2INT(k) != m_wday(dat)) - return Qfalse; - - c_nth_kday_to_jd(m_year(dat), m_mon(dat), - NUM2INT(n), NUM2INT(k), m_virtual_sg(dat), /* !=m_sg() */ - &rjd, &ns); - if (m_local_jd(dat) != rjd) - return Qfalse; - return Qtrue; -} -#endif - -/* - * call-seq: - * hour -> integer - * - * Returns the hour in range (0..23): - * - * DateTime.new(2001, 2, 3, 4, 5, 6).hour # => 4 - * - */ -static VALUE -d_lite_hour(VALUE self) -{ - get_d1(self); - return INT2FIX(m_hour(dat)); -} - -/* - * call-seq: - * min -> integer - * - * Returns the minute in range (0..59): - * - * DateTime.new(2001, 2, 3, 4, 5, 6).min # => 5 - * - */ -static VALUE -d_lite_min(VALUE self) -{ - get_d1(self); - return INT2FIX(m_min(dat)); -} - -/* - * call-seq: - * sec -> integer - * - * Returns the second in range (0..59): - * - * DateTime.new(2001, 2, 3, 4, 5, 6).sec # => 6 - * - */ -static VALUE -d_lite_sec(VALUE self) -{ - get_d1(self); - return INT2FIX(m_sec(dat)); -} - -/* - * call-seq: - * sec_fraction -> rational - * - * Returns the fractional part of the second in range - * (Rational(0, 1)...Rational(1, 1)): - * - * DateTime.new(2001, 2, 3, 4, 5, 6.5).sec_fraction # => (1/2) - * - */ -static VALUE -d_lite_sec_fraction(VALUE self) -{ - get_d1(self); - return m_sf_in_sec(dat); -} - -/* - * call-seq: - * d.offset -> rational - * - * Returns the offset. - * - * DateTime.parse('04pm+0730').offset #=> (5/16) - */ -static VALUE -d_lite_offset(VALUE self) -{ - get_d1(self); - return m_of_in_day(dat); -} - -/* - * call-seq: - * d.zone -> string - * - * Returns the timezone. - * - * DateTime.parse('04pm+0730').zone #=> "+07:30" - */ -static VALUE -d_lite_zone(VALUE self) -{ - get_d1(self); - return m_zone(dat); -} - -/* - * call-seq: - * d.julian? -> true or false - * - * Returns +true+ if the date is before the date of calendar reform, - * +false+ otherwise: - * - * (Date.new(1582, 10, 15) - 1).julian? # => true - * Date.new(1582, 10, 15).julian? # => false - * - */ -static VALUE -d_lite_julian_p(VALUE self) -{ - get_d1(self); - return f_boolcast(m_julian_p(dat)); -} - -/* - * call-seq: - * gregorian? -> true or false - * - * Returns +true+ if the date is on or after - * the date of calendar reform, +false+ otherwise: - * - * Date.new(1582, 10, 15).gregorian? # => true - * (Date.new(1582, 10, 15) - 1).gregorian? # => false - * - */ -static VALUE -d_lite_gregorian_p(VALUE self) -{ - get_d1(self); - return f_boolcast(m_gregorian_p(dat)); -} - -/* - * call-seq: - * leap? -> true or false - * - * Returns +true+ if the year is a leap year, +false+ otherwise: - * - * Date.new(2000).leap? # => true - * Date.new(2001).leap? # => false - * - */ -static VALUE -d_lite_leap_p(VALUE self) -{ - int rjd, ns, ry, rm, rd; - - get_d1(self); - if (m_gregorian_p(dat)) - return f_boolcast(c_gregorian_leap_p(m_year(dat))); - - c_civil_to_jd(m_year(dat), 3, 1, m_virtual_sg(dat), - &rjd, &ns); - c_jd_to_civil(rjd - 1, m_virtual_sg(dat), &ry, &rm, &rd); - return f_boolcast(rd == 29); -} - -/* - * call-seq: - * start -> float - * - * Returns the Julian start date for calendar reform; - * if not an infinity, the returned value is suitable - * for passing to Date#jd: - * - * d = Date.new(2001, 2, 3, Date::ITALY) - * s = d.start # => 2299161.0 - * Date.jd(s).to_s # => "1582-10-15" - * - * d = Date.new(2001, 2, 3, Date::ENGLAND) - * s = d.start # => 2361222.0 - * Date.jd(s).to_s # => "1752-09-14" - * - * Date.new(2001, 2, 3, Date::GREGORIAN).start # => -Infinity - * Date.new(2001, 2, 3, Date::JULIAN).start # => Infinity - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - */ -static VALUE -d_lite_start(VALUE self) -{ - get_d1(self); - return DBL2NUM(m_sg(dat)); -} - -static void -clear_civil(union DateData *x) -{ - if (simple_dat_p(x)) { - x->s.year = 0; -#ifndef USE_PACK - x->s.mon = 0; - x->s.mday = 0; -#else - x->s.pc = 0; -#endif - x->s.flags &= ~HAVE_CIVIL; - } - else { - x->c.year = 0; -#ifndef USE_PACK - x->c.mon = 0; - x->c.mday = 0; - x->c.hour = 0; - x->c.min = 0; - x->c.sec = 0; -#else - x->c.pc = 0; -#endif - x->c.flags &= ~(HAVE_CIVIL | HAVE_TIME); - } -} - -static void -set_sg(union DateData *x, double sg) -{ - if (simple_dat_p(x)) { - get_s_jd(x); - clear_civil(x); - x->s.sg = (date_sg_t)sg; - } else { - get_c_jd(x); - get_c_df(x); - clear_civil(x); - x->c.sg = (date_sg_t)sg; - } -} - -static VALUE -dup_obj_with_new_start(VALUE obj, double sg) -{ - volatile VALUE dup = dup_obj(obj); - { - get_d1(dup); - set_sg(dat, sg); - } - return dup; -} - -/* - * call-seq: - * new_start(start = Date::ITALY) -> new_date - * - * Returns a copy of +self+ with the given +start+ value: - * - * d0 = Date.new(2000, 2, 3) - * d0.julian? # => false - * d1 = d0.new_start(Date::JULIAN) - * d1.julian? # => true - * - * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. - * - */ -static VALUE -d_lite_new_start(int argc, VALUE *argv, VALUE self) -{ - VALUE vsg; - double sg; - - rb_scan_args(argc, argv, "01", &vsg); - - sg = DEFAULT_SG; - if (argc >= 1) - val2sg(vsg, sg); - - return dup_obj_with_new_start(self, sg); -} - -/* - * call-seq: - * italy -> new_date - * - * Equivalent to Date#new_start with argument Date::ITALY. - * - */ -static VALUE -d_lite_italy(VALUE self) -{ - return dup_obj_with_new_start(self, ITALY); -} - -/* - * call-seq: - * england -> new_date - * - * Equivalent to Date#new_start with argument Date::ENGLAND. - */ -static VALUE -d_lite_england(VALUE self) -{ - return dup_obj_with_new_start(self, ENGLAND); -} - -/* - * call-seq: - * julian -> new_date - * - * Equivalent to Date#new_start with argument Date::JULIAN. - */ -static VALUE -d_lite_julian(VALUE self) -{ - return dup_obj_with_new_start(self, JULIAN); -} - -/* - * call-seq: - * gregorian -> new_date - * - * Equivalent to Date#new_start with argument Date::GREGORIAN. - */ -static VALUE -d_lite_gregorian(VALUE self) -{ - return dup_obj_with_new_start(self, GREGORIAN); -} - -static void -set_of(union DateData *x, int of) -{ - assert(complex_dat_p(x)); - get_c_jd(x); - get_c_df(x); - clear_civil(x); - x->c.of = of; -} - -static VALUE -dup_obj_with_new_offset(VALUE obj, int of) -{ - volatile VALUE dup = dup_obj_as_complex(obj); - { - get_d1(dup); - set_of(dat, of); - } - return dup; -} - -/* - * call-seq: - * d.new_offset([offset=0]) -> date - * - * Duplicates self and resets its offset. - * - * d = DateTime.new(2001,2,3,4,5,6,'-02:00') - * #=> # - * d.new_offset('+09:00') #=> # - */ -static VALUE -d_lite_new_offset(int argc, VALUE *argv, VALUE self) -{ - VALUE vof; - int rof; - - rb_scan_args(argc, argv, "01", &vof); - - rof = 0; - if (argc >= 1) - val2off(vof, rof); - - return dup_obj_with_new_offset(self, rof); -} - -/* - * call-seq: - * d + other -> date - * - * Returns a date object pointing +other+ days after self. The other - * should be a numeric value. If the other is a fractional number, - * assumes its precision is at most nanosecond. - * - * Date.new(2001,2,3) + 1 #=> # - * DateTime.new(2001,2,3) + Rational(1,2) - * #=> # - * DateTime.new(2001,2,3) + Rational(-1,2) - * #=> # - * DateTime.jd(0,12) + DateTime.new(2001,2,3).ajd - * #=> # - */ -static VALUE -d_lite_plus(VALUE self, VALUE other) -{ - int try_rational = 1; - get_d1(self); - - again: - switch (TYPE(other)) { - case T_FIXNUM: - { - VALUE nth; - long t; - int jd; - - nth = m_nth(dat); - t = FIX2LONG(other); - if (DIV(t, CM_PERIOD)) { - nth = f_add(nth, INT2FIX(DIV(t, CM_PERIOD))); - t = MOD(t, CM_PERIOD); - } - - if (!t) - jd = m_jd(dat); - else { - jd = m_jd(dat) + (int)t; - canonicalize_jd(nth, jd); - } - - if (simple_dat_p(dat)) - return d_simple_new_internal(rb_obj_class(self), - nth, jd, - dat->s.sg, - 0, 0, 0, - (dat->s.flags | HAVE_JD) & - ~HAVE_CIVIL); - else - return d_complex_new_internal(rb_obj_class(self), - nth, jd, - dat->c.df, dat->c.sf, - dat->c.of, dat->c.sg, - 0, 0, 0, -#ifndef USE_PACK - dat->c.hour, - dat->c.min, - dat->c.sec, -#else - EX_HOUR(dat->c.pc), - EX_MIN(dat->c.pc), - EX_SEC(dat->c.pc), -#endif - (dat->c.flags | HAVE_JD) & - ~HAVE_CIVIL); - } - break; - case T_BIGNUM: - { - VALUE nth; - int jd, s; - - if (f_positive_p(other)) - s = +1; - else { - s = -1; - other = f_negate(other); - } - - nth = f_idiv(other, INT2FIX(CM_PERIOD)); - jd = FIX2INT(f_mod(other, INT2FIX(CM_PERIOD))); - - if (s < 0) { - nth = f_negate(nth); - jd = -jd; - } - - if (!jd) - jd = m_jd(dat); - else { - jd = m_jd(dat) + jd; - canonicalize_jd(nth, jd); - } - - if (f_zero_p(nth)) - nth = m_nth(dat); - else - nth = f_add(m_nth(dat), nth); - - if (simple_dat_p(dat)) - return d_simple_new_internal(rb_obj_class(self), - nth, jd, - dat->s.sg, - 0, 0, 0, - (dat->s.flags | HAVE_JD) & - ~HAVE_CIVIL); - else - return d_complex_new_internal(rb_obj_class(self), - nth, jd, - dat->c.df, dat->c.sf, - dat->c.of, dat->c.sg, - 0, 0, 0, -#ifndef USE_PACK - dat->c.hour, - dat->c.min, - dat->c.sec, -#else - EX_HOUR(dat->c.pc), - EX_MIN(dat->c.pc), - EX_SEC(dat->c.pc), -#endif - (dat->c.flags | HAVE_JD) & - ~HAVE_CIVIL); - } - break; - case T_FLOAT: - { - double jd, o, tmp; - int s, df; - VALUE nth, sf; - - o = RFLOAT_VALUE(other); - - if (o > 0) - s = +1; - else { - s = -1; - o = -o; - } - - o = modf(o, &tmp); - - if (!floor(tmp / CM_PERIOD)) { - nth = INT2FIX(0); - jd = (int)tmp; - } - else { - double i, f; - - f = modf(tmp / CM_PERIOD, &i); - nth = f_floor(DBL2NUM(i)); - jd = (int)(f * CM_PERIOD); - } - - o *= DAY_IN_SECONDS; - o = modf(o, &tmp); - df = (int)tmp; - o *= SECOND_IN_NANOSECONDS; - sf = INT2FIX((int)round(o)); - - if (s < 0) { - jd = -jd; - df = -df; - sf = f_negate(sf); - } - - if (f_zero_p(sf)) - sf = m_sf(dat); - else { - sf = f_add(m_sf(dat), sf); - if (f_lt_p(sf, INT2FIX(0))) { - df -= 1; - sf = f_add(sf, INT2FIX(SECOND_IN_NANOSECONDS)); - } - else if (f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) { - df += 1; - sf = f_sub(sf, INT2FIX(SECOND_IN_NANOSECONDS)); - } - } - - if (!df) - df = m_df(dat); - else { - df = m_df(dat) + df; - if (df < 0) { - jd -= 1; - df += DAY_IN_SECONDS; - } - else if (df >= DAY_IN_SECONDS) { - jd += 1; - df -= DAY_IN_SECONDS; - } - } - - if (!jd) - jd = m_jd(dat); - else { - jd = m_jd(dat) + jd; - canonicalize_jd(nth, jd); - } - - if (f_zero_p(nth)) - nth = m_nth(dat); - else - nth = f_add(m_nth(dat), nth); - - if (!df && f_zero_p(sf) && !m_of(dat)) - return d_simple_new_internal(rb_obj_class(self), - nth, (int)jd, - m_sg(dat), - 0, 0, 0, - (dat->s.flags | HAVE_JD) & - ~(HAVE_CIVIL | HAVE_TIME | - COMPLEX_DAT)); - else - return d_complex_new_internal(rb_obj_class(self), - nth, (int)jd, - df, sf, - m_of(dat), m_sg(dat), - 0, 0, 0, - 0, 0, 0, - (dat->c.flags | - HAVE_JD | HAVE_DF) & - ~(HAVE_CIVIL | HAVE_TIME)); - } - break; - default: - expect_numeric(other); - other = f_to_r(other); - if (!k_rational_p(other)) { - if (!try_rational) Check_Type(other, T_RATIONAL); - try_rational = 0; - goto again; - } - /* fall through */ - case T_RATIONAL: - { - VALUE nth, sf, t; - int jd, df, s; - - if (wholenum_p(other)) { - other = rb_rational_num(other); - goto again; - } - - if (f_positive_p(other)) - s = +1; - else { - s = -1; - other = f_negate(other); - } - - nth = f_idiv(other, INT2FIX(CM_PERIOD)); - t = f_mod(other, INT2FIX(CM_PERIOD)); - - jd = FIX2INT(f_idiv(t, INT2FIX(1))); - t = f_mod(t, INT2FIX(1)); - - t = f_mul(t, INT2FIX(DAY_IN_SECONDS)); - df = FIX2INT(f_idiv(t, INT2FIX(1))); - t = f_mod(t, INT2FIX(1)); - - sf = f_mul(t, INT2FIX(SECOND_IN_NANOSECONDS)); - - if (s < 0) { - nth = f_negate(nth); - jd = -jd; - df = -df; - sf = f_negate(sf); - } - - if (f_zero_p(sf)) - sf = m_sf(dat); - else { - sf = f_add(m_sf(dat), sf); - if (f_lt_p(sf, INT2FIX(0))) { - df -= 1; - sf = f_add(sf, INT2FIX(SECOND_IN_NANOSECONDS)); - } - else if (f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) { - df += 1; - sf = f_sub(sf, INT2FIX(SECOND_IN_NANOSECONDS)); - } - } - - if (!df) - df = m_df(dat); - else { - df = m_df(dat) + df; - if (df < 0) { - jd -= 1; - df += DAY_IN_SECONDS; - } - else if (df >= DAY_IN_SECONDS) { - jd += 1; - df -= DAY_IN_SECONDS; - } - } - - if (!jd) - jd = m_jd(dat); - else { - jd = m_jd(dat) + jd; - canonicalize_jd(nth, jd); - } - - if (f_zero_p(nth)) - nth = m_nth(dat); - else - nth = f_add(m_nth(dat), nth); - - if (!df && f_zero_p(sf) && !m_of(dat)) - return d_simple_new_internal(rb_obj_class(self), - nth, jd, - m_sg(dat), - 0, 0, 0, - (dat->s.flags | HAVE_JD) & - ~(HAVE_CIVIL | HAVE_TIME | - COMPLEX_DAT)); - else - return d_complex_new_internal(rb_obj_class(self), - nth, jd, - df, sf, - m_of(dat), m_sg(dat), - 0, 0, 0, - 0, 0, 0, - (dat->c.flags | - HAVE_JD | HAVE_DF) & - ~(HAVE_CIVIL | HAVE_TIME)); - } - break; - } -} - -static VALUE -minus_dd(VALUE self, VALUE other) -{ - get_d2(self, other); - - { - int d, df; - VALUE n, sf, r; - - n = f_sub(m_nth(adat), m_nth(bdat)); - d = m_jd(adat) - m_jd(bdat); - df = m_df(adat) - m_df(bdat); - sf = f_sub(m_sf(adat), m_sf(bdat)); - canonicalize_jd(n, d); - - if (df < 0) { - d -= 1; - df += DAY_IN_SECONDS; - } - else if (df >= DAY_IN_SECONDS) { - d += 1; - df -= DAY_IN_SECONDS; - } - - if (f_lt_p(sf, INT2FIX(0))) { - df -= 1; - sf = f_add(sf, INT2FIX(SECOND_IN_NANOSECONDS)); - } - else if (f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) { - df += 1; - sf = f_sub(sf, INT2FIX(SECOND_IN_NANOSECONDS)); - } - - if (f_zero_p(n)) - r = INT2FIX(0); - else - r = f_mul(n, INT2FIX(CM_PERIOD)); - - if (d) - r = f_add(r, rb_rational_new1(INT2FIX(d))); - if (df) - r = f_add(r, isec_to_day(df)); - if (f_nonzero_p(sf)) - r = f_add(r, ns_to_day(sf)); - - if (RB_TYPE_P(r, T_RATIONAL)) - return r; - return rb_rational_new1(r); - } -} - -/* - * call-seq: - * d - other -> date or rational - * - * If the other is a date object, returns a Rational - * whose value is the difference between the two dates in days. - * If the other is a numeric value, returns a date object - * pointing +other+ days before self. - * If the other is a fractional number, - * assumes its precision is at most nanosecond. - * - * Date.new(2001,2,3) - 1 #=> # - * DateTime.new(2001,2,3) - Rational(1,2) - * #=> # - * Date.new(2001,2,3) - Date.new(2001) - * #=> (33/1) - * DateTime.new(2001,2,3) - DateTime.new(2001,2,2,12) - * #=> (1/2) - */ -static VALUE -d_lite_minus(VALUE self, VALUE other) -{ - if (k_date_p(other)) - return minus_dd(self, other); - - switch (TYPE(other)) { - case T_FIXNUM: - return d_lite_plus(self, LONG2NUM(-FIX2LONG(other))); - case T_FLOAT: - return d_lite_plus(self, DBL2NUM(-RFLOAT_VALUE(other))); - default: - expect_numeric(other); - /* fall through */ - case T_BIGNUM: - case T_RATIONAL: - return d_lite_plus(self, f_negate(other)); - } -} - -/* - * call-seq: - * next_day(n = 1) -> new_date - * - * Equivalent to Date#+ with argument +n+. - */ -static VALUE -d_lite_next_day(int argc, VALUE *argv, VALUE self) -{ - VALUE n; - - rb_scan_args(argc, argv, "01", &n); - if (argc < 1) - n = INT2FIX(1); - return d_lite_plus(self, n); -} - -/* - * call-seq: - * prev_day(n = 1) -> new_date - * - * Equivalent to Date#- with argument +n+. - */ -static VALUE -d_lite_prev_day(int argc, VALUE *argv, VALUE self) -{ - VALUE n; - - rb_scan_args(argc, argv, "01", &n); - if (argc < 1) - n = INT2FIX(1); - return d_lite_minus(self, n); -} - -/* - * call-seq: - * d.next -> new_date - * - * Returns a new \Date object representing the following day: - * - * d = Date.new(2001, 2, 3) - * d.to_s # => "2001-02-03" - * d.next.to_s # => "2001-02-04" - * - */ -static VALUE -d_lite_next(VALUE self) -{ - return d_lite_next_day(0, (VALUE *)NULL, self); -} - -/* - * call-seq: - * d >> n -> new_date - * - * Returns a new \Date object representing the date - * +n+ months later; +n+ should be a numeric: - * - * (Date.new(2001, 2, 3) >> 1).to_s # => "2001-03-03" - * (Date.new(2001, 2, 3) >> -2).to_s # => "2000-12-03" - * - * When the same day does not exist for the new month, - * the last day of that month is used instead: - * - * (Date.new(2001, 1, 31) >> 1).to_s # => "2001-02-28" - * (Date.new(2001, 1, 31) >> -4).to_s # => "2000-09-30" - * - * This results in the following, possibly unexpected, behaviors: - * - * d0 = Date.new(2001, 1, 31) - * d1 = d0 >> 1 # => # - * d2 = d1 >> 1 # => # - * - * d0 = Date.new(2001, 1, 31) - * d1 = d0 >> 1 # => # - * d2 = d1 >> -1 # => # - * - */ -static VALUE -d_lite_rshift(VALUE self, VALUE other) -{ - VALUE t, y, nth, rjd2; - int m, d, rjd; - double sg; - - get_d1(self); - t = f_add3(f_mul(m_real_year(dat), INT2FIX(12)), - INT2FIX(m_mon(dat) - 1), - other); - if (FIXNUM_P(t)) { - long it = FIX2LONG(t); - y = LONG2NUM(DIV(it, 12)); - it = MOD(it, 12); - m = (int)it + 1; - } - else { - y = f_idiv(t, INT2FIX(12)); - t = f_mod(t, INT2FIX(12)); - m = FIX2INT(t) + 1; - } - d = m_mday(dat); - sg = m_sg(dat); - - while (1) { - int ry, rm, rd, ns; - - if (valid_civil_p(y, m, d, sg, - &nth, &ry, - &rm, &rd, &rjd, &ns)) - break; - if (--d < 1) - rb_raise(eDateError, "invalid date"); - } - encode_jd(nth, rjd, &rjd2); - return d_lite_plus(self, f_sub(rjd2, m_real_local_jd(dat))); -} - -/* - * call-seq: - * d << n -> date - * - * Returns a new \Date object representing the date - * +n+ months earlier; +n+ should be a numeric: - * - * (Date.new(2001, 2, 3) << 1).to_s # => "2001-01-03" - * (Date.new(2001, 2, 3) << -2).to_s # => "2001-04-03" - * - * When the same day does not exist for the new month, - * the last day of that month is used instead: - * - * (Date.new(2001, 3, 31) << 1).to_s # => "2001-02-28" - * (Date.new(2001, 3, 31) << -6).to_s # => "2001-09-30" - * - * This results in the following, possibly unexpected, behaviors: - * - * d0 = Date.new(2001, 3, 31) - * d0 << 2 # => # - * d0 << 1 << 1 # => # - * - * d0 = Date.new(2001, 3, 31) - * d1 = d0 << 1 # => # - * d2 = d1 << -1 # => # - * - */ -static VALUE -d_lite_lshift(VALUE self, VALUE other) -{ - expect_numeric(other); - return d_lite_rshift(self, f_negate(other)); -} - -/* - * call-seq: - * next_month(n = 1) -> new_date - * - * Equivalent to #>> with argument +n+. - */ -static VALUE -d_lite_next_month(int argc, VALUE *argv, VALUE self) -{ - VALUE n; - - rb_scan_args(argc, argv, "01", &n); - if (argc < 1) - n = INT2FIX(1); - return d_lite_rshift(self, n); -} - -/* - * call-seq: - * prev_month(n = 1) -> new_date - * - * Equivalent to #<< with argument +n+. - */ -static VALUE -d_lite_prev_month(int argc, VALUE *argv, VALUE self) -{ - VALUE n; - - rb_scan_args(argc, argv, "01", &n); - if (argc < 1) - n = INT2FIX(1); - return d_lite_lshift(self, n); -} - -/* - * call-seq: - * next_year(n = 1) -> new_date - * - * Equivalent to #>> with argument n * 12. - */ -static VALUE -d_lite_next_year(int argc, VALUE *argv, VALUE self) -{ - VALUE n; - - rb_scan_args(argc, argv, "01", &n); - if (argc < 1) - n = INT2FIX(1); - return d_lite_rshift(self, f_mul(n, INT2FIX(12))); -} - -/* - * call-seq: - * prev_year(n = 1) -> new_date - * - * Equivalent to #<< with argument n * 12. - */ -static VALUE -d_lite_prev_year(int argc, VALUE *argv, VALUE self) -{ - VALUE n; - - rb_scan_args(argc, argv, "01", &n); - if (argc < 1) - n = INT2FIX(1); - return d_lite_lshift(self, f_mul(n, INT2FIX(12))); -} - -static VALUE d_lite_cmp(VALUE, VALUE); - -/* - * call-seq: - * step(limit, step = 1){|date| ... } -> self - * - * Calls the block with specified dates; - * returns +self+. - * - * - The first +date+ is +self+. - * - Each successive +date+ is date + step, - * where +step+ is the numeric step size in days. - * - The last date is the last one that is before or equal to +limit+, - * which should be a \Date object. - * - * Example: - * - * limit = Date.new(2001, 12, 31) - * Date.new(2001).step(limit){|date| p date.to_s if date.mday == 31 } - * - * Output: - * - * "2001-01-31" - * "2001-03-31" - * "2001-05-31" - * "2001-07-31" - * "2001-08-31" - * "2001-10-31" - * "2001-12-31" - * - * Returns an Enumerator if no block is given. - */ -static VALUE -d_lite_step(int argc, VALUE *argv, VALUE self) -{ - VALUE limit, step, date; - int c; - - rb_scan_args(argc, argv, "11", &limit, &step); - - if (argc < 2) - step = INT2FIX(1); - -#if 0 - if (f_zero_p(step)) - rb_raise(rb_eArgError, "step can't be 0"); -#endif - - RETURN_ENUMERATOR(self, argc, argv); - - date = self; - c = f_cmp(step, INT2FIX(0)); - if (c < 0) { - while (FIX2INT(d_lite_cmp(date, limit)) >= 0) { - rb_yield(date); - date = d_lite_plus(date, step); - } - } - else if (c == 0) { - while (1) - rb_yield(date); - } - else /* if (c > 0) */ { - while (FIX2INT(d_lite_cmp(date, limit)) <= 0) { - rb_yield(date); - date = d_lite_plus(date, step); - } - } - return self; -} - -/* - * call-seq: - * upto(max){|date| ... } -> self - * - * Equivalent to #step with arguments +max+ and +1+. - */ -static VALUE -d_lite_upto(VALUE self, VALUE max) -{ - VALUE date; - - RETURN_ENUMERATOR(self, 1, &max); - - date = self; - while (FIX2INT(d_lite_cmp(date, max)) <= 0) { - rb_yield(date); - date = d_lite_plus(date, INT2FIX(1)); - } - return self; -} - -/* - * call-seq: - * downto(min){|date| ... } -> self - * - * Equivalent to #step with arguments +min+ and -1. - */ -static VALUE -d_lite_downto(VALUE self, VALUE min) -{ - VALUE date; - - RETURN_ENUMERATOR(self, 1, &min); - - date = self; - while (FIX2INT(d_lite_cmp(date, min)) >= 0) { - rb_yield(date); - date = d_lite_plus(date, INT2FIX(-1)); - } - return self; -} - -static VALUE -cmp_gen(VALUE self, VALUE other) -{ - get_d1(self); - - if (k_numeric_p(other)) - return INT2FIX(f_cmp(m_ajd(dat), other)); - else if (k_date_p(other)) - return INT2FIX(f_cmp(m_ajd(dat), f_ajd(other))); - return rb_num_coerce_cmp(self, other, id_cmp); -} - -static VALUE -cmp_dd(VALUE self, VALUE other) -{ - get_d2(self, other); - - { - VALUE a_nth, b_nth, - a_sf, b_sf; - int a_jd, b_jd, - a_df, b_df; - - m_canonicalize_jd(self, adat); - m_canonicalize_jd(other, bdat); - a_nth = m_nth(adat); - b_nth = m_nth(bdat); - if (f_eqeq_p(a_nth, b_nth)) { - a_jd = m_jd(adat); - b_jd = m_jd(bdat); - if (a_jd == b_jd) { - a_df = m_df(adat); - b_df = m_df(bdat); - if (a_df == b_df) { - a_sf = m_sf(adat); - b_sf = m_sf(bdat); - if (f_eqeq_p(a_sf, b_sf)) { - return INT2FIX(0); - } - else if (f_lt_p(a_sf, b_sf)) { - return INT2FIX(-1); - } - else { - return INT2FIX(1); - } - } - else if (a_df < b_df) { - return INT2FIX(-1); - } - else { - return INT2FIX(1); - } - } - else if (a_jd < b_jd) { - return INT2FIX(-1); - } - else { - return INT2FIX(1); - } - } - else if (f_lt_p(a_nth, b_nth)) { - return INT2FIX(-1); - } - else { - return INT2FIX(1); - } - } -} - -/* - * call-seq: - * self <=> other -> -1, 0, 1 or nil - * - * Compares +self+ and +other+, returning: - * - * - -1 if +other+ is larger. - * - 0 if the two are equal. - * - 1 if +other+ is smaller. - * - +nil+ if the two are incomparable. - * - * Argument +other+ may be: - * - * - Another \Date object: - * - * d = Date.new(2022, 7, 27) # => # - * prev_date = d.prev_day # => # - * next_date = d.next_day # => # - * d <=> next_date # => -1 - * d <=> d # => 0 - * d <=> prev_date # => 1 - * - * - A DateTime object: - * - * d <=> DateTime.new(2022, 7, 26) # => 1 - * d <=> DateTime.new(2022, 7, 27) # => 0 - * d <=> DateTime.new(2022, 7, 28) # => -1 - * - * - A numeric (compares self.ajd to +other+): - * - * d <=> 2459788 # => -1 - * d <=> 2459787 # => 1 - * d <=> 2459786 # => 1 - * d <=> d.ajd # => 0 - * - * - Any other object: - * - * d <=> Object.new # => nil - * - */ -static VALUE -d_lite_cmp(VALUE self, VALUE other) -{ - if (!k_date_p(other)) - return cmp_gen(self, other); - - { - get_d2(self, other); - - if (!(simple_dat_p(adat) && simple_dat_p(bdat) && - m_gregorian_p(adat) == m_gregorian_p(bdat))) - return cmp_dd(self, other); - - { - VALUE a_nth, b_nth; - int a_jd, b_jd; - - m_canonicalize_jd(self, adat); - m_canonicalize_jd(other, bdat); - a_nth = m_nth(adat); - b_nth = m_nth(bdat); - if (f_eqeq_p(a_nth, b_nth)) { - a_jd = m_jd(adat); - b_jd = m_jd(bdat); - if (a_jd == b_jd) { - return INT2FIX(0); - } - else if (a_jd < b_jd) { - return INT2FIX(-1); - } - else { - return INT2FIX(1); - } - } - else if (f_lt_p(a_nth, b_nth)) { - return INT2FIX(-1); - } - else { - return INT2FIX(1); - } - } - } -} - -static VALUE -equal_gen(VALUE self, VALUE other) -{ - get_d1(self); - - if (k_numeric_p(other)) - return f_eqeq_p(m_real_local_jd(dat), other); - else if (k_date_p(other)) - return f_eqeq_p(m_real_local_jd(dat), f_jd(other)); - return rb_num_coerce_cmp(self, other, id_eqeq_p); -} - -/* - * call-seq: - * self === other -> true, false, or nil. - * - * Returns +true+ if +self+ and +other+ represent the same date, - * +false+ if not, +nil+ if the two are not comparable. - * - * Argument +other+ may be: - * - * - Another \Date object: - * - * d = Date.new(2022, 7, 27) # => # - * prev_date = d.prev_day # => # - * next_date = d.next_day # => # - * d === prev_date # => false - * d === d # => true - * d === next_date # => false - * - * - A DateTime object: - * - * d === DateTime.new(2022, 7, 26) # => false - * d === DateTime.new(2022, 7, 27) # => true - * d === DateTime.new(2022, 7, 28) # => false - * - * - A numeric (compares self.jd to +other+): - * - * d === 2459788 # => true - * d === 2459787 # => false - * d === 2459786 # => false - * d === d.jd # => true - * - * - An object not comparable: - * - * d === Object.new # => nil - * - */ -static VALUE -d_lite_equal(VALUE self, VALUE other) -{ - if (!k_date_p(other)) - return equal_gen(self, other); - - { - get_d2(self, other); - - if (!(m_gregorian_p(adat) == m_gregorian_p(bdat))) - return equal_gen(self, other); - - { - VALUE a_nth, b_nth; - int a_jd, b_jd; - - m_canonicalize_jd(self, adat); - m_canonicalize_jd(other, bdat); - a_nth = m_nth(adat); - b_nth = m_nth(bdat); - a_jd = m_local_jd(adat); - b_jd = m_local_jd(bdat); - if (f_eqeq_p(a_nth, b_nth) && - a_jd == b_jd) - return Qtrue; - return Qfalse; - } - } -} - -/* :nodoc: */ -static VALUE -d_lite_eql_p(VALUE self, VALUE other) -{ - if (!k_date_p(other)) - return Qfalse; - return f_zero_p(d_lite_cmp(self, other)); -} - -/* :nodoc: */ -static VALUE -d_lite_hash(VALUE self) -{ - st_index_t v, h[5]; - VALUE nth; - - get_d1(self); - nth = m_nth(dat); - - if (FIXNUM_P(nth)) { - h[0] = 0; - h[1] = (st_index_t)nth; - } else { - h[0] = 1; - h[1] = (st_index_t)FIX2LONG(rb_hash(nth)); - } - - h[2] = m_jd(dat); - h[3] = m_df(dat); - h[4] = m_sf(dat); - - v = rb_memhash(h, sizeof(h)); - return ST2FIX(v); -} - -#include "date_tmx.h" -static void set_tmx(VALUE, struct tmx *); -static VALUE strftimev(const char *, VALUE, - void (*)(VALUE, struct tmx *)); - -/* - * call-seq: - * to_s -> string - * - * Returns a string representation of the date in +self+ - * in {ISO 8601 extended date format}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications] - * ('%Y-%m-%d'): - * - * Date.new(2001, 2, 3).to_s # => "2001-02-03" - * - */ -static VALUE -d_lite_to_s(VALUE self) -{ - return strftimev("%Y-%m-%d", self, set_tmx); -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -mk_inspect_raw(union DateData *x, VALUE klass) -{ - char flags[6]; - - flags[0] = (x->flags & COMPLEX_DAT) ? 'C' : 'S'; - flags[1] = (x->flags & HAVE_JD) ? 'j' : '-'; - flags[2] = (x->flags & HAVE_DF) ? 'd' : '-'; - flags[3] = (x->flags & HAVE_CIVIL) ? 'c' : '-'; - flags[4] = (x->flags & HAVE_TIME) ? 't' : '-'; - flags[5] = '\0'; - - if (simple_dat_p(x)) { - return rb_enc_sprintf(rb_usascii_encoding(), - "#<%"PRIsVALUE": " - "(%+"PRIsVALUE"th,%dj),+0s,%.0fj; " - "%dy%dm%dd; %s>", - klass, - x->s.nth, x->s.jd, x->s.sg, -#ifndef USE_PACK - x->s.year, x->s.mon, x->s.mday, -#else - x->s.year, - EX_MON(x->s.pc), EX_MDAY(x->s.pc), -#endif - flags); - } - else { - return rb_enc_sprintf(rb_usascii_encoding(), - "#<%"PRIsVALUE": " - "(%+"PRIsVALUE"th,%dj,%ds,%+"PRIsVALUE"n)," - "%+ds,%.0fj; " - "%dy%dm%dd %dh%dm%ds; %s>", - klass, - x->c.nth, x->c.jd, x->c.df, x->c.sf, - x->c.of, x->c.sg, -#ifndef USE_PACK - x->c.year, x->c.mon, x->c.mday, - x->c.hour, x->c.min, x->c.sec, -#else - x->c.year, - EX_MON(x->c.pc), EX_MDAY(x->c.pc), - EX_HOUR(x->c.pc), EX_MIN(x->c.pc), - EX_SEC(x->c.pc), -#endif - flags); - } -} - -/* :nodoc: */ -static VALUE -d_lite_inspect_raw(VALUE self) -{ - get_d1(self); - return mk_inspect_raw(dat, rb_obj_class(self)); -} -#endif - -static VALUE -mk_inspect(union DateData *x, VALUE klass, VALUE to_s) -{ - return rb_enc_sprintf(rb_usascii_encoding(), - "#<%"PRIsVALUE": %"PRIsVALUE" " - "((%+"PRIsVALUE"j,%ds,%+"PRIsVALUE"n),%+ds,%.0fj)>", - klass, to_s, - m_real_jd(x), m_df(x), m_sf(x), - m_of(x), m_sg(x)); -} - -/* - * call-seq: - * inspect -> string - * - * Returns a string representation of +self+: - * - * Date.new(2001, 2, 3).inspect - * # => "#" - * - */ -static VALUE -d_lite_inspect(VALUE self) -{ - get_d1(self); - return mk_inspect(dat, rb_obj_class(self), self); -} - -#include -#include "date_tmx.h" - -size_t date_strftime(char *s, size_t maxsize, const char *format, - const struct tmx *tmx); - -#define SMALLBUF 100 -static size_t -date_strftime_alloc(char **buf, const char *format, - struct tmx *tmx) -{ - size_t size, len, flen; - - (*buf)[0] = '\0'; - flen = strlen(format); - if (flen == 0) { - return 0; - } - errno = 0; - len = date_strftime(*buf, SMALLBUF, format, tmx); - if (len != 0 || (**buf == '\0' && errno != ERANGE)) return len; - for (size=1024; ; size*=2) { - *buf = xmalloc(size); - (*buf)[0] = '\0'; - len = date_strftime(*buf, size, format, tmx); - /* - * buflen can be zero EITHER because there's not enough - * room in the string, or because the control command - * goes to the empty string. Make a reasonable guess that - * if the buffer is 1024 times bigger than the length of the - * format string, it's not failing for lack of room. - */ - if (len > 0) break; - xfree(*buf); - if (size >= 1024 * flen) { - rb_sys_fail(format); - break; - } - } - return len; -} - -static VALUE -tmx_m_secs(union DateData *x) -{ - VALUE s; - int df; - - s = day_to_sec(f_sub(m_real_jd(x), - UNIX_EPOCH_IN_CJD)); - if (simple_dat_p(x)) - return s; - df = m_df(x); - if (df) - s = f_add(s, INT2FIX(df)); - return s; -} - -#define MILLISECOND_IN_NANOSECONDS 1000000 - -static VALUE -tmx_m_msecs(union DateData *x) -{ - VALUE s, sf; - - s = sec_to_ms(tmx_m_secs(x)); - if (simple_dat_p(x)) - return s; - sf = m_sf(x); - if (f_nonzero_p(sf)) - s = f_add(s, f_div(sf, INT2FIX(MILLISECOND_IN_NANOSECONDS))); - return s; -} - -static int -tmx_m_of(union DateData *x) -{ - return m_of(x); -} - -static char * -tmx_m_zone(union DateData *x) -{ - VALUE zone = m_zone(x); - /* TODO: fix potential dangling pointer */ - return RSTRING_PTR(zone); -} - -static const struct tmx_funcs tmx_funcs = { - (VALUE (*)(void *))m_real_year, - (int (*)(void *))m_yday, - (int (*)(void *))m_mon, - (int (*)(void *))m_mday, - (VALUE (*)(void *))m_real_cwyear, - (int (*)(void *))m_cweek, - (int (*)(void *))m_cwday, - (int (*)(void *))m_wnum0, - (int (*)(void *))m_wnum1, - (int (*)(void *))m_wday, - (int (*)(void *))m_hour, - (int (*)(void *))m_min, - (int (*)(void *))m_sec, - (VALUE (*)(void *))m_sf_in_sec, - (VALUE (*)(void *))tmx_m_secs, - (VALUE (*)(void *))tmx_m_msecs, - (int (*)(void *))tmx_m_of, - (char *(*)(void *))tmx_m_zone -}; - -static void -set_tmx(VALUE self, struct tmx *tmx) -{ - get_d1(self); - tmx->dat = (void *)dat; - tmx->funcs = &tmx_funcs; -} - -static VALUE -date_strftime_internal(int argc, VALUE *argv, VALUE self, - const char *default_fmt, - void (*func)(VALUE, struct tmx *)) -{ - VALUE vfmt; - const char *fmt; - long len; - char buffer[SMALLBUF], *buf = buffer; - struct tmx tmx; - VALUE str; - - rb_scan_args(argc, argv, "01", &vfmt); - - if (argc < 1) - vfmt = rb_usascii_str_new2(default_fmt); - else { - StringValue(vfmt); - if (!rb_enc_str_asciicompat_p(vfmt)) { - rb_raise(rb_eArgError, - "format should have ASCII compatible encoding"); - } - } - fmt = RSTRING_PTR(vfmt); - len = RSTRING_LEN(vfmt); - (*func)(self, &tmx); - if (memchr(fmt, '\0', len)) { - /* Ruby string may contain \0's. */ - const char *p = fmt, *pe = fmt + len; - - str = rb_str_new(0, 0); - while (p < pe) { - len = date_strftime_alloc(&buf, p, &tmx); - rb_str_cat(str, buf, len); - p += strlen(p); - if (buf != buffer) { - xfree(buf); - buf = buffer; - } - for (fmt = p; p < pe && !*p; ++p); - if (p > fmt) rb_str_cat(str, fmt, p - fmt); - } - rb_enc_copy(str, vfmt); - return str; - } - else - len = date_strftime_alloc(&buf, fmt, &tmx); - - str = rb_str_new(buf, len); - if (buf != buffer) xfree(buf); - rb_enc_copy(str, vfmt); - return str; -} - -/* - * call-seq: - * strftime(format = '%F') -> string - * - * Returns a string representation of the date in +self+, - * formatted according the given +format+: - * - * Date.new(2001, 2, 3).strftime # => "2001-02-03" - * - * For other formats, see - * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. - * - */ -static VALUE -d_lite_strftime(int argc, VALUE *argv, VALUE self) -{ - return date_strftime_internal(argc, argv, self, - "%Y-%m-%d", set_tmx); -} - -static VALUE -strftimev(const char *fmt, VALUE self, - void (*func)(VALUE, struct tmx *)) -{ - char buffer[SMALLBUF], *buf = buffer; - struct tmx tmx; - long len; - VALUE str; - - (*func)(self, &tmx); - len = date_strftime_alloc(&buf, fmt, &tmx); - RB_GC_GUARD(self); - str = rb_usascii_str_new(buf, len); - if (buf != buffer) xfree(buf); - return str; -} - -/* - * call-seq: - * asctime -> string - * - * Equivalent to #strftime with argument '%a %b %e %T %Y' - * (or its {shorthand form}[rdoc-ref:language/strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] - * '%c'): - * - * Date.new(2001, 2, 3).asctime # => "Sat Feb 3 00:00:00 2001" - * - * See {asctime}[https://man7.org/linux/man-pages/man3/asctime.3p.html]. - * - */ -static VALUE -d_lite_asctime(VALUE self) -{ - return strftimev("%a %b %e %H:%M:%S %Y", self, set_tmx); -} - -/* - * call-seq: - * iso8601 -> string - * - * Equivalent to #strftime with argument '%Y-%m-%d' - * (or its {shorthand form}[rdoc-ref:language/strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] - * '%F'); - * - * Date.new(2001, 2, 3).iso8601 # => "2001-02-03" - * - */ -static VALUE -d_lite_iso8601(VALUE self) -{ - return strftimev("%Y-%m-%d", self, set_tmx); -} - -/* - * call-seq: - * rfc3339 -> string - * - * Equivalent to #strftime with argument '%FT%T%:z'; - * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: - * - * Date.new(2001, 2, 3).rfc3339 # => "2001-02-03T00:00:00+00:00" - * - */ -static VALUE -d_lite_rfc3339(VALUE self) -{ - return strftimev("%Y-%m-%dT%H:%M:%S%:z", self, set_tmx); -} - -/* - * call-seq: - * rfc2822 -> string - * - * Equivalent to #strftime with argument '%a, %-d %b %Y %T %z'; - * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: - * - * Date.new(2001, 2, 3).rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" - * - */ -static VALUE -d_lite_rfc2822(VALUE self) -{ - return strftimev("%a, %-d %b %Y %T %z", self, set_tmx); -} - -/* - * call-seq: - * httpdate -> string - * - * Equivalent to #strftime with argument '%a, %d %b %Y %T GMT'; - * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: - * - * Date.new(2001, 2, 3).httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" - * - */ -static VALUE -d_lite_httpdate(VALUE self) -{ - volatile VALUE dup = dup_obj_with_new_offset(self, 0); - return strftimev("%a, %d %b %Y %T GMT", dup, set_tmx); -} - -enum { - DECIMAL_SIZE_OF_LONG = DECIMAL_SIZE_OF_BITS(CHAR_BIT*sizeof(long)), - JISX0301_DATE_SIZE = DECIMAL_SIZE_OF_LONG+8 -}; - -static const char * -jisx0301_date_format(char *fmt, size_t size, VALUE jd, VALUE y) -{ - if (FIXNUM_P(jd)) { - long d = FIX2INT(jd); - long s; - char c; - if (d < 2405160) - return "%Y-%m-%d"; - if (d < 2419614) { - c = 'M'; - s = 1867; - } - else if (d < 2424875) { - c = 'T'; - s = 1911; - } - else if (d < 2447535) { - c = 'S'; - s = 1925; - } - else if (d < 2458605) { - c = 'H'; - s = 1988; - } - else { - c = 'R'; - s = 2018; - } - snprintf(fmt, size, "%c%02ld" ".%%m.%%d", c, FIX2INT(y) - s); - return fmt; - } - return "%Y-%m-%d"; -} - -/* - * call-seq: - * jisx0301 -> string - * - * Returns a string representation of the date in +self+ - * in JIS X 0301 format. - * - * Date.new(2001, 2, 3).jisx0301 # => "H13.02.03" - * - */ -static VALUE -d_lite_jisx0301(VALUE self) -{ - char fmtbuf[JISX0301_DATE_SIZE]; - const char *fmt; - - get_d1(self); - fmt = jisx0301_date_format(fmtbuf, sizeof(fmtbuf), - m_real_local_jd(dat), - m_real_year(dat)); - return strftimev(fmt, self, set_tmx); -} - -static VALUE -deconstruct_keys(VALUE self, VALUE keys, int is_datetime) -{ - VALUE h = rb_hash_new(); - long i; - - get_d1(self); - - if (NIL_P(keys)) { - rb_hash_aset(h, sym_year, m_real_year(dat)); - rb_hash_aset(h, sym_month, INT2FIX(m_mon(dat))); - rb_hash_aset(h, sym_day, INT2FIX(m_mday(dat))); - rb_hash_aset(h, sym_yday, INT2FIX(m_yday(dat))); - rb_hash_aset(h, sym_wday, INT2FIX(m_wday(dat))); - if (is_datetime) { - rb_hash_aset(h, sym_hour, INT2FIX(m_hour(dat))); - rb_hash_aset(h, sym_min, INT2FIX(m_min(dat))); - rb_hash_aset(h, sym_sec, INT2FIX(m_sec(dat))); - rb_hash_aset(h, sym_sec_fraction, m_sf_in_sec(dat)); - rb_hash_aset(h, sym_zone, m_zone(dat)); - } - - return h; - } - if (!RB_TYPE_P(keys, T_ARRAY)) { - rb_raise(rb_eTypeError, - "wrong argument type %"PRIsVALUE" (expected Array or nil)", - rb_obj_class(keys)); - - } - - for (i=0; i hash - * - * Returns a hash of the name/value pairs, to use in pattern matching. - * Possible keys are: :year, :month, :day, - * :wday, :yday. - * - * Possible usages: - * - * d = Date.new(2022, 10, 5) - * - * if d in wday: 3, day: ..7 # uses deconstruct_keys underneath - * puts "first Wednesday of the month" - * end - * #=> prints "first Wednesday of the month" - * - * case d - * in year: ...2022 - * puts "too old" - * in month: ..9 - * puts "quarter 1-3" - * in wday: 1..5, month: - * puts "working day in month #{month}" - * end - * #=> prints "working day in month 10" - * - * Note that deconstruction by pattern can also be combined with class check: - * - * if d in Date(wday: 3, day: ..7) - * puts "first Wednesday of the month" - * end - * - */ -static VALUE -d_lite_deconstruct_keys(VALUE self, VALUE keys) -{ - return deconstruct_keys(self, keys, /* is_datetime=false */ 0); -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -d_lite_marshal_dump_old(VALUE self) -{ - VALUE a; - - get_d1(self); - - a = rb_ary_new3(3, - m_ajd(dat), - m_of_in_day(dat), - DBL2NUM(m_sg(dat))); - - rb_copy_generic_ivar(a, self); - - return a; -} -#endif - -/* :nodoc: */ -static VALUE -d_lite_marshal_dump(VALUE self) -{ - VALUE a; - - get_d1(self); - - a = rb_ary_new3(6, - m_nth(dat), - INT2FIX(m_jd(dat)), - INT2FIX(m_df(dat)), - m_sf(dat), - INT2FIX(m_of(dat)), - DBL2NUM(m_sg(dat))); - - - rb_copy_generic_ivar(a, self); - - return a; -} - -/* :nodoc: */ -static VALUE -d_lite_marshal_load(VALUE self, VALUE a) -{ - VALUE nth, sf; - int jd, df, of; - double sg; - - get_d1(self); - - rb_check_frozen(self); - - if (!RB_TYPE_P(a, T_ARRAY)) - rb_raise(rb_eTypeError, "expected an array"); - - switch (RARRAY_LEN(a)) { - case 2: /* 1.6.x */ - case 3: /* 1.8.x, 1.9.2 */ - { - VALUE ajd, vof, vsg; - - if (RARRAY_LEN(a) == 2) { - ajd = f_sub(RARRAY_AREF(a, 0), half_days_in_day); - vof = INT2FIX(0); - vsg = RARRAY_AREF(a, 1); - if (!k_numeric_p(vsg)) - vsg = DBL2NUM(RTEST(vsg) ? GREGORIAN : JULIAN); - } - else { - ajd = RARRAY_AREF(a, 0); - vof = RARRAY_AREF(a, 1); - vsg = RARRAY_AREF(a, 2); - } - - old_to_new(ajd, vof, vsg, - &nth, &jd, &df, &sf, &of, &sg); - } - break; - case 6: - { - nth = RARRAY_AREF(a, 0); - jd = NUM2INT(RARRAY_AREF(a, 1)); - df = NUM2INT(RARRAY_AREF(a, 2)); - sf = RARRAY_AREF(a, 3); - of = NUM2INT(RARRAY_AREF(a, 4)); - sg = NUM2DBL(RARRAY_AREF(a, 5)); - } - break; - default: - rb_raise(rb_eTypeError, "invalid size"); - break; - } - - if (simple_dat_p(dat)) { - if (df || !f_zero_p(sf) || of) { - /* loading a fractional date; promote to complex */ - dat = ruby_xrealloc(dat, sizeof(struct ComplexDateData)); - RTYPEDDATA(self)->data = dat; - goto complex_data; - } - set_to_simple(self, &dat->s, nth, jd, sg, 0, 0, 0, HAVE_JD); - } else { - complex_data: - set_to_complex(self, &dat->c, nth, jd, df, sf, of, sg, - 0, 0, 0, 0, 0, 0, - HAVE_JD | HAVE_DF); - } - - rb_copy_generic_ivar(self, a); - - return self; -} - -/* :nodoc: */ -static VALUE -date_s__load(VALUE klass, VALUE s) -{ - VALUE a, obj; - - a = rb_marshal_load(s); - obj = d_lite_s_alloc(klass); - return d_lite_marshal_load(obj, a); -} - -/* datetime */ - -/* - * call-seq: - * DateTime.jd([jd=0[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]) -> datetime - * - * Creates a DateTime object denoting the given chronological Julian - * day number. - * - * DateTime.jd(2451944) #=> # - * DateTime.jd(2451945) #=> # - * DateTime.jd(Rational('0.5')) - * #=> # - */ -static VALUE -datetime_s_jd(int argc, VALUE *argv, VALUE klass) -{ - VALUE vjd, vh, vmin, vs, vof, vsg, jd, fr, fr2, ret; - int h, min, s, rof; - double sg; - - rb_scan_args(argc, argv, "06", &vjd, &vh, &vmin, &vs, &vof, &vsg); - - jd = INT2FIX(0); - - h = min = s = 0; - fr2 = INT2FIX(0); - rof = 0; - sg = DEFAULT_SG; - - switch (argc) { - case 6: - val2sg(vsg, sg); - case 5: - val2off(vof, rof); - case 4: - check_numeric(vs, "second"); - num2int_with_frac(s, positive_inf); - case 3: - check_numeric(vmin, "minute"); - num2int_with_frac(min, 3); - case 2: - check_numeric(vh, "hour"); - num2int_with_frac(h, 2); - case 1: - check_numeric(vjd, "jd"); - num2num_with_frac(jd, 1); - } - - { - VALUE nth; - int rh, rmin, rs, rjd, rjd2; - - if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(eDateError, "invalid date"); - canon24oc(); - - decode_jd(jd, &nth, &rjd); - rjd2 = jd_local_to_utc(rjd, - time_to_df(rh, rmin, rs), - rof); - - ret = d_complex_new_internal(klass, - nth, rjd2, - 0, INT2FIX(0), - rof, sg, - 0, 0, 0, - rh, rmin, rs, - HAVE_JD | HAVE_TIME); - } - add_frac(); - return ret; -} - -/* - * call-seq: - * DateTime.ordinal([year=-4712[, yday=1[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]]) -> datetime - * - * Creates a DateTime object denoting the given ordinal date. - * - * DateTime.ordinal(2001,34) #=> # - * DateTime.ordinal(2001,34,4,5,6,'+7') - * #=> # - * DateTime.ordinal(2001,-332,-20,-55,-54,'+7') - * #=> # - */ -static VALUE -datetime_s_ordinal(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vd, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; - int d, h, min, s, rof; - double sg; - - rb_scan_args(argc, argv, "07", &vy, &vd, &vh, &vmin, &vs, &vof, &vsg); - - y = INT2FIX(-4712); - d = 1; - - h = min = s = 0; - fr2 = INT2FIX(0); - rof = 0; - sg = DEFAULT_SG; - - switch (argc) { - case 7: - val2sg(vsg, sg); - case 6: - val2off(vof, rof); - case 5: - check_numeric(vs, "second"); - num2int_with_frac(s, positive_inf); - case 4: - check_numeric(vmin, "minute"); - num2int_with_frac(min, 4); - case 3: - check_numeric(vh, "hour"); - num2int_with_frac(h, 3); - case 2: - check_numeric(vd, "yday"); - num2int_with_frac(d, 2); - case 1: - check_numeric(vy, "year"); - y = vy; - } - - { - VALUE nth; - int ry, rd, rh, rmin, rs, rjd, rjd2, ns; - - if (!valid_ordinal_p(y, d, sg, - &nth, &ry, - &rd, &rjd, - &ns)) - rb_raise(eDateError, "invalid date"); - if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(eDateError, "invalid date"); - canon24oc(); - - rjd2 = jd_local_to_utc(rjd, - time_to_df(rh, rmin, rs), - rof); - - ret = d_complex_new_internal(klass, - nth, rjd2, - 0, INT2FIX(0), - rof, sg, - 0, 0, 0, - rh, rmin, rs, - HAVE_JD | HAVE_TIME); - } - add_frac(); - return ret; -} - -/* - * Same as DateTime.new. - */ -static VALUE -datetime_s_civil(int argc, VALUE *argv, VALUE klass) -{ - return datetime_initialize(argc, argv, d_lite_s_alloc_complex(klass)); -} - -static VALUE -datetime_initialize(int argc, VALUE *argv, VALUE self) -{ - VALUE vy, vm, vd, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; - int m, d, h, min, s, rof; - double sg; - struct ComplexDateData *dat = rb_check_typeddata(self, &d_lite_type); - - if (!complex_dat_p(dat)) { - rb_raise(rb_eTypeError, "DateTime expected"); - } - - rb_scan_args(argc, argv, "08", &vy, &vm, &vd, &vh, &vmin, &vs, &vof, &vsg); - - y = INT2FIX(-4712); - m = 1; - d = 1; - - h = min = s = 0; - fr2 = INT2FIX(0); - rof = 0; - sg = DEFAULT_SG; - - switch (argc) { - case 8: - val2sg(vsg, sg); - case 7: - val2off(vof, rof); - case 6: - check_numeric(vs, "second"); - num2int_with_frac(s, positive_inf); - case 5: - check_numeric(vmin, "minute"); - num2int_with_frac(min, 5); - case 4: - check_numeric(vh, "hour"); - num2int_with_frac(h, 4); - case 3: - check_numeric(vd, "day"); - num2int_with_frac(d, 3); - case 2: - check_numeric(vm, "month"); - m = NUM2INT(vm); - case 1: - check_numeric(vy, "year"); - y = vy; - } - - if (guess_style(y, sg) < 0) { - VALUE nth; - int ry, rm, rd, rh, rmin, rs; - - if (!valid_gregorian_p(y, m, d, - &nth, &ry, - &rm, &rd)) - rb_raise(eDateError, "invalid date"); - if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(eDateError, "invalid date"); - canon24oc(); - - set_to_complex(self, dat, - nth, 0, - 0, INT2FIX(0), - rof, sg, - ry, rm, rd, - rh, rmin, rs, - HAVE_CIVIL | HAVE_TIME); - } - else { - VALUE nth; - int ry, rm, rd, rh, rmin, rs, rjd, rjd2, ns; - - if (!valid_civil_p(y, m, d, sg, - &nth, &ry, - &rm, &rd, &rjd, - &ns)) - rb_raise(eDateError, "invalid date"); - if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(eDateError, "invalid date"); - canon24oc(); - - rjd2 = jd_local_to_utc(rjd, - time_to_df(rh, rmin, rs), - rof); - - set_to_complex(self, dat, - nth, rjd2, - 0, INT2FIX(0), - rof, sg, - ry, rm, rd, - rh, rmin, rs, - HAVE_JD | HAVE_CIVIL | HAVE_TIME); - } - ret = self; - add_frac(); - return ret; -} - -/* - * call-seq: - * DateTime.commercial([cwyear=-4712[, cweek=1[, cwday=1[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]]]) -> datetime - * - * Creates a DateTime object denoting the given week date. - * - * DateTime.commercial(2001) #=> # - * DateTime.commercial(2002) #=> # - * DateTime.commercial(2001,5,6,4,5,6,'+7') - * #=> # - */ -static VALUE -datetime_s_commercial(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vw, vd, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; - int w, d, h, min, s, rof; - double sg; - - rb_scan_args(argc, argv, "08", &vy, &vw, &vd, &vh, &vmin, &vs, &vof, &vsg); - - y = INT2FIX(-4712); - w = 1; - d = 1; - - h = min = s = 0; - fr2 = INT2FIX(0); - rof = 0; - sg = DEFAULT_SG; - - switch (argc) { - case 8: - val2sg(vsg, sg); - case 7: - val2off(vof, rof); - case 6: - check_numeric(vs, "second"); - num2int_with_frac(s, positive_inf); - case 5: - check_numeric(vmin, "minute"); - num2int_with_frac(min, 5); - case 4: - check_numeric(vh, "hour"); - num2int_with_frac(h, 4); - case 3: - check_numeric(vd, "cwday"); - num2int_with_frac(d, 3); - case 2: - check_numeric(vw, "cweek"); - w = NUM2INT(vw); - case 1: - check_numeric(vy, "year"); - y = vy; - } - - { - VALUE nth; - int ry, rw, rd, rh, rmin, rs, rjd, rjd2, ns; - - if (!valid_commercial_p(y, w, d, sg, - &nth, &ry, - &rw, &rd, &rjd, - &ns)) - rb_raise(eDateError, "invalid date"); - if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(eDateError, "invalid date"); - canon24oc(); - - rjd2 = jd_local_to_utc(rjd, - time_to_df(rh, rmin, rs), - rof); - - ret = d_complex_new_internal(klass, - nth, rjd2, - 0, INT2FIX(0), - rof, sg, - 0, 0, 0, - rh, rmin, rs, - HAVE_JD | HAVE_TIME); - } - add_frac(); - return ret; -} - -#ifndef NDEBUG -/* :nodoc: */ -static VALUE -datetime_s_weeknum(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vw, vd, vf, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; - int w, d, f, h, min, s, rof; - double sg; - - rb_scan_args(argc, argv, "09", &vy, &vw, &vd, &vf, - &vh, &vmin, &vs, &vof, &vsg); - - y = INT2FIX(-4712); - w = 0; - d = 1; - f = 0; - - h = min = s = 0; - fr2 = INT2FIX(0); - rof = 0; - sg = DEFAULT_SG; - - switch (argc) { - case 9: - val2sg(vsg, sg); - case 8: - val2off(vof, rof); - case 7: - num2int_with_frac(s, positive_inf); - case 6: - num2int_with_frac(min, 6); - case 5: - num2int_with_frac(h, 5); - case 4: - f = NUM2INT(vf); - case 3: - num2int_with_frac(d, 4); - case 2: - w = NUM2INT(vw); - case 1: - y = vy; - } - - { - VALUE nth; - int ry, rw, rd, rh, rmin, rs, rjd, rjd2, ns; - - if (!valid_weeknum_p(y, w, d, f, sg, - &nth, &ry, - &rw, &rd, &rjd, - &ns)) - rb_raise(eDateError, "invalid date"); - if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(eDateError, "invalid date"); - canon24oc(); - - rjd2 = jd_local_to_utc(rjd, - time_to_df(rh, rmin, rs), - rof); - ret = d_complex_new_internal(klass, - nth, rjd2, - 0, INT2FIX(0), - rof, sg, - 0, 0, 0, - rh, rmin, rs, - HAVE_JD | HAVE_TIME); - } - add_frac(); - return ret; -} - -/* :nodoc: */ -static VALUE -datetime_s_nth_kday(int argc, VALUE *argv, VALUE klass) -{ - VALUE vy, vm, vn, vk, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; - int m, n, k, h, min, s, rof; - double sg; - - rb_scan_args(argc, argv, "09", &vy, &vm, &vn, &vk, - &vh, &vmin, &vs, &vof, &vsg); - - y = INT2FIX(-4712); - m = 1; - n = 1; - k = 1; - - h = min = s = 0; - fr2 = INT2FIX(0); - rof = 0; - sg = DEFAULT_SG; - - switch (argc) { - case 9: - val2sg(vsg, sg); - case 8: - val2off(vof, rof); - case 7: - num2int_with_frac(s, positive_inf); - case 6: - num2int_with_frac(min, 6); - case 5: - num2int_with_frac(h, 5); - case 4: - num2int_with_frac(k, 4); - case 3: - n = NUM2INT(vn); - case 2: - m = NUM2INT(vm); - case 1: - y = vy; - } - - { - VALUE nth; - int ry, rm, rn, rk, rh, rmin, rs, rjd, rjd2, ns; - - if (!valid_nth_kday_p(y, m, n, k, sg, - &nth, &ry, - &rm, &rn, &rk, &rjd, - &ns)) - rb_raise(eDateError, "invalid date"); - if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(eDateError, "invalid date"); - canon24oc(); - - rjd2 = jd_local_to_utc(rjd, - time_to_df(rh, rmin, rs), - rof); - ret = d_complex_new_internal(klass, - nth, rjd2, - 0, INT2FIX(0), - rof, sg, - 0, 0, 0, - rh, rmin, rs, - HAVE_JD | HAVE_TIME); - } - add_frac(); - return ret; -} -#endif - -/* - * call-seq: - * DateTime.now([start=Date::ITALY]) -> datetime - * - * Creates a DateTime object denoting the present time. - * - * DateTime.now #=> # - */ -static VALUE -datetime_s_now(int argc, VALUE *argv, VALUE klass) -{ - VALUE vsg, nth, ret; - double sg; -#ifdef HAVE_CLOCK_GETTIME - struct timespec ts; -#else - struct timeval tv; -#endif - time_t sec; - struct tm tm; - long sf, of; - int y, ry, m, d, h, min, s; - - rb_scan_args(argc, argv, "01", &vsg); - - if (argc < 1) - sg = DEFAULT_SG; - else - sg = NUM2DBL(vsg); - -#ifdef HAVE_CLOCK_GETTIME - if (clock_gettime(CLOCK_REALTIME, &ts) == -1) - rb_sys_fail("clock_gettime"); - sec = ts.tv_sec; -#else - if (gettimeofday(&tv, NULL) == -1) - rb_sys_fail("gettimeofday"); - sec = tv.tv_sec; -#endif - tzset(); - if (!localtime_r(&sec, &tm)) - rb_sys_fail("localtime"); - - y = tm.tm_year + 1900; - m = tm.tm_mon + 1; - d = tm.tm_mday; - h = tm.tm_hour; - min = tm.tm_min; - s = tm.tm_sec; - if (s == 60) - s = 59; -#ifdef HAVE_STRUCT_TM_TM_GMTOFF - of = tm.tm_gmtoff; -#elif defined(HAVE_TIMEZONE) -#if defined(HAVE_ALTZONE) && !defined(_AIX) - of = (long)-((tm.tm_isdst > 0) ? altzone : timezone); -#else - of = (long)-timezone; - if (tm.tm_isdst) { - time_t sec2; - - tm.tm_isdst = 0; - sec2 = mktime(&tm); - of += (long)difftime(sec2, sec); - } -#endif -#elif defined(HAVE_TIMEGM) - { - time_t sec2; - - sec2 = timegm(&tm); - of = (long)difftime(sec2, sec); - } -#else - { - struct tm tm2; - time_t sec2; - - if (!gmtime_r(&sec, &tm2)) - rb_sys_fail("gmtime"); - tm2.tm_isdst = tm.tm_isdst; - sec2 = mktime(&tm2); - of = (long)difftime(sec, sec2); - } -#endif -#ifdef HAVE_CLOCK_GETTIME - sf = ts.tv_nsec; -#else - sf = tv.tv_usec * 1000; -#endif - - if (of < -DAY_IN_SECONDS || of > DAY_IN_SECONDS) { - of = 0; - rb_warning("invalid offset is ignored"); - } - - decode_year(INT2FIX(y), -1, &nth, &ry); - - ret = d_complex_new_internal(klass, - nth, 0, - 0, LONG2NUM(sf), - (int)of, GREGORIAN, - ry, m, d, - h, min, s, - HAVE_CIVIL | HAVE_TIME); - { - get_d1(ret); - set_sg(dat, sg); - } - return ret; -} - -static VALUE -dt_new_by_frags(VALUE klass, VALUE hash, VALUE sg) -{ - VALUE jd, sf, t; - int df, of; - - if (!c_valid_start_p(NUM2DBL(sg))) { - sg = INT2FIX(DEFAULT_SG); - rb_warning("invalid start is ignored"); - } - - if (NIL_P(hash)) - rb_raise(eDateError, "invalid date"); - - if (NIL_P(ref_hash("jd")) && - NIL_P(ref_hash("yday")) && - !NIL_P(ref_hash("year")) && - !NIL_P(ref_hash("mon")) && - !NIL_P(ref_hash("mday"))) { - jd = rt__valid_civil_p(ref_hash("year"), - ref_hash("mon"), - ref_hash("mday"), sg); - - if (NIL_P(ref_hash("hour"))) - set_hash("hour", INT2FIX(0)); - if (NIL_P(ref_hash("min"))) - set_hash("min", INT2FIX(0)); - if (NIL_P(ref_hash("sec"))) - set_hash("sec", INT2FIX(0)); - else if (f_eqeq_p(ref_hash("sec"), INT2FIX(60))) - set_hash("sec", INT2FIX(59)); - } - else { - hash = rt_rewrite_frags(hash); - hash = rt_complete_frags(klass, hash); - jd = rt__valid_date_frags_p(hash, sg); - } - - if (NIL_P(jd)) - rb_raise(eDateError, "invalid date"); - - { - int rh, rmin, rs; - - if (!c_valid_time_p(NUM2INT(ref_hash("hour")), - NUM2INT(ref_hash("min")), - NUM2INT(ref_hash("sec")), - &rh, &rmin, &rs)) - rb_raise(eDateError, "invalid date"); - - df = time_to_df(rh, rmin, rs); - } - - t = ref_hash("sec_fraction"); - if (NIL_P(t)) - sf = INT2FIX(0); - else - sf = sec_to_ns(t); - - t = ref_hash("offset"); - if (NIL_P(t)) - of = 0; - else { - of = NUM2INT(t); - if (of < -DAY_IN_SECONDS || of > DAY_IN_SECONDS) { - of = 0; - rb_warning("invalid offset is ignored"); - } - } - { - VALUE nth; - int rjd, rjd2; - - decode_jd(jd, &nth, &rjd); - rjd2 = jd_local_to_utc(rjd, df, of); - df = df_local_to_utc(df, of); - - return d_complex_new_internal(klass, - nth, rjd2, - df, sf, - of, NUM2DBL(sg), - 0, 0, 0, - 0, 0, 0, - HAVE_JD | HAVE_DF); - } -} - -/* - * call-seq: - * DateTime._strptime(string[, format='%FT%T%z']) -> hash - * - * Parses the given representation of date and time with the given - * template, and returns a hash of parsed elements. _strptime does - * not support specification of flags and width unlike strftime. - * - * See also strptime(3) and #strftime. - */ -static VALUE -datetime_s__strptime(int argc, VALUE *argv, VALUE klass) -{ - return date_s__strptime_internal(argc, argv, klass, "%FT%T%z"); -} - -/* - * call-seq: - * DateTime.strptime([string='-4712-01-01T00:00:00+00:00'[, format='%FT%T%z'[ ,start=Date::ITALY]]]) -> datetime - * - * Parses the given representation of date and time with the given - * template, and creates a DateTime object. strptime does not support - * specification of flags and width unlike strftime. - * - * DateTime.strptime('2001-02-03T04:05:06+07:00', '%Y-%m-%dT%H:%M:%S%z') - * #=> # - * DateTime.strptime('03-02-2001 04:05:06 PM', '%d-%m-%Y %I:%M:%S %p') - * #=> # - * DateTime.strptime('2001-W05-6T04:05:06+07:00', '%G-W%V-%uT%H:%M:%S%z') - * #=> # - * DateTime.strptime('2001 04 6 04 05 06 +7', '%Y %U %w %H %M %S %z') - * #=> # - * DateTime.strptime('2001 05 6 04 05 06 +7', '%Y %W %u %H %M %S %z') - * #=> # - * DateTime.strptime('-1', '%s') - * #=> # - * DateTime.strptime('-1000', '%Q') - * #=> # - * DateTime.strptime('sat3feb014pm+7', '%a%d%b%y%H%p%z') - * #=> # - * - * See also strptime(3) and #strftime. - */ -static VALUE -datetime_s_strptime(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, fmt, sg; - - rb_scan_args(argc, argv, "03", &str, &fmt, &sg); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATETIME); - case 1: - fmt = rb_str_new2("%FT%T%z"); - case 2: - sg = INT2FIX(DEFAULT_SG); - } - - { - VALUE argv2[2], hash; - - argv2[0] = str; - argv2[1] = fmt; - hash = date_s__strptime(2, argv2, klass); - return dt_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * DateTime.parse(string='-4712-01-01T00:00:00+00:00'[, comp=true[, start=Date::ITALY]], limit: 128) -> datetime - * - * Parses the given representation of date and time, and creates a - * DateTime object. - * - * This method *does* *not* function as a validator. If the input - * string does not match valid formats strictly, you may get a cryptic - * result. Should consider to use DateTime.strptime instead of this - * method as possible. - * - * If the optional second argument is true and the detected year is in - * the range "00" to "99", makes it full. - * - * DateTime.parse('2001-02-03T04:05:06+07:00') - * #=> # - * DateTime.parse('20010203T040506+0700') - * #=> # - * DateTime.parse('3rd Feb 2001 04:05:06 PM') - * #=> # - * - * Raise an ArgumentError when the string length is longer than _limit_. - * You can stop this check by passing limit: nil, but note - * that it may take a long time to parse. - */ -static VALUE -datetime_s_parse(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, comp, sg, opt; - - argc = rb_scan_args(argc, argv, "03:", &str, &comp, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATETIME); - case 1: - comp = Qtrue; - case 2: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 2; - VALUE argv2[3], hash; - argv2[0] = str; - argv2[1] = comp; - argv2[2] = opt; - if (!NIL_P(opt)) argc2++; - hash = date_s__parse(argc2, argv2, klass); - return dt_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * DateTime.iso8601(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime - * - * Creates a new DateTime object by parsing from a string according to - * some typical ISO 8601 formats. - * - * DateTime.iso8601('2001-02-03T04:05:06+07:00') - * #=> # - * DateTime.iso8601('20010203T040506+0700') - * #=> # - * DateTime.iso8601('2001-W05-6T04:05:06+07:00') - * #=> # - * - * Raise an ArgumentError when the string length is longer than _limit_. - * You can stop this check by passing limit: nil, but note - * that it may take a long time to parse. - */ -static VALUE -datetime_s_iso8601(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATETIME); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - argv2[1] = opt; - if (!NIL_P(opt)) argc2++; - hash = date_s__iso8601(argc2, argv2, klass); - return dt_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * DateTime.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime - * - * Creates a new DateTime object by parsing from a string according to - * some typical RFC 3339 formats. - * - * DateTime.rfc3339('2001-02-03T04:05:06+07:00') - * #=> # - * - * Raise an ArgumentError when the string length is longer than _limit_. - * You can stop this check by passing limit: nil, but note - * that it may take a long time to parse. - */ -static VALUE -datetime_s_rfc3339(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATETIME); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - argv2[1] = opt; - if (!NIL_P(opt)) argc2++; - hash = date_s__rfc3339(argc2, argv2, klass); - return dt_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * DateTime.xmlschema(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime - * - * Creates a new DateTime object by parsing from a string according to - * some typical XML Schema formats. - * - * DateTime.xmlschema('2001-02-03T04:05:06+07:00') - * #=> # - * - * Raise an ArgumentError when the string length is longer than _limit_. - * You can stop this check by passing limit: nil, but note - * that it may take a long time to parse. - */ -static VALUE -datetime_s_xmlschema(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATETIME); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - argv2[1] = opt; - if (!NIL_P(opt)) argc2++; - hash = date_s__xmlschema(argc2, argv2, klass); - return dt_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * DateTime.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime - * DateTime.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime - * - * Creates a new DateTime object by parsing from a string according to - * some typical RFC 2822 formats. - * - * DateTime.rfc2822('Sat, 3 Feb 2001 04:05:06 +0700') - * #=> # - * - * Raise an ArgumentError when the string length is longer than _limit_. - * You can stop this check by passing limit: nil, but note - * that it may take a long time to parse. - */ -static VALUE -datetime_s_rfc2822(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATETIME_RFC3339); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - argv2[1] = opt; - if (!NIL_P(opt)) argc2++; - hash = date_s__rfc2822(argc2, argv2, klass); - return dt_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * DateTime.httpdate(string='Mon, 01 Jan -4712 00:00:00 GMT'[, start=Date::ITALY]) -> datetime - * - * Creates a new DateTime object by parsing from a string according to - * some RFC 2616 format. - * - * DateTime.httpdate('Sat, 03 Feb 2001 04:05:06 GMT') - * #=> # - * - * Raise an ArgumentError when the string length is longer than _limit_. - * You can stop this check by passing limit: nil, but note - * that it may take a long time to parse. - */ -static VALUE -datetime_s_httpdate(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATETIME_HTTPDATE); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - argv2[1] = opt; - if (!NIL_P(opt)) argc2++; - hash = date_s__httpdate(argc2, argv2, klass); - return dt_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * DateTime.jisx0301(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime - * - * Creates a new DateTime object by parsing from a string according to - * some typical JIS X 0301 formats. - * - * DateTime.jisx0301('H13.02.03T04:05:06+07:00') - * #=> # - * - * For no-era year, legacy format, Heisei is assumed. - * - * DateTime.jisx0301('13.02.03T04:05:06+07:00') - * #=> # - * - * Raise an ArgumentError when the string length is longer than _limit_. - * You can stop this check by passing limit: nil, but note - * that it may take a long time to parse. - */ -static VALUE -datetime_s_jisx0301(int argc, VALUE *argv, VALUE klass) -{ - VALUE str, sg, opt; - - argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); - - switch (argc) { - case 0: - str = rb_str_new2(JULIAN_EPOCH_DATETIME); - case 1: - sg = INT2FIX(DEFAULT_SG); - } - - { - int argc2 = 1; - VALUE argv2[2], hash; - argv2[0] = str; - argv2[1] = opt; - if (!NIL_P(opt)) argc2++; - hash = date_s__jisx0301(argc2, argv2, klass); - return dt_new_by_frags(klass, hash, sg); - } -} - -/* - * call-seq: - * dt.to_s -> string - * - * Returns a string in an ISO 8601 format. (This method doesn't use the - * expanded representations.) - * - * DateTime.new(2001,2,3,4,5,6,'-7').to_s - * #=> "2001-02-03T04:05:06-07:00" - */ -static VALUE -dt_lite_to_s(VALUE self) -{ - return strftimev("%Y-%m-%dT%H:%M:%S%:z", self, set_tmx); -} - -/* - * call-seq: - * strftime(format = '%FT%T%:z') -> string - * - * Returns a string representation of +self+, - * formatted according the given +format: - * - * DateTime.now.strftime # => "2022-07-01T11:03:19-05:00" - * - * For other formats, - * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: - * - */ -static VALUE -dt_lite_strftime(int argc, VALUE *argv, VALUE self) -{ - return date_strftime_internal(argc, argv, self, - "%Y-%m-%dT%H:%M:%S%:z", set_tmx); -} - -static VALUE -iso8601_timediv(VALUE self, long n) -{ - static const char timefmt[] = "T%H:%M:%S"; - static const char zone[] = "%:z"; - char fmt[sizeof(timefmt) + sizeof(zone) + rb_strlen_lit(".%N") + - DECIMAL_SIZE_OF_LONG]; - char *p = fmt; - - memcpy(p, timefmt, sizeof(timefmt)-1); - p += sizeof(timefmt)-1; - if (n > 0) p += snprintf(p, fmt+sizeof(fmt)-p, ".%%%ldN", n); - memcpy(p, zone, sizeof(zone)); - return strftimev(fmt, self, set_tmx); -} - -/* - * call-seq: - * dt.iso8601([n=0]) -> string - * dt.xmlschema([n=0]) -> string - * - * This method is equivalent to strftime('%FT%T%:z'). - * The optional argument +n+ is the number of digits for fractional seconds. - * - * DateTime.parse('2001-02-03T04:05:06.123456789+07:00').iso8601(9) - * #=> "2001-02-03T04:05:06.123456789+07:00" - */ -static VALUE -dt_lite_iso8601(int argc, VALUE *argv, VALUE self) -{ - long n = 0; - - rb_check_arity(argc, 0, 1); - if (argc >= 1) - n = NUM2LONG(argv[0]); - - return rb_str_append(strftimev("%Y-%m-%d", self, set_tmx), - iso8601_timediv(self, n)); -} - -/* - * call-seq: - * dt.rfc3339([n=0]) -> string - * - * This method is equivalent to strftime('%FT%T%:z'). - * The optional argument +n+ is the number of digits for fractional seconds. - * - * DateTime.parse('2001-02-03T04:05:06.123456789+07:00').rfc3339(9) - * #=> "2001-02-03T04:05:06.123456789+07:00" - */ -static VALUE -dt_lite_rfc3339(int argc, VALUE *argv, VALUE self) -{ - return dt_lite_iso8601(argc, argv, self); -} - -/* - * call-seq: - * dt.jisx0301([n=0]) -> string - * - * Returns a string in a JIS X 0301 format. - * The optional argument +n+ is the number of digits for fractional seconds. - * - * DateTime.parse('2001-02-03T04:05:06.123456789+07:00').jisx0301(9) - * #=> "H13.02.03T04:05:06.123456789+07:00" - */ -static VALUE -dt_lite_jisx0301(int argc, VALUE *argv, VALUE self) -{ - long n = 0; - - rb_check_arity(argc, 0, 1); - if (argc >= 1) - n = NUM2LONG(argv[0]); - - return rb_str_append(d_lite_jisx0301(self), - iso8601_timediv(self, n)); -} - -/* - * call-seq: - * deconstruct_keys(array_of_names_or_nil) -> hash - * - * Returns a hash of the name/value pairs, to use in pattern matching. - * Possible keys are: :year, :month, :day, - * :wday, :yday, :hour, :min, - * :sec, :sec_fraction, :zone. - * - * Possible usages: - * - * dt = DateTime.new(2022, 10, 5, 13, 30) - * - * if d in wday: 1..5, hour: 10..18 # uses deconstruct_keys underneath - * puts "Working time" - * end - * #=> prints "Working time" - * - * case dt - * in year: ...2022 - * puts "too old" - * in month: ..9 - * puts "quarter 1-3" - * in wday: 1..5, month: - * puts "working day in month #{month}" - * end - * #=> prints "working day in month 10" - * - * Note that deconstruction by pattern can also be combined with class check: - * - * if d in DateTime(wday: 1..5, hour: 10..18, day: ..7) - * puts "Working time, first week of the month" - * end - * - */ -static VALUE -dt_lite_deconstruct_keys(VALUE self, VALUE keys) -{ - return deconstruct_keys(self, keys, /* is_datetime=true */ 1); -} - -/* conversions */ - -#define f_subsec(x) rb_funcall(x, rb_intern("subsec"), 0) -#define f_utc_offset(x) rb_funcall(x, rb_intern("utc_offset"), 0) -#define f_local3(x,y,m,d) rb_funcall(x, rb_intern("local"), 3, y, m, d) - -/* - * call-seq: - * t.to_time -> time - * - * Returns self. - */ -static VALUE -time_to_time(VALUE self) -{ - return self; -} - -/* - * call-seq: - * t.to_date -> date - * - * Returns a Date object which denotes self. - */ -static VALUE -time_to_date(VALUE self) -{ - VALUE y, nth, ret; - int ry, m, d; - - y = f_year(self); - m = FIX2INT(f_mon(self)); - d = FIX2INT(f_mday(self)); - - decode_year(y, -1, &nth, &ry); - - ret = d_simple_new_internal(cDate, - nth, 0, - GREGORIAN, - ry, m, d, - HAVE_CIVIL); - { - get_d1(ret); - set_sg(dat, DEFAULT_SG); - } - return ret; -} - -/* - * call-seq: - * t.to_datetime -> datetime - * - * Returns a DateTime object which denotes self. - */ -static VALUE -time_to_datetime(VALUE self) -{ - VALUE y, sf, nth, ret; - int ry, m, d, h, min, s, of; - - y = f_year(self); - m = FIX2INT(f_mon(self)); - d = FIX2INT(f_mday(self)); - - h = FIX2INT(f_hour(self)); - min = FIX2INT(f_min(self)); - s = FIX2INT(f_sec(self)); - if (s == 60) - s = 59; - - sf = sec_to_ns(f_subsec(self)); - of = FIX2INT(f_utc_offset(self)); - - decode_year(y, -1, &nth, &ry); - - ret = d_complex_new_internal(cDateTime, - nth, 0, - 0, sf, - of, GREGORIAN, - ry, m, d, - h, min, s, - HAVE_CIVIL | HAVE_TIME); - { - get_d1(ret); - set_sg(dat, DEFAULT_SG); - } - return ret; -} - -/* - * call-seq: - * to_time -> time - * - * Returns a new Time object with the same value as +self+; - * if +self+ is a Julian date, derives its Gregorian date - * for conversion to the \Time object: - * - * Date.new(2001, 2, 3).to_time # => 2001-02-03 00:00:00 -0600 - * Date.new(2001, 2, 3, Date::JULIAN).to_time # => 2001-02-16 00:00:00 -0600 - * - */ -static VALUE -date_to_time(VALUE self) -{ - VALUE t; - - get_d1a(self); - - if (m_julian_p(adat)) { - VALUE g = d_lite_gregorian(self); - get_d1b(g); - adat = bdat; - self = g; - } - - t = f_local3(rb_cTime, - m_real_year(adat), - INT2FIX(m_mon(adat)), - INT2FIX(m_mday(adat))); - RB_GC_GUARD(self); /* may be the converted gregorian */ - return t; -} - -/* - * call-seq: - * to_date -> self - * - * Returns +self+. - */ -static VALUE -date_to_date(VALUE self) -{ - return self; -} - -/* - * call-seq: - * d.to_datetime -> datetime - * - * Returns a DateTime whose value is the same as +self+: - * - * Date.new(2001, 2, 3).to_datetime # => # - * - */ -static VALUE -date_to_datetime(VALUE self) -{ - get_d1a(self); - - if (simple_dat_p(adat)) { - VALUE new = d_lite_s_alloc_simple(cDateTime); - { - get_d1b(new); - bdat->s = adat->s; - return new; - } - } - else { - VALUE new = d_lite_s_alloc_complex(cDateTime); - { - get_d1b(new); - bdat->c = adat->c; - bdat->c.df = 0; - RB_OBJ_WRITE(new, &bdat->c.sf, INT2FIX(0)); -#ifndef USE_PACK - bdat->c.hour = 0; - bdat->c.min = 0; - bdat->c.sec = 0; -#else - bdat->c.pc = PACK5(EX_MON(adat->c.pc), EX_MDAY(adat->c.pc), - 0, 0, 0); - bdat->c.flags |= HAVE_DF | HAVE_TIME; -#endif - return new; - } - } -} - -/* - * call-seq: - * dt.to_time -> time - * - * Returns a Time object which denotes self. - */ -static VALUE -datetime_to_time(VALUE self) -{ - get_d1(self); - - if (m_julian_p(dat)) { - VALUE g = d_lite_gregorian(self); - get_d1a(g); - dat = adat; - self = g; - } - - { - VALUE t; - - t = rb_funcall(rb_cTime, - rb_intern("new"), - 7, - m_real_year(dat), - INT2FIX(m_mon(dat)), - INT2FIX(m_mday(dat)), - INT2FIX(m_hour(dat)), - INT2FIX(m_min(dat)), - f_add(INT2FIX(m_sec(dat)), - m_sf_in_sec(dat)), - INT2FIX(m_of(dat))); - RB_GC_GUARD(self); /* may be the converted gregorian */ - return t; - } -} - -/* - * call-seq: - * dt.to_date -> date - * - * Returns a Date object which denotes self. - */ -static VALUE -datetime_to_date(VALUE self) -{ - get_d1a(self); - - if (simple_dat_p(adat)) { - VALUE new = d_lite_s_alloc_simple(cDate); - { - get_d1b(new); - bdat->s = adat->s; - bdat->s.jd = m_local_jd(adat); - return new; - } - } - else { - VALUE new = d_lite_s_alloc_simple(cDate); - { - get_d1b(new); - copy_complex_to_simple(new, &bdat->s, &adat->c); - bdat->s.jd = m_local_jd(adat); - bdat->s.flags &= ~(HAVE_DF | HAVE_TIME | COMPLEX_DAT); - return new; - } - } -} - -/* - * call-seq: - * dt.to_datetime -> self - * - * Returns self. - */ -static VALUE -datetime_to_datetime(VALUE self) -{ - return self; -} - -#ifndef NDEBUG -/* tests */ - -#define MIN_YEAR -4713 -#define MAX_YEAR 1000000 -#define MIN_JD -327 -#define MAX_JD 366963925 - -/* :nodoc: */ -static int -test_civil(int from, int to, double sg) -{ - int j; - - fprintf(stderr, "test_civil: %d...%d (%d) - %.0f\n", - from, to, to - from, sg); - for (j = from; j <= to; j++) { - int y, m, d, rj, ns; - - c_jd_to_civil(j, sg, &y, &m, &d); - c_civil_to_jd(y, m, d, sg, &rj, &ns); - if (j != rj) { - fprintf(stderr, "%d != %d\n", j, rj); - return 0; - } - } - return 1; -} - -/* :nodoc: */ -static VALUE -date_s_test_civil(VALUE klass) -{ - if (!test_civil(MIN_JD, MIN_JD + 366, GREGORIAN)) - return Qfalse; - if (!test_civil(2305814, 2598007, GREGORIAN)) - return Qfalse; - if (!test_civil(MAX_JD - 366, MAX_JD, GREGORIAN)) - return Qfalse; - - if (!test_civil(MIN_JD, MIN_JD + 366, ITALY)) - return Qfalse; - if (!test_civil(2305814, 2598007, ITALY)) - return Qfalse; - if (!test_civil(MAX_JD - 366, MAX_JD, ITALY)) - return Qfalse; - - return Qtrue; -} - -/* :nodoc: */ -static int -test_ordinal(int from, int to, double sg) -{ - int j; - - fprintf(stderr, "test_ordinal: %d...%d (%d) - %.0f\n", - from, to, to - from, sg); - for (j = from; j <= to; j++) { - int y, d, rj, ns; - - c_jd_to_ordinal(j, sg, &y, &d); - c_ordinal_to_jd(y, d, sg, &rj, &ns); - if (j != rj) { - fprintf(stderr, "%d != %d\n", j, rj); - return 0; - } - } - return 1; -} - -/* :nodoc: */ -static VALUE -date_s_test_ordinal(VALUE klass) -{ - if (!test_ordinal(MIN_JD, MIN_JD + 366, GREGORIAN)) - return Qfalse; - if (!test_ordinal(2305814, 2598007, GREGORIAN)) - return Qfalse; - if (!test_ordinal(MAX_JD - 366, MAX_JD, GREGORIAN)) - return Qfalse; - - if (!test_ordinal(MIN_JD, MIN_JD + 366, ITALY)) - return Qfalse; - if (!test_ordinal(2305814, 2598007, ITALY)) - return Qfalse; - if (!test_ordinal(MAX_JD - 366, MAX_JD, ITALY)) - return Qfalse; - - return Qtrue; -} - -/* :nodoc: */ -static int -test_commercial(int from, int to, double sg) -{ - int j; - - fprintf(stderr, "test_commercial: %d...%d (%d) - %.0f\n", - from, to, to - from, sg); - for (j = from; j <= to; j++) { - int y, w, d, rj, ns; - - c_jd_to_commercial(j, sg, &y, &w, &d); - c_commercial_to_jd(y, w, d, sg, &rj, &ns); - if (j != rj) { - fprintf(stderr, "%d != %d\n", j, rj); - return 0; - } - } - return 1; -} - -/* :nodoc: */ -static VALUE -date_s_test_commercial(VALUE klass) -{ - if (!test_commercial(MIN_JD, MIN_JD + 366, GREGORIAN)) - return Qfalse; - if (!test_commercial(2305814, 2598007, GREGORIAN)) - return Qfalse; - if (!test_commercial(MAX_JD - 366, MAX_JD, GREGORIAN)) - return Qfalse; - - if (!test_commercial(MIN_JD, MIN_JD + 366, ITALY)) - return Qfalse; - if (!test_commercial(2305814, 2598007, ITALY)) - return Qfalse; - if (!test_commercial(MAX_JD - 366, MAX_JD, ITALY)) - return Qfalse; - - return Qtrue; -} - -/* :nodoc: */ -static int -test_weeknum(int from, int to, int f, double sg) -{ - int j; - - fprintf(stderr, "test_weeknum: %d...%d (%d) - %.0f\n", - from, to, to - from, sg); - for (j = from; j <= to; j++) { - int y, w, d, rj, ns; - - c_jd_to_weeknum(j, f, sg, &y, &w, &d); - c_weeknum_to_jd(y, w, d, f, sg, &rj, &ns); - if (j != rj) { - fprintf(stderr, "%d != %d\n", j, rj); - return 0; - } - } - return 1; -} - -/* :nodoc: */ -static VALUE -date_s_test_weeknum(VALUE klass) -{ - int f; - - for (f = 0; f <= 1; f++) { - if (!test_weeknum(MIN_JD, MIN_JD + 366, f, GREGORIAN)) - return Qfalse; - if (!test_weeknum(2305814, 2598007, f, GREGORIAN)) - return Qfalse; - if (!test_weeknum(MAX_JD - 366, MAX_JD, f, GREGORIAN)) - return Qfalse; - - if (!test_weeknum(MIN_JD, MIN_JD + 366, f, ITALY)) - return Qfalse; - if (!test_weeknum(2305814, 2598007, f, ITALY)) - return Qfalse; - if (!test_weeknum(MAX_JD - 366, MAX_JD, f, ITALY)) - return Qfalse; - } - - return Qtrue; -} - -/* :nodoc: */ -static int -test_nth_kday(int from, int to, double sg) -{ - int j; - - fprintf(stderr, "test_nth_kday: %d...%d (%d) - %.0f\n", - from, to, to - from, sg); - for (j = from; j <= to; j++) { - int y, m, n, k, rj, ns; - - c_jd_to_nth_kday(j, sg, &y, &m, &n, &k); - c_nth_kday_to_jd(y, m, n, k, sg, &rj, &ns); - if (j != rj) { - fprintf(stderr, "%d != %d\n", j, rj); - return 0; - } - } - return 1; -} - -/* :nodoc: */ -static VALUE -date_s_test_nth_kday(VALUE klass) -{ - if (!test_nth_kday(MIN_JD, MIN_JD + 366, GREGORIAN)) - return Qfalse; - if (!test_nth_kday(2305814, 2598007, GREGORIAN)) - return Qfalse; - if (!test_nth_kday(MAX_JD - 366, MAX_JD, GREGORIAN)) - return Qfalse; - - if (!test_nth_kday(MIN_JD, MIN_JD + 366, ITALY)) - return Qfalse; - if (!test_nth_kday(2305814, 2598007, ITALY)) - return Qfalse; - if (!test_nth_kday(MAX_JD - 366, MAX_JD, ITALY)) - return Qfalse; - - return Qtrue; -} - -/* :nodoc: */ -static int -test_unit_v2v(VALUE i, - VALUE (* conv1)(VALUE), - VALUE (* conv2)(VALUE)) -{ - VALUE c, o; - c = (*conv1)(i); - o = (*conv2)(c); - return f_eqeq_p(o, i); -} - -/* :nodoc: */ -static int -test_unit_v2v_iter2(VALUE (* conv1)(VALUE), - VALUE (* conv2)(VALUE)) -{ - if (!test_unit_v2v(INT2FIX(0), conv1, conv2)) - return 0; - if (!test_unit_v2v(INT2FIX(1), conv1, conv2)) - return 0; - if (!test_unit_v2v(INT2FIX(2), conv1, conv2)) - return 0; - if (!test_unit_v2v(INT2FIX(3), conv1, conv2)) - return 0; - if (!test_unit_v2v(INT2FIX(11), conv1, conv2)) - return 0; - if (!test_unit_v2v(INT2FIX(65535), conv1, conv2)) - return 0; - if (!test_unit_v2v(INT2FIX(1073741823), conv1, conv2)) - return 0; - if (!test_unit_v2v(INT2NUM(1073741824), conv1, conv2)) - return 0; - if (!test_unit_v2v(rb_rational_new2(INT2FIX(0), INT2FIX(1)), conv1, conv2)) - return 0; - if (!test_unit_v2v(rb_rational_new2(INT2FIX(1), INT2FIX(1)), conv1, conv2)) - return 0; - if (!test_unit_v2v(rb_rational_new2(INT2FIX(1), INT2FIX(2)), conv1, conv2)) - return 0; - if (!test_unit_v2v(rb_rational_new2(INT2FIX(2), INT2FIX(3)), conv1, conv2)) - return 0; - return 1; -} - -/* :nodoc: */ -static int -test_unit_v2v_iter(VALUE (* conv1)(VALUE), - VALUE (* conv2)(VALUE)) -{ - if (!test_unit_v2v_iter2(conv1, conv2)) - return 0; - if (!test_unit_v2v_iter2(conv2, conv1)) - return 0; - return 1; -} - -/* :nodoc: */ -static VALUE -date_s_test_unit_conv(VALUE klass) -{ - if (!test_unit_v2v_iter(sec_to_day, day_to_sec)) - return Qfalse; - if (!test_unit_v2v_iter(ms_to_sec, sec_to_ms)) - return Qfalse; - if (!test_unit_v2v_iter(ns_to_day, day_to_ns)) - return Qfalse; - if (!test_unit_v2v_iter(ns_to_sec, sec_to_ns)) - return Qfalse; - return Qtrue; -} - -/* :nodoc: */ -static VALUE -date_s_test_all(VALUE klass) -{ - if (date_s_test_civil(klass) == Qfalse) - return Qfalse; - if (date_s_test_ordinal(klass) == Qfalse) - return Qfalse; - if (date_s_test_commercial(klass) == Qfalse) - return Qfalse; - if (date_s_test_weeknum(klass) == Qfalse) - return Qfalse; - if (date_s_test_nth_kday(klass) == Qfalse) - return Qfalse; - if (date_s_test_unit_conv(klass) == Qfalse) - return Qfalse; - return Qtrue; -} -#endif - -static const char *monthnames[] = { - NULL, - "January", "February", "March", - "April", "May", "June", - "July", "August", "September", - "October", "November", "December" -}; - -static const char *abbr_monthnames[] = { - NULL, - "Jan", "Feb", "Mar", "Apr", - "May", "Jun", "Jul", "Aug", - "Sep", "Oct", "Nov", "Dec" -}; - -static const char *daynames[] = { - "Sunday", "Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday" -}; - -static const char *abbr_daynames[] = { - "Sun", "Mon", "Tue", "Wed", - "Thu", "Fri", "Sat" -}; - -static VALUE -mk_ary_of_str(long len, const char *a[]) -{ - VALUE o; - long i; - - o = rb_ary_new2(len); - for (i = 0; i < len; i++) { - VALUE e; - - if (!a[i]) - e = Qnil; - else { - e = rb_usascii_str_new2(a[i]); - rb_obj_freeze(e); - } - rb_ary_push(o, e); - } - rb_ary_freeze(o); - return o; -} - -/* :nodoc: */ -static VALUE -d_lite_zero(VALUE x) -{ - return INT2FIX(0); -} - -void -Init_date_core(void) -{ - #ifdef HAVE_RB_EXT_RACTOR_SAFE - RB_EXT_RACTOR_SAFE(true); - #endif - id_cmp = rb_intern_const("<=>"); - id_le_p = rb_intern_const("<="); - id_ge_p = rb_intern_const(">="); - id_eqeq_p = rb_intern_const("=="); - - sym_year = ID2SYM(rb_intern_const("year")); - sym_month = ID2SYM(rb_intern_const("month")); - sym_yday = ID2SYM(rb_intern_const("yday")); - sym_wday = ID2SYM(rb_intern_const("wday")); - sym_day = ID2SYM(rb_intern_const("day")); - sym_hour = ID2SYM(rb_intern_const("hour")); - sym_min = ID2SYM(rb_intern_const("min")); - sym_sec = ID2SYM(rb_intern_const("sec")); - sym_sec_fraction = ID2SYM(rb_intern_const("sec_fraction")); - sym_zone = ID2SYM(rb_intern_const("zone")); - - half_days_in_day = rb_rational_new2(INT2FIX(1), INT2FIX(2)); - rb_gc_register_mark_object(half_days_in_day); - -#if (LONG_MAX / DAY_IN_SECONDS) > SECOND_IN_NANOSECONDS - day_in_nanoseconds = LONG2NUM((long)DAY_IN_SECONDS * - SECOND_IN_NANOSECONDS); -#elif defined HAVE_LONG_LONG - day_in_nanoseconds = LL2NUM((LONG_LONG)DAY_IN_SECONDS * - SECOND_IN_NANOSECONDS); -#else - day_in_nanoseconds = f_mul(INT2FIX(DAY_IN_SECONDS), - INT2FIX(SECOND_IN_NANOSECONDS)); -#endif - rb_gc_register_mark_object(day_in_nanoseconds); - - positive_inf = +INFINITY; - negative_inf = -INFINITY; - - /* - * \Class \Date provides methods for storing and manipulating - * calendar dates. - * - * Consider using - * {class Time}[rdoc-ref:Time] - * instead of class \Date if: - * - * - You need both dates and times; \Date handles only dates. - * - You need only Gregorian dates (and not Julian dates); - * see {Julian and Gregorian Calendars}[rdoc-ref:language/calendars.rdoc]. - * - * A \Date object, once created, is immutable, and cannot be modified. - * - * == Creating a \Date - * - * You can create a date for the current date, using Date.today: - * - * Date.today # => # - * - * You can create a specific date from various combinations of arguments: - * - * - Date.new takes integer year, month, and day-of-month: - * - * Date.new(1999, 12, 31) # => # - * - * - Date.ordinal takes integer year and day-of-year: - * - * Date.ordinal(1999, 365) # => # - * - * - Date.jd takes integer Julian day: - * - * Date.jd(2451544) # => # - * - * - Date.commercial takes integer commercial data (year, week, day-of-week): - * - * Date.commercial(1999, 52, 5) # => # - * - * - Date.parse takes a string, which it parses heuristically: - * - * Date.parse('1999-12-31') # => # - * Date.parse('31-12-1999') # => # - * Date.parse('1999-365') # => # - * Date.parse('1999-W52-5') # => # - * - * - Date.strptime takes a date string and a format string, - * then parses the date string according to the format string: - * - * Date.strptime('1999-12-31', '%Y-%m-%d') # => # - * Date.strptime('31-12-1999', '%d-%m-%Y') # => # - * Date.strptime('1999-365', '%Y-%j') # => # - * Date.strptime('1999-W52-5', '%G-W%V-%u') # => # - * Date.strptime('1999 52 5', '%Y %U %w') # => # - * Date.strptime('1999 52 5', '%Y %W %u') # => # - * Date.strptime('fri31dec99', '%a%d%b%y') # => # - * - * See also the specialized methods in - * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] - * - * == Argument +limit+ - * - * Certain singleton methods in \Date that parse string arguments - * also take optional keyword argument +limit+, - * which can limit the length of the string argument. - * - * When +limit+ is: - * - * - Non-negative: - * raises ArgumentError if the string length is greater than _limit_. - * - Other numeric or +nil+: ignores +limit+. - * - Other non-numeric: raises TypeError. - * - */ - cDate = rb_define_class("Date", rb_cObject); - - /* Exception for invalid date/time */ - eDateError = rb_define_class_under(cDate, "Error", rb_eArgError); - - rb_include_module(cDate, rb_mComparable); - - /* An array of strings of full month names in English. The first - * element is nil. - */ - rb_define_const(cDate, "MONTHNAMES", mk_ary_of_str(13, monthnames)); - - /* An array of strings of abbreviated month names in English. The - * first element is nil. - */ - rb_define_const(cDate, "ABBR_MONTHNAMES", - mk_ary_of_str(13, abbr_monthnames)); - - /* An array of strings of the full names of days of the week in English. - * The first is "Sunday". - */ - rb_define_const(cDate, "DAYNAMES", mk_ary_of_str(7, daynames)); - - /* An array of strings of abbreviated day names in English. The - * first is "Sun". - */ - rb_define_const(cDate, "ABBR_DAYNAMES", mk_ary_of_str(7, abbr_daynames)); - - /* The Julian day number of the day of calendar reform for Italy - * and some catholic countries. - */ - rb_define_const(cDate, "ITALY", INT2FIX(ITALY)); - - /* The Julian day number of the day of calendar reform for England - * and her colonies. - */ - rb_define_const(cDate, "ENGLAND", INT2FIX(ENGLAND)); - - /* The Julian day number of the day of calendar reform for the - * proleptic Julian calendar. - */ - rb_define_const(cDate, "JULIAN", DBL2NUM(JULIAN)); - - /* The Julian day number of the day of calendar reform for the - * proleptic Gregorian calendar. - */ - rb_define_const(cDate, "GREGORIAN", DBL2NUM(GREGORIAN)); - - rb_define_alloc_func(cDate, d_lite_s_alloc_simple); - -#ifndef NDEBUG - rb_define_private_method(CLASS_OF(cDate), "_valid_jd?", - date_s__valid_jd_p, -1); - rb_define_private_method(CLASS_OF(cDate), "_valid_ordinal?", - date_s__valid_ordinal_p, -1); - rb_define_private_method(CLASS_OF(cDate), "_valid_civil?", - date_s__valid_civil_p, -1); - rb_define_private_method(CLASS_OF(cDate), "_valid_date?", - date_s__valid_civil_p, -1); - rb_define_private_method(CLASS_OF(cDate), "_valid_commercial?", - date_s__valid_commercial_p, -1); - rb_define_private_method(CLASS_OF(cDate), "_valid_weeknum?", - date_s__valid_weeknum_p, -1); - rb_define_private_method(CLASS_OF(cDate), "_valid_nth_kday?", - date_s__valid_nth_kday_p, -1); -#endif - - rb_define_singleton_method(cDate, "valid_jd?", date_s_valid_jd_p, -1); - rb_define_singleton_method(cDate, "valid_ordinal?", - date_s_valid_ordinal_p, -1); - rb_define_singleton_method(cDate, "valid_civil?", date_s_valid_civil_p, -1); - rb_define_singleton_method(cDate, "valid_date?", date_s_valid_civil_p, -1); - rb_define_singleton_method(cDate, "valid_commercial?", - date_s_valid_commercial_p, -1); - -#ifndef NDEBUG - rb_define_private_method(CLASS_OF(cDate), "valid_weeknum?", - date_s_valid_weeknum_p, -1); - rb_define_private_method(CLASS_OF(cDate), "valid_nth_kday?", - date_s_valid_nth_kday_p, -1); - rb_define_private_method(CLASS_OF(cDate), "zone_to_diff", - date_s_zone_to_diff, 1); -#endif - - rb_define_singleton_method(cDate, "julian_leap?", date_s_julian_leap_p, 1); - rb_define_singleton_method(cDate, "gregorian_leap?", - date_s_gregorian_leap_p, 1); - rb_define_singleton_method(cDate, "leap?", - date_s_gregorian_leap_p, 1); - -#ifndef NDEBUG - rb_define_singleton_method(cDate, "new!", date_s_new_bang, -1); - rb_define_alias(rb_singleton_class(cDate), "new_l!", "new"); -#endif - - rb_define_singleton_method(cDate, "jd", date_s_jd, -1); - rb_define_singleton_method(cDate, "ordinal", date_s_ordinal, -1); - rb_define_singleton_method(cDate, "civil", date_s_civil, -1); - rb_define_singleton_method(cDate, "commercial", date_s_commercial, -1); - -#ifndef NDEBUG - rb_define_singleton_method(cDate, "weeknum", date_s_weeknum, -1); - rb_define_singleton_method(cDate, "nth_kday", date_s_nth_kday, -1); -#endif - - rb_define_singleton_method(cDate, "today", date_s_today, -1); - rb_define_singleton_method(cDate, "_strptime", date_s__strptime, -1); - rb_define_singleton_method(cDate, "strptime", date_s_strptime, -1); - rb_define_singleton_method(cDate, "_parse", date_s__parse, -1); - rb_define_singleton_method(cDate, "parse", date_s_parse, -1); - rb_define_singleton_method(cDate, "_iso8601", date_s__iso8601, -1); - rb_define_singleton_method(cDate, "iso8601", date_s_iso8601, -1); - rb_define_singleton_method(cDate, "_rfc3339", date_s__rfc3339, -1); - rb_define_singleton_method(cDate, "rfc3339", date_s_rfc3339, -1); - rb_define_singleton_method(cDate, "_xmlschema", date_s__xmlschema, -1); - rb_define_singleton_method(cDate, "xmlschema", date_s_xmlschema, -1); - rb_define_singleton_method(cDate, "_rfc2822", date_s__rfc2822, -1); - rb_define_singleton_method(cDate, "_rfc822", date_s__rfc2822, -1); - rb_define_singleton_method(cDate, "rfc2822", date_s_rfc2822, -1); - rb_define_singleton_method(cDate, "rfc822", date_s_rfc2822, -1); - rb_define_singleton_method(cDate, "_httpdate", date_s__httpdate, -1); - rb_define_singleton_method(cDate, "httpdate", date_s_httpdate, -1); - rb_define_singleton_method(cDate, "_jisx0301", date_s__jisx0301, -1); - rb_define_singleton_method(cDate, "jisx0301", date_s_jisx0301, -1); - - rb_define_method(cDate, "initialize", date_initialize, -1); - rb_define_method(cDate, "initialize_copy", d_lite_initialize_copy, 1); - -#ifndef NDEBUG - rb_define_method(cDate, "fill", d_lite_fill, 0); -#endif - - rb_define_method(cDate, "ajd", d_lite_ajd, 0); - rb_define_method(cDate, "amjd", d_lite_amjd, 0); - rb_define_method(cDate, "jd", d_lite_jd, 0); - rb_define_method(cDate, "mjd", d_lite_mjd, 0); - rb_define_method(cDate, "ld", d_lite_ld, 0); - - rb_define_method(cDate, "year", d_lite_year, 0); - rb_define_method(cDate, "yday", d_lite_yday, 0); - rb_define_method(cDate, "mon", d_lite_mon, 0); - rb_define_method(cDate, "month", d_lite_mon, 0); - rb_define_method(cDate, "mday", d_lite_mday, 0); - rb_define_method(cDate, "day", d_lite_mday, 0); - rb_define_method(cDate, "day_fraction", d_lite_day_fraction, 0); - - rb_define_method(cDate, "cwyear", d_lite_cwyear, 0); - rb_define_method(cDate, "cweek", d_lite_cweek, 0); - rb_define_method(cDate, "cwday", d_lite_cwday, 0); - -#ifndef NDEBUG - rb_define_private_method(cDate, "wnum0", d_lite_wnum0, 0); - rb_define_private_method(cDate, "wnum1", d_lite_wnum1, 0); -#endif - - rb_define_method(cDate, "wday", d_lite_wday, 0); - - rb_define_method(cDate, "sunday?", d_lite_sunday_p, 0); - rb_define_method(cDate, "monday?", d_lite_monday_p, 0); - rb_define_method(cDate, "tuesday?", d_lite_tuesday_p, 0); - rb_define_method(cDate, "wednesday?", d_lite_wednesday_p, 0); - rb_define_method(cDate, "thursday?", d_lite_thursday_p, 0); - rb_define_method(cDate, "friday?", d_lite_friday_p, 0); - rb_define_method(cDate, "saturday?", d_lite_saturday_p, 0); - -#ifndef NDEBUG - rb_define_method(cDate, "nth_kday?", d_lite_nth_kday_p, 2); -#endif - - rb_define_private_method(cDate, "hour", d_lite_zero, 0); - rb_define_private_method(cDate, "min", d_lite_zero, 0); - rb_define_private_method(cDate, "minute", d_lite_zero, 0); - rb_define_private_method(cDate, "sec", d_lite_zero, 0); - rb_define_private_method(cDate, "second", d_lite_zero, 0); - - rb_define_method(cDate, "julian?", d_lite_julian_p, 0); - rb_define_method(cDate, "gregorian?", d_lite_gregorian_p, 0); - rb_define_method(cDate, "leap?", d_lite_leap_p, 0); - - rb_define_method(cDate, "start", d_lite_start, 0); - rb_define_method(cDate, "new_start", d_lite_new_start, -1); - rb_define_method(cDate, "italy", d_lite_italy, 0); - rb_define_method(cDate, "england", d_lite_england, 0); - rb_define_method(cDate, "julian", d_lite_julian, 0); - rb_define_method(cDate, "gregorian", d_lite_gregorian, 0); - - rb_define_method(cDate, "+", d_lite_plus, 1); - rb_define_method(cDate, "-", d_lite_minus, 1); - - rb_define_method(cDate, "next_day", d_lite_next_day, -1); - rb_define_method(cDate, "prev_day", d_lite_prev_day, -1); - rb_define_method(cDate, "next", d_lite_next, 0); - rb_define_method(cDate, "succ", d_lite_next, 0); - - rb_define_method(cDate, ">>", d_lite_rshift, 1); - rb_define_method(cDate, "<<", d_lite_lshift, 1); - - rb_define_method(cDate, "next_month", d_lite_next_month, -1); - rb_define_method(cDate, "prev_month", d_lite_prev_month, -1); - rb_define_method(cDate, "next_year", d_lite_next_year, -1); - rb_define_method(cDate, "prev_year", d_lite_prev_year, -1); - - rb_define_method(cDate, "step", d_lite_step, -1); - rb_define_method(cDate, "upto", d_lite_upto, 1); - rb_define_method(cDate, "downto", d_lite_downto, 1); - - rb_define_method(cDate, "<=>", d_lite_cmp, 1); - rb_define_method(cDate, "===", d_lite_equal, 1); - rb_define_method(cDate, "eql?", d_lite_eql_p, 1); - rb_define_method(cDate, "hash", d_lite_hash, 0); - - rb_define_method(cDate, "to_s", d_lite_to_s, 0); -#ifndef NDEBUG - rb_define_method(cDate, "inspect_raw", d_lite_inspect_raw, 0); -#endif - rb_define_method(cDate, "inspect", d_lite_inspect, 0); - - rb_define_method(cDate, "strftime", d_lite_strftime, -1); - - rb_define_method(cDate, "asctime", d_lite_asctime, 0); - rb_define_method(cDate, "ctime", d_lite_asctime, 0); - rb_define_method(cDate, "iso8601", d_lite_iso8601, 0); - rb_define_method(cDate, "xmlschema", d_lite_iso8601, 0); - rb_define_method(cDate, "rfc3339", d_lite_rfc3339, 0); - rb_define_method(cDate, "rfc2822", d_lite_rfc2822, 0); - rb_define_method(cDate, "rfc822", d_lite_rfc2822, 0); - rb_define_method(cDate, "httpdate", d_lite_httpdate, 0); - rb_define_method(cDate, "jisx0301", d_lite_jisx0301, 0); - - rb_define_method(cDate, "deconstruct_keys", d_lite_deconstruct_keys, 1); - -#ifndef NDEBUG - rb_define_method(cDate, "marshal_dump_old", d_lite_marshal_dump_old, 0); -#endif - rb_define_method(cDate, "marshal_dump", d_lite_marshal_dump, 0); - rb_define_method(cDate, "marshal_load", d_lite_marshal_load, 1); - rb_define_singleton_method(cDate, "_load", date_s__load, 1); - - /* - * == DateTime - * - * A subclass of Date that easily handles date, hour, minute, second, - * and offset. - * - * DateTime class is considered deprecated. Use Time class. - * - * DateTime does not consider any leap seconds, does not track - * any summer time rules. - * - * A DateTime object is created with DateTime::new, DateTime::jd, - * DateTime::ordinal, DateTime::commercial, DateTime::parse, - * DateTime::strptime, DateTime::now, Time#to_datetime, etc. - * - * require 'date' - * - * DateTime.new(2001,2,3,4,5,6) - * #=> # - * - * The last element of day, hour, minute, or second can be a - * fractional number. The fractional number's precision is assumed - * at most nanosecond. - * - * DateTime.new(2001,2,3.5) - * #=> # - * - * An optional argument, the offset, indicates the difference - * between the local time and UTC. For example, Rational(3,24) - * represents ahead of 3 hours of UTC, Rational(-5,24) represents - * behind of 5 hours of UTC. The offset should be -1 to +1, and - * its precision is assumed at most second. The default value is - * zero (equals to UTC). - * - * DateTime.new(2001,2,3,4,5,6,Rational(3,24)) - * #=> # - * - * The offset also accepts string form: - * - * DateTime.new(2001,2,3,4,5,6,'+03:00') - * #=> # - * - * An optional argument, the day of calendar reform (+start+), denotes - * a Julian day number, which should be 2298874 to 2426355 or - * negative/positive infinity. - * The default value is +Date::ITALY+ (2299161=1582-10-15). - * - * A DateTime object has various methods. See each reference. - * - * d = DateTime.parse('3rd Feb 2001 04:05:06+03:30') - * #=> # - * d.hour #=> 4 - * d.min #=> 5 - * d.sec #=> 6 - * d.offset #=> (7/48) - * d.zone #=> "+03:30" - * d += Rational('1.5') - * #=> # - * d = d.new_offset('+09:00') - * #=> # - * d.strftime('%I:%M:%S %p') - * #=> "09:35:06 PM" - * d > DateTime.new(1999) - * #=> true - * - * === When should you use DateTime and when should you use Time? - * - * It's a common misconception that - * {William Shakespeare}[https://en.wikipedia.org/wiki/William_Shakespeare] - * and - * {Miguel de Cervantes}[https://en.wikipedia.org/wiki/Miguel_de_Cervantes] - * died on the same day in history - - * so much so that UNESCO named April 23 as - * {World Book Day because of this fact}[https://en.wikipedia.org/wiki/World_Book_Day]. - * However, because England hadn't yet adopted the - * {Gregorian Calendar Reform}[https://en.wikipedia.org/wiki/Gregorian_calendar#Gregorian_reform] - * (and wouldn't until {1752}[https://en.wikipedia.org/wiki/Calendar_(New_Style)_Act_1750]) - * their deaths are actually 10 days apart. - * Since Ruby's Time class implements a - * {proleptic Gregorian calendar}[https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar] - * and has no concept of calendar reform there's no way - * to express this with Time objects. This is where DateTime steps in: - * - * shakespeare = DateTime.iso8601('1616-04-23', Date::ENGLAND) - * #=> Tue, 23 Apr 1616 00:00:00 +0000 - * cervantes = DateTime.iso8601('1616-04-23', Date::ITALY) - * #=> Sat, 23 Apr 1616 00:00:00 +0000 - * - * Already you can see something is weird - the days of the week - * are different. Taking this further: - * - * cervantes == shakespeare - * #=> false - * (shakespeare - cervantes).to_i - * #=> 10 - * - * This shows that in fact they died 10 days apart (in reality - * 11 days since Cervantes died a day earlier but was buried on - * the 23rd). We can see the actual date of Shakespeare's death by - * using the #gregorian method to convert it: - * - * shakespeare.gregorian - * #=> Tue, 03 May 1616 00:00:00 +0000 - * - * So there's an argument that all the celebrations that take - * place on the 23rd April in Stratford-upon-Avon are actually - * the wrong date since England is now using the Gregorian calendar. - * You can see why when we transition across the reform - * date boundary: - * - * # start off with the anniversary of Shakespeare's birth in 1751 - * shakespeare = DateTime.iso8601('1751-04-23', Date::ENGLAND) - * #=> Tue, 23 Apr 1751 00:00:00 +0000 - * - * # add 366 days since 1752 is a leap year and April 23 is after February 29 - * shakespeare + 366 - * #=> Thu, 23 Apr 1752 00:00:00 +0000 - * - * # add another 365 days to take us to the anniversary in 1753 - * shakespeare + 366 + 365 - * #=> Fri, 04 May 1753 00:00:00 +0000 - * - * As you can see, if we're accurately tracking the number of - * {solar years}[https://en.wikipedia.org/wiki/Tropical_year] - * since Shakespeare's birthday then the correct anniversary date - * would be the 4th May and not the 23rd April. - * - * So when should you use DateTime in Ruby and when should - * you use Time? Almost certainly you'll want to use Time - * since your app is probably dealing with current dates and - * times. However, if you need to deal with dates and times in a - * historical context you'll want to use DateTime to avoid - * making the same mistakes as UNESCO. If you also have to deal - * with timezones then best of luck - just bear in mind that - * you'll probably be dealing with - * {local solar times}[https://en.wikipedia.org/wiki/Solar_time], - * since it wasn't until the 19th century that the introduction - * of the railways necessitated the need for - * {Standard Time}[https://en.wikipedia.org/wiki/Standard_time#Great_Britain] - * and eventually timezones. - */ - - cDateTime = rb_define_class("DateTime", cDate); - rb_define_alloc_func(cDateTime, d_lite_s_alloc_complex); - - rb_define_singleton_method(cDateTime, "jd", datetime_s_jd, -1); - rb_define_singleton_method(cDateTime, "ordinal", datetime_s_ordinal, -1); - rb_define_singleton_method(cDateTime, "civil", datetime_s_civil, -1); - rb_define_singleton_method(cDateTime, "new", datetime_s_civil, -1); - rb_define_singleton_method(cDateTime, "commercial", - datetime_s_commercial, -1); - -#ifndef NDEBUG - rb_define_singleton_method(cDateTime, "weeknum", - datetime_s_weeknum, -1); - rb_define_singleton_method(cDateTime, "nth_kday", - datetime_s_nth_kday, -1); -#endif - - rb_undef_method(CLASS_OF(cDateTime), "today"); - - rb_define_singleton_method(cDateTime, "now", datetime_s_now, -1); - rb_define_singleton_method(cDateTime, "_strptime", - datetime_s__strptime, -1); - rb_define_singleton_method(cDateTime, "strptime", - datetime_s_strptime, -1); - rb_define_singleton_method(cDateTime, "parse", - datetime_s_parse, -1); - rb_define_singleton_method(cDateTime, "iso8601", - datetime_s_iso8601, -1); - rb_define_singleton_method(cDateTime, "rfc3339", - datetime_s_rfc3339, -1); - rb_define_singleton_method(cDateTime, "xmlschema", - datetime_s_xmlschema, -1); - rb_define_singleton_method(cDateTime, "rfc2822", - datetime_s_rfc2822, -1); - rb_define_singleton_method(cDateTime, "rfc822", - datetime_s_rfc2822, -1); - rb_define_singleton_method(cDateTime, "httpdate", - datetime_s_httpdate, -1); - rb_define_singleton_method(cDateTime, "jisx0301", - datetime_s_jisx0301, -1); - - rb_define_method(cDateTime, "hour", d_lite_hour, 0); - rb_define_method(cDateTime, "min", d_lite_min, 0); - rb_define_method(cDateTime, "minute", d_lite_min, 0); - rb_define_method(cDateTime, "sec", d_lite_sec, 0); - rb_define_method(cDateTime, "second", d_lite_sec, 0); - rb_define_method(cDateTime, "sec_fraction", d_lite_sec_fraction, 0); - rb_define_method(cDateTime, "second_fraction", d_lite_sec_fraction, 0); - rb_define_method(cDateTime, "offset", d_lite_offset, 0); - rb_define_method(cDateTime, "zone", d_lite_zone, 0); - rb_define_method(cDateTime, "new_offset", d_lite_new_offset, -1); - - rb_define_method(cDateTime, "to_s", dt_lite_to_s, 0); - - rb_define_method(cDateTime, "strftime", dt_lite_strftime, -1); - - rb_define_method(cDateTime, "iso8601", dt_lite_iso8601, -1); - rb_define_method(cDateTime, "xmlschema", dt_lite_iso8601, -1); - rb_define_method(cDateTime, "rfc3339", dt_lite_rfc3339, -1); - rb_define_method(cDateTime, "jisx0301", dt_lite_jisx0301, -1); - - rb_define_method(cDateTime, "deconstruct_keys", dt_lite_deconstruct_keys, 1); - - /* conversions */ - - rb_define_method(rb_cTime, "to_time", time_to_time, 0); - rb_define_method(rb_cTime, "to_date", time_to_date, 0); - rb_define_method(rb_cTime, "to_datetime", time_to_datetime, 0); - - rb_define_method(cDate, "to_time", date_to_time, 0); - rb_define_method(cDate, "to_date", date_to_date, 0); - rb_define_method(cDate, "to_datetime", date_to_datetime, 0); - - rb_define_method(cDateTime, "to_time", datetime_to_time, 0); - rb_define_method(cDateTime, "to_date", datetime_to_date, 0); - rb_define_method(cDateTime, "to_datetime", datetime_to_datetime, 0); - -#ifndef NDEBUG - /* tests */ - - rb_define_singleton_method(cDate, "test_civil", date_s_test_civil, 0); - rb_define_singleton_method(cDate, "test_ordinal", date_s_test_ordinal, 0); - rb_define_singleton_method(cDate, "test_commercial", - date_s_test_commercial, 0); - rb_define_singleton_method(cDate, "test_weeknum", date_s_test_weeknum, 0); - rb_define_singleton_method(cDate, "test_nth_kday", date_s_test_nth_kday, 0); - rb_define_singleton_method(cDate, "test_unit_conv", - date_s_test_unit_conv, 0); - rb_define_singleton_method(cDate, "test_all", date_s_test_all, 0); -#endif -} - -/* -Local variables: -c-file-style: "ruby" -End: -*/ diff --git a/ext/date/date_parse.c b/ext/date/date_parse.c deleted file mode 100644 index a1600e47..00000000 --- a/ext/date/date_parse.c +++ /dev/null @@ -1,3086 +0,0 @@ -/* - date_parse.c: Coded by Tadayoshi Funaba 2011,2012 -*/ - -#include "ruby.h" -#include "ruby/encoding.h" -#include "ruby/re.h" -#include - -#undef strncasecmp -#define strncasecmp STRNCASECMP - -RUBY_EXTERN VALUE rb_int_positive_pow(long x, unsigned long y); -RUBY_EXTERN unsigned long ruby_scan_digits(const char *str, ssize_t len, int base, size_t *retlen, int *overflow); - -/* #define TIGHT_PARSER */ - -#define sizeof_array(o) (sizeof o / sizeof o[0]) - -#define f_negate(x) rb_funcall(x, rb_intern("-@"), 0) -#define f_add(x,y) rb_funcall(x, '+', 1, y) -#define f_sub(x,y) rb_funcall(x, '-', 1, y) -#define f_mul(x,y) rb_funcall(x, '*', 1, y) -#define f_div(x,y) rb_funcall(x, '/', 1, y) -#define f_idiv(x,y) rb_funcall(x, rb_intern("div"), 1, y) -#define f_mod(x,y) rb_funcall(x, '%', 1, y) -#define f_expt(x,y) rb_funcall(x, rb_intern("**"), 1, y) - -#define f_lt_p(x,y) rb_funcall(x, '<', 1, y) -#define f_gt_p(x,y) rb_funcall(x, '>', 1, y) -#define f_le_p(x,y) rb_funcall(x, rb_intern("<="), 1, y) -#define f_ge_p(x,y) rb_funcall(x, rb_intern(">="), 1, y) - -#define f_to_s(x) rb_funcall(x, rb_intern("to_s"), 0) - -#define f_match(r,s) rb_funcall(r, rb_intern("match"), 1, s) -#define f_aref(o,i) rb_funcall(o, rb_intern("[]"), 1, i) -#define f_aref2(o,i,j) rb_funcall(o, rb_intern("[]"), 2, i, j) -#define f_begin(o,i) rb_funcall(o, rb_intern("begin"), 1, i) -#define f_end(o,i) rb_funcall(o, rb_intern("end"), 1, i) -#define f_aset(o,i,v) rb_funcall(o, rb_intern("[]="), 2, i, v) -#define f_aset2(o,i,j,v) rb_funcall(o, rb_intern("[]="), 3, i, j, v) -#define f_sub_bang(s,r,x) rb_funcall(s, rb_intern("sub!"), 2, r, x) -#define f_gsub_bang(s,r,x) rb_funcall(s, rb_intern("gsub!"), 2, r, x) - -#define set_hash(k,v) rb_hash_aset(hash, ID2SYM(rb_intern(k"")), v) -#define ref_hash(k) rb_hash_aref(hash, ID2SYM(rb_intern(k""))) -#define del_hash(k) rb_hash_delete(hash, ID2SYM(rb_intern(k""))) - -#define cstr2num(s) rb_cstr_to_inum(s, 10, 0) -#define str2num(s) rb_str_to_inum(s, 10, 0) - -static const char abbr_days[][4] = { - "sun", "mon", "tue", "wed", - "thu", "fri", "sat" -}; - -static const char abbr_months[][4] = { - "jan", "feb", "mar", "apr", "may", "jun", - "jul", "aug", "sep", "oct", "nov", "dec" -}; - -#define issign(c) ((c) == '-' || (c) == '+') -#define asp_string() rb_str_new(" ", 1) -#ifdef TIGHT_PARSER -#define asuba_string() rb_str_new("\001", 1) -#define asubb_string() rb_str_new("\002", 1) -#define asubw_string() rb_str_new("\027", 1) -#define asubt_string() rb_str_new("\024", 1) -#endif - -static size_t -digit_span(const char *s, const char *e) -{ - size_t i = 0; - while (s + i < e && isdigit((unsigned char)s[i])) i++; - return i; -} - -static void -s3e(VALUE hash, VALUE y, VALUE m, VALUE d, int bc) -{ - VALUE vbuf = 0; - VALUE c = Qnil; - - if (!RB_TYPE_P(m, T_STRING)) - m = f_to_s(m); - - if (!NIL_P(y) && !NIL_P(m) && NIL_P(d)) { - VALUE oy = y; - VALUE om = m; - VALUE od = d; - - y = od; - m = oy; - d = om; - } - - if (NIL_P(y)) { - if (!NIL_P(d) && RSTRING_LEN(d) > 2) { - y = d; - d = Qnil; - } - if (!NIL_P(d) && RSTRING_LEN(d) > 0 && *RSTRING_PTR(d) == '\'') { - y = d; - d = Qnil; - } - } - - if (!NIL_P(y)) { - const char *s, *bp, *ep; - size_t l; - - s = RSTRING_PTR(y); - ep = RSTRING_END(y); - while (s < ep && !issign(*s) && !isdigit((unsigned char)*s)) - s++; - if (s >= ep) goto no_date; - bp = s; - if (issign((unsigned char)*s)) - s++; - l = digit_span(s, ep); - ep = s + l; - if (*ep) { - y = d; - d = rb_str_new(bp, ep - bp); - } - no_date:; - } - - if (!NIL_P(m)) { - const char *s; - - s = RSTRING_PTR(m); - if (*s == '\'' || RSTRING_LEN(m) > 2) { - /* us -> be */ - VALUE oy = y; - VALUE om = m; - VALUE od = d; - - y = om; - m = od; - d = oy; - } - } - - if (!NIL_P(d)) { - const char *s; - - s = RSTRING_PTR(d); - if (*s == '\'' || RSTRING_LEN(d) > 2) { - VALUE oy = y; - VALUE od = d; - - y = od; - d = oy; - } - } - - if (!NIL_P(y)) { - const char *s, *bp, *ep; - int sign = 0; - size_t l; - VALUE iy; - - s = RSTRING_PTR(y); - ep = RSTRING_END(y); - while (s < ep && !issign(*s) && !isdigit((unsigned char)*s)) - s++; - if (s >= ep) goto no_year; - bp = s; - if (issign(*s)) { - s++; - sign = 1; - } - if (sign) - c = Qfalse; - l = digit_span(s, ep); - ep = s + l; - if (l > 2) - c = Qfalse; - { - char *buf; - - buf = ALLOCV_N(char, vbuf, ep - bp + 1); - memcpy(buf, bp, ep - bp); - buf[ep - bp] = '\0'; - iy = cstr2num(buf); - ALLOCV_END(vbuf); - } - set_hash("year", iy); - no_year:; - } - - if (bc) - set_hash("_bc", Qtrue); - - if (!NIL_P(m)) { - const char *s, *bp, *ep; - size_t l; - VALUE im; - - s = RSTRING_PTR(m); - ep = RSTRING_END(m); - while (s < ep && !isdigit((unsigned char)*s)) - s++; - if (s >= ep) goto no_month; - bp = s; - l = digit_span(s, ep); - ep = s + l; - { - char *buf; - - buf = ALLOCV_N(char, vbuf, ep - bp + 1); - memcpy(buf, bp, ep - bp); - buf[ep - bp] = '\0'; - im = cstr2num(buf); - ALLOCV_END(vbuf); - } - set_hash("mon", im); - no_month:; - } - - if (!NIL_P(d)) { - const char *s, *bp, *ep; - size_t l; - VALUE id; - - s = RSTRING_PTR(d); - ep = RSTRING_END(d); - while (s < ep && !isdigit((unsigned char)*s)) - s++; - if (s >= ep) goto no_mday; - bp = s; - l = digit_span(s, ep); - ep = s + l; - { - char *buf; - - buf = ALLOCV_N(char, vbuf, ep - bp + 1); - memcpy(buf, bp, ep - bp); - buf[ep - bp] = '\0'; - id = cstr2num(buf); - ALLOCV_END(vbuf); - } - set_hash("mday", id); - no_mday:; - } - - if (!NIL_P(c)) - set_hash("_comp", c); -} - -#define DAYS "sunday|monday|tuesday|wednesday|thursday|friday|saturday" -#define MONTHS "january|february|march|april|may|june|july|august|september|october|november|december" -#define ABBR_DAYS "sun|mon|tue|wed|thu|fri|sat" -#define ABBR_MONTHS "jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec" - -#define NUMBER "(? n && isspace((unsigned char)s[l - n - 1])); - return n; -} - -static long -shrunk_size(const char *s, long l) -{ - long i, ni; - int sp = 0; - for (i = ni = 0; i < l; ++i) { - if (!isspace((unsigned char)s[i])) { - if (sp) ni++; - sp = 0; - ni++; - } - else { - sp = 1; - } - } - return ni < l ? ni : 0; -} - -static long -shrink_space(char *d, const char *s, long l) -{ - long i, ni; - int sp = 0; - for (i = ni = 0; i < l; ++i) { - if (!isspace((unsigned char)s[i])) { - if (sp) d[ni++] = ' '; - sp = 0; - d[ni++] = s[i]; - } - else { - sp = 1; - } - } - return ni; -} - -VALUE -date_zone_to_diff(VALUE str) -{ - VALUE offset = Qnil; - long l = RSTRING_LEN(str); - const char *s = RSTRING_PTR(str); - - { - int dst = 0; - int w; - - if ((w = str_end_with_word(s, l, "time")) > 0) { - int wtime = w; - l -= w; - if ((w = str_end_with_word(s, l, "standard")) > 0) { - l -= w; - } - else if ((w = str_end_with_word(s, l, "daylight")) > 0) { - l -= w; - dst = 1; - } - else { - l += wtime; - } - } - else if ((w = str_end_with_word(s, l, "dst")) > 0) { - l -= w; - dst = 1; - } - - { - const char *zn = s; - long sl = shrunk_size(s, l); - char shrunk_buff[MAX_WORD_LENGTH]; /* no terminator to be added */ - const struct zone *z = 0; - - if (sl <= 0) { - sl = l; - } - else if (sl <= MAX_WORD_LENGTH) { - char *d = shrunk_buff; - sl = shrink_space(d, s, l); - zn = d; - } - - if (sl > 0 && sl <= MAX_WORD_LENGTH) { - z = zonetab(zn, (unsigned int)sl); - } - - if (z) { - int d = z->offset; - if (dst) - d += 3600; - offset = INT2FIX(d); - goto ok; - } - } - - { - char *p; - int sign = 0; - long hour = 0, min = 0, sec = 0; - - if (l > 3 && - (strncasecmp(s, "gmt", 3) == 0 || - strncasecmp(s, "utc", 3) == 0)) { - s += 3; - l -= 3; - } - if (issign(*s)) { - sign = *s == '-'; - s++; - l--; - -#define out_of_range(v, min, max) ((v) < (min) || (max) < (v)) - hour = STRTOUL(s, &p, 10); - if (*p == ':') { - if (out_of_range(hour, 0, 23)) return Qnil; - s = ++p; - min = STRTOUL(s, &p, 10); - if (out_of_range(min, 0, 59)) return Qnil; - if (*p == ':') { - s = ++p; - sec = STRTOUL(s, &p, 10); - if (out_of_range(sec, 0, 59)) return Qnil; - } - } - else if (*p == ',' || *p == '.') { - /* fractional hour */ - size_t n; - int ov; - /* no over precision for offset; 10**-7 hour = 0.36 - * milliseconds should be enough. */ - const size_t max_digits = 7; /* 36 * 10**7 < 32-bit FIXNUM_MAX */ - - if (out_of_range(hour, 0, 23)) return Qnil; - - n = (s + l) - ++p; - if (n > max_digits) n = max_digits; - sec = ruby_scan_digits(p, n, 10, &n, &ov); - if ((p += n) < s + l && *p >= ('5' + !(sec & 1)) && *p <= '9') { - /* round half to even */ - sec++; - } - sec *= 36; - if (sign) { - hour = -hour; - sec = -sec; - } - if (n <= 2) { - /* HH.nn or HH.n */ - if (n == 1) sec *= 10; - offset = INT2FIX(sec + hour * 3600); - } - else { - VALUE denom = rb_int_positive_pow(10, (int)(n - 2)); - offset = f_add(rb_rational_new(INT2FIX(sec), denom), INT2FIX(hour * 3600)); - if (rb_rational_den(offset) == INT2FIX(1)) { - offset = rb_rational_num(offset); - } - } - goto ok; - } - else if (l > 2) { - size_t n; - int ov; - - if (l >= 1) - hour = ruby_scan_digits(&s[0], 2 - l % 2, 10, &n, &ov); - if (l >= 3) - min = ruby_scan_digits(&s[2 - l % 2], 2, 10, &n, &ov); - if (l >= 5) - sec = ruby_scan_digits(&s[4 - l % 2], 2, 10, &n, &ov); - } - sec += min * 60 + hour * 3600; - if (sign) sec = -sec; - offset = INT2FIX(sec); -#undef out_of_range - } - } - } - RB_GC_GUARD(str); - ok: - return offset; -} - -static int -day_num(VALUE s) -{ - int i; - - for (i = 0; i < (int)sizeof_array(abbr_days); i++) - if (strncasecmp(abbr_days[i], RSTRING_PTR(s), 3) == 0) - break; - return i; -} - -static int -mon_num(VALUE s) -{ - int i; - - for (i = 0; i < (int)sizeof_array(abbr_months); i++) - if (strncasecmp(abbr_months[i], RSTRING_PTR(s), 3) == 0) - break; - return i + 1; -} - -static int -parse_day_cb(VALUE m, VALUE hash) -{ - VALUE s; - - s = rb_reg_nth_match(1, m); - set_hash("wday", INT2FIX(day_num(s))); - return 1; -} - -static int -parse_day(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "\\b(" ABBR_DAYS ")[^-/\\d\\s]*" -#else - "(" VALID_DAYS ")" -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); -#ifndef TIGHT_PARSER - SUBS(str, pat, parse_day_cb); -#else - SUBW(str, pat, parse_day_cb); -#endif -} - -static int -parse_time2_cb(VALUE m, VALUE hash) -{ - VALUE h, min, s, f, p; - - h = rb_reg_nth_match(1, m); - h = str2num(h); - - min = rb_reg_nth_match(2, m); - if (!NIL_P(min)) - min = str2num(min); - - s = rb_reg_nth_match(3, m); - if (!NIL_P(s)) - s = str2num(s); - - f = rb_reg_nth_match(4, m); - - if (!NIL_P(f)) - f = rb_rational_new2(str2num(f), - f_expt(INT2FIX(10), LONG2NUM(RSTRING_LEN(f)))); - - p = rb_reg_nth_match(5, m); - - if (!NIL_P(p)) { - int ih = NUM2INT(h); - ih %= 12; - if (*RSTRING_PTR(p) == 'P' || *RSTRING_PTR(p) == 'p') - ih += 12; - h = INT2FIX(ih); - } - - set_hash("hour", h); - if (!NIL_P(min)) - set_hash("min", min); - if (!NIL_P(s)) - set_hash("sec", s); - if (!NIL_P(f)) - set_hash("sec_fraction", f); - - return 1; -} - -static int -parse_time_cb(VALUE m, VALUE hash) -{ - static const char pat_source[] = - "\\A(\\d+)h?" - "(?:\\s*:?\\s*(\\d+)m?" - "(?:" - "\\s*:?\\s*(\\d+)(?:[,.](\\d+))?s?" - ")?" - ")?" - "(?:\\s*([ap])(?:m\\b|\\.m\\.))?"; - static VALUE pat = Qnil; - VALUE s1, s2; - - s1 = rb_reg_nth_match(1, m); - s2 = rb_reg_nth_match(2, m); - - if (!NIL_P(s2)) - set_hash("zone", s2); - - REGCOMP_I(pat); - - { - VALUE m = f_match(pat, s1); - - if (NIL_P(m)) - return 0; - parse_time2_cb(m, hash); - } - - return 1; -} - -static int -parse_time(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "(" - "" NUMBER "+\\s*" - "(?:" - "(?:" - ":\\s*\\d+" - "(?:" -#ifndef TIGHT_PARSER - "\\s*:\\s*\\d+(?:[,.]\\d*)?" -#else - "\\s*:\\s*\\d+(?:[,.]\\d+)?" -#endif - ")?" - "|" - "h(?:\\s*\\d+m?(?:\\s*\\d+s?)?)?" - ")" - "(?:" - "\\s*" - "[ap](?:m\\b|\\.m\\.)" - ")?" - "|" - "[ap](?:m\\b|\\.m\\.)" - ")" - ")" - "(?:" - "\\s*" - "(" - "(?:gmt|utc?)?[-+]\\d+(?:[,.:]\\d+(?::\\d+)?)?" - "|" - "(?-i:[[:alpha:].\\s]+)(?:standard|daylight)\\stime\\b" - "|" - "(?-i:[[:alpha:]]+)(?:\\sdst)?\\b" - ")" - ")?"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); -#ifndef TIGHT_PARSER - SUBS(str, pat, parse_time_cb); -#else - SUBT(str, pat, parse_time_cb); -#endif -} - -#define BEGIN_ERA "\\b" -#define END_ERA "(?!(? 1) - return 0; - return 1; -} -#endif - -static int -parse_eu_cb(VALUE m, VALUE hash) -{ -#ifndef TIGHT_PARSER - VALUE y, mon, d, b; - - d = rb_reg_nth_match(1, m); - mon = rb_reg_nth_match(2, m); - b = rb_reg_nth_match(3, m); - y = rb_reg_nth_match(4, m); - - mon = INT2FIX(mon_num(mon)); - - s3e(hash, y, mon, d, !NIL_P(b) && - (*RSTRING_PTR(b) == 'B' || - *RSTRING_PTR(b) == 'b')); -#else - VALUE y, mon, d; - - d = rb_reg_nth_match(1, m); - mon = rb_reg_nth_match(2, m); - y = rb_reg_nth_match(3, m); - - if (!check_apost(d, mon, y)) - return 0; - - mon = INT2FIX(mon_num(mon)); - - s3e(hash, y, mon, d, 0); -#endif - return 1; -} - -static int -parse_eu(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifdef TIGHT_PARSER - BOS - FPW_COM FPT_COM -#endif -#ifndef TIGHT_PARSER - "('?" NUMBER "+)[^-\\d\\s]*" -#else - "(\\d+)(?:(?:st|nd|rd|th)\\b)?" -#endif - "\\s*" -#ifndef TIGHT_PARSER - "(" ABBR_MONTHS ")[^-\\d\\s']*" -#else - "(" VALID_MONTHS ")" -#endif - "(?:" - "\\s*" -#ifndef TIGHT_PARSER - "(?:" - BEGIN_ERA - "(c(?:e|\\.e\\.)|b(?:ce|\\.c\\.e\\.)|a(?:d|\\.d\\.)|b(?:c|\\.c\\.))" - END_ERA - ")?" - "\\s*" - "('?-?\\d+(?:(?:st|nd|rd|th)\\b)?)" -#else - "(?:" FPA ")?" - "\\s*" - "([-']?\\d+)" - "\\s*" - "(?:" FPA "|" FPB ")?" -#endif - ")?" -#ifdef TIGHT_PARSER - COM_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_eu_cb); -} - -static int -parse_us_cb(VALUE m, VALUE hash) -{ -#ifndef TIGHT_PARSER - VALUE y, mon, d, b; - - mon = rb_reg_nth_match(1, m); - d = rb_reg_nth_match(2, m); - - b = rb_reg_nth_match(3, m); - y = rb_reg_nth_match(4, m); - - mon = INT2FIX(mon_num(mon)); - - s3e(hash, y, mon, d, !NIL_P(b) && - (*RSTRING_PTR(b) == 'B' || - *RSTRING_PTR(b) == 'b')); -#else - VALUE y, mon, d; - - mon = rb_reg_nth_match(1, m); - d = rb_reg_nth_match(2, m); - y = rb_reg_nth_match(3, m); - - if (!check_apost(mon, d, y)) - return 0; - - mon = INT2FIX(mon_num(mon)); - - s3e(hash, y, mon, d, 0); -#endif - return 1; -} - -static int -parse_us(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifdef TIGHT_PARSER - BOS - FPW_COM FPT_COM -#endif -#ifndef TIGHT_PARSER - "\\b(" ABBR_MONTHS ")[^-\\d\\s']*" -#else - "\\b(" VALID_MONTHS ")" -#endif - "\\s*" -#ifndef TIGHT_PARSER - "('?\\d+)[^-\\d\\s']*" -#else - "('?\\d+)(?:(?:st|nd|rd|th)\\b)?" - COM_FPT -#endif - "(?:" - "\\s*+,?" - "\\s*+" -#ifndef TIGHT_PARSER - "(c(?:e|\\.e\\.)|b(?:ce|\\.c\\.e\\.)|a(?:d|\\.d\\.)|b(?:c|\\.c\\.))?" - "\\s*" - "('?-?\\d+)" -#else - "(?:" FPA ")?" - "\\s*" - "([-']?\\d+)" - "\\s*" - "(?:" FPA "|" FPB ")?" -#endif - ")?" -#ifdef TIGHT_PARSER - COM_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_us_cb); -} - -static int -parse_iso_cb(VALUE m, VALUE hash) -{ - VALUE y, mon, d; - - y = rb_reg_nth_match(1, m); - mon = rb_reg_nth_match(2, m); - d = rb_reg_nth_match(3, m); - -#ifdef TIGHT_PARSER - if (!check_apost(y, mon, d)) - return 0; -#endif - - s3e(hash, y, mon, d, 0); - return 1; -} - -static int -parse_iso(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "('?[-+]?" NUMBER "+)-(\\d+)-('?-?\\d+)" -#else - BOS - FPW_COM FPT_COM - "([-+']?\\d+)-(\\d+)-([-']?\\d+)" - TEE_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_0(pat); - SUBS(str, pat, parse_iso_cb); -} - -static int -parse_iso21_cb(VALUE m, VALUE hash) -{ - VALUE y, w, d; - - y = rb_reg_nth_match(1, m); - w = rb_reg_nth_match(2, m); - d = rb_reg_nth_match(3, m); - - if (!NIL_P(y)) - set_hash("cwyear", str2num(y)); - set_hash("cweek", str2num(w)); - if (!NIL_P(d)) - set_hash("cwday", str2num(d)); - - return 1; -} - -static int -parse_iso21(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "\\b(\\d{2}|\\d{4})?-?w(\\d{2})(?:-?(\\d))?\\b" -#else - BOS - FPW_COM FPT_COM - "(\\d{2}|\\d{4})?-?w(\\d{2})(?:-?(\\d))?" - TEE_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_iso21_cb); -} - -static int -parse_iso22_cb(VALUE m, VALUE hash) -{ - VALUE d; - - d = rb_reg_nth_match(1, m); - set_hash("cwday", str2num(d)); - return 1; -} - -static int -parse_iso22(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "-w-(\\d)\\b" -#else - BOS - FPW_COM FPT_COM - "-w-(\\d)" - TEE_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_iso22_cb); -} - -static int -parse_iso23_cb(VALUE m, VALUE hash) -{ - VALUE mon, d; - - mon = rb_reg_nth_match(1, m); - d = rb_reg_nth_match(2, m); - - if (!NIL_P(mon)) - set_hash("mon", str2num(mon)); - set_hash("mday", str2num(d)); - - return 1; -} - -static int -parse_iso23(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "--(\\d{2})?-(\\d{2})\\b" -#else - BOS - FPW_COM FPT_COM - "--(\\d{2})?-(\\d{2})" - TEE_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_0(pat); - SUBS(str, pat, parse_iso23_cb); -} - -static int -parse_iso24_cb(VALUE m, VALUE hash) -{ - VALUE mon, d; - - mon = rb_reg_nth_match(1, m); - d = rb_reg_nth_match(2, m); - - set_hash("mon", str2num(mon)); - if (!NIL_P(d)) - set_hash("mday", str2num(d)); - - return 1; -} - -static int -parse_iso24(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "--(\\d{2})(\\d{2})?\\b" -#else - BOS - FPW_COM FPT_COM - "--(\\d{2})(\\d{2})?" - TEE_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_0(pat); - SUBS(str, pat, parse_iso24_cb); -} - -static int -parse_iso25_cb(VALUE m, VALUE hash) -{ - VALUE y, d; - - y = rb_reg_nth_match(1, m); - d = rb_reg_nth_match(2, m); - - set_hash("year", str2num(y)); - set_hash("yday", str2num(d)); - - return 1; -} - -static int -parse_iso25(VALUE str, VALUE hash) -{ - static const char pat0_source[] = -#ifndef TIGHT_PARSER - "[,.](\\d{2}|\\d{4})-\\d{3}\\b" -#else - BOS - FPW_COM FPT_COM - "[,.](\\d{2}|\\d{4})-\\d{3}" - TEE_FPT COM_FPW - EOS -#endif - ; - static VALUE pat0 = Qnil; - static const char pat_source[] = -#ifndef TIGHT_PARSER - "\\b(\\d{2}|\\d{4})-(\\d{3})\\b" -#else - BOS - FPW_COM FPT_COM - "(\\d{2}|\\d{4})-(\\d{3})" - TEE_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_0(pat0); - REGCOMP_0(pat); - - if (!NIL_P(f_match(pat0, str))) - return 0; - SUBS(str, pat, parse_iso25_cb); -} - -static int -parse_iso26_cb(VALUE m, VALUE hash) -{ - VALUE d; - - d = rb_reg_nth_match(1, m); - set_hash("yday", str2num(d)); - - return 1; -} -static int -parse_iso26(VALUE str, VALUE hash) -{ - static const char pat0_source[] = -#ifndef TIGHT_PARSER - "\\d-\\d{3}\\b" -#else - BOS - FPW_COM FPT_COM - "\\d-\\d{3}" - TEE_FPT COM_FPW - EOS -#endif - ; - static VALUE pat0 = Qnil; - static const char pat_source[] = -#ifndef TIGHT_PARSER - "\\b-(\\d{3})\\b" -#else - BOS - FPW_COM FPT_COM - "-(\\d{3})" - TEE_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_0(pat0); - REGCOMP_0(pat); - - if (!NIL_P(f_match(pat0, str))) - return 0; - SUBS(str, pat, parse_iso26_cb); -} - -static int -parse_iso2(VALUE str, VALUE hash) -{ - if (parse_iso21(str, hash)) - goto ok; - if (parse_iso22(str, hash)) - goto ok; - if (parse_iso23(str, hash)) - goto ok; - if (parse_iso24(str, hash)) - goto ok; - if (parse_iso25(str, hash)) - goto ok; - if (parse_iso26(str, hash)) - goto ok; - return 0; - - ok: - return 1; -} - -#define JISX0301_ERA_INITIALS "mtshr" -#define JISX0301_DEFAULT_ERA 'H' /* obsolete */ - -static int -gengo(int c) -{ - int e; - - switch (c) { - case 'M': case 'm': e = 1867; break; - case 'T': case 't': e = 1911; break; - case 'S': case 's': e = 1925; break; - case 'H': case 'h': e = 1988; break; - case 'R': case 'r': e = 2018; break; - default: e = 0; break; - } - return e; -} - -static int -parse_jis_cb(VALUE m, VALUE hash) -{ - VALUE e, y, mon, d; - int ep; - - e = rb_reg_nth_match(1, m); - y = rb_reg_nth_match(2, m); - mon = rb_reg_nth_match(3, m); - d = rb_reg_nth_match(4, m); - - ep = gengo(*RSTRING_PTR(e)); - - set_hash("year", f_add(str2num(y), INT2FIX(ep))); - set_hash("mon", str2num(mon)); - set_hash("mday", str2num(d)); - - return 1; -} - -static int -parse_jis(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "\\b([" JISX0301_ERA_INITIALS "])(\\d+)\\.(\\d+)\\.(\\d+)" -#else - BOS - FPW_COM FPT_COM - "([" JISX0301_ERA_INITIALS "])(\\d+)\\.(\\d+)\\.(\\d+)" - TEE_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_jis_cb); -} - -static int -parse_vms11_cb(VALUE m, VALUE hash) -{ - VALUE y, mon, d; - - d = rb_reg_nth_match(1, m); - mon = rb_reg_nth_match(2, m); - y = rb_reg_nth_match(3, m); - -#ifdef TIGHT_PARSER - if (!check_apost(d, mon, y)) - return 0; -#endif - - mon = INT2FIX(mon_num(mon)); - - s3e(hash, y, mon, d, 0); - return 1; -} - -static int -parse_vms11(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "('?-?" NUMBER "+)-(" ABBR_MONTHS ")[^-/.]*" - "-('?-?\\d+)" -#else - BOS - FPW_COM FPT_COM - "([-']?\\d+)-(" DOTLESS_VALID_MONTHS ")" - "-([-']?\\d+)" - COM_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_vms11_cb); -} - -static int -parse_vms12_cb(VALUE m, VALUE hash) -{ - VALUE y, mon, d; - - mon = rb_reg_nth_match(1, m); - d = rb_reg_nth_match(2, m); - y = rb_reg_nth_match(3, m); - -#ifdef TIGHT_PARSER - if (!check_apost(mon, d, y)) - return 0; -#endif - - mon = INT2FIX(mon_num(mon)); - - s3e(hash, y, mon, d, 0); - return 1; -} - -static int -parse_vms12(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "\\b(" ABBR_MONTHS ")[^-/.]*" - "-('?-?\\d+)(?:-('?-?\\d+))?" -#else - BOS - FPW_COM FPT_COM - "(" DOTLESS_VALID_MONTHS ")" - "-([-']?\\d+)(?:-([-']?\\d+))?" - COM_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_vms12_cb); -} - -static int -parse_vms(VALUE str, VALUE hash) -{ - if (parse_vms11(str, hash)) - goto ok; - if (parse_vms12(str, hash)) - goto ok; - return 0; - - ok: - return 1; -} - -static int -parse_sla_cb(VALUE m, VALUE hash) -{ - VALUE y, mon, d; - - y = rb_reg_nth_match(1, m); - mon = rb_reg_nth_match(2, m); - d = rb_reg_nth_match(3, m); - -#ifdef TIGHT_PARSER - if (!check_apost(y, mon, d)) - return 0; -#endif - - s3e(hash, y, mon, d, 0); - return 1; -} - -static int -parse_sla(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "('?-?" NUMBER "+)/\\s*('?\\d+)(?:\\D\\s*('?-?\\d+))?" -#else - BOS - FPW_COM FPT_COM - "([-']?\\d+)/\\s*('?\\d+)(?:(?:[-/]|\\s+)\\s*([-']?\\d+))?" - COM_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_sla_cb); -} - -#ifdef TIGHT_PARSER -static int -parse_sla2_cb(VALUE m, VALUE hash) -{ - VALUE y, mon, d; - - d = rb_reg_nth_match(1, m); - mon = rb_reg_nth_match(2, m); - y = rb_reg_nth_match(3, m); - - if (!check_apost(d, mon, y)) - return 0; - - mon = INT2FIX(mon_num(mon)); - - s3e(hash, y, mon, d, 0); - return 1; -} - -static int -parse_sla2(VALUE str, VALUE hash) -{ - static const char pat_source[] = - BOS - FPW_COM FPT_COM - "([-']?\\d+)/\\s*(" DOTLESS_VALID_MONTHS ")(?:(?:[-/]|\\s+)\\s*([-']?\\d+))?" - COM_FPT COM_FPW - EOS - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_sla2_cb); -} - -static int -parse_sla3_cb(VALUE m, VALUE hash) -{ - VALUE y, mon, d; - - mon = rb_reg_nth_match(1, m); - d = rb_reg_nth_match(2, m); - y = rb_reg_nth_match(3, m); - - if (!check_apost(mon, d, y)) - return 0; - - mon = INT2FIX(mon_num(mon)); - - s3e(hash, y, mon, d, 0); - return 1; -} - -static int -parse_sla3(VALUE str, VALUE hash) -{ - static const char pat_source[] = - BOS - FPW_COM FPT_COM - "(" DOTLESS_VALID_MONTHS ")/\\s*([-']?\\d+)(?:(?:[-/]|\\s+)\\s*([-']?\\d+))?" - COM_FPT COM_FPW - EOS - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_sla3_cb); -} -#endif - -static int -parse_dot_cb(VALUE m, VALUE hash) -{ - VALUE y, mon, d; - - y = rb_reg_nth_match(1, m); - mon = rb_reg_nth_match(2, m); - d = rb_reg_nth_match(3, m); - -#ifdef TIGHT_PARSER - if (!check_apost(y, mon, d)) - return 0; -#endif - - s3e(hash, y, mon, d, 0); - return 1; -} - -static int -parse_dot(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "('?-?" NUMBER "+)\\.\\s*('?\\d+)\\.\\s*('?-?\\d+)" -#else - BOS - FPW_COM FPT_COM - "([-']?\\d+)\\.\\s*(\\d+)\\.\\s*([-']?\\d+)" - COM_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_dot_cb); -} - -#ifdef TIGHT_PARSER -static int -parse_dot2_cb(VALUE m, VALUE hash) -{ - VALUE y, mon, d; - - d = rb_reg_nth_match(1, m); - mon = rb_reg_nth_match(2, m); - y = rb_reg_nth_match(3, m); - - if (!check_apost(d, mon, y)) - return 0; - - mon = INT2FIX(mon_num(mon)); - - s3e(hash, y, mon, d, 0); - return 1; -} - -static int -parse_dot2(VALUE str, VALUE hash) -{ - static const char pat_source[] = - BOS - FPW_COM FPT_COM - "([-']?\\d+)\\.\\s*(" DOTLESS_VALID_MONTHS ")(?:(?:[./])\\s*([-']?\\d+))?" - COM_FPT COM_FPW - EOS - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_dot2_cb); -} - -static int -parse_dot3_cb(VALUE m, VALUE hash) -{ - VALUE y, mon, d; - - mon = rb_reg_nth_match(1, m); - d = rb_reg_nth_match(2, m); - y = rb_reg_nth_match(3, m); - - if (!check_apost(mon, d, y)) - return 0; - - mon = INT2FIX(mon_num(mon)); - - s3e(hash, y, mon, d, 0); - return 1; -} - -static int -parse_dot3(VALUE str, VALUE hash) -{ - static const char pat_source[] = - BOS - FPW_COM FPT_COM - "(" DOTLESS_VALID_MONTHS ")\\.\\s*([-']?\\d+)(?:(?:[./])\\s*([-']?\\d+))?" - COM_FPT COM_FPW - EOS - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_dot3_cb); -} -#endif - -static int -parse_year_cb(VALUE m, VALUE hash) -{ - VALUE y; - - y = rb_reg_nth_match(1, m); - set_hash("year", str2num(y)); - return 1; -} - -static int -parse_year(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "'(\\d+)\\b" -#else - BOS - FPW_COM FPT_COM - "'(\\d+)" - COM_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_0(pat); - SUBS(str, pat, parse_year_cb); -} - -static int -parse_mon_cb(VALUE m, VALUE hash) -{ - VALUE mon; - - mon = rb_reg_nth_match(1, m); - set_hash("mon", INT2FIX(mon_num(mon))); - return 1; -} - -static int -parse_mon(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "\\b(" ABBR_MONTHS ")\\S*" -#else - BOS - FPW_COM FPT_COM - "(" VALID_MONTHS ")" - COM_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_mon_cb); -} - -static int -parse_mday_cb(VALUE m, VALUE hash) -{ - VALUE d; - - d = rb_reg_nth_match(1, m); - set_hash("mday", str2num(d)); - return 1; -} - -static int -parse_mday(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifndef TIGHT_PARSER - "(" NUMBER "+)(st|nd|rd|th)\\b" -#else - BOS - FPW_COM FPT_COM - "(\\d+)(st|nd|rd|th)" - COM_FPT COM_FPW - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_mday_cb); -} - -static int -n2i(const char *s, long f, long w) -{ - long e, i; - int v; - - e = f + w; - v = 0; - for (i = f; i < e; i++) { - v *= 10; - v += s[i] - '0'; - } - return v; -} - -static int -parse_ddd_cb(VALUE m, VALUE hash) -{ - VALUE s1, s2, s3, s4, s5; - const char *cs2, *cs3, *cs5; - long l2, l3, l4, l5; - - s1 = rb_reg_nth_match(1, m); - s2 = rb_reg_nth_match(2, m); - s3 = rb_reg_nth_match(3, m); - s4 = rb_reg_nth_match(4, m); - s5 = rb_reg_nth_match(5, m); - - cs2 = RSTRING_PTR(s2); - l2 = RSTRING_LEN(s2); - - switch (l2) { - case 2: - if (NIL_P(s3) && !NIL_P(s4)) - set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); - else - set_hash("mday", INT2FIX(n2i(cs2, 0, 2))); - break; - case 4: - if (NIL_P(s3) && !NIL_P(s4)) { - set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); - set_hash("min", INT2FIX(n2i(cs2, l2-4, 2))); - } - else { - set_hash("mon", INT2FIX(n2i(cs2, 0, 2))); - set_hash("mday", INT2FIX(n2i(cs2, 2, 2))); - } - break; - case 6: - if (NIL_P(s3) && !NIL_P(s4)) { - set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); - set_hash("min", INT2FIX(n2i(cs2, l2-4, 2))); - set_hash("hour", INT2FIX(n2i(cs2, l2-6, 2))); - } - else { - int y = n2i(cs2, 0, 2); - if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') - y = -y; - set_hash("year", INT2FIX(y)); - set_hash("mon", INT2FIX(n2i(cs2, 2, 2))); - set_hash("mday", INT2FIX(n2i(cs2, 4, 2))); - } - break; - case 8: - case 10: - case 12: - case 14: - if (NIL_P(s3) && !NIL_P(s4)) { - set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); - set_hash("min", INT2FIX(n2i(cs2, l2-4, 2))); - set_hash("hour", INT2FIX(n2i(cs2, l2-6, 2))); - set_hash("mday", INT2FIX(n2i(cs2, l2-8, 2))); - if (l2 >= 10) - set_hash("mon", INT2FIX(n2i(cs2, l2-10, 2))); - if (l2 == 12) { - int y = n2i(cs2, l2-12, 2); - if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') - y = -y; - set_hash("year", INT2FIX(y)); - } - if (l2 == 14) { - int y = n2i(cs2, l2-14, 4); - if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') - y = -y; - set_hash("year", INT2FIX(y)); - set_hash("_comp", Qfalse); - } - } - else { - int y = n2i(cs2, 0, 4); - if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') - y = -y; - set_hash("year", INT2FIX(y)); - set_hash("mon", INT2FIX(n2i(cs2, 4, 2))); - set_hash("mday", INT2FIX(n2i(cs2, 6, 2))); - if (l2 >= 10) - set_hash("hour", INT2FIX(n2i(cs2, 8, 2))); - if (l2 >= 12) - set_hash("min", INT2FIX(n2i(cs2, 10, 2))); - if (l2 >= 14) - set_hash("sec", INT2FIX(n2i(cs2, 12, 2))); - set_hash("_comp", Qfalse); - } - break; - case 3: - if (NIL_P(s3) && !NIL_P(s4)) { - set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); - set_hash("min", INT2FIX(n2i(cs2, l2-3, 1))); - } - else - set_hash("yday", INT2FIX(n2i(cs2, 0, 3))); - break; - case 5: - if (NIL_P(s3) && !NIL_P(s4)) { - set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); - set_hash("min", INT2FIX(n2i(cs2, l2-4, 2))); - set_hash("hour", INT2FIX(n2i(cs2, l2-5, 1))); - } - else { - int y = n2i(cs2, 0, 2); - if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') - y = -y; - set_hash("year", INT2FIX(y)); - set_hash("yday", INT2FIX(n2i(cs2, 2, 3))); - } - break; - case 7: - if (NIL_P(s3) && !NIL_P(s4)) { - set_hash("sec", INT2FIX(n2i(cs2, l2-2, 2))); - set_hash("min", INT2FIX(n2i(cs2, l2-4, 2))); - set_hash("hour", INT2FIX(n2i(cs2, l2-6, 2))); - set_hash("mday", INT2FIX(n2i(cs2, l2-7, 1))); - } - else { - int y = n2i(cs2, 0, 4); - if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-') - y = -y; - set_hash("year", INT2FIX(y)); - set_hash("yday", INT2FIX(n2i(cs2, 4, 3))); - } - break; - } - RB_GC_GUARD(s2); - if (!NIL_P(s3)) { - cs3 = RSTRING_PTR(s3); - l3 = RSTRING_LEN(s3); - - if (!NIL_P(s4)) { - switch (l3) { - case 2: - case 4: - case 6: - set_hash("sec", INT2FIX(n2i(cs3, l3-2, 2))); - if (l3 >= 4) - set_hash("min", INT2FIX(n2i(cs3, l3-4, 2))); - if (l3 >= 6) - set_hash("hour", INT2FIX(n2i(cs3, l3-6, 2))); - break; - } - } - else { - switch (l3) { - case 2: - case 4: - case 6: - set_hash("hour", INT2FIX(n2i(cs3, 0, 2))); - if (l3 >= 4) - set_hash("min", INT2FIX(n2i(cs3, 2, 2))); - if (l3 >= 6) - set_hash("sec", INT2FIX(n2i(cs3, 4, 2))); - break; - } - } - RB_GC_GUARD(s3); - } - if (!NIL_P(s4)) { - l4 = RSTRING_LEN(s4); - - set_hash("sec_fraction", - rb_rational_new2(str2num(s4), - f_expt(INT2FIX(10), LONG2NUM(l4)))); - } - if (!NIL_P(s5)) { - cs5 = RSTRING_PTR(s5); - l5 = RSTRING_LEN(s5); - - set_hash("zone", s5); - - if (*cs5 == '[') { - const char *s1, *s2; - VALUE zone; - - l5 -= 2; - s1 = cs5 + 1; - s2 = memchr(s1, ':', l5); - if (s2) { - s2++; - zone = rb_str_subseq(s5, s2 - cs5, l5 - (s2 - s1)); - s5 = rb_str_subseq(s5, 1, s2 - s1); - } - else { - zone = rb_str_subseq(s5, 1, l5); - if (isdigit((unsigned char)*s1)) - s5 = rb_str_append(rb_str_new_cstr("+"), zone); - else - s5 = zone; - } - set_hash("zone", zone); - set_hash("offset", date_zone_to_diff(s5)); - } - RB_GC_GUARD(s5); - } - - return 1; -} - -static int -parse_ddd(VALUE str, VALUE hash) -{ - static const char pat_source[] = -#ifdef TIGHT_PARSER - BOS -#endif - "([-+]?)(" NUMBER "{2,14})" - "(?:" - "\\s*" - "t?" - "\\s*" - "(\\d{2,6})?(?:[,.](\\d*))?" - ")?" - "(?:" - "\\s*" - "(" - "z\\b" - "|" - "[-+]\\d{1,4}\\b" - "|" - "\\[[-+]?\\d[^\\]]*\\]" - ")" - ")?" -#ifdef TIGHT_PARSER - EOS -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_ddd_cb); -} - -#ifndef TIGHT_PARSER -static int -parse_bc_cb(VALUE m, VALUE hash) -{ - set_hash("_bc", Qtrue); - return 1; -} - -static int -parse_bc(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\b(bc\\b|bce\\b|b\\.c\\.|b\\.c\\.e\\.)"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_bc_cb); -} - -static int -parse_frag_cb(VALUE m, VALUE hash) -{ - VALUE s, n; - - s = rb_reg_nth_match(1, m); - - if (!NIL_P(ref_hash("hour")) && NIL_P(ref_hash("mday"))) { - n = str2num(s); - if (f_ge_p(n, INT2FIX(1)) && - f_le_p(n, INT2FIX(31))) - set_hash("mday", n); - } - if (!NIL_P(ref_hash("mday")) && NIL_P(ref_hash("hour"))) { - n = str2num(s); - if (f_ge_p(n, INT2FIX(0)) && - f_le_p(n, INT2FIX(24))) - set_hash("hour", n); - } - - return 1; -} - -static int -parse_frag(VALUE str, VALUE hash) -{ - static const char pat_source[] = "\\A\\s*(\\d{1,2})\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - SUBS(str, pat, parse_frag_cb); -} -#endif - -#ifdef TIGHT_PARSER -static int -parse_dummy_cb(VALUE m, VALUE hash) -{ - return 1; -} - -static int -parse_wday_only(VALUE str, VALUE hash) -{ - static const char pat_source[] = "\\A\\s*" FPW "\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_0(pat); - SUBS(str, pat, parse_dummy_cb); -} - -static int -parse_time_only(VALUE str, VALUE hash) -{ - static const char pat_source[] = "\\A\\s*" FPT "\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_0(pat); - SUBS(str, pat, parse_dummy_cb); -} - -static int -parse_wday_and_time(VALUE str, VALUE hash) -{ - static const char pat_source[] = "\\A\\s*(" FPW "\\s+" FPT "|" FPT "\\s+" FPW ")\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_0(pat); - SUBS(str, pat, parse_dummy_cb); -} - -static unsigned -have_invalid_char_p(VALUE s) -{ - long i; - - for (i = 0; i < RSTRING_LEN(s); i++) - if (iscntrl((unsigned char)RSTRING_PTR(s)[i]) && - !isspace((unsigned char)RSTRING_PTR(s)[i])) - return 1; - return 0; -} -#endif - -#define HAVE_ALPHA (1<<0) -#define HAVE_DIGIT (1<<1) -#define HAVE_DASH (1<<2) -#define HAVE_DOT (1<<3) -#define HAVE_SLASH (1<<4) - -static unsigned -check_class(VALUE s) -{ - unsigned flags; - long i; - - flags = 0; - for (i = 0; i < RSTRING_LEN(s); i++) { - if (isalpha((unsigned char)RSTRING_PTR(s)[i])) - flags |= HAVE_ALPHA; - if (isdigit((unsigned char)RSTRING_PTR(s)[i])) - flags |= HAVE_DIGIT; - if (RSTRING_PTR(s)[i] == '-') - flags |= HAVE_DASH; - if (RSTRING_PTR(s)[i] == '.') - flags |= HAVE_DOT; - if (RSTRING_PTR(s)[i] == '/') - flags |= HAVE_SLASH; - } - return flags; -} - -#define HAVE_ELEM_P(x) ((check_class(str) & (x)) == (x)) - -#ifdef TIGHT_PARSER -#define PARSER_ERROR return rb_hash_new() -#endif - -VALUE -date__parse(VALUE str, VALUE comp) -{ - VALUE backref, hash; - -#ifdef TIGHT_PARSER - if (have_invalid_char_p(str)) - PARSER_ERROR; -#endif - - backref = rb_backref_get(); - rb_match_busy(backref); - - { - static const char pat_source[] = -#ifndef TIGHT_PARSER - "[^-+',./:@[:alnum:]\\[\\]]+" -#else - "[^[:graph:]]+" -#endif - ; - static VALUE pat = Qnil; - - REGCOMP_0(pat); - str = rb_str_dup(str); - f_gsub_bang(str, pat, asp_string()); - } - - hash = rb_hash_new(); - set_hash("_comp", comp); - - if (HAVE_ELEM_P(HAVE_ALPHA)) - parse_day(str, hash); - if (HAVE_ELEM_P(HAVE_DIGIT)) - parse_time(str, hash); - -#ifdef TIGHT_PARSER - if (HAVE_ELEM_P(HAVE_ALPHA)) - parse_era(str, hash); -#endif - - if (HAVE_ELEM_P(HAVE_ALPHA|HAVE_DIGIT)) { - if (parse_eu(str, hash)) - goto ok; - if (parse_us(str, hash)) - goto ok; - } - if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_DASH)) - if (parse_iso(str, hash)) - goto ok; - if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_DOT)) - if (parse_jis(str, hash)) - goto ok; - if (HAVE_ELEM_P(HAVE_ALPHA|HAVE_DIGIT|HAVE_DASH)) - if (parse_vms(str, hash)) - goto ok; - if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_SLASH)) - if (parse_sla(str, hash)) - goto ok; -#ifdef TIGHT_PARSER - if (HAVE_ELEM_P(HAVE_ALPHA|HAVE_DIGIT|HAVE_SLASH)) { - if (parse_sla2(str, hash)) - goto ok; - if (parse_sla3(str, hash)) - goto ok; - } -#endif - if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_DOT)) - if (parse_dot(str, hash)) - goto ok; -#ifdef TIGHT_PARSER - if (HAVE_ELEM_P(HAVE_ALPHA|HAVE_DIGIT|HAVE_DOT)) { - if (parse_dot2(str, hash)) - goto ok; - if (parse_dot3(str, hash)) - goto ok; - } -#endif - if (HAVE_ELEM_P(HAVE_DIGIT)) - if (parse_iso2(str, hash)) - goto ok; - if (HAVE_ELEM_P(HAVE_DIGIT)) - if (parse_year(str, hash)) - goto ok; - if (HAVE_ELEM_P(HAVE_ALPHA)) - if (parse_mon(str, hash)) - goto ok; - if (HAVE_ELEM_P(HAVE_DIGIT)) - if (parse_mday(str, hash)) - goto ok; - if (HAVE_ELEM_P(HAVE_DIGIT)) - if (parse_ddd(str, hash)) - goto ok; - -#ifdef TIGHT_PARSER - if (parse_wday_only(str, hash)) - goto ok; - if (parse_time_only(str, hash)) - goto ok; - if (parse_wday_and_time(str, hash)) - goto ok; - - PARSER_ERROR; /* not found */ -#endif - - ok: -#ifndef TIGHT_PARSER - if (HAVE_ELEM_P(HAVE_ALPHA)) - parse_bc(str, hash); - if (HAVE_ELEM_P(HAVE_DIGIT)) - parse_frag(str, hash); -#endif - - { - if (RTEST(del_hash("_bc"))) { - VALUE y; - - y = ref_hash("cwyear"); - if (!NIL_P(y)) { - y = f_add(f_negate(y), INT2FIX(1)); - set_hash("cwyear", y); - } - y = ref_hash("year"); - if (!NIL_P(y)) { - y = f_add(f_negate(y), INT2FIX(1)); - set_hash("year", y); - } - } - - if (RTEST(del_hash("_comp"))) { - VALUE y; - - y = ref_hash("cwyear"); - if (!NIL_P(y)) - if (f_ge_p(y, INT2FIX(0)) && f_le_p(y, INT2FIX(99))) { - if (f_ge_p(y, INT2FIX(69))) - set_hash("cwyear", f_add(y, INT2FIX(1900))); - else - set_hash("cwyear", f_add(y, INT2FIX(2000))); - } - y = ref_hash("year"); - if (!NIL_P(y)) - if (f_ge_p(y, INT2FIX(0)) && f_le_p(y, INT2FIX(99))) { - if (f_ge_p(y, INT2FIX(69))) - set_hash("year", f_add(y, INT2FIX(1900))); - else - set_hash("year", f_add(y, INT2FIX(2000))); - } - } - - } - - { - VALUE zone = ref_hash("zone"); - if (!NIL_P(zone) && NIL_P(ref_hash("offset"))) - set_hash("offset", date_zone_to_diff(zone)); - } - - rb_backref_set(backref); - - return hash; -} - -static VALUE -comp_year69(VALUE y) -{ - if (f_ge_p(y, INT2FIX(69))) - return f_add(y, INT2FIX(1900)); - return f_add(y, INT2FIX(2000)); -} - -static VALUE -comp_year50(VALUE y) -{ - if (f_ge_p(y, INT2FIX(50))) - return f_add(y, INT2FIX(1900)); - return f_add(y, INT2FIX(2000)); -} - -static VALUE -sec_fraction(VALUE f) -{ - return rb_rational_new2(str2num(f), - f_expt(INT2FIX(10), - LONG2NUM(RSTRING_LEN(f)))); -} - -#define SNUM 14 - -static int -iso8601_ext_datetime_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1], y; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - if (!NIL_P(s[1])) { - if (!NIL_P(s[3])) set_hash("mday", str2num(s[3])); - if (strcmp(RSTRING_PTR(s[1]), "-") != 0) { - y = str2num(s[1]); - if (RSTRING_LEN(s[1]) < 4) - y = comp_year69(y); - set_hash("year", y); - } - if (NIL_P(s[2])) { - if (strcmp(RSTRING_PTR(s[1]), "-") != 0) - return 0; - } - else - set_hash("mon", str2num(s[2])); - } - else if (!NIL_P(s[5])) { - set_hash("yday", str2num(s[5])); - if (!NIL_P(s[4])) { - y = str2num(s[4]); - if (RSTRING_LEN(s[4]) < 4) - y = comp_year69(y); - set_hash("year", y); - } - } - else if (!NIL_P(s[8])) { - set_hash("cweek", str2num(s[7])); - set_hash("cwday", str2num(s[8])); - if (!NIL_P(s[6])) { - y = str2num(s[6]); - if (RSTRING_LEN(s[6]) < 4) - y = comp_year69(y); - set_hash("cwyear", y); - } - } - else if (!NIL_P(s[9])) { - set_hash("cwday", str2num(s[9])); - } - if (!NIL_P(s[10])) { - set_hash("hour", str2num(s[10])); - set_hash("min", str2num(s[11])); - if (!NIL_P(s[12])) - set_hash("sec", str2num(s[12])); - } - if (!NIL_P(s[13])) { - set_hash("sec_fraction", sec_fraction(s[13])); - } - if (!NIL_P(s[14])) { - set_hash("zone", s[14]); - set_hash("offset", date_zone_to_diff(s[14])); - } - - return 1; -} - -static int -iso8601_ext_datetime(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(?:([-+]?\\d{2,}|-)-(\\d{2})?(?:-(\\d{2}))?|" - "([-+]?\\d{2,})?-(\\d{3})|" - "(\\d{4}|\\d{2})?-w(\\d{2})-(\\d)|" - "-w-(\\d))" - "(?:t" - "(\\d{2}):(\\d{2})(?::(\\d{2})(?:[,.](\\d+))?)?" - "(z|[-+]\\d{2}(?::?\\d{2})?)?)?\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, iso8601_ext_datetime_cb); -} - -#undef SNUM -#define SNUM 17 - -static int -iso8601_bas_datetime_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1], y; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - if (!NIL_P(s[3])) { - set_hash("mday", str2num(s[3])); - if (strcmp(RSTRING_PTR(s[1]), "--") != 0) { - y = str2num(s[1]); - if (RSTRING_LEN(s[1]) < 4) - y = comp_year69(y); - set_hash("year", y); - } - if (*RSTRING_PTR(s[2]) == '-') { - if (strcmp(RSTRING_PTR(s[1]), "--") != 0) - return 0; - } - else - set_hash("mon", str2num(s[2])); - } - else if (!NIL_P(s[5])) { - set_hash("yday", str2num(s[5])); - y = str2num(s[4]); - if (RSTRING_LEN(s[4]) < 4) - y = comp_year69(y); - set_hash("year", y); - } - else if (!NIL_P(s[6])) { - set_hash("yday", str2num(s[6])); - } - else if (!NIL_P(s[9])) { - set_hash("cweek", str2num(s[8])); - set_hash("cwday", str2num(s[9])); - y = str2num(s[7]); - if (RSTRING_LEN(s[7]) < 4) - y = comp_year69(y); - set_hash("cwyear", y); - } - else if (!NIL_P(s[11])) { - set_hash("cweek", str2num(s[10])); - set_hash("cwday", str2num(s[11])); - } - else if (!NIL_P(s[12])) { - set_hash("cwday", str2num(s[12])); - } - if (!NIL_P(s[13])) { - set_hash("hour", str2num(s[13])); - set_hash("min", str2num(s[14])); - if (!NIL_P(s[15])) - set_hash("sec", str2num(s[15])); - } - if (!NIL_P(s[16])) { - set_hash("sec_fraction", sec_fraction(s[16])); - } - if (!NIL_P(s[17])) { - set_hash("zone", s[17]); - set_hash("offset", date_zone_to_diff(s[17])); - } - - return 1; -} - -static int -iso8601_bas_datetime(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(?:([-+]?(?:\\d{4}|\\d{2})|--)(\\d{2}|-)(\\d{2})|" - "([-+]?(?:\\d{4}|\\d{2}))(\\d{3})|" - "-(\\d{3})|" - "(\\d{4}|\\d{2})w(\\d{2})(\\d)|" - "-w(\\d{2})(\\d)|" - "-w-(\\d))" - "(?:t?" - "(\\d{2})(\\d{2})(?:(\\d{2})(?:[,.](\\d+))?)?" - "(z|[-+]\\d{2}(?:\\d{2})?)?)?\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, iso8601_bas_datetime_cb); -} - -#undef SNUM -#define SNUM 5 - -static int -iso8601_ext_time_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1]; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - set_hash("hour", str2num(s[1])); - set_hash("min", str2num(s[2])); - if (!NIL_P(s[3])) - set_hash("sec", str2num(s[3])); - if (!NIL_P(s[4])) - set_hash("sec_fraction", sec_fraction(s[4])); - if (!NIL_P(s[5])) { - set_hash("zone", s[5]); - set_hash("offset", date_zone_to_diff(s[5])); - } - - return 1; -} - -#define iso8601_bas_time_cb iso8601_ext_time_cb - -static int -iso8601_ext_time(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(\\d{2}):(\\d{2})(?::(\\d{2})(?:[,.](\\d+))?" - "(z|[-+]\\d{2}(:?\\d{2})?)?)?\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, iso8601_ext_time_cb); -} - -static int -iso8601_bas_time(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(\\d{2})(\\d{2})(?:(\\d{2})(?:[,.](\\d+))?" - "(z|[-+]\\d{2}(\\d{2})?)?)?\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, iso8601_bas_time_cb); -} - -VALUE -date__iso8601(VALUE str) -{ - VALUE backref, hash; - - backref = rb_backref_get(); - rb_match_busy(backref); - - hash = rb_hash_new(); - - if (iso8601_ext_datetime(str, hash)) - goto ok; - if (iso8601_bas_datetime(str, hash)) - goto ok; - if (iso8601_ext_time(str, hash)) - goto ok; - if (iso8601_bas_time(str, hash)) - goto ok; - - ok: - rb_backref_set(backref); - - return hash; -} - -#undef SNUM -#define SNUM 8 - -static int -rfc3339_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1]; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - set_hash("year", str2num(s[1])); - set_hash("mon", str2num(s[2])); - set_hash("mday", str2num(s[3])); - set_hash("hour", str2num(s[4])); - set_hash("min", str2num(s[5])); - set_hash("sec", str2num(s[6])); - set_hash("zone", s[8]); - set_hash("offset", date_zone_to_diff(s[8])); - if (!NIL_P(s[7])) - set_hash("sec_fraction", sec_fraction(s[7])); - - return 1; -} - -static int -rfc3339(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(-?\\d{4})-(\\d{2})-(\\d{2})" - "(?:t|\\s)" - "(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?" - "(z|[-+]\\d{2}:\\d{2})\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, rfc3339_cb); -} - -VALUE -date__rfc3339(VALUE str) -{ - VALUE backref, hash; - - backref = rb_backref_get(); - rb_match_busy(backref); - - hash = rb_hash_new(); - rfc3339(str, hash); - rb_backref_set(backref); - return hash; -} - -#undef SNUM -#define SNUM 8 - -static int -xmlschema_datetime_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1]; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - set_hash("year", str2num(s[1])); - if (!NIL_P(s[2])) - set_hash("mon", str2num(s[2])); - if (!NIL_P(s[3])) - set_hash("mday", str2num(s[3])); - if (!NIL_P(s[4])) - set_hash("hour", str2num(s[4])); - if (!NIL_P(s[5])) - set_hash("min", str2num(s[5])); - if (!NIL_P(s[6])) - set_hash("sec", str2num(s[6])); - if (!NIL_P(s[7])) - set_hash("sec_fraction", sec_fraction(s[7])); - if (!NIL_P(s[8])) { - set_hash("zone", s[8]); - set_hash("offset", date_zone_to_diff(s[8])); - } - - return 1; -} - -static int -xmlschema_datetime(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(-?\\d{4,})(?:-(\\d{2})(?:-(\\d{2}))?)?" - "(?:t" - "(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?)?" - "(z|[-+]\\d{2}:\\d{2})?\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, xmlschema_datetime_cb); -} - -#undef SNUM -#define SNUM 5 - -static int -xmlschema_time_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1]; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - set_hash("hour", str2num(s[1])); - set_hash("min", str2num(s[2])); - if (!NIL_P(s[3])) - set_hash("sec", str2num(s[3])); - if (!NIL_P(s[4])) - set_hash("sec_fraction", sec_fraction(s[4])); - if (!NIL_P(s[5])) { - set_hash("zone", s[5]); - set_hash("offset", date_zone_to_diff(s[5])); - } - - return 1; -} - -static int -xmlschema_time(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?" - "(z|[-+]\\d{2}:\\d{2})?\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, xmlschema_time_cb); -} - -#undef SNUM -#define SNUM 4 - -static int -xmlschema_trunc_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1]; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - if (!NIL_P(s[1])) - set_hash("mon", str2num(s[1])); - if (!NIL_P(s[2])) - set_hash("mday", str2num(s[2])); - if (!NIL_P(s[3])) - set_hash("mday", str2num(s[3])); - if (!NIL_P(s[4])) { - set_hash("zone", s[4]); - set_hash("offset", date_zone_to_diff(s[4])); - } - - return 1; -} - -static int -xmlschema_trunc(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(?:--(\\d{2})(?:-(\\d{2}))?|---(\\d{2}))" - "(z|[-+]\\d{2}:\\d{2})?\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, xmlschema_trunc_cb); -} - -VALUE -date__xmlschema(VALUE str) -{ - VALUE backref, hash; - - backref = rb_backref_get(); - rb_match_busy(backref); - - hash = rb_hash_new(); - - if (xmlschema_datetime(str, hash)) - goto ok; - if (xmlschema_time(str, hash)) - goto ok; - if (xmlschema_trunc(str, hash)) - goto ok; - - ok: - rb_backref_set(backref); - - return hash; -} - -#undef SNUM -#define SNUM 8 - -static int -rfc2822_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1], y; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - if (!NIL_P(s[1])) { - set_hash("wday", INT2FIX(day_num(s[1]))); - } - set_hash("mday", str2num(s[2])); - set_hash("mon", INT2FIX(mon_num(s[3]))); - y = str2num(s[4]); - if (RSTRING_LEN(s[4]) < 4) - y = comp_year50(y); - set_hash("year", y); - set_hash("hour", str2num(s[5])); - set_hash("min", str2num(s[6])); - if (!NIL_P(s[7])) - set_hash("sec", str2num(s[7])); - set_hash("zone", s[8]); - set_hash("offset", date_zone_to_diff(s[8])); - - return 1; -} - -static int -rfc2822(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(?:(" ABBR_DAYS ")\\s*,\\s+)?" - "(\\d{1,2})\\s+" - "(" ABBR_MONTHS ")\\s+" - "(-?\\d{2,})\\s+" - "(\\d{2}):(\\d{2})(?::(\\d{2}))?\\s*" - "([-+]\\d{4}|ut|gmt|e[sd]t|c[sd]t|m[sd]t|p[sd]t|[a-ik-z])\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, rfc2822_cb); -} - -VALUE -date__rfc2822(VALUE str) -{ - VALUE backref, hash; - - backref = rb_backref_get(); - rb_match_busy(backref); - - hash = rb_hash_new(); - rfc2822(str, hash); - rb_backref_set(backref); - return hash; -} - -#undef SNUM -#define SNUM 8 - -static int -httpdate_type1_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1]; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - set_hash("wday", INT2FIX(day_num(s[1]))); - set_hash("mday", str2num(s[2])); - set_hash("mon", INT2FIX(mon_num(s[3]))); - set_hash("year", str2num(s[4])); - set_hash("hour", str2num(s[5])); - set_hash("min", str2num(s[6])); - set_hash("sec", str2num(s[7])); - set_hash("zone", s[8]); - set_hash("offset", INT2FIX(0)); - - return 1; -} - -static int -httpdate_type1(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(" ABBR_DAYS ")\\s*,\\s+" - "(\\d{2})\\s+" - "(" ABBR_MONTHS ")\\s+" - "(-?\\d{4})\\s+" - "(\\d{2}):(\\d{2}):(\\d{2})\\s+" - "(gmt)\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, httpdate_type1_cb); -} - -#undef SNUM -#define SNUM 8 - -static int -httpdate_type2_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1], y; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - set_hash("wday", INT2FIX(day_num(s[1]))); - set_hash("mday", str2num(s[2])); - set_hash("mon", INT2FIX(mon_num(s[3]))); - y = str2num(s[4]); - if (f_ge_p(y, INT2FIX(0)) && f_le_p(y, INT2FIX(99))) - y = comp_year69(y); - set_hash("year", y); - set_hash("hour", str2num(s[5])); - set_hash("min", str2num(s[6])); - set_hash("sec", str2num(s[7])); - set_hash("zone", s[8]); - set_hash("offset", INT2FIX(0)); - - return 1; -} - -static int -httpdate_type2(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(" DAYS ")\\s*,\\s+" - "(\\d{2})\\s*-\\s*" - "(" ABBR_MONTHS ")\\s*-\\s*" - "(\\d{2})\\s+" - "(\\d{2}):(\\d{2}):(\\d{2})\\s+" - "(gmt)\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, httpdate_type2_cb); -} - -#undef SNUM -#define SNUM 7 - -static int -httpdate_type3_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1]; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - set_hash("wday", INT2FIX(day_num(s[1]))); - set_hash("mon", INT2FIX(mon_num(s[2]))); - set_hash("mday", str2num(s[3])); - set_hash("hour", str2num(s[4])); - set_hash("min", str2num(s[5])); - set_hash("sec", str2num(s[6])); - set_hash("year", str2num(s[7])); - - return 1; -} - -static int -httpdate_type3(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*(" ABBR_DAYS ")\\s+" - "(" ABBR_MONTHS ")\\s+" - "(\\d{1,2})\\s+" - "(\\d{2}):(\\d{2}):(\\d{2})\\s+" - "(\\d{4})\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, httpdate_type3_cb); -} - -VALUE -date__httpdate(VALUE str) -{ - VALUE backref, hash; - - backref = rb_backref_get(); - rb_match_busy(backref); - - hash = rb_hash_new(); - - if (httpdate_type1(str, hash)) - goto ok; - if (httpdate_type2(str, hash)) - goto ok; - if (httpdate_type3(str, hash)) - goto ok; - - ok: - rb_backref_set(backref); - - return hash; -} - -#undef SNUM -#define SNUM 9 - -static int -jisx0301_cb(VALUE m, VALUE hash) -{ - VALUE s[SNUM + 1]; - int ep; - - { - int i; - s[0] = Qnil; - for (i = 1; i <= SNUM; i++) - s[i] = rb_reg_nth_match(i, m); - } - - ep = gengo(NIL_P(s[1]) ? JISX0301_DEFAULT_ERA : *RSTRING_PTR(s[1])); - set_hash("year", f_add(str2num(s[2]), INT2FIX(ep))); - set_hash("mon", str2num(s[3])); - set_hash("mday", str2num(s[4])); - if (!NIL_P(s[5])) { - set_hash("hour", str2num(s[5])); - if (!NIL_P(s[6])) - set_hash("min", str2num(s[6])); - if (!NIL_P(s[7])) - set_hash("sec", str2num(s[7])); - } - if (!NIL_P(s[8])) - set_hash("sec_fraction", sec_fraction(s[8])); - if (!NIL_P(s[9])) { - set_hash("zone", s[9]); - set_hash("offset", date_zone_to_diff(s[9])); - } - - return 1; -} - -static int -jisx0301(VALUE str, VALUE hash) -{ - static const char pat_source[] = - "\\A\\s*([" JISX0301_ERA_INITIALS "])?(\\d{2})\\.(\\d{2})\\.(\\d{2})" - "(?:t" - "(?:(\\d{2}):(\\d{2})(?::(\\d{2})(?:[,.](\\d*))?)?" - "(z|[-+]\\d{2}(?::?\\d{2})?)?)?)?\\s*\\z"; - static VALUE pat = Qnil; - - REGCOMP_I(pat); - MATCH(str, pat, jisx0301_cb); -} - -VALUE -date__jisx0301(VALUE str) -{ - VALUE backref, hash; - - backref = rb_backref_get(); - rb_match_busy(backref); - - hash = rb_hash_new(); - if (jisx0301(str, hash)) - goto ok; - hash = date__iso8601(str); - - ok: - rb_backref_set(backref); - return hash; -} - -/* -Local variables: -c-file-style: "ruby" -End: -*/ diff --git a/ext/date/date_strftime.c b/ext/date/date_strftime.c deleted file mode 100644 index d7f28989..00000000 --- a/ext/date/date_strftime.c +++ /dev/null @@ -1,638 +0,0 @@ -/* - date_strftime.c: based on a public-domain implementation of ANSI C - library routine strftime, which is originally written by Arnold - Robbins. - */ - -#include "ruby/ruby.h" -#include "date_tmx.h" - -#include -#include -#include -#include - -#if defined(HAVE_SYS_TIME_H) -#include -#endif - -#undef strchr /* avoid AIX weirdness */ - -#define range(low, item, hi) (item) - -#define add(x,y) (rb_funcall((x), '+', 1, (y))) -#define sub(x,y) (rb_funcall((x), '-', 1, (y))) -#define mul(x,y) (rb_funcall((x), '*', 1, (y))) -#define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y))) -#define div(x,y) (rb_funcall((x), rb_intern("div"), 1, (y))) -#define mod(x,y) (rb_funcall((x), '%', 1, (y))) - -static void -upcase(char *s, size_t i) -{ - do { - if (ISLOWER(*s)) - *s = TOUPPER(*s); - } while (s++, --i); -} - -static void -downcase(char *s, size_t i) -{ - do { - if (ISUPPER(*s)) - *s = TOLOWER(*s); - } while (s++, --i); -} - -/* strftime --- produce formatted time */ - -static size_t -date_strftime_with_tmx(char *s, const size_t maxsize, const char *format, - const struct tmx *tmx) -{ - char *endp = s + maxsize; - char *start = s; - const char *sp, *tp; - auto char tbuf[100]; - ptrdiff_t i; - int v, w; - size_t colons; - int precision, flags; - char padding; - /* LOCALE_[OE] and COLONS are actually modifiers, not flags */ - enum {LEFT, CHCASE, LOWER, UPPER, LOCALE_O, LOCALE_E, COLONS}; -#define BIT_OF(n) (1U<<(n)) - - /* various tables for locale C */ - static const char days_l[][10] = { - "Sunday", "Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday", - }; - static const char months_l[][10] = { - "January", "February", "March", "April", - "May", "June", "July", "August", "September", - "October", "November", "December", - }; - static const char ampm[][3] = { "AM", "PM", }; - - if (s == NULL || format == NULL || tmx == NULL || maxsize == 0) - return 0; - - /* quick check if we even need to bother */ - if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) { - err: - errno = ERANGE; - return 0; - } - - for (; *format && s < endp - 1; format++) { -#define FLAG_FOUND() do { \ - if (precision > 0 || flags & (BIT_OF(LOCALE_E) | BIT_OF(LOCALE_O) | BIT_OF(COLONS))) \ - goto unknown; \ - } while (0) -#define NEEDS(n) do if (s >= endp || (n) >= endp - s - 1) goto err; while (0) -#define FILL_PADDING(i) do { \ - if (!(flags & BIT_OF(LEFT)) && precision > (i)) { \ - NEEDS(precision); \ - memset(s, padding ? padding : ' ', precision - (i)); \ - s += precision - (i); \ - } \ - else { \ - NEEDS(i); \ - } \ - } while (0); -#define FMT(def_pad, def_prec, fmt, val) \ - do { \ - int l; \ - if (precision <= 0) precision = (def_prec); \ - if (flags & BIT_OF(LEFT)) precision = 1; \ - l = snprintf(s, endp - s, \ - ((padding == '0' || (!padding && (def_pad) == '0')) ? \ - "%0*"fmt : "%*"fmt), \ - precision, (val)); \ - if (l < 0) goto err; \ - s += l; \ - } while (0) -#define STRFTIME(fmt) \ - do { \ - i = date_strftime_with_tmx(s, endp - s, (fmt), tmx); \ - if (!i) return 0; \ - if (flags & BIT_OF(UPPER)) \ - upcase(s, i); \ - if (!(flags & BIT_OF(LEFT)) && precision > i) { \ - if (start + maxsize < s + precision) { \ - errno = ERANGE; \ - return 0; \ - } \ - memmove(s + precision - i, s, i); \ - memset(s, padding ? padding : ' ', precision - i); \ - s += precision; \ - } \ - else s += i; \ - } while (0) -#define FMTV(def_pad, def_prec, fmt, val) \ - do { \ - VALUE tmp = (val); \ - if (FIXNUM_P(tmp)) { \ - FMT((def_pad), (def_prec), "l"fmt, FIX2LONG(tmp)); \ - } \ - else { \ - VALUE args[2], result; \ - size_t l; \ - if (precision <= 0) precision = (def_prec); \ - if (flags & BIT_OF(LEFT)) precision = 1; \ - args[0] = INT2FIX(precision); \ - args[1] = (val); \ - if (padding == '0' || (!padding && (def_pad) == '0')) \ - result = rb_str_format(2, args, rb_str_new2("%0*"fmt)); \ - else \ - result = rb_str_format(2, args, rb_str_new2("%*"fmt)); \ - l = strlcpy(s, StringValueCStr(result), endp - s); \ - if ((size_t)(endp - s) <= l) \ - goto err; \ - s += l; \ - } \ - } while (0) - - if (*format != '%') { - *s++ = *format; - continue; - } - tp = tbuf; - sp = format; - precision = -1; - flags = 0; - padding = 0; - colons = 0; - again: - switch (*++format) { - case '\0': - format--; - goto unknown; - - case 'A': /* full weekday name */ - case 'a': /* abbreviated weekday name */ - if (flags & BIT_OF(CHCASE)) { - flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE)); - flags |= BIT_OF(UPPER); - } - { - int wday = tmx_wday; - if (wday < 0 || wday > 6) - i = 1, tp = "?"; - else { - if (*format == 'A') - i = strlen(tp = days_l[wday]); - else - i = 3, tp = days_l[wday]; - } - } - break; - - case 'B': /* full month name */ - case 'b': /* abbreviated month name */ - case 'h': /* same as %b */ - if (flags & BIT_OF(CHCASE)) { - flags &= ~(BIT_OF(LOWER) | BIT_OF(CHCASE)); - flags |= BIT_OF(UPPER); - } - { - int mon = tmx_mon; - if (mon < 1 || mon > 12) - i = 1, tp = "?"; - else { - if (*format == 'B') - i = strlen(tp = months_l[mon - 1]); - else - i = 3, tp = months_l[mon - 1]; - } - } - break; - - case 'C': /* century (year/100) */ - FMTV('0', 2, "d", div(tmx_year, INT2FIX(100))); - continue; - - case 'c': /* appropriate date and time representation */ - STRFTIME("%a %b %e %H:%M:%S %Y"); - continue; - - case 'D': - STRFTIME("%m/%d/%y"); - continue; - - case 'd': /* day of the month, 01 - 31 */ - case 'e': /* day of month, blank padded */ - v = range(1, tmx_mday, 31); - FMT((*format == 'd') ? '0' : ' ', 2, "d", v); - continue; - - case 'F': - STRFTIME("%Y-%m-%d"); - continue; - - case 'G': /* year of ISO week with century */ - case 'Y': /* year with century */ - { - VALUE year = (*format == 'G') ? tmx_cwyear : tmx_year; - if (FIXNUM_P(year)) { - long y = FIX2LONG(year); - FMT('0', 0 <= y ? 4 : 5, "ld", y); - } - else { - FMTV('0', 4, "d", year); - } - } - continue; - - case 'g': /* year of ISO week without a century */ - case 'y': /* year without a century */ - v = NUM2INT(mod((*format == 'g') ? tmx_cwyear : tmx_year, INT2FIX(100))); - FMT('0', 2, "d", v); - continue; - - case 'H': /* hour, 24-hour clock, 00 - 23 */ - case 'k': /* hour, 24-hour clock, blank pad */ - v = range(0, tmx_hour, 23); - FMT((*format == 'H') ? '0' : ' ', 2, "d", v); - continue; - - case 'I': /* hour, 12-hour clock, 01 - 12 */ - case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */ - v = range(0, tmx_hour, 23); - if (v == 0) - v = 12; - else if (v > 12) - v -= 12; - FMT((*format == 'I') ? '0' : ' ', 2, "d", v); - continue; - - case 'j': /* day of the year, 001 - 366 */ - v = range(1, tmx_yday, 366); - FMT('0', 3, "d", v); - continue; - - case 'L': /* millisecond */ - case 'N': /* nanosecond */ - if (*format == 'L') - w = 3; - else - w = 9; - if (precision <= 0) - precision = w; - NEEDS(precision); - - { - VALUE subsec = tmx_sec_fraction; - int ww; - long n; - - ww = precision; - while (9 <= ww) { - subsec = mul(subsec, INT2FIX(1000000000)); - ww -= 9; - } - n = 1; - for (; 0 < ww; ww--) - n *= 10; - if (n != 1) - subsec = mul(subsec, INT2FIX(n)); - subsec = div(subsec, INT2FIX(1)); - - if (FIXNUM_P(subsec)) { - (void)snprintf(s, endp - s, "%0*ld", - precision, FIX2LONG(subsec)); - s += precision; - } - else { - VALUE args[2], result; - args[0] = INT2FIX(precision); - args[1] = subsec; - result = rb_str_format(2, args, rb_str_new2("%0*d")); - (void)strlcpy(s, StringValueCStr(result), endp - s); - s += precision; - } - } - continue; - - case 'M': /* minute, 00 - 59 */ - v = range(0, tmx_min, 59); - FMT('0', 2, "d", v); - continue; - - case 'm': /* month, 01 - 12 */ - v = range(1, tmx_mon, 12); - FMT('0', 2, "d", v); - continue; - - case 'n': /* same as \n */ - FILL_PADDING(1); - *s++ = '\n'; - continue; - - case 't': /* same as \t */ - FILL_PADDING(1); - *s++ = '\t'; - continue; - - case 'P': /* am or pm based on 12-hour clock */ - case 'p': /* AM or PM based on 12-hour clock */ - if ((*format == 'p' && (flags & BIT_OF(CHCASE))) || - (*format == 'P' && !(flags & (BIT_OF(CHCASE) | BIT_OF(UPPER))))) { - flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE)); - flags |= BIT_OF(LOWER); - } - v = range(0, tmx_hour, 23); - if (v < 12) - tp = ampm[0]; - else - tp = ampm[1]; - i = 2; - break; - - case 'Q': /* milliseconds since Unix epoch */ - FMTV('0', 1, "d", tmx_msecs); - continue; - - case 'R': - STRFTIME("%H:%M"); - continue; - - case 'r': - STRFTIME("%I:%M:%S %p"); - continue; - - case 'S': /* second, 00 - 59 */ - v = range(0, tmx_sec, 59); - FMT('0', 2, "d", v); - continue; - - case 's': /* seconds since Unix epoch */ - FMTV('0', 1, "d", tmx_secs); - continue; - - case 'T': - STRFTIME("%H:%M:%S"); - continue; - - case 'U': /* week of year, Sunday is first day of week */ - case 'W': /* week of year, Monday is first day of week */ - v = range(0, (*format == 'U') ? tmx_wnum0 : tmx_wnum1, 53); - FMT('0', 2, "d", v); - continue; - - case 'u': /* weekday, Monday == 1, 1 - 7 */ - v = range(1, tmx_cwday, 7); - FMT('0', 1, "d", v); - continue; - - case 'V': /* week of year according ISO 8601 */ - v = range(1, tmx_cweek, 53); - FMT('0', 2, "d", v); - continue; - - case 'v': - STRFTIME("%e-%^b-%Y"); - continue; - - case 'w': /* weekday, Sunday == 0, 0 - 6 */ - v = range(0, tmx_wday, 6); - FMT('0', 1, "d", v); - continue; - - case 'X': /* appropriate time representation */ - STRFTIME("%H:%M:%S"); - continue; - - case 'x': /* appropriate date representation */ - STRFTIME("%m/%d/%y"); - continue; - - case 'Z': /* time zone name or abbreviation */ - if (flags & BIT_OF(CHCASE)) { - flags &= ~(BIT_OF(UPPER) | BIT_OF(CHCASE)); - flags |= BIT_OF(LOWER); - } - { - char *zone = tmx_zone; - if (zone == NULL) - tp = ""; - else - tp = zone; - i = strlen(tp); - } - break; - - case 'z': /* offset from UTC */ - { - long off, aoff; - int hl, hw; - - off = tmx_offset; - aoff = off; - if (aoff < 0) - aoff = -off; - - if ((aoff / 3600) < 10) - hl = 1; - else - hl = 2; - hw = 2; - if (flags & BIT_OF(LEFT) && hl == 1) - hw = 1; - - switch (colons) { - case 0: /* %z -> +hhmm */ - precision = precision <= (3 + hw) ? hw : precision - 3; - NEEDS(precision + 3); - break; - - case 1: /* %:z -> +hh:mm */ - precision = precision <= (4 + hw) ? hw : precision - 4; - NEEDS(precision + 4); - break; - - case 2: /* %::z -> +hh:mm:ss */ - precision = precision <= (7 + hw) ? hw : precision - 7; - NEEDS(precision + 7); - break; - - case 3: /* %:::z -> +hh[:mm[:ss]] */ - { - if (aoff % 3600 == 0) { - precision = precision <= (1 + hw) ? - hw : precision - 1; - NEEDS(precision + 3); - } - else if (aoff % 60 == 0) { - precision = precision <= (4 + hw) ? - hw : precision - 4; - NEEDS(precision + 4); - } - else { - precision = precision <= (7 + hw) ? - hw : precision - 7; - NEEDS(precision + 7); - } - } - break; - - default: - format--; - goto unknown; - } - if (padding == ' ' && precision > hl) { - i = snprintf(s, endp - s, "%*s", precision - hl, ""); - precision = hl; - if (i < 0) goto err; - s += i; - } - if (off < 0) { - off = -off; - *s++ = '-'; - } else { - *s++ = '+'; - } - i = snprintf(s, endp - s, "%.*ld", precision, off / 3600); - if (i < 0) goto err; - s += i; - off = off % 3600; - if (colons == 3 && off == 0) - continue; - if (1 <= colons) - *s++ = ':'; - i = snprintf(s, endp - s, "%02d", (int)(off / 60)); - if (i < 0) goto err; - s += i; - off = off % 60; - if (colons == 3 && off == 0) - continue; - if (2 <= colons) { - *s++ = ':'; - i = snprintf(s, endp - s, "%02d", (int)off); - if (i < 0) goto err; - s += i; - } - } - continue; - - case '+': - STRFTIME("%a %b %e %H:%M:%S %Z %Y"); - continue; - - case 'E': - /* POSIX locale extensions, ignored for now */ - flags |= BIT_OF(LOCALE_E); - if (*(format + 1) && strchr("cCxXyY", *(format + 1))) - goto again; - goto unknown; - case 'O': - /* POSIX locale extensions, ignored for now */ - flags |= BIT_OF(LOCALE_O); - if (*(format + 1) && strchr("deHkIlmMSuUVwWy", *(format + 1))) - goto again; - goto unknown; - - case ':': - flags |= BIT_OF(COLONS); - { - size_t l = strspn(format, ":"); - format += l; - if (*format == 'z') { - colons = l; - format--; - goto again; - } - format -= l; - } - goto unknown; - - case '_': - FLAG_FOUND(); - padding = ' '; - goto again; - - case '-': - FLAG_FOUND(); - flags |= BIT_OF(LEFT); - goto again; - - case '^': - FLAG_FOUND(); - flags |= BIT_OF(UPPER); - goto again; - - case '#': - FLAG_FOUND(); - flags |= BIT_OF(CHCASE); - goto again; - - case '0': - FLAG_FOUND(); - padding = '0'; - case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - { - char *e; - unsigned long prec = strtoul(format, &e, 10); - if (prec > INT_MAX || prec > maxsize) { - errno = ERANGE; - return 0; - } - precision = (int)prec; - format = e - 1; - goto again; - } - - case '%': - FILL_PADDING(1); - *s++ = '%'; - continue; - - default: - unknown: - i = format - sp + 1; - tp = sp; - precision = -1; - flags = 0; - padding = 0; - colons = 0; - break; - } - if (i) { - FILL_PADDING(i); - memcpy(s, tp, i); - switch (flags & (BIT_OF(UPPER) | BIT_OF(LOWER))) { - case BIT_OF(UPPER): - upcase(s, i); - break; - case BIT_OF(LOWER): - downcase(s, i); - break; - } - s += i; - } - } - if (s >= endp) { - goto err; - } - if (*format == '\0') { - *s = '\0'; - return (s - start); - } - return 0; -} - -size_t -date_strftime(char *s, size_t maxsize, const char *format, - const struct tmx *tmx) -{ - return date_strftime_with_tmx(s, maxsize, format, tmx); -} - -/* -Local variables: -c-file-style: "ruby" -End: -*/ diff --git a/ext/date/date_strptime.c b/ext/date/date_strptime.c deleted file mode 100644 index 1dde5fa3..00000000 --- a/ext/date/date_strptime.c +++ /dev/null @@ -1,704 +0,0 @@ -/* - date_strptime.c: Coded by Tadayoshi Funaba 2011,2012 -*/ - -#include "ruby.h" -#include "ruby/encoding.h" -#include "ruby/re.h" -#include - -#undef strncasecmp -#define strncasecmp STRNCASECMP - -static const char *day_names[] = { - "Sunday", "Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday", -}; -static const int ABBREVIATED_DAY_NAME_LENGTH = 3; - -static const char *month_names[] = { - "January", "February", "March", "April", - "May", "June", "July", "August", "September", - "October", "November", "December", -}; -static const int ABBREVIATED_MONTH_NAME_LENGTH = 3; - -#define sizeof_array(o) (sizeof o / sizeof o[0]) - -#define f_negate(x) rb_funcall(x, rb_intern("-@"), 0) -#define f_add(x,y) rb_funcall(x, '+', 1, y) -#define f_sub(x,y) rb_funcall(x, '-', 1, y) -#define f_mul(x,y) rb_funcall(x, '*', 1, y) -#define f_div(x,y) rb_funcall(x, '/', 1, y) -#define f_idiv(x,y) rb_funcall(x, rb_intern("div"), 1, y) -#define f_mod(x,y) rb_funcall(x, '%', 1, y) -#define f_expt(x,y) rb_funcall(x, rb_intern("**"), 1, y) - -#define f_lt_p(x,y) rb_funcall(x, '<', 1, y) -#define f_gt_p(x,y) rb_funcall(x, '>', 1, y) -#define f_le_p(x,y) rb_funcall(x, rb_intern("<="), 1, y) -#define f_ge_p(x,y) rb_funcall(x, rb_intern(">="), 1, y) - -#define f_match(r,s) rb_funcall(r, rb_intern("match"), 1, s) -#define f_aref(o,i) rb_funcall(o, rb_intern("[]"), 1, i) -#define f_end(o,i) rb_funcall(o, rb_intern("end"), 1, i) - -#define issign(c) ((c) == '-' || (c) == '+') - -static int -num_pattern_p(const char *s) -{ - if (isdigit((unsigned char)*s)) - return 1; - if (*s == '%') { - s++; - if (*s == 'E' || *s == 'O') - s++; - if (*s && - (strchr("CDdeFGgHIjkLlMmNQRrSsTUuVvWwXxYy", *s) || - isdigit((unsigned char)*s))) - return 1; - } - return 0; -} - -#define NUM_PATTERN_P() num_pattern_p(&fmt[fi + 1]) - -static long -read_digits(const char *s, size_t slen, VALUE *n, size_t width) -{ - size_t l; - - if (!width) - return 0; - - l = 0; - while (l < slen && ISDIGIT(s[l])) { - if (++l == width) break; - } - - if (l == 0) - return 0; - - if ((4 * l * sizeof(char)) <= (sizeof(long)*CHAR_BIT)) { - const char *os = s; - long v; - - v = 0; - while ((size_t)(s - os) < l) { - v *= 10; - v += *s - '0'; - s++; - } - if (os == s) - return 0; - *n = LONG2NUM(v); - return l; - } - else { - VALUE vbuf = 0; - char *s2 = ALLOCV_N(char, vbuf, l + 1); - memcpy(s2, s, l); - s2[l] = '\0'; - *n = rb_cstr_to_inum(s2, 10, 0); - ALLOCV_END(vbuf); - return l; - } -} - -#define set_hash(k,v) rb_hash_aset(hash, ID2SYM(rb_intern(k"")), v) -#define ref_hash(k) rb_hash_aref(hash, ID2SYM(rb_intern(k""))) -#define del_hash(k) rb_hash_delete(hash, ID2SYM(rb_intern(k""))) - -#define fail() \ -do { \ - set_hash("_fail", Qtrue); \ - return 0; \ -} while (0) - -#define fail_p() (!NIL_P(ref_hash("_fail"))) - -#define READ_DIGITS(n,w) \ -do { \ - size_t l; \ - l = read_digits(&str[si], slen - si, &n, w); \ - if (l == 0) { \ - fail(); \ - } \ - si += l; \ -} while (0) - -#define READ_DIGITS_MAX(n) READ_DIGITS(n, LONG_MAX) - -static int -valid_range_p(VALUE v, int a, int b) -{ - if (FIXNUM_P(v)) { - int vi = FIX2INT(v); - return !(vi < a || vi > b); - } - return !(f_lt_p(v, INT2NUM(a)) || f_gt_p(v, INT2NUM(b))); -} - -#define recur(fmt) \ -do { \ - size_t l; \ - l = date__strptime_internal(&str[si], slen - si, \ - fmt, sizeof fmt - 1, hash); \ - if (fail_p()) \ - return 0; \ - si += l; \ -} while (0) - -VALUE date_zone_to_diff(VALUE); - -static inline int -head_match_p(size_t len, const char *name, const char *str, size_t slen, size_t si) -{ - return slen - si >= len && strncasecmp(name, &str[si], len) == 0; -} - -static size_t -date__strptime_internal(const char *str, size_t slen, - const char *fmt, size_t flen, VALUE hash) -{ - size_t si, fi; - int c; - -#define HEAD_MATCH_P(len, name) head_match_p(len, name, str, slen, si) - si = fi = 0; - - while (fi < flen) { - if (isspace((unsigned char)fmt[fi])) { - while (si < slen && isspace((unsigned char)str[si])) - si++; - while (++fi < flen && isspace((unsigned char)fmt[fi])); - continue; - } - - if (si >= slen) fail(); - - switch (fmt[fi]) { - case '%': - - again: - fi++; - c = fmt[fi]; - - switch (c) { - case 'E': - if (fmt[fi + 1] && strchr("cCxXyY", fmt[fi + 1])) - goto again; - fi--; - goto ordinal; - case 'O': - if (fmt[fi + 1] && strchr("deHImMSuUVwWy", fmt[fi + 1])) - goto again; - fi--; - goto ordinal; - case ':': - { - int i; - - for (i = 1; i < 3 && fi + i < flen && fmt[fi+i] == ':'; ++i); - if (fmt[fi+i] == 'z') { - fi += i - 1; - goto again; - } - fail(); - } - - case 'A': - case 'a': - { - int i; - - for (i = 0; i < (int)sizeof_array(day_names); i++) { - const char *day_name = day_names[i]; - size_t l = strlen(day_name); - if (HEAD_MATCH_P(l, day_name) || - HEAD_MATCH_P(l = ABBREVIATED_DAY_NAME_LENGTH, day_name)) { - si += l; - set_hash("wday", INT2FIX(i)); - goto matched; - } - } - fail(); - } - case 'B': - case 'b': - case 'h': - { - int i; - - for (i = 0; i < (int)sizeof_array(month_names); i++) { - const char *month_name = month_names[i]; - size_t l = strlen(month_name); - if (HEAD_MATCH_P(l, month_name) || - HEAD_MATCH_P(l = ABBREVIATED_MONTH_NAME_LENGTH, month_name)) { - si += l; - set_hash("mon", INT2FIX(i + 1)); - goto matched; - } - } - fail(); - } - - case 'C': - { - VALUE n; - - if (NUM_PATTERN_P()) - READ_DIGITS(n, 2); - else - READ_DIGITS_MAX(n); - set_hash("_cent", n); - goto matched; - } - - case 'c': - recur("%a %b %e %H:%M:%S %Y"); - goto matched; - - case 'D': - recur("%m/%d/%y"); - goto matched; - - case 'd': - case 'e': - { - VALUE n; - - if (str[si] == ' ') { - si++; - READ_DIGITS(n, 1); - } else { - READ_DIGITS(n, 2); - } - if (!valid_range_p(n, 1, 31)) - fail(); - set_hash("mday", n); - goto matched; - } - - case 'F': - recur("%Y-%m-%d"); - goto matched; - - case 'G': - { - VALUE n; - - if (NUM_PATTERN_P()) - READ_DIGITS(n, 4); - else - READ_DIGITS_MAX(n); - set_hash("cwyear", n); - goto matched; - } - - case 'g': - { - VALUE n; - - READ_DIGITS(n, 2); - if (!valid_range_p(n, 0, 99)) - fail(); - set_hash("cwyear",n); - if (NIL_P(ref_hash("_cent"))) - set_hash("_cent", - INT2FIX(f_ge_p(n, INT2FIX(69)) ? 19 : 20)); - goto matched; - } - - case 'H': - case 'k': - { - VALUE n; - - if (str[si] == ' ') { - si++; - READ_DIGITS(n, 1); - } else { - READ_DIGITS(n, 2); - } - if (!valid_range_p(n, 0, 24)) - fail(); - set_hash("hour", n); - goto matched; - } - - case 'I': - case 'l': - { - VALUE n; - - if (str[si] == ' ') { - si++; - READ_DIGITS(n, 1); - } else { - READ_DIGITS(n, 2); - } - if (!valid_range_p(n, 1, 12)) - fail(); - set_hash("hour", n); - goto matched; - } - - case 'j': - { - VALUE n; - - READ_DIGITS(n, 3); - if (!valid_range_p(n, 1, 366)) - fail(); - set_hash("yday", n); - goto matched; - } - - case 'L': - case 'N': - { - VALUE n; - int sign = 1; - size_t osi; - - if (issign(str[si])) { - if (str[si] == '-') - sign = -1; - si++; - } - osi = si; - if (NUM_PATTERN_P()) - READ_DIGITS(n, c == 'L' ? 3 : 9); - else - READ_DIGITS_MAX(n); - if (sign == -1) - n = f_negate(n); - set_hash("sec_fraction", - rb_rational_new2(n, - f_expt(INT2FIX(10), - ULONG2NUM(si - osi)))); - goto matched; - } - - case 'M': - { - VALUE n; - - READ_DIGITS(n, 2); - if (!valid_range_p(n, 0, 59)) - fail(); - set_hash("min", n); - goto matched; - } - - case 'm': - { - VALUE n; - - READ_DIGITS(n, 2); - if (!valid_range_p(n, 1, 12)) - fail(); - set_hash("mon", n); - goto matched; - } - - case 'n': - case 't': - recur(" "); - goto matched; - - case 'P': - case 'p': - if (slen - si < 2) fail(); - { - char c = str[si]; - const int hour = (c == 'P' || c == 'p') ? 12 : 0; - if (!hour && !(c == 'A' || c == 'a')) fail(); - if ((c = str[si+1]) == '.') { - if (slen - si < 4 || str[si+3] != '.') fail(); - c = str[si += 2]; - } - if (!(c == 'M' || c == 'm')) fail(); - si += 2; - set_hash("_merid", INT2FIX(hour)); - goto matched; - } - - case 'Q': - { - VALUE n; - int sign = 1; - - if (str[si] == '-') { - sign = -1; - si++; - } - READ_DIGITS_MAX(n); - if (sign == -1) - n = f_negate(n); - set_hash("seconds", - rb_rational_new2(n, INT2FIX(1000))); - goto matched; - } - - case 'R': - recur("%H:%M"); - goto matched; - - case 'r': - recur("%I:%M:%S %p"); - goto matched; - - case 'S': - { - VALUE n; - - READ_DIGITS(n, 2); - if (!valid_range_p(n, 0, 60)) - fail(); - set_hash("sec", n); - goto matched; - } - - case 's': - { - VALUE n; - int sign = 1; - - if (str[si] == '-') { - sign = -1; - si++; - } - READ_DIGITS_MAX(n); - if (sign == -1) - n = f_negate(n); - set_hash("seconds", n); - goto matched; - } - - case 'T': - recur("%H:%M:%S"); - goto matched; - - case 'U': - case 'W': - { - VALUE n; - - READ_DIGITS(n, 2); - if (!valid_range_p(n, 0, 53)) - fail(); - set_hash(c == 'U' ? "wnum0" : "wnum1", n); - goto matched; - } - - case 'u': - { - VALUE n; - - READ_DIGITS(n, 1); - if (!valid_range_p(n, 1, 7)) - fail(); - set_hash("cwday", n); - goto matched; - } - - case 'V': - { - VALUE n; - - READ_DIGITS(n, 2); - if (!valid_range_p(n, 1, 53)) - fail(); - set_hash("cweek", n); - goto matched; - } - - case 'v': - recur("%e-%b-%Y"); - goto matched; - - case 'w': - { - VALUE n; - - READ_DIGITS(n, 1); - if (!valid_range_p(n, 0, 6)) - fail(); - set_hash("wday", n); - goto matched; - } - - case 'X': - recur("%H:%M:%S"); - goto matched; - - case 'x': - recur("%m/%d/%y"); - goto matched; - - case 'Y': - { - VALUE n; - int sign = 1; - - if (issign(str[si])) { - if (str[si] == '-') - sign = -1; - si++; - } - if (NUM_PATTERN_P()) - READ_DIGITS(n, 4); - else - READ_DIGITS_MAX(n); - if (sign == -1) - n = f_negate(n); - set_hash("year", n); - goto matched; - } - - case 'y': - { - VALUE n; - int sign = 1; - - READ_DIGITS(n, 2); - if (!valid_range_p(n, 0, 99)) - fail(); - if (sign == -1) - n = f_negate(n); - set_hash("year", n); - if (NIL_P(ref_hash("_cent"))) - set_hash("_cent", - INT2FIX(f_ge_p(n, INT2FIX(69)) ? 19 : 20)); - goto matched; - } - - case 'Z': - case 'z': - { - static const char pat_source[] = - "\\A(" - "(?:gmt|utc?)?[-+]\\d+(?:[,.:]\\d+(?::\\d+)?)?" - "|(?-i:[[:alpha:].\\s]+)(?:standard|daylight)\\s+time\\b" - "|(?-i:[[:alpha:]]+)(?:\\s+dst)?\\b" - ")"; - static VALUE pat = Qnil; - VALUE m, b; - - if (NIL_P(pat)) { - pat = rb_reg_new(pat_source, sizeof pat_source - 1, - ONIG_OPTION_IGNORECASE); - rb_obj_freeze(pat); - rb_gc_register_mark_object(pat); - } - - b = rb_backref_get(); - rb_match_busy(b); - m = f_match(pat, rb_usascii_str_new(&str[si], slen - si)); - - if (!NIL_P(m)) { - VALUE s, l, o; - - s = rb_reg_nth_match(1, m); - l = f_end(m, INT2FIX(0)); - o = date_zone_to_diff(s); - si += NUM2LONG(l); - set_hash("zone", s); - set_hash("offset", o); - rb_backref_set(b); - goto matched; - } - rb_backref_set(b); - fail(); - } - - case '%': - if (str[si] != '%') - fail(); - si++; - goto matched; - - case '+': - recur("%a %b %e %H:%M:%S %Z %Y"); - goto matched; - - default: - if (str[si] != '%') - fail(); - si++; - if (fi < flen) { - if (si >= slen || str[si] != fmt[fi]) - fail(); - si++; - } - goto matched; - } - default: - ordinal: - if (str[si] != fmt[fi]) - fail(); - si++; - fi++; - break; - matched: - fi++; - break; - } - } - - return si; -} - -VALUE -date__strptime(const char *str, size_t slen, - const char *fmt, size_t flen, VALUE hash) -{ - size_t si; - VALUE cent, merid; - - si = date__strptime_internal(str, slen, fmt, flen, hash); - - if (fail_p()) - return Qnil; - - if (slen > si) { - VALUE s; - - s = rb_usascii_str_new(&str[si], slen - si); - set_hash("leftover", s); - } - - cent = del_hash("_cent"); - if (!NIL_P(cent)) { - VALUE year; - - year = ref_hash("cwyear"); - if (!NIL_P(year)) - set_hash("cwyear", f_add(year, f_mul(cent, INT2FIX(100)))); - year = ref_hash("year"); - if (!NIL_P(year)) - set_hash("year", f_add(year, f_mul(cent, INT2FIX(100)))); - } - - merid = del_hash("_merid"); - if (!NIL_P(merid)) { - VALUE hour; - - hour = ref_hash("hour"); - if (!NIL_P(hour)) { - hour = f_mod(hour, INT2FIX(12)); - set_hash("hour", f_add(hour, merid)); - } - } - - return hash; -} - -/* -Local variables: -c-file-style: "ruby" -End: -*/ diff --git a/ext/date/date_tmx.h b/ext/date/date_tmx.h deleted file mode 100644 index 993a1532..00000000 --- a/ext/date/date_tmx.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef DATE_TMX_H -#define DATE_TMX_H - -struct tmx_funcs { - VALUE (*year)(void *dat); - int (*yday)(void *dat); - int (*mon)(void *dat); - int (*mday)(void *dat); - VALUE (*cwyear)(void *dat); - int (*cweek)(void *dat); - int (*cwday)(void *dat); - int (*wnum0)(void *dat); - int (*wnum1)(void *dat); - int (*wday)(void *dat); - int (*hour)(void *dat); - int (*min)(void *dat); - int (*sec)(void *dat); - VALUE (*sec_fraction)(void *dat); - VALUE (*secs)(void *dat); - VALUE (*msecs)(void *dat); - int (*offset)(void *dat); - char *(*zone)(void *dat); -}; -struct tmx { - void *dat; - const struct tmx_funcs *funcs; -}; - -#define tmx_attr(x) (tmx->funcs->x)(tmx->dat) - -#define tmx_year tmx_attr(year) -#define tmx_yday tmx_attr(yday) -#define tmx_mon tmx_attr(mon) -#define tmx_mday tmx_attr(mday) -#define tmx_cwyear tmx_attr(cwyear) -#define tmx_cweek tmx_attr(cweek) -#define tmx_cwday tmx_attr(cwday) -#define tmx_wnum0 tmx_attr(wnum0) -#define tmx_wnum1 tmx_attr(wnum1) -#define tmx_wday tmx_attr(wday) -#define tmx_hour tmx_attr(hour) -#define tmx_min tmx_attr(min) -#define tmx_sec tmx_attr(sec) -#define tmx_sec_fraction tmx_attr(sec_fraction) -#define tmx_secs tmx_attr(secs) -#define tmx_msecs tmx_attr(msecs) -#define tmx_offset tmx_attr(offset) -#define tmx_zone tmx_attr(zone) - -#endif - -/* -Local variables: -c-file-style: "ruby" -End: -*/ diff --git a/ext/date/extconf.rb b/ext/date/extconf.rb deleted file mode 100644 index 8a1467df..00000000 --- a/ext/date/extconf.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true -require 'mkmf' - -config_string("strict_warnflags") {|w| $warnflags += " #{w}"} - -append_cflags("-Wno-compound-token-split-by-macro") if RUBY_VERSION < "2.7." -have_func("rb_category_warn") -with_werror("", {:werror => true}) do |opt, | - have_var("timezone", "time.h", opt) - have_var("altzone", "time.h", opt) -end - -create_makefile('date_core') diff --git a/ext/date/generate-zonetab-rb b/ext/date/generate-zonetab-rb new file mode 100755 index 00000000..41c45a1e --- /dev/null +++ b/ext/date/generate-zonetab-rb @@ -0,0 +1,48 @@ +# -*- mode: ruby -*- +# Generate lib/date/zonetab.rb from ext/date/zonetab.list +# +# Usage: ruby ext/date/generate-zonetab-rb ext/date/zonetab.list lib/date/zonetab.rb +# +# This script reads zonetab.list (the same source used to generate zonetab.h +# via gperf) and produces the equivalent Ruby hash table. + +list_path, output_path = ARGV + +entries = {} +sections = 0 + +File.foreach(list_path, chomp: true) do |line| + break if line == '%%' and (sections += 1) > 1 + next if sections < 1 + + abbr, offset_expr = line.split(',', 2) + next unless offset_expr + + abbr.strip! + + # Evaluate offset expression (e.g., "0*3600", "-5*3600", "-(1*3600+1800)", "16200") + offset = eval(offset_expr) + + entries[abbr] = offset +end + +sorted = entries.sort + +max_key_len = sorted.map { |k, _| k.length }.max + +File.open(output_path, 'w') do |f| + f.puts '# frozen_string_literal: true' + f.puts + f.puts '# Timezone name => UTC offset (seconds) mapping table.' + f.puts "# Auto-generated from #{list_path} by ext/date/generate-zonetab-rb." + f.puts '# Do not edit manually.' + f.puts 'class Date' + f.puts ' ZONE_TABLE = {' + sorted.each do |abbr, offset| + f.printf " %-*p => %d,\n", max_key_len + 3, abbr, offset + end + f.puts ' }.freeze' + f.puts 'end' +end + +puts "Generated #{output_path} with #{entries.size} entries." diff --git a/ext/date/prereq.mk b/ext/date/prereq.mk index b5d271a3..ee761907 100644 --- a/ext/date/prereq.mk +++ b/ext/date/prereq.mk @@ -1,15 +1,7 @@ -.SUFFIXES: .list - -.list.h: - gperf --ignore-case -L ANSI-C -C -c -P -p -j1 -i 1 -g -o -t -N $(*F) $< \ - | sed -f $(top_srcdir)/tool/gperf.sed \ - > $(@F) - -zonetab.h: zonetab.list - .PHONY: update-zonetab update-zonetab: $(RUBY) -C $(srcdir) update-abbr + $(RUBY) -C $(top_srcdir) ext/date/generate-zonetab-rb ext/date/zonetab.list lib/date/zonetab.rb .PHONY: update-nothing update-nothing: diff --git a/ext/date/zonetab.h b/ext/date/zonetab.h deleted file mode 100644 index 2a2e8910..00000000 --- a/ext/date/zonetab.h +++ /dev/null @@ -1,1564 +0,0 @@ -/* ANSI-C code produced by gperf version 3.1 */ -/* Command-line: gperf --ignore-case -L ANSI-C -C -c -P -p -j1 -i 1 -g -o -t -N zonetab zonetab.list */ -/* Computed positions: -k'1-4,9' */ - -#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ - && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ - && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ - && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ - && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ - && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ - && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ - && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ - && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ - && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ - && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ - && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ - && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ - && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ - && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ - && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ - && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ - && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ - && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ - && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ - && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ - && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ - && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) -/* The character set is not based on ISO-646. */ -#error "gperf generated tables don't work with this execution character set. Please report a bug to ." -#endif - -#line 1 "zonetab.list" - -#define GPERF_DOWNCASE 1 -#define GPERF_CASE_STRNCMP 1 -#define gperf_case_strncmp strncasecmp -struct zone { - int name; - int offset; -}; -static const struct zone *zonetab(register const char *str, register size_t len); -#line 12 "zonetab.list" -struct zone; - -#define TOTAL_KEYWORDS 316 -#define MIN_WORD_LENGTH 1 -#define MAX_WORD_LENGTH 17 -#define MIN_HASH_VALUE 2 -#define MAX_HASH_VALUE 619 -/* maximum key range = 618, duplicates = 0 */ - -#ifndef GPERF_DOWNCASE -#define GPERF_DOWNCASE 1 -static unsigned char gperf_downcase[256] = - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, - 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, - 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, - 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, - 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, - 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, - 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, - 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, - 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, - 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, - 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, - 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, - 255 - }; -#endif - -#ifndef GPERF_CASE_STRNCMP -#define GPERF_CASE_STRNCMP 1 -static int -gperf_case_strncmp (register const char *s1, register const char *s2, register size_t n) -{ - for (; n > 0;) - { - unsigned char c1 = gperf_downcase[(unsigned char)*s1++]; - unsigned char c2 = gperf_downcase[(unsigned char)*s2++]; - if (c1 != 0 && c1 == c2) - { - n--; - continue; - } - return (int)c1 - (int)c2; - } - return 0; -} -#endif - -#ifdef __GNUC__ -__inline -#else -#ifdef __cplusplus -inline -#endif -#endif -static unsigned int -hash (register const char *str, register size_t len) -{ - static const unsigned short asso_values[] = - { - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 17, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 3, 2, 620, 620, 620, - 620, 620, 70, 8, 3, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 39, 176, 207, 70, 168, - 1, 5, 18, 74, 218, 2, 117, 130, 48, 88, - 125, 225, 92, 1, 1, 12, 54, 30, 36, 13, - 48, 168, 263, 59, 114, 166, 109, 39, 176, 207, - 70, 168, 1, 5, 18, 74, 218, 2, 117, 130, - 48, 88, 125, 225, 92, 1, 1, 12, 54, 30, - 36, 13, 48, 168, 263, 59, 114, 166, 109, 27, - 104, 1, 9, 4, 309, 190, 188, 177, 255, 108, - 2, 341, 3, 620, 620, 620, 620, 620, 620, 12, - 54, 30, 36, 13, 48, 168, 263, 59, 114, 166, - 109, 27, 104, 1, 9, 4, 309, 190, 188, 177, - 255, 108, 2, 341, 3, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620, 620, 620, - 620, 620, 620, 620, 620, 620, 620, 620 - }; - register unsigned int hval = (unsigned int)len; - - switch (hval) - { - default: - hval += asso_values[(unsigned char)str[8]]; - /*FALLTHROUGH*/ - case 8: - case 7: - case 6: - case 5: - case 4: - hval += asso_values[(unsigned char)str[3]]; - /*FALLTHROUGH*/ - case 3: - hval += asso_values[(unsigned char)str[2]]; - /*FALLTHROUGH*/ - case 2: - hval += asso_values[(unsigned char)str[1]+6]; - /*FALLTHROUGH*/ - case 1: - hval += asso_values[(unsigned char)str[0]+52]; - break; - } - return (unsigned int)hval; -} - -struct stringpool_t - { - char stringpool_str2[sizeof("o")]; - char stringpool_str3[sizeof("x")]; - char stringpool_str4[sizeof("z")]; - char stringpool_str5[sizeof("q")]; - char stringpool_str8[sizeof("omst")]; - char stringpool_str9[sizeof("omsst")]; - char stringpool_str10[sizeof("p")]; - char stringpool_str13[sizeof("a")]; - char stringpool_str14[sizeof("e")]; - char stringpool_str15[sizeof("pet")]; - char stringpool_str16[sizeof("pmst")]; - char stringpool_str17[sizeof("pett")]; - char stringpool_str18[sizeof("petst")]; - char stringpool_str19[sizeof("eet")]; - char stringpool_str20[sizeof("aest")]; - char stringpool_str21[sizeof("eest")]; - char stringpool_str22[sizeof("eat")]; - char stringpool_str24[sizeof("east")]; - char stringpool_str25[sizeof("easst")]; - char stringpool_str26[sizeof("pst")]; - char stringpool_str27[sizeof("eastern")]; - char stringpool_str28[sizeof("m")]; - char stringpool_str29[sizeof("ast")]; - char stringpool_str30[sizeof("est")]; - char stringpool_str31[sizeof("c")]; - char stringpool_str32[sizeof("mmt")]; - char stringpool_str33[sizeof("met")]; - char stringpool_str35[sizeof("mest")]; - char stringpool_str36[sizeof("cet")]; - char stringpool_str37[sizeof("d")]; - char stringpool_str38[sizeof("cest")]; - char stringpool_str39[sizeof("cat")]; - char stringpool_str41[sizeof("cast")]; - char stringpool_str42[sizeof("magt")]; - char stringpool_str43[sizeof("magst")]; - char stringpool_str44[sizeof("mst")]; - char stringpool_str45[sizeof("msk")]; - char stringpool_str46[sizeof("cot")]; - char stringpool_str47[sizeof("cst")]; - char stringpool_str48[sizeof("aqtt")]; - char stringpool_str49[sizeof("f")]; - char stringpool_str52[sizeof("art")]; - char stringpool_str53[sizeof("fnt")]; - char stringpool_str54[sizeof("fet")]; - char stringpool_str55[sizeof("b")]; - char stringpool_str57[sizeof("anat")]; - char stringpool_str58[sizeof("anast")]; - char stringpool_str59[sizeof("bnt")]; - char stringpool_str60[sizeof("i")]; - char stringpool_str61[sizeof("pht")]; - char stringpool_str62[sizeof("at")]; - char stringpool_str63[sizeof("zp6")]; - char stringpool_str64[sizeof("mewt")]; - char stringpool_str65[sizeof("fst")]; - char stringpool_str66[sizeof("ahst")]; - char stringpool_str67[sizeof("mawt")]; - char stringpool_str68[sizeof("zp5")]; - char stringpool_str70[sizeof("bot")]; - char stringpool_str71[sizeof("bst")]; - char stringpool_str72[sizeof("pwt")]; - char stringpool_str74[sizeof("pont")]; - char stringpool_str75[sizeof("iot")]; - char stringpool_str76[sizeof("ist")]; - char stringpool_str77[sizeof("awst")]; - char stringpool_str79[sizeof("mht")]; - char stringpool_str80[sizeof("mez")]; - char stringpool_str81[sizeof("orat")]; - char stringpool_str82[sizeof("mesz")]; - char stringpool_str84[sizeof("chst")]; - char stringpool_str85[sizeof("pmdt")]; - char stringpool_str88[sizeof("central")]; - char stringpool_str89[sizeof("aedt")]; - char stringpool_str90[sizeof("act")]; - char stringpool_str91[sizeof("ect")]; - char stringpool_str92[sizeof("acst")]; - char stringpool_str93[sizeof("eadt")]; - char stringpool_str94[sizeof("brt")]; - char stringpool_str95[sizeof("chut")]; - char stringpool_str96[sizeof("brst")]; - char stringpool_str97[sizeof("cen. australia")]; - char stringpool_str100[sizeof("davt")]; - char stringpool_str101[sizeof("irst")]; - char stringpool_str102[sizeof("irkt")]; - char stringpool_str103[sizeof("irkst")]; - char stringpool_str104[sizeof("bt")]; - char stringpool_str105[sizeof("n")]; - char stringpool_str106[sizeof("btt")]; - char stringpool_str107[sizeof("mountain")]; - char stringpool_str108[sizeof("cct")]; - char stringpool_str109[sizeof("w")]; - char stringpool_str110[sizeof("l")]; - char stringpool_str111[sizeof("fwt")]; - char stringpool_str113[sizeof("msd")]; - char stringpool_str114[sizeof("wet")]; - char stringpool_str116[sizeof("west")]; - char stringpool_str117[sizeof("wat")]; - char stringpool_str119[sizeof("wast")]; - char stringpool_str120[sizeof("wakt")]; - char stringpool_str121[sizeof("nst")]; - char stringpool_str122[sizeof("acwst")]; - char stringpool_str123[sizeof("chast")]; - char stringpool_str124[sizeof("cist")]; - char stringpool_str125[sizeof("azt")]; - char stringpool_str126[sizeof("clt")]; - char stringpool_str127[sizeof("azst")]; - char stringpool_str128[sizeof("clst")]; - char stringpool_str129[sizeof("mart")]; - char stringpool_str130[sizeof("zp4")]; - char stringpool_str131[sizeof("jst")]; - char stringpool_str132[sizeof("central asia")]; - char stringpool_str133[sizeof("aft")]; - char stringpool_str134[sizeof("e. south america")]; - char stringpool_str135[sizeof("central america")]; - char stringpool_str137[sizeof("ict")]; - char stringpool_str143[sizeof("pgt")]; - char stringpool_str144[sizeof("nrt")]; - char stringpool_str145[sizeof("mexico")]; - char stringpool_str146[sizeof("awdt")]; - char stringpool_str147[sizeof("egt")]; - char stringpool_str148[sizeof("cxt")]; - char stringpool_str149[sizeof("egst")]; - char stringpool_str150[sizeof("phot")]; - char stringpool_str151[sizeof("alaskan")]; - char stringpool_str154[sizeof("nt")]; - char stringpool_str158[sizeof("wt")]; - char stringpool_str160[sizeof("west asia")]; - char stringpool_str161[sizeof("acdt")]; - char stringpool_str162[sizeof("npt")]; - char stringpool_str163[sizeof("lhst")]; - char stringpool_str164[sizeof("afghanistan")]; - char stringpool_str167[sizeof("k")]; - char stringpool_str169[sizeof("g")]; - char stringpool_str170[sizeof("irdt")]; - char stringpool_str171[sizeof("chot")]; - char stringpool_str172[sizeof("chost")]; - char stringpool_str173[sizeof("gmt")]; - char stringpool_str174[sizeof("get")]; - char stringpool_str175[sizeof("novt")]; - char stringpool_str176[sizeof("novst")]; - char stringpool_str177[sizeof("fjt")]; - char stringpool_str178[sizeof("u")]; - char stringpool_str179[sizeof("fjst")]; - char stringpool_str181[sizeof("pyst")]; - char stringpool_str182[sizeof("nct")]; - char stringpool_str183[sizeof("kst")]; - char stringpool_str184[sizeof("kost")]; - char stringpool_str185[sizeof("gst")]; - char stringpool_str186[sizeof("iran")]; - char stringpool_str187[sizeof("e. africa")]; - char stringpool_str188[sizeof("wadt")]; - char stringpool_str189[sizeof("t")]; - char stringpool_str190[sizeof("e. australia")]; - char stringpool_str191[sizeof("s")]; - char stringpool_str192[sizeof("chadt")]; - char stringpool_str193[sizeof("tmt")]; - char stringpool_str194[sizeof("cidst")]; - char stringpool_str195[sizeof("aoe")]; - char stringpool_str197[sizeof("myt")]; - char stringpool_str198[sizeof("west pacific")]; - char stringpool_str199[sizeof("mut")]; - char stringpool_str200[sizeof("wit")]; - char stringpool_str201[sizeof("sast")]; - char stringpool_str202[sizeof("sakt")]; - char stringpool_str203[sizeof("new zealand")]; - char stringpool_str204[sizeof("tot")]; - char stringpool_str205[sizeof("china")]; - char stringpool_str206[sizeof("tost")]; - char stringpool_str207[sizeof("sst")]; - char stringpool_str209[sizeof("india")]; - char stringpool_str211[sizeof("warst")]; - char stringpool_str212[sizeof("sbt")]; - char stringpool_str214[sizeof("azot")]; - char stringpool_str215[sizeof("azost")]; - char stringpool_str216[sizeof("taht")]; - char stringpool_str217[sizeof("nzt")]; - char stringpool_str218[sizeof("dateline")]; - char stringpool_str219[sizeof("nzst")]; - char stringpool_str220[sizeof("tokyo")]; - char stringpool_str221[sizeof("central pacific")]; - char stringpool_str223[sizeof("qyzt")]; - char stringpool_str224[sizeof("atlantic")]; - char stringpool_str225[sizeof("nft")]; - char stringpool_str227[sizeof("ut")]; - char stringpool_str228[sizeof("trt")]; - char stringpool_str229[sizeof("wft")]; - char stringpool_str230[sizeof("srt")]; - char stringpool_str231[sizeof("pdt")]; - char stringpool_str232[sizeof("lhdt")]; - char stringpool_str234[sizeof("adt")]; - char stringpool_str235[sizeof("edt")]; - char stringpool_str238[sizeof("pkt")]; - char stringpool_str239[sizeof("almt")]; - char stringpool_str240[sizeof("wita")]; - char stringpool_str242[sizeof("wgt")]; - char stringpool_str243[sizeof("akst")]; - char stringpool_str244[sizeof("wgst")]; - char stringpool_str246[sizeof("krat")]; - char stringpool_str247[sizeof("krast")]; - char stringpool_str248[sizeof("mid-atlantic")]; - char stringpool_str249[sizeof("mdt")]; - char stringpool_str250[sizeof("lint")]; - char stringpool_str251[sizeof("malay peninsula")]; - char stringpool_str252[sizeof("cdt")]; - char stringpool_str253[sizeof("swt")]; - char stringpool_str255[sizeof("se asia")]; - char stringpool_str256[sizeof("v")]; - char stringpool_str258[sizeof("tonga")]; - char stringpool_str259[sizeof("ckt")]; - char stringpool_str261[sizeof("vet")]; - char stringpool_str262[sizeof("caucasus")]; - char stringpool_str263[sizeof("central europe")]; - char stringpool_str264[sizeof("h")]; - char stringpool_str265[sizeof("central european")]; - char stringpool_str266[sizeof("newfoundland")]; - char stringpool_str267[sizeof("arab")]; - char stringpool_str268[sizeof("sct")]; - char stringpool_str269[sizeof("arabic")]; - char stringpool_str270[sizeof("arabian")]; - char stringpool_str271[sizeof("ddut")]; - char stringpool_str273[sizeof("vost")]; - char stringpool_str274[sizeof("hast")]; - char stringpool_str275[sizeof("nepal")]; - char stringpool_str276[sizeof("nut")]; - char stringpool_str277[sizeof("fkt")]; - char stringpool_str279[sizeof("fkst")]; - char stringpool_str280[sizeof("hst")]; - char stringpool_str281[sizeof("idt")]; - char stringpool_str284[sizeof("tlt")]; - char stringpool_str285[sizeof("w. australia")]; - char stringpool_str286[sizeof("egypt")]; - char stringpool_str287[sizeof("myanmar")]; - char stringpool_str288[sizeof("nzdt")]; - char stringpool_str289[sizeof("gft")]; - char stringpool_str290[sizeof("uzt")]; - char stringpool_str293[sizeof("north asia")]; - char stringpool_str294[sizeof("mvt")]; - char stringpool_str295[sizeof("galt")]; - char stringpool_str296[sizeof("nfdt")]; - char stringpool_str297[sizeof("cvt")]; - char stringpool_str298[sizeof("north asia east")]; - char stringpool_str300[sizeof("kgt")]; - char stringpool_str301[sizeof("aus central")]; - char stringpool_str302[sizeof("pacific")]; - char stringpool_str304[sizeof("canada central")]; - char stringpool_str306[sizeof("pacific sa")]; - char stringpool_str307[sizeof("azores")]; - char stringpool_str308[sizeof("gamt")]; - char stringpool_str309[sizeof("tft")]; - char stringpool_str310[sizeof("r")]; - char stringpool_str311[sizeof("fle")]; - char stringpool_str312[sizeof("akdt")]; - char stringpool_str313[sizeof("ulat")]; - char stringpool_str314[sizeof("ulast")]; - char stringpool_str315[sizeof("ret")]; - char stringpool_str317[sizeof("tjt")]; - char stringpool_str319[sizeof("south africa")]; - char stringpool_str324[sizeof("sgt")]; - char stringpool_str326[sizeof("ndt")]; - char stringpool_str327[sizeof("rott")]; - char stringpool_str330[sizeof("samt")]; - char stringpool_str332[sizeof("tasmania")]; - char stringpool_str334[sizeof("hovt")]; - char stringpool_str335[sizeof("hovst")]; - char stringpool_str338[sizeof("gyt")]; - char stringpool_str342[sizeof("y")]; - char stringpool_str343[sizeof("hadt")]; - char stringpool_str344[sizeof("sa western")]; - char stringpool_str345[sizeof("hawaiian")]; - char stringpool_str347[sizeof("uyt")]; - char stringpool_str349[sizeof("uyst")]; - char stringpool_str350[sizeof("yekt")]; - char stringpool_str351[sizeof("yekst")]; - char stringpool_str352[sizeof("kuyt")]; - char stringpool_str353[sizeof("yakt")]; - char stringpool_str354[sizeof("yakst")]; - char stringpool_str358[sizeof("yst")]; - char stringpool_str359[sizeof("jerusalem")]; - char stringpool_str365[sizeof("sri lanka")]; - char stringpool_str367[sizeof("yakutsk")]; - char stringpool_str375[sizeof("wib")]; - char stringpool_str377[sizeof("aus eastern")]; - char stringpool_str378[sizeof("gilt")]; - char stringpool_str387[sizeof("us mountain")]; - char stringpool_str391[sizeof("vlat")]; - char stringpool_str392[sizeof("vlast")]; - char stringpool_str395[sizeof("gtb")]; - char stringpool_str398[sizeof("taipei")]; - char stringpool_str399[sizeof("sret")]; - char stringpool_str408[sizeof("cape verde")]; - char stringpool_str417[sizeof("tkt")]; - char stringpool_str418[sizeof("samoa")]; - char stringpool_str421[sizeof("sa pacific")]; - char stringpool_str427[sizeof("vut")]; - char stringpool_str428[sizeof("idlw")]; - char stringpool_str432[sizeof("fiji")]; - char stringpool_str435[sizeof("utc")]; - char stringpool_str443[sizeof("korea")]; - char stringpool_str445[sizeof("e. europe")]; - char stringpool_str449[sizeof("syot")]; - char stringpool_str452[sizeof("n. central asia")]; - char stringpool_str455[sizeof("tvt")]; - char stringpool_str458[sizeof("w. central africa")]; - char stringpool_str466[sizeof("ekaterinburg")]; - char stringpool_str468[sizeof("vladivostok")]; - char stringpool_str476[sizeof("yapt")]; - char stringpool_str477[sizeof("us eastern")]; - char stringpool_str482[sizeof("sa eastern")]; - char stringpool_str485[sizeof("hdt")]; - char stringpool_str486[sizeof("russian")]; - char stringpool_str492[sizeof("hkt")]; - char stringpool_str497[sizeof("romance")]; - char stringpool_str540[sizeof("w. europe")]; - char stringpool_str563[sizeof("ydt")]; - char stringpool_str566[sizeof("idle")]; - char stringpool_str567[sizeof("greenwich")]; - char stringpool_str619[sizeof("greenland")]; - }; -static const struct stringpool_t stringpool_contents = - { - "o", - "x", - "z", - "q", - "omst", - "omsst", - "p", - "a", - "e", - "pet", - "pmst", - "pett", - "petst", - "eet", - "aest", - "eest", - "eat", - "east", - "easst", - "pst", - "eastern", - "m", - "ast", - "est", - "c", - "mmt", - "met", - "mest", - "cet", - "d", - "cest", - "cat", - "cast", - "magt", - "magst", - "mst", - "msk", - "cot", - "cst", - "aqtt", - "f", - "art", - "fnt", - "fet", - "b", - "anat", - "anast", - "bnt", - "i", - "pht", - "at", - "zp6", - "mewt", - "fst", - "ahst", - "mawt", - "zp5", - "bot", - "bst", - "pwt", - "pont", - "iot", - "ist", - "awst", - "mht", - "mez", - "orat", - "mesz", - "chst", - "pmdt", - "central", - "aedt", - "act", - "ect", - "acst", - "eadt", - "brt", - "chut", - "brst", - "cen. australia", - "davt", - "irst", - "irkt", - "irkst", - "bt", - "n", - "btt", - "mountain", - "cct", - "w", - "l", - "fwt", - "msd", - "wet", - "west", - "wat", - "wast", - "wakt", - "nst", - "acwst", - "chast", - "cist", - "azt", - "clt", - "azst", - "clst", - "mart", - "zp4", - "jst", - "central asia", - "aft", - "e. south america", - "central america", - "ict", - "pgt", - "nrt", - "mexico", - "awdt", - "egt", - "cxt", - "egst", - "phot", - "alaskan", - "nt", - "wt", - "west asia", - "acdt", - "npt", - "lhst", - "afghanistan", - "k", - "g", - "irdt", - "chot", - "chost", - "gmt", - "get", - "novt", - "novst", - "fjt", - "u", - "fjst", - "pyst", - "nct", - "kst", - "kost", - "gst", - "iran", - "e. africa", - "wadt", - "t", - "e. australia", - "s", - "chadt", - "tmt", - "cidst", - "aoe", - "myt", - "west pacific", - "mut", - "wit", - "sast", - "sakt", - "new zealand", - "tot", - "china", - "tost", - "sst", - "india", - "warst", - "sbt", - "azot", - "azost", - "taht", - "nzt", - "dateline", - "nzst", - "tokyo", - "central pacific", - "qyzt", - "atlantic", - "nft", - "ut", - "trt", - "wft", - "srt", - "pdt", - "lhdt", - "adt", - "edt", - "pkt", - "almt", - "wita", - "wgt", - "akst", - "wgst", - "krat", - "krast", - "mid-atlantic", - "mdt", - "lint", - "malay peninsula", - "cdt", - "swt", - "se asia", - "v", - "tonga", - "ckt", - "vet", - "caucasus", - "central europe", - "h", - "central european", - "newfoundland", - "arab", - "sct", - "arabic", - "arabian", - "ddut", - "vost", - "hast", - "nepal", - "nut", - "fkt", - "fkst", - "hst", - "idt", - "tlt", - "w. australia", - "egypt", - "myanmar", - "nzdt", - "gft", - "uzt", - "north asia", - "mvt", - "galt", - "nfdt", - "cvt", - "north asia east", - "kgt", - "aus central", - "pacific", - "canada central", - "pacific sa", - "azores", - "gamt", - "tft", - "r", - "fle", - "akdt", - "ulat", - "ulast", - "ret", - "tjt", - "south africa", - "sgt", - "ndt", - "rott", - "samt", - "tasmania", - "hovt", - "hovst", - "gyt", - "y", - "hadt", - "sa western", - "hawaiian", - "uyt", - "uyst", - "yekt", - "yekst", - "kuyt", - "yakt", - "yakst", - "yst", - "jerusalem", - "sri lanka", - "yakutsk", - "wib", - "aus eastern", - "gilt", - "us mountain", - "vlat", - "vlast", - "gtb", - "taipei", - "sret", - "cape verde", - "tkt", - "samoa", - "sa pacific", - "vut", - "idlw", - "fiji", - "utc", - "korea", - "e. europe", - "syot", - "n. central asia", - "tvt", - "w. central africa", - "ekaterinburg", - "vladivostok", - "yapt", - "us eastern", - "sa eastern", - "hdt", - "russian", - "hkt", - "romance", - "w. europe", - "ydt", - "idle", - "greenwich", - "greenland" - }; -#define stringpool ((const char *) &stringpool_contents) -const struct zone * -zonetab (register const char *str, register size_t len) -{ - static const struct zone wordlist[] = - { - {-1}, {-1}, -#line 37 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str2, -2*3600}, -#line 46 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str3, -11*3600}, -#line 48 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str4, 0*3600}, -#line 39 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str5, -4*3600}, - {-1}, {-1}, -#line 272 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str8,21600}, -#line 271 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str9,25200}, -#line 38 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str10, -3*3600}, - {-1}, {-1}, -#line 24 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str13, 1*3600}, -#line 28 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str14, 5*3600}, -#line 274 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str15,-18000}, -#line 282 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str16,-10800}, -#line 276 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str17,43200}, -#line 275 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str18,43200}, -#line 83 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str19, 2*3600}, -#line 189 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str20,36000}, -#line 91 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str21, 3*3600}, -#line 90 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str22, 3*3600}, - {-1}, -#line 104 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str24,-6*3600}, -#line 220 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str25,-18000}, -#line 22 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str26, -8*3600}, -#line 136 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str27, -18000}, -#line 35 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str28, 12*3600}, -#line 59 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str29, -4*3600}, -#line 16 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str30, -5*3600}, -#line 26 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str31, 3*3600}, -#line 259 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str32,23400}, -#line 76 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str33, 1*3600}, - {-1}, -#line 85 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str35, 2*3600}, -#line 74 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str36, 1*3600}, -#line 27 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str37, 4*3600}, -#line 82 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str38, 2*3600}, -#line 68 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str39,2*3600}, - {-1}, -#line 205 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str41,28800}, -#line 255 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str42,39600}, -#line 254 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str43,43200}, -#line 20 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str44, -7*3600}, -#line 92 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str45, 3*3600}, -#line 215 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str46,-18000}, -#line 18 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str47, -6*3600}, -#line 195 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str48,18000}, -#line 29 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str49, 6*3600}, - {-1}, {-1}, -#line 54 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str52, -3*3600}, -#line 229 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str53,-7200}, -#line 224 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str54,10800}, -#line 25 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str55, 2*3600}, - {-1}, -#line 193 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str57,43200}, -#line 192 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str58,43200}, -#line 202 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str59,28800}, -#line 32 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str60, 9*3600}, -#line 279 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str61,28800}, -#line 51 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str62, -2*3600}, -#line 97 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str63, 6*3600}, -#line 77 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str64, 1*3600}, -#line 84 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str65, 2*3600}, -#line 67 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str66,-10*3600}, -#line 257 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str67,18000}, -#line 95 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str68, 5*3600}, - {-1}, -#line 203 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str70,-14400}, -#line 73 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str71, 1*3600}, -#line 284 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str72,32400}, - {-1}, -#line 283 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str74,39600}, -#line 241 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str75,21600}, -#line 96 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str76, (5*3600+1800)}, -#line 197 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str77,28800}, - {-1}, -#line 258 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str79,43200}, -#line 78 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str80, 1*3600}, -#line 273 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str81,18000}, -#line 86 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str82, 2*3600}, - {-1}, -#line 210 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str84,36000}, -#line 281 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str85,-7200}, - {-1}, {-1}, -#line 129 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str88, -21600}, -#line 188 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str89,39600}, -#line 186 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str90,-18000}, -#line 221 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str91,-18000}, -#line 185 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str92,34200}, -#line 106 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str93,11*3600}, -#line 56 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str94, -3*3600}, -#line 211 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str95,36000}, -#line 52 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str96,-2*3600}, -#line 123 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str97, 34200}, - {-1}, {-1}, -#line 218 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str100,25200}, -#line 245 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str101,12600}, -#line 244 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str102,28800}, -#line 243 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str103,32400}, -#line 89 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str104, 3*3600}, -#line 36 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str105, -1*3600}, -#line 204 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str106,21600}, -#line 151 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str107, -25200}, -#line 99 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str108, (6*3600+1800)}, -#line 45 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str109, -10*3600}, -#line 34 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str110, 11*3600}, -#line 75 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str111, 1*3600}, - {-1}, -#line 93 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str113, 4*3600}, -#line 50 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str114, 0*3600}, - {-1}, -#line 81 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str116, 1*3600}, -#line 80 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str117, 1*3600}, - {-1}, -#line 98 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str119, 2*3600}, -#line 316 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str120,43200}, -#line 58 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str121, -(2*3600+1800)}, -#line 187 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str122,31500}, -#line 207 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str123,45900}, -#line 213 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str124,-18000}, -#line 201 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str125,14400}, -#line 60 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str126, -4*3600}, -#line 200 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str127,18000}, -#line 57 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str128,-3*3600}, -#line 256 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str129,-30600}, -#line 94 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str130, 4*3600}, -#line 102 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str131, 9*3600}, -#line 125 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str132, 21600}, -#line 190 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str133,16200}, -#line 135 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str134, -10800}, -#line 124 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str135, -21600}, - {-1}, -#line 239 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str137,25200}, - {-1}, {-1}, {-1}, {-1}, {-1}, -#line 277 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str143,36000}, -#line 269 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str144,43200}, -#line 149 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str145, -21600}, -#line 196 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str146,32400}, -#line 223 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str147,-3600}, -#line 217 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str148,25200}, -#line 222 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str149,0}, -#line 278 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str150,46800}, -#line 112 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str151, -32400}, - {-1}, {-1}, -#line 71 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str154, -11*3600}, - {-1}, {-1}, {-1}, -#line 324 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str158,0}, - {-1}, -#line 181 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str160, 18000}, -#line 184 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str161,37800}, -#line 268 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str162,20700}, -#line 252 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str163,37800}, -#line 111 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str164, 16200}, - {-1}, {-1}, -#line 33 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str167, 10*3600}, - {-1}, -#line 30 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str169, 7*3600}, -#line 242 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str170,16200}, -#line 209 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str171,28800}, -#line 208 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str172,32400}, -#line 15 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str173, 0*3600}, -#line 232 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str174,14400}, -#line 267 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str175,25200}, -#line 266 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str176,25200}, -#line 226 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str177,43200}, -#line 43 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str178, -8*3600}, -#line 225 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str179,46800}, - {-1}, -#line 285 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str181,-10800}, -#line 263 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str182,39600}, -#line 103 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str183, 9*3600}, -#line 247 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str184,39600}, -#line 105 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str185, 10*3600}, -#line 146 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str186, 12600}, -#line 132 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str187, 10800}, -#line 101 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str188, 8*3600}, -#line 42 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str189, -7*3600}, -#line 133 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str190, 36000}, -#line 41 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str191, -6*3600}, -#line 206 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str192,49500}, -#line 301 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str193,18000}, -#line 212 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str194,-14400}, -#line 194 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str195,-43200}, - {-1}, -#line 262 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str197,28800}, -#line 182 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str198, 36000}, -#line 260 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str199,14400}, -#line 322 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str200,32400}, -#line 87 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str201, 2*3600}, -#line 289 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str202,39600}, -#line 155 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str203, 43200}, -#line 303 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str204,46800}, -#line 130 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str205, 28800}, -#line 302 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str206,50400}, -#line 88 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str207, -11*3600}, - {-1}, -#line 145 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str209, 19800}, - {-1}, -#line 317 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str211,-10800}, -#line 291 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str212,39600}, - {-1}, -#line 199 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str214,-3600}, -#line 198 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str215,0}, -#line 296 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str216,-36000}, -#line 109 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str217, 12*3600}, -#line 131 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str218, -43200}, -#line 108 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str219,12*3600}, -#line 173 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str220, 32400}, -#line 128 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str221, 39600}, - {-1}, -#line 286 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str223,21600}, -#line 116 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str224, -14400}, -#line 265 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str225,39600}, - {-1}, -#line 14 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str227, 0*3600}, -#line 304 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str228,10800}, -#line 318 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str229,43200}, -#line 294 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str230,-10800}, -#line 23 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str231, -7*3600}, -#line 251 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str232,39600}, - {-1}, -#line 55 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str234, -3*3600}, -#line 17 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str235, -4*3600}, - {-1}, {-1}, -#line 280 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str238,18000}, -#line 191 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str239,21600}, -#line 323 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str240,28800}, - {-1}, -#line 320 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str242,-7200}, -#line 63 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str243,-9*3600}, -#line 319 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str244,-3600}, - {-1}, -#line 249 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str246,25200}, -#line 248 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str247,28800}, -#line 150 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str248, -7200}, -#line 21 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str249, -6*3600}, -#line 253 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str250,50400}, -#line 168 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str251, 28800}, -#line 19 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str252, -5*3600}, -#line 79 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str253, 1*3600}, - {-1}, -#line 167 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str255, 25200}, -#line 44 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str256, -9*3600}, - {-1}, -#line 174 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str258, 46800}, -#line 214 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str259,-36000}, - {-1}, -#line 311 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str261,-14400}, -#line 122 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str262, 14400}, -#line 126 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str263, 3600}, -#line 31 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str264, 8*3600}, -#line 127 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str265, 3600}, -#line 156 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str266, -12600}, -#line 113 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str267, 10800}, -#line 292 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str268,14400}, -#line 115 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str269, 10800}, -#line 114 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str270, 14400}, -#line 219 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str271,36000}, - {-1}, -#line 314 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str273,21600}, -#line 69 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str274,-10*3600}, -#line 154 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str275, 20700}, -#line 270 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str276,-39600}, -#line 228 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str277,-14400}, - {-1}, -#line 227 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str279,-10800}, -#line 70 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str280,-10*3600}, -#line 240 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str281,10800}, - {-1}, {-1}, -#line 300 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str284,32400}, -#line 178 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str285, 28800}, -#line 137 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str286, 7200}, -#line 152 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str287, 23400}, -#line 110 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str288,13*3600}, -#line 233 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str289,-10800}, -#line 310 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str290,18000}, - {-1}, {-1}, -#line 158 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str293, 25200}, -#line 261 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str294,18000}, -#line 230 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str295,-21600}, -#line 264 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str296,43200}, -#line 216 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str297,-3600}, -#line 157 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str298, 28800}, - {-1}, -#line 246 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str300,21600}, -#line 117 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str301, 34200}, -#line 160 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str302, -28800}, - {-1}, -#line 120 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str304, -21600}, - {-1}, -#line 159 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str306, -14400}, -#line 119 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str307, -3600}, -#line 231 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str308,-32400}, -#line 297 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str309,18000}, -#line 40 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str310, -5*3600}, -#line 140 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str311, 7200}, -#line 61 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str312,-8*3600}, -#line 307 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str313,28800}, -#line 306 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str314,32400}, -#line 287 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str315,14400}, - {-1}, -#line 298 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str317,18000}, - {-1}, -#line 169 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str319, 7200}, - {-1}, {-1}, {-1}, {-1}, -#line 100 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str324, 8*3600}, - {-1}, -#line 53 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str326, -(1*3600+1800)}, -#line 288 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str327,-10800}, - {-1}, {-1}, -#line 290 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str330,14400}, - {-1}, -#line 172 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str332, 36000}, - {-1}, -#line 238 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str334,25200}, -#line 237 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str335,28800}, - {-1}, {-1}, -#line 235 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str338,-14400}, - {-1}, {-1}, {-1}, -#line 47 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str342, -12*3600}, -#line 64 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str343,-9*3600}, -#line 165 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str344, -14400}, -#line 144 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str345, -36000}, - {-1}, -#line 309 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str347,-10800}, - {-1}, -#line 308 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str349,-7200}, -#line 329 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str350,18000}, -#line 328 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str351,21600}, -#line 250 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str352,14400}, -#line 326 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str353,32400}, -#line 325 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str354,36000}, - {-1}, {-1}, {-1}, -#line 66 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str358, -9*3600}, -#line 147 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str359, 7200}, - {-1}, {-1}, {-1}, {-1}, {-1}, -#line 170 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str365, 21600}, - {-1}, -#line 183 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str367, 32400}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, -#line 321 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str375,25200}, - {-1}, -#line 118 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str377, 36000}, -#line 234 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str378,43200}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, -#line 176 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str387, -25200}, - {-1}, {-1}, {-1}, -#line 313 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str391,36000}, -#line 312 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str392,39600}, - {-1}, {-1}, -#line 143 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str395, 7200}, - {-1}, {-1}, -#line 171 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str398, 28800}, -#line 293 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str399,39600}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, -#line 121 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str408, -3600}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, -#line 299 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str417,46800}, -#line 166 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str418, -39600}, - {-1}, {-1}, -#line 164 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str421, -18000}, - {-1}, {-1}, {-1}, {-1}, {-1}, -#line 315 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str427,39600}, -#line 72 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str428,-12*3600}, - {-1}, {-1}, {-1}, -#line 139 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str432, 43200}, - {-1}, {-1}, -#line 49 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str435, 0*3600}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, -#line 148 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str443, 32400}, - {-1}, -#line 134 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str445, 7200}, - {-1}, {-1}, {-1}, -#line 295 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str449,10800}, - {-1}, {-1}, -#line 153 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str452, 21600}, - {-1}, {-1}, -#line 305 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str455,43200}, - {-1}, {-1}, -#line 179 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str458, 3600}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, -#line 138 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str466, 18000}, - {-1}, -#line 177 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str468, 36000}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, -#line 327 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str476,36000}, -#line 175 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str477, -18000}, - {-1}, {-1}, {-1}, {-1}, -#line 163 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str482, -10800}, - {-1}, {-1}, -#line 65 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str485, -9*3600}, -#line 162 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str486, 10800}, - {-1}, {-1}, {-1}, {-1}, {-1}, -#line 236 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str492,28800}, - {-1}, {-1}, {-1}, {-1}, -#line 161 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str497, 3600}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, -#line 180 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str540, 3600}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, -#line 62 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str563, -8*3600}, - {-1}, {-1}, -#line 107 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str566,12*3600}, -#line 142 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str567, 0}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, - {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, -#line 141 "zonetab.list" - {(int)(size_t)&((struct stringpool_t *)0)->stringpool_str619, -10800} - }; - - if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) - { - register unsigned int key = hash (str, len); - - if (key <= MAX_HASH_VALUE) - { - register int o = wordlist[key].name; - if (o >= 0) - { - register const char *s = o + stringpool; - - if ((((unsigned char)*str ^ (unsigned char)*s) & ~32) == 0 && !gperf_case_strncmp (str, s, len) && s[len] == '\0') - return &wordlist[key]; - } - } - } - return 0; -} -#line 330 "zonetab.list" - diff --git a/lib/date.rb b/lib/date.rb index 0cb76301..d7052b75 100644 --- a/lib/date.rb +++ b/lib/date.rb @@ -1,11 +1,19 @@ # frozen_string_literal: true # date.rb: Written by Tadayoshi Funaba 1998-2011 -require 'date_core' +require 'strscan' -class Date - VERSION = "3.5.1" # :nodoc: +require_relative "date/version" +require_relative "date/shared" +require_relative "date/constants" +require_relative "date/core" +require_relative "date/strftime" +require_relative "date/parse" +require_relative "date/strptime" +require_relative "date/time" +require_relative "date/datetime" +class Date # call-seq: # infinite? -> false # @@ -64,7 +72,5 @@ def to_f -Float::INFINITY end end - end - end diff --git a/lib/date/constants.rb b/lib/date/constants.rb new file mode 100644 index 00000000..19bf8f6e --- /dev/null +++ b/lib/date/constants.rb @@ -0,0 +1,342 @@ +# encoding: US-ASCII +# frozen_string_literal: true + +# Constants +class Date + MONTHNAMES = [nil, 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December'].freeze + ABBR_MONTHNAMES = [nil, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'].freeze + DAYNAMES = %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday].freeze + ABBR_DAYNAMES = %w[Sun Mon Tue Wed Thu Fri Sat].freeze + + ITALY = 2299161 # 1582-10-15 + ENGLAND = 2361222 # 1752-09-14 + JULIAN = Float::INFINITY + GREGORIAN = -Float::INFINITY + + DEFAULT_SG = ITALY + private_constant :DEFAULT_SG + + # 3-byte integer key lookup tables for O(1) abbreviated name matching. + # Key = (byte0_lower << 16) | (byte1_lower << 8) | byte2_lower + # Value = [index, full_name_length] + ABBR_DAY_3KEY = ABBR_DAYNAMES.each_with_index.to_h { |n, i| + [compute_3key(n), [i, DAYNAMES[i].length].freeze] + }.freeze + ABBR_MONTH_3KEY = ABBR_MONTHNAMES.each_with_index.each_with_object({}) { |(n, i), h| + next if n.nil? + h[compute_3key(n)] = [i, MONTHNAMES[i].length].freeze + }.freeze + private_constant :ABBR_DAY_3KEY, :ABBR_MONTH_3KEY + + # Case-insensitive abbreviated month name -> month number (1-12) + ABBR_MONTH_NUM = ABBR_MONTHNAMES.each_with_index.each_with_object({}) { |(n, i), h| + next if n.nil? + h[n.downcase] = i + }.freeze + + # Case-insensitive abbreviated day name -> wday number (0-6) + ABBR_DAY_NUM = ABBR_DAYNAMES.each_with_index.to_h { |n, i| [n.downcase, i] }.freeze + private_constant :ABBR_MONTH_NUM, :ABBR_DAY_NUM + + # Lowercase full name strings for StringScanner-based head matching + DAY_LOWER_STRS = DAYNAMES.map { |n| n.downcase.freeze }.freeze + MONTH_LOWER_STRS = MONTHNAMES.map { |n| n&.downcase&.freeze }.freeze + private_constant :DAY_LOWER_STRS, :MONTH_LOWER_STRS + + + JULIAN_EPOCH_DATE = '-4712-01-01'.freeze + JULIAN_EPOCH_DATETIME = '-4712-01-01T00:00:00+00:00'.freeze + JULIAN_EPOCH_DATETIME_RFC2822 = 'Mon, 1 Jan -4712 00:00:00 +0000'.freeze + JULIAN_EPOCH_DATETIME_HTTPDATE = 'Mon, 01 Jan -4712 00:00:00 GMT'.freeze + private_constant :JULIAN_EPOCH_DATE, :JULIAN_EPOCH_DATETIME, + :JULIAN_EPOCH_DATETIME_RFC2822, :JULIAN_EPOCH_DATETIME_HTTPDATE + + # === Calendar computation (from core.rb) === + + # Days in month for Gregorian calendar. + DAYS_IN_MONTH_GREGORIAN = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31].freeze + private_constant :DAYS_IN_MONTH_GREGORIAN + + # Precomputed month offset for Julian Day computation: + # GJD_MONTH_OFFSET[m] == (306001 * (gm + 1)) / 10000 + # where gm = m + 12 for m <= 2, else gm = m. + # Used to avoid an integer multiply in the hot path of civil_to_jd. + GJD_MONTH_OFFSET = [nil, 428, 459, 122, 153, 183, 214, 244, 275, 306, 336, 367, 397].freeze + private_constant :GJD_MONTH_OFFSET + + ASCTIME_DAYS = %w[Sun Mon Tue Wed Thu Fri Sat].freeze + ASCTIME_MONS = [nil, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'].freeze + RFC2822_DAYS = ASCTIME_DAYS + private_constant :ASCTIME_DAYS, :ASCTIME_MONS, :RFC2822_DAYS + + # Pre-computed " Mon " strings for rfc2822/httpdate: " Jan ", " Feb ", ... + RFC_MON_SPACE = ASCTIME_MONS.map { |m| m ? " #{m} ".freeze : nil }.freeze + private_constant :RFC_MON_SPACE + + ERA_TABLE = [ + [2458605, 'R', 2018], # Reiwa: 2019-05-01 + [2447535, 'H', 1988], # Heisei: 1989-01-08 + [2424875, 'S', 1925], # Showa: 1926-12-25 + [2419614, 'T', 1911], # Taisho: 1912-07-30 + [2405160, 'M', 1867], # Meiji: 1873-01-01 + ].freeze + private_constant :ERA_TABLE + + # Pre-built "-MM-DD" suffixes for all valid month/day combinations. + # Indexed by [month][day]. Avoids per-call format() for the month/day portion. + MONTH_DAY_SUFFIX = Array.new(13) { |m| + Array.new(32) { |d| + next nil if m == 0 || d == 0 + format('-%02d-%02d', m, d).freeze + }.freeze + }.freeze + private_constant :MONTH_DAY_SUFFIX + + # === String formatting (from strftime.rb) === + + DEFAULT_STRFTIME_FMT = '%F' + private_constant :DEFAULT_STRFTIME_FMT + + YMD_FMT = '%Y-%m-%d' + private_constant :YMD_FMT + + # Locale-independent month/day name tables (same as C ext) + STRFTIME_MONTHS_FULL = MONTHNAMES.freeze + STRFTIME_MONTHS_ABBR = ABBR_MONTHNAMES.freeze + STRFTIME_DAYS_FULL = DAYNAMES.freeze + STRFTIME_DAYS_ABBR = ABBR_DAYNAMES.freeze + private_constant :STRFTIME_MONTHS_FULL, :STRFTIME_MONTHS_ABBR, + :STRFTIME_DAYS_FULL, :STRFTIME_DAYS_ABBR + + # Pre-computed "Sun Jan " prefix table for %c / asctime [wday][month] + ASCTIME_PREFIX = Array.new(7) { |w| + Array.new(13) { |m| + m == 0 ? nil : "#{STRFTIME_DAYS_ABBR[w]} #{STRFTIME_MONTHS_ABBR[m]} ".freeze + }.freeze + }.freeze + private_constant :ASCTIME_PREFIX + + # Pre-computed "Saturday, " prefix table for %A format [wday] + DAY_FULL_COMMA = STRFTIME_DAYS_FULL.map { |d| "#{d}, ".freeze }.freeze + # Pre-computed "March " prefix table for %B format [month] + MONTH_FULL_SPACE = STRFTIME_MONTHS_FULL.map { |m| m ? "#{m} ".freeze : nil }.freeze + private_constant :DAY_FULL_COMMA, :MONTH_FULL_SPACE + + # Pre-computed 2-digit zero-padded strings for 0..99 + PAD2 = (0..99).map { |n| format('%02d', n).freeze }.freeze + private_constant :PAD2 + + # Map composite spec bytes to their expansion strings + STRFTIME_COMPOSITE_BYTE = { + 99 => '%a %b %e %H:%M:%S %Y', # 'c' + 68 => '%m/%d/%y', # 'D' + 70 => '%Y-%m-%d', # 'F' + 110 => "\n", # 'n' + 114 => '%I:%M:%S %p', # 'r' + 82 => '%H:%M', # 'R' + 116 => "\t", # 't' + 84 => '%H:%M:%S', # 'T' + 118 => '%e-%^b-%4Y', # 'v' + 88 => '%H:%M:%S', # 'X' + 120 => '%m/%d/%y', # 'x' + }.freeze + private_constant :STRFTIME_COMPOSITE_BYTE + + # Valid specs for %E locale modifier (as byte values) + # c=99, C=67, x=120, X=88, y=121, Y=89 + STRFTIME_E_VALID_BYTES = [99, 67, 120, 88, 121, 89].freeze + # Valid specs for %O locale modifier (as byte values) + # d=100, e=101, H=72, k=107, I=73, l=108, m=109, M=77, S=83, u=117, U=85, V=86, w=119, W=87, y=121 + STRFTIME_O_VALID_BYTES = [100, 101, 72, 107, 73, 108, 109, 77, 83, 117, 85, 86, 119, 87, 121].freeze + private_constant :STRFTIME_E_VALID_BYTES, :STRFTIME_O_VALID_BYTES + + # Maximum allowed format width to prevent unreasonable memory allocation + STRFTIME_MAX_WIDTH = 65535 + # Maximum length for a single formatted field (matches C's STRFTIME_MAX_COPY_LEN) + STRFTIME_MAX_COPY_LEN = 1024 + private_constant :STRFTIME_MAX_WIDTH, :STRFTIME_MAX_COPY_LEN + + # === Parse regex patterns (from parse.rb) === + + RFC3339_RE = /\A\s*(-?\d{4})-(\d{2})-(\d{2})[Tt ](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(Z|[-+]\d{2}:\d{2})\s*\z/i + private_constant :RFC3339_RE + + HTTPDATE_TYPE1_RE = /\A\s*(sun|mon|tue|wed|thu|fri|sat)\s*,\s+(\d{2})\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+(-?\d{4})\s+(\d{2}):(\d{2}):(\d{2})\s+(gmt)\s*\z/i + HTTPDATE_TYPE2_RE = /\A\s*(sunday|monday|tuesday|wednesday|thursday|friday|saturday)\s*,\s+(\d{2})\s*-\s*(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s*-\s*(\d{2})\s+(\d{2}):(\d{2}):(\d{2})\s+(gmt)\s*\z/i + HTTPDATE_TYPE3_RE = /\A\s*(sun|mon|tue|wed|thu|fri|sat)\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+(\d{1,2})\s+(\d{2}):(\d{2}):(\d{2})\s+(\d{4})\s*\z/i + # Fast path: simplified Type 1 with generic [a-zA-Z] instead of alternation + FAST_HTTPDATE_TYPE1_RE = /\A\s*([a-zA-Z]{3}),\s+(\d{2})\s+([a-zA-Z]{3})\s+(\d{4})\s+(\d{2}):(\d{2}):(\d{2})\s+(GMT)\s*\z/i + private_constant :HTTPDATE_TYPE1_RE, :HTTPDATE_TYPE2_RE, :HTTPDATE_TYPE3_RE, + :FAST_HTTPDATE_TYPE1_RE + + # Wday lookup from abbreviated day name (3-char lowercase key) + HTTPDATE_WDAY = {'sun'=>0,'mon'=>1,'tue'=>2,'wed'=>3,'thu'=>4,'fri'=>5,'sat'=>6}.freeze + HTTPDATE_FULL_WDAY = {'sunday'=>0,'monday'=>1,'tuesday'=>2,'wednesday'=>3,'thursday'=>4,'friday'=>5,'saturday'=>6}.freeze + private_constant :HTTPDATE_WDAY, :HTTPDATE_FULL_WDAY + + RFC2822_RE = /\A\s*(?:(sun|mon|tue|wed|thu|fri|sat)\s*,\s+)?(\d{1,2})\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+(-?\d{2,})\s+(\d{2}):(\d{2})(?::(\d{2}))?\s*([-+]\d{4}|ut|gmt|e[sd]t|c[sd]t|m[sd]t|p[sd]t|[a-ik-z])\s*\z/i + private_constant :RFC2822_RE + + XMLSCHEMA_DATETIME_RE = /\A\s*(-?\d{4,})(?:-(\d{2})(?:-(\d{2}))?)?(?:t(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?)?(z|[-+]\d{2}:\d{2})?\s*\z/i + XMLSCHEMA_TIME_RE = /\A\s*(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(z|[-+]\d{2}:\d{2})?\s*\z/i + XMLSCHEMA_TRUNC_RE = /\A\s*(?:--(\d{2})(?:-(\d{2}))?|---(\d{2}))(z|[-+]\d{2}:\d{2})?\s*\z/i + private_constant :XMLSCHEMA_DATETIME_RE, :XMLSCHEMA_TIME_RE, :XMLSCHEMA_TRUNC_RE + + ISO8601_EXT_DATETIME_RE = %r{\A\s* + (?: + ([-+]?\d{2,}|-)-(\d{2})?(?:-(\d{2}))? | # year-mon-mday or --mon-mday or ---mday + ([-+]?\d{2,})?-(\d{3}) | # year-yday + (\d{4}|\d{2})?-w(\d{2})(?:-(\d))? | # cwyear-wNN-D + -w-(\d) # -w-D + ) + (?:t + (\d{2}):(\d{2})(?::(\d{2})(?:[,.](\d+))?)? + (z|[-+]\d{2}(?::?\d{2})?)? + )? + \s*\z}xi + + ISO8601_BAS_DATETIME_RE = %r{\A\s* + (?: + ([-+]?(?:\d{4}|\d{2})|--)(\d{2}|-)(\d{2}) | # yyyymmdd / --mmdd / ----dd + ([-+]?(?:\d{4}|\d{2}))(\d{3}) | # yyyyddd + -(\d{3}) | # -ddd + (\d{4}|\d{2})w(\d{2})(\d) | # yyyywwwd + -w(\d{2})(\d) | # -wNN-D + -w-(\d) # -w-D + ) + (?:t? + (\d{2})(\d{2})(?:(\d{2})(?:[,.](\d+))?)? + (z|[-+]\d{2}(\d{2})?)? + )? + \s*\z}xi + + ISO8601_EXT_TIME_RE = /\A\s*(\d{2}):(\d{2})(?::(\d{2})(?:[,.](\d+))?(z|[-+]\d{2}(?::?\d{2})?)?)?\s*\z/i + ISO8601_BAS_TIME_RE = /\A\s*(\d{2})(\d{2})(?:(\d{2})(?:[,.](\d+))?(z|[-+]\d{2}(\d{2})?)?)?\s*\z/i + private_constant :ISO8601_EXT_DATETIME_RE, :ISO8601_BAS_DATETIME_RE, + :ISO8601_EXT_TIME_RE, :ISO8601_BAS_TIME_RE + + JISX0301_ERA = { 'm' => 1867, 't' => 1911, 's' => 1925, 'h' => 1988, 'r' => 2018 }.freeze + JISX0301_RE = /\A\s*([mtshr])?(\d{2})\.(\d{2})\.(\d{2})(?:t(?:(\d{2}):(\d{2})(?::(\d{2})(?:[,.](\d*))?)?(z|[-+]\d{2}(?::?\d{2})?)?)?)?\s*\z/i + private_constant :JISX0301_ERA, :JISX0301_RE + + PARSE_DAYS_RE = /\b(sun|mon|tue|wed|thu|fri|sat)[^-\/\d\s]*/i + PARSE_MON_RE = /\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\S*/i + PARSE_MDAY_RE = /(?= 1 && month <= 12 && day >= 1 && day <= 28 + # Days 1..28 exist in every month of both the Julian and Gregorian + # calendars, so such a date can only be invalid if it falls in the gap + # skipped at the calendar reform (gregorian jd < start <= julian jd). + # Inline civil_to_jd so the reform gap can be detected without a second + # pass: keep both the Gregorian and Julian Julian Day numbers. + yy = month <= 2 ? year - 1 : year + gjd_base = (1461 * (yy + 4716)) / 4 + GJD_MONTH_OFFSET[month] + day + a = yy / 100 + gjd = gjd_base - 1524 + 2 - a + a / 4 + if gjd >= start + return new_from_jd(gjd, start) + else + jjd = gjd_base - 1524 + return new_from_jd(jjd, start) if jjd < start + # gjd < start <= jjd: date falls in the reform gap; fall through to + # civil_fallback, which raises "invalid date". + end + end + civil_fallback(year, month, day, start) + end + + # call-seq: + # Date.new(year = -4712, month = 1, mday = 1, start = Date::ITALY) -> date + # + # Returns a new \Date object constructed from the given arguments: + # + # Date.new(2022).to_s # => "2022-01-01" + # Date.new(2022, 2).to_s # => "2022-02-01" + # Date.new(2022, 2, 4).to_s # => "2022-02-04" + # + # Argument +month+ should be in range (1..12) or range (-12..-1); + # when the argument is negative, counts backward from the end of the year: + # + # Date.new(2022, -11, 4).to_s # => "2022-02-04" + # + # Argument +mday+ should be in range (1..n) or range (-n..-1) + # where +n+ is the number of days in the month; + # when the argument is negative, counts backward from the end of the month. + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + # Related: Date.jd. + def new(year = -4712, month = 1, day = 1, start = DEFAULT_SG) + civil(year, month, day, start) + end + + # call-seq: + # Date.valid_civil?(year, month, mday, start = Date::ITALY) -> true or false + # + # Returns +true+ if the arguments define a valid ordinal date, + # +false+ otherwise: + # + # Date.valid_date?(2001, 2, 3) # => true + # Date.valid_date?(2001, 2, 29) # => false + # Date.valid_date?(2001, 2, -1) # => true + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + # Related: Date.jd, Date.new. + def valid_civil?(year, month, day, start = DEFAULT_SG) + return false unless year.is_a?(Numeric) && month.is_a?(Numeric) && day.is_a?(Numeric) + !!internal_valid_civil?(year, month, day, start) + end + alias_method :valid_date?, :valid_civil? + + # call-seq: + # Date.jd(jd = 0, start = Date::ITALY) -> date + # + # Returns a new \Date object formed from the arguments: + # + # Date.jd(2451944).to_s # => "2001-02-03" + # Date.jd(2451945).to_s # => "2001-02-04" + # Date.jd(0).to_s # => "-4712-01-01" + # + # The returned date is: + # + # - Gregorian, if the argument is greater than or equal to +start+: + # + # Date::ITALY # => 2299161 + # Date.jd(Date::ITALY).gregorian? # => true + # Date.jd(Date::ITALY + 1).gregorian? # => true + # + # - Julian, otherwise + # + # Date.jd(Date::ITALY - 1).julian? # => true + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + # Related: Date.new. + def jd(jd = 0, start = DEFAULT_SG) + check_numeric(jd, "jd") + jd = Integer(jd) + obj = allocate + obj.__send__(:init_from_jd, jd, start) + obj + end + + # call-seq: + # Date.valid_jd?(jd, start = Date::ITALY) -> true + # + # Implemented for compatibility; + # returns +true+ unless +jd+ is invalid (i.e., not a Numeric). + # + # Date.valid_jd?(2451944) # => true + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + # Related: Date.jd. + def valid_jd?(jd, _start = DEFAULT_SG) + jd.is_a?(Numeric) + end + + # call-seq: + # Date.gregorian_leap?(year) -> true or false + # + # Returns +true+ if the given year is a leap year + # in the {proleptic Gregorian calendar}[https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar], +false+ otherwise: + # + # Date.gregorian_leap?(2000) # => true + # Date.gregorian_leap?(2001) # => false + # + # Related: Date.julian_leap?. + def gregorian_leap?(year) + raise TypeError, "expected numeric" unless year.is_a?(Numeric) + internal_gregorian_leap?(year) + end + alias_method :leap?, :gregorian_leap? + + # call-seq: + # Date.julian_leap?(year) -> true or false + # + # Returns +true+ if the given year is a leap year + # in the {proleptic Julian calendar}[https://en.wikipedia.org/wiki/Proleptic_Julian_calendar], +false+ otherwise: + # + # Date.julian_leap?(1900) # => true + # Date.julian_leap?(1901) # => false + # + # Related: Date.gregorian_leap?. + def julian_leap?(year) + raise TypeError, "expected numeric" unless year.is_a?(Numeric) + internal_julian_leap?(year) + end + + # call-seq: + # Date.ordinal(year = -4712, yday = 1, start = Date::ITALY) -> date + # + # Returns a new \Date object formed fom the arguments. + # + # With no arguments, returns the date for January 1, -4712: + # + # Date.ordinal.to_s # => "-4712-01-01" + # + # With argument +year+, returns the date for January 1 of that year: + # + # Date.ordinal(2001).to_s # => "2001-01-01" + # Date.ordinal(-2001).to_s # => "-2001-01-01" + # + # With positive argument +yday+ == +n+, + # returns the date for the +nth+ day of the given year: + # + # Date.ordinal(2001, 14).to_s # => "2001-01-14" + # + # With negative argument +yday+, counts backward from the end of the year: + # + # Date.ordinal(2001, -14).to_s # => "2001-12-18" + # + # Raises an exception if +yday+ is zero or out of range. + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + # Related: Date.jd, Date.new. + def ordinal(year = -4712, yday = 1, start = DEFAULT_SG) + if Integer === year && Integer === yday && yday >= 1 && yday <= 365 + jd1 = civil_to_jd(year, 1, 1, start) + jd = jd1 + yday - 1 + # Guard against the shortened reform year (fewer than 365 days), where + # some ydays <= 365 do not exist: the day must still precede next Jan 1. + if jd < civil_to_jd(year + 1, 1, 1, start) + return new_from_jd(jd, start) + end + end + check_numeric(yday, "yday") + check_numeric(year, "year") + year = Integer(year) + yday = Integer(yday) + jd = internal_valid_ordinal?(year, yday, start) + raise Date::Error, "invalid date" unless jd + new_from_jd(jd, start) + end + + # call-seq: + # Date.valid_ordinal?(year, yday, start = Date::ITALY) -> true or false + # + # Returns +true+ if the arguments define a valid ordinal date, + # +false+ otherwise: + # + # Date.valid_ordinal?(2001, 34) # => true + # Date.valid_ordinal?(2001, 366) # => false + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + # Related: Date.jd, Date.ordinal. + def valid_ordinal?(year, day, start = DEFAULT_SG) + return false unless year.is_a?(Numeric) && day.is_a?(Numeric) + !!internal_valid_ordinal?(year, day, start) + end + + # call-seq: + # Date.commercial(cwyear = -4712, cweek = 1, cwday = 1, start = Date::ITALY) -> date + # + # Returns a new \Date object constructed from the arguments. + # + # Argument +cwyear+ gives the year, and should be an integer. + # + # Argument +cweek+ gives the index of the week within the year, + # and should be in range (1..53) or (-53..-1); + # in some years, 53 or -53 will be out-of-range; + # if negative, counts backward from the end of the year: + # + # Date.commercial(2022, 1, 1).to_s # => "2022-01-03" + # Date.commercial(2022, 52, 1).to_s # => "2022-12-26" + # + # Argument +cwday+ gives the indes of the weekday within the week, + # and should be in range (1..7) or (-7..-1); + # 1 or -7 is Monday; + # if negative, counts backward from the end of the week: + # + # Date.commercial(2022, 1, 1).to_s # => "2022-01-03" + # Date.commercial(2022, 1, -7).to_s # => "2022-01-03" + # + # When +cweek+ is 1: + # + # - If January 1 is a Friday, Saturday, or Sunday, + # the first week begins in the week after: + # + # Date::ABBR_DAYNAMES[Date.new(2023, 1, 1).wday] # => "Sun" + # Date.commercial(2023, 1, 1).to_s # => "2023-01-02" + # Date.commercial(2023, 1, 7).to_s # => "2023-01-08" + # + # - Otherwise, the first week is the week of January 1, + # which may mean some of the days fall on the year before: + # + # Date::ABBR_DAYNAMES[Date.new(2020, 1, 1).wday] # => "Wed" + # Date.commercial(2020, 1, 1).to_s # => "2019-12-30" + # Date.commercial(2020, 1, 7).to_s # => "2020-01-05" + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + # Related: Date.jd, Date.new, Date.ordinal. + def commercial(cwyear = -4712, cweek = 1, cwday = 1, start = DEFAULT_SG) + if Integer === cwyear && Integer === cweek && Integer === cwday && + cweek >= 1 && cweek <= 52 && cwday >= 1 && cwday <= 7 + # ISO 8601: every normal year has at least 52 weeks, but the + # calendar-reform year is shorter; confirm the day precedes the start of + # the next commercial year before trusting the fast path. + jd = commercial_to_jd(cwyear, cweek, cwday, start) + return new_from_jd(jd, start) if jd < commercial_to_jd(cwyear + 1, 1, 1, start) + end + check_numeric(cwday, "cwday") + check_numeric(cweek, "cweek") + check_numeric(cwyear, "year") + cwyear = Integer(cwyear) + cweek = Integer(cweek) + cwday = Integer(cwday) + jd = internal_valid_commercial?(cwyear, cweek, cwday, start) + raise Date::Error, "invalid date" unless jd + new_from_jd(jd, start) + end + + # call-seq: + # Date.valid_commercial?(cwyear, cweek, cwday, start = Date::ITALY) -> true or false + # + # Returns +true+ if the arguments define a valid commercial date, + # +false+ otherwise: + # + # Date.valid_commercial?(2001, 5, 6) # => true + # Date.valid_commercial?(2001, 5, 8) # => false + # + # See Date.commercial. + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + # Related: Date.jd, Date.commercial. + def valid_commercial?(year, week, day, start = DEFAULT_SG) + return false unless year.is_a?(Numeric) && week.is_a?(Numeric) && day.is_a?(Numeric) + !!internal_valid_commercial?(year, week, day, start) + end + + # call-seq: + # Date.today(start = Date::ITALY) -> date + # + # Returns a new \Date object constructed from the present date: + # + # Date.today.to_s # => "2022-07-06" + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + def today(start = DEFAULT_SG) + t = Time.now + jd = civil_to_jd(t.year, t.mon, t.mday, start) + new_from_jd(jd, start) + end + + # :nodoc: + def _load(s) + a = Marshal.load(s) + obj = allocate + obj.marshal_load(a) + obj + end + + private + + # Reject non-Numeric arguments, mirroring C's check_numeric. + # The C extension uses NUM2INT/check_numeric and raises TypeError for + # non-Numeric input, whereas Integer() would coerce strings like "0xa". + def check_numeric(obj, field) + unless Numeric === obj + raise TypeError, "invalid #{field} (not numeric)" + end + end + + def civil_fallback(year, month, day, start) + check_numeric(day, "day") + check_numeric(month, "month") + check_numeric(year, "year") + year = Integer(year) + month = Integer(month) + day = Integer(day) + jd = internal_valid_civil?(year, month, day, start) + raise Date::Error, "invalid date" unless jd + new_from_jd(jd, start) + end + + # --------------------------------------------------------------------------- + # Internal calendar arithmetic (pure Ruby, no C dependency) + # --------------------------------------------------------------------------- + + # Gregorian leap year? + def internal_gregorian_leap?(y) + (y % 4 == 0 && y % 100 != 0) || y % 400 == 0 + end + + # Julian leap year? + def internal_julian_leap?(y) + y % 4 == 0 + end + + def days_in_month_gregorian(y, m) + if m == 2 && internal_gregorian_leap?(y) + 29 + else + DAYS_IN_MONTH_GREGORIAN[m] + end + end + + # Days in month for Julian calendar. + def days_in_month_julian(y, m) + if m == 2 && internal_julian_leap?(y) + 29 + else + DAYS_IN_MONTH_GREGORIAN[m] + end + end + + # Gregorian civil (year, month, day) -> Julian Day Number + def gregorian_to_jd(y, m, d) + if m <= 2 + y -= 1 + m += 12 + end + a = y / 100 + b = 2 - a + a / 4 + (1461 * (y + 4716)) / 4 + (306001 * (m + 1)) / 10000 + d + b - 1524 + end + + # Julian civil (year, month, day) -> Julian Day Number + def julian_to_jd(y, m, d) + if m <= 2 + y -= 1 + m += 12 + end + (1461 * (y + 4716)) / 4 + (306001 * (m + 1)) / 10000 + d - 1524 + end + + # Civil (year, month, day) -> JD, respecting start (cutover). + # Uses a unified formula: gjd >= sg -> Gregorian JD, else Julian JD. + # Works for all sg values: Float::INFINITY (always Julian), + # -Float::INFINITY (always Gregorian), or integer cutover JD. + def civil_to_jd(y, m, d, sg) + offset = GJD_MONTH_OFFSET[m] + y -= 1 if m <= 2 + gjd_base = (1461 * (y + 4716)) / 4 + offset + d + a = y / 100 + gjd = gjd_base - 1524 + 2 - a + a / 4 + gjd >= sg ? gjd : gjd_base - 1524 + end + + # Gregorian JD -> (year, month, day) + def jd_to_gregorian(jd) + a = jd + 32044 + b = (4 * a + 3).div(146097) + c = a - (146097 * b).div(4) + d = (4 * c + 3).div(1461) + e = c - (1461 * d).div(4) + m = (5 * e + 2).div(153) + day = e - (153 * m + 2).div(5) + 1 + mon = m + 3 - 12 * m.div(10) + year = 100 * b + d - 4800 + m.div(10) + [year, mon, day] + end + + # Julian JD -> (year, month, day) + def jd_to_julian(jd) + c = jd + 32082 + d = (4 * c + 3).div(1461) + e = c - (1461 * d).div(4) + m = (5 * e + 2).div(153) + day = e - (153 * m + 2).div(5) + 1 + mon = m + 3 - 12 * m.div(10) + year = d - 4800 + m.div(10) + [year, mon, day] + end + + # JD -> (year, month, day), respecting start (cutover). + def jd_to_civil(jd, sg) + if sg == Float::INFINITY + jd_to_julian(jd) + elsif sg == -Float::INFINITY || jd >= sg + jd_to_gregorian(jd) + else + jd_to_julian(jd) + end + end + + # Ordinal (year, day-of-year) -> JD + def ordinal_to_jd(y, d, sg) + civil_to_jd(y, 1, 1, sg) + d - 1 + end + + # JD -> (year, day-of-year) + def jd_to_ordinal(jd, sg) + y, = jd_to_civil(jd, sg) + jd_jan1 = civil_to_jd(y, 1, 1, sg) + [y, jd - jd_jan1 + 1] + end + + # Commercial (ISO week: cwyear, cweek, cwday) -> JD + def commercial_to_jd(y, w, d, sg = -Float::INFINITY) + # Jan 4 is always in week 1 (ISO 8601) + jd_jan4 = civil_to_jd(y, 1, 4, sg) + # Monday of week 1 + wday_jan4 = (jd_jan4 + 1) % 7 # 0=Sun + iso_wday_jan4 = wday_jan4 == 0 ? 7 : wday_jan4 + mon_wk1 = jd_jan4 - (iso_wday_jan4 - 1) + mon_wk1 + (w - 1) * 7 + (d - 1) + end + + # JD -> (cwyear, cweek, cwday) + def jd_to_commercial(jd, sg = -Float::INFINITY) + wday = (jd + 1) % 7 # 0=Sun + cwday = wday == 0 ? 7 : wday # 1=Mon..7=Sun + # Thursday of the same ISO week + thursday = jd + (4 - cwday) + y, = thursday >= sg ? jd_to_gregorian(thursday) : jd_to_julian(thursday) + jd_jan4 = civil_to_jd(y, 1, 4, sg) + wday_jan4 = (jd_jan4 + 1) % 7 + iso_wday_jan4 = wday_jan4 == 0 ? 7 : wday_jan4 + mon_wk1 = jd_jan4 - (iso_wday_jan4 - 1) + cweek = (jd - mon_wk1) / 7 + 1 + [y, cweek, cwday] + end + + # --------------------------------------------------------------------------- + # Validation helpers + # --------------------------------------------------------------------------- + + def internal_valid_jd?(jd, _sg) + jd.is_a?(Numeric) ? jd.to_i : nil + end + + def internal_valid_civil?(y, m, d, sg) + return nil unless y.is_a?(Numeric) && m.is_a?(Numeric) && d.is_a?(Numeric) + y = y.to_i + m = m.to_i + d = d.to_i + # Handle negative month + m += 13 if m < 0 + return nil if m < 1 || m > 12 + + if sg == Float::INFINITY + # Proleptic Julian calendar. + dim = days_in_month_julian(y, m) + d += dim + 1 if d < 0 + return nil if d < 1 || d > dim + return julian_to_jd(y, m, d) + end + + # Gregorian reckoning: valid for GREGORIAN and for the post-reform side of + # a finite cutover. Days skipped by the reform have a Gregorian JD before + # the cutover (gjd < sg) and are rejected here, then handled below. + gdim = days_in_month_gregorian(y, m) + gd = d < 0 ? d + gdim + 1 : d + if gd >= 1 && gd <= gdim + gjd = gregorian_to_jd(y, m, gd) + return gjd if gjd >= sg + end + return nil if sg == -Float::INFINITY + + # Pre-reform (Julian) side. This also accepts Julian-only leap days such as + # 1500-02-29 under ITALY. A day skipped by the reform has jjd >= sg. + jdim = days_in_month_julian(y, m) + jd2 = d < 0 ? d + jdim + 1 : d + return nil if jd2 < 1 || jd2 > jdim + jjd = julian_to_jd(y, m, jd2) + jjd < sg ? jjd : nil + end + + def internal_valid_ordinal?(y, yday, sg) + return nil unless y.is_a?(Numeric) && yday.is_a?(Numeric) + y = y.to_i + yday = yday.to_i + # The difference of the two Jan 1 Julian Day numbers is the true length of + # the year under this cutover, including the shortened reform year. Ordinal + # days are continuous from Jan 1, so a bounds check is sufficient. + jd_jan1 = civil_to_jd(y, 1, 1, sg) + diy = civil_to_jd(y + 1, 1, 1, sg) - jd_jan1 + yday += diy + 1 if yday < 0 + return nil if yday < 1 || yday > diy + jd_jan1 + yday - 1 + end + + def internal_valid_commercial?(y, w, d, sg) + return nil unless y.is_a?(Numeric) && w.is_a?(Numeric) && d.is_a?(Numeric) + y = y.to_i + w = w.to_i + d = d.to_i + # ISO cwday: 1=Mon..7=Sun + d += 8 if d < 0 + return nil if d < 1 || d > 7 + # Weeks in year: Dec 28 is always in the last ISO week + jd_dec28 = civil_to_jd(y, 12, 28, sg) + _, max_week, = jd_to_commercial(jd_dec28, sg) + w += max_week + 1 if w < 0 + return nil if w < 1 || w > max_week + commercial_to_jd(y, w, d, sg) + end + + # --------------------------------------------------------------------------- + # Internal object factory + # --------------------------------------------------------------------------- + + # Build a Date from a Julian Day Number (integer part), start, and optional day fraction. + def new_from_jd(jd, sg, df = nil) + obj = allocate + obj.__send__(:init_from_jd, jd, sg, df) + obj + end + + # Parse offset string like "+09:00", "-07:30", "Z" to seconds. + def offset_str_to_sec(str) + case str + when 'Z', 'z', 'UTC', 'GMT' + 0 + when /\A([+-])(\d{1,2}):?(\d{2})(?::(\d{2}))?\z/ + sign = $1 == '+' ? 1 : -1 + h, m, s = $2.to_i, $3.to_i, ($4 || '0').to_i + sign * (h * 3600 + m * 60 + s) + when /\A([+-])(\d{2})(\d{2})\z/ + sign = $1 == '+' ? 1 : -1 + h, m = $2.to_i, $3.to_i + sign * (h * 3600 + m * 60) + else + 0 + end + end + + end + + # --------------------------------------------------------------------------- + # Instance methods - basic attributes + # --------------------------------------------------------------------------- + + # call-seq: + # year -> integer + # + # Returns the year: + # + # Date.new(2001, 2, 3).year # => 2001 + # (Date.new(1, 1, 1) - 1).year # => 0 + # + def year + internal_civil unless @year + @year + end + + # call-seq: + # mon -> integer + # + # Returns the month in range (1..12): + # + # Date.new(2001, 2, 3).mon # => 2 + # + def month + internal_civil unless @year + @month + end + alias mon month + + # call-seq: + # mday -> integer + # + # Returns the day of the month in range (1..31): + # + # Date.new(2001, 2, 3).mday # => 3 + # + def day + internal_civil unless @year + @day + end + alias mday day + + # call-seq: + # d.jd -> integer + # + # Returns the Julian day number. This is a whole number, which is + # adjusted by the offset as the local time. + # + # DateTime.new(2001,2,3,4,5,6,'+7').jd #=> 2451944 + # DateTime.new(2001,2,3,4,5,6,'-7').jd #=> 2451944 + def jd + @jd + end + + # call-seq: + # start -> float + # + # Returns the Julian start date for calendar reform; + # if not an infinity, the returned value is suitable + # for passing to Date#jd: + # + # d = Date.new(2001, 2, 3, Date::ITALY) + # s = d.start # => 2299161.0 + # Date.jd(s).to_s # => "1582-10-15" + # + # d = Date.new(2001, 2, 3, Date::ENGLAND) + # s = d.start # => 2361222.0 + # Date.jd(s).to_s # => "1752-09-14" + # + # Date.new(2001, 2, 3, Date::GREGORIAN).start # => -Infinity + # Date.new(2001, 2, 3, Date::JULIAN).start # => Infinity + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + def start + @start + end + + # call-seq: + # d.ajd -> rational + # + # Returns the astronomical Julian day number. This is a fractional + # number, which is not adjusted by the offset. + # + # DateTime.new(2001,2,3,4,5,6,'+7').ajd #=> (11769328217/4800) + # DateTime.new(2001,2,2,14,5,6,'-7').ajd #=> (11769328217/4800) + def ajd + r = Rational(@jd * 2 - 1, 2) + r + day_fraction + end + + # call-seq: + # d.amjd -> rational + # + # Returns the astronomical modified Julian day number. This is + # a fractional number, which is not adjusted by the offset. + # + # DateTime.new(2001,2,3,4,5,6,'+7').amjd #=> (249325817/4800) + # DateTime.new(2001,2,2,14,5,6,'-7').amjd #=> (249325817/4800) + def amjd + ajd - Rational(4800001, 2) + end + + # call-seq: + # d.mjd -> integer + # + # Returns the modified Julian day number. This is a whole number, + # which is adjusted by the offset as the local time. + # + # DateTime.new(2001,2,3,4,5,6,'+7').mjd #=> 51943 + # DateTime.new(2001,2,3,4,5,6,'-7').mjd #=> 51943 + def mjd + @jd - 2400001 + end + + # call-seq: + # ld -> integer + # + # Returns the + # {Lilian day number}[https://en.wikipedia.org/wiki/Lilian_date], + # which is the number of days since the beginning of the Gregorian + # calendar, October 15, 1582. + # + # Date.new(2001, 2, 3).ld # => 152784 + # + def ld + @jd - 2299160 + end + + # call-seq: + # yday -> integer + # + # Returns the day of the year, in range (1..366): + # + # Date.new(2001, 2, 3).yday # => 34 + # + def yday + internal_civil unless @year + jd_jan1 = self.class.__send__(:civil_to_jd, @year, 1, 1, @start) + @jd - jd_jan1 + 1 + end + + # call-seq: + # wday -> integer + # + # Returns the day of week in range (0..6); Sunday is 0: + # + # Date.new(2001, 2, 3).wday # => 6 + # + def wday + (@jd + 1) % 7 + end + + # call-seq: + # cwday -> integer + # + # Returns the commercial-date weekday index for +self+ + # (see Date.commercial); + # 1 is Monday: + # + # Date.new(2001, 2, 3).cwday # => 6 + # + def cwday + w = wday + w == 0 ? 7 : w + end + + # call-seq: + # cweek -> integer + # + # Returns commercial-date week index for +self+ + # (see Date.commercial): + # + # Date.new(2001, 2, 3).cweek # => 5 + # + def cweek + compute_commercial[1] + end + + # call-seq: + # cwyear -> integer + # + # Returns commercial-date year for +self+ + # (see Date.commercial): + # + # Date.new(2001, 2, 3).cwyear # => 2001 + # Date.new(2000, 1, 1).cwyear # => 1999 + # + def cwyear + compute_commercial[0] + end + + # call-seq: + # day_fraction -> rational + # + # Returns the fractional part of the day in range (Rational(0, 1)...Rational(1, 1)): + # + # DateTime.new(2001,2,3,12).day_fraction # => (1/2) + # + def day_fraction + # C returns Integer 0 (INT2FIX(0)) for a simple Date without a fraction, + # and a Rational only when a day fraction is present. + @day_fraction || 0 + end + + # call-seq: + # leap? -> true or false + # + # Returns +true+ if the year is a leap year, +false+ otherwise: + # + # Date.new(2000).leap? # => true + # Date.new(2001).leap? # => false + # + def leap? + internal_civil unless @year + if @jd < @start # julian? + @year % 4 == 0 + else + (@year % 4 == 0 && @year % 100 != 0) || @year % 400 == 0 + end + end + + # call-seq: + # gregorian? -> true or false + # + # Returns +true+ if the date is on or after + # the date of calendar reform, +false+ otherwise: + # + # Date.new(1582, 10, 15).gregorian? # => true + # (Date.new(1582, 10, 15) - 1).gregorian? # => false + # + def gregorian? + jd >= @start + end + + # call-seq: + # d.julian? -> true or false + # + # Returns +true+ if the date is before the date of calendar reform, + # +false+ otherwise: + # + # (Date.new(1582, 10, 15) - 1).julian? # => true + # Date.new(1582, 10, 15).julian? # => false + # + def julian? + !gregorian? + end + + # call-seq: + # new_start(start = Date::ITALY]) -> new_date + # + # Returns a copy of +self+ with the given +start+ value: + # + # d0 = Date.new(2000, 2, 3) + # d0.julian? # => false + # d1 = d0.new_start(Date::JULIAN) + # d1.julian? # => true + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + def new_start(start = DEFAULT_SG) + obj = self.class.allocate + obj.instance_variable_set(:@jd, @jd) + # Store the cutover as a Float, matching the C extension (see init_from_jd). + obj.instance_variable_set(:@start, start.to_f) + obj + end + + # call-seq: + # gregorian -> new_date + # + # Equivalent to Date#new_start with argument Date::GREGORIAN. + def gregorian + new_start(GREGORIAN) + end + + # call-seq: + # italy -> new_date + # + # Equivalent to Date#new_start with argument Date::ITALY. + # + def italy + new_start(ITALY) + end + + # call-seq: + # england -> new_date + # + # Equivalent to Date#new_start with argument Date::ENGLAND. + def england + new_start(ENGLAND) + end + + # call-seq: + # julian -> new_date + # + # Equivalent to Date#new_start with argument Date::JULIAN. + def julian + new_start(JULIAN) + end + + # call-seq: + # sunday? -> true or false + # + # Returns +true+ if +self+ is a Sunday, +false+ otherwise. + def sunday? + wday == 0 + end + # call-seq: + # monday? -> true or false + # + # Returns +true+ if +self+ is a Monday, +false+ otherwise. + def monday? + wday == 1 + end + # call-seq: + # tuesday? -> true or false + # + # Returns +true+ if +self+ is a Tuesday, +false+ otherwise. + def tuesday? + wday == 2 + end + # call-seq: + # wednesday? -> true or false + # + # Returns +true+ if +self+ is a Wednesday, +false+ otherwise. + def wednesday? + wday == 3 + end + # call-seq: + # thursday? -> true or false + # + # Returns +true+ if +self+ is a Thursday, +false+ otherwise. + def thursday? + wday == 4 + end + # call-seq: + # friday? -> true or false + # + # Returns +true+ if +self+ is a Friday, +false+ otherwise. + def friday? + wday == 5 + end + # call-seq: + # saturday? -> true or false + # + # Returns +true+ if +self+ is a Saturday, +false+ otherwise. + def saturday? + wday == 6 + end + + # --------------------------------------------------------------------------- + # Comparison + # --------------------------------------------------------------------------- + + # call-seq: + # self <=> other -> -1, 0, 1 or nil + # + # Compares +self+ and +other+, returning: + # + # - -1 if +other+ is larger. + # - 0 if the two are equal. + # - 1 if +other+ is smaller. + # - +nil+ if the two are incomparable. + # + # Argument +other+ may be: + # + # - Another \Date object: + # + # d = Date.new(2022, 7, 27) # => # + # prev_date = d.prev_day # => # + # next_date = d.next_day # => # + # d <=> next_date # => -1 + # d <=> d # => 0 + # d <=> prev_date # => 1 + # + # - A DateTime object: + # + # d <=> DateTime.new(2022, 7, 26) # => 1 + # d <=> DateTime.new(2022, 7, 27) # => 0 + # d <=> DateTime.new(2022, 7, 28) # => -1 + # + # - A numeric (compares self.ajd to +other+): + # + # d <=> 2459788 # => -1 + # d <=> 2459787 # => 1 + # d <=> 2459786 # => 1 + # d <=> d.ajd # => 0 + # + # - Any other object: + # + # d <=> Object.new # => nil + # + def <=>(other) + case other + when Date + d = @jd <=> other.jd + d != 0 ? d : day_fraction <=> other.day_fraction + when Numeric + ajd <=> other + else + nil + end + end + + # #<, #>, #==, #<=, #>= are all provided by Comparable via #<=>, matching + # the C extension, which includes Comparable and defines only #<=> and #===. + + def eql?(other) + other.is_a?(Date) && @jd == other.jd && day_fraction == other.day_fraction + end + + def hash + [@jd, @start].hash + end + + # call-seq: + # self === other -> true, false, or nil. + # + # Returns +true+ if +self+ and +other+ represent the same date, + # +false+ if not, +nil+ if the two are not comparable. + # + # Argument +other+ may be: + # + # - Another \Date object: + # + # d = Date.new(2022, 7, 27) # => # + # prev_date = d.prev_day # => # + # next_date = d.next_day # => # + # d === prev_date # => false + # d === d # => true + # d === next_date # => false + # + # - A DateTime object: + # + # d === DateTime.new(2022, 7, 26) # => false + # d === DateTime.new(2022, 7, 27) # => true + # d === DateTime.new(2022, 7, 28) # => false + # + # - A numeric (compares self.jd to +other+): + # + # d === 2459788 # => true + # d === 2459787 # => false + # d === 2459786 # => false + # d === d.jd # => true + # + # - An object not comparable: + # + # d === Object.new # => nil + # + def ===(other) + case other + when Numeric + jd == other.to_i + when Date + jd == other.jd + else + nil + end + end + + # --------------------------------------------------------------------------- + # Arithmetic + # --------------------------------------------------------------------------- + + # call-seq: + # d + other -> date + # + # Returns a date object pointing +other+ days after self. The other + # should be a numeric value. If the other is a fractional number, + # assumes its precision is at most nanosecond. + # + # Date.new(2001,2,3) + 1 #=> # + # DateTime.new(2001,2,3) + Rational(1,2) + # #=> # + # DateTime.new(2001,2,3) + Rational(-1,2) + # #=> # + # DateTime.jd(0,12) + DateTime.new(2001,2,3).ajd + # #=> # + def +(other) + case other + when Integer + self.class.__send__(:new_from_jd, @jd + other, @start, @day_fraction) + when Numeric + r = other.to_r + raise TypeError, "#{other.class} can't be coerced into Integer" unless r.is_a?(Rational) + total = r + day_fraction + days = total.floor + frac = total - days + self.class.__send__(:new_from_jd, @jd + days, @start, frac == 0 ? nil : frac) + else + raise TypeError, "expected numeric" + end + end + + # call-seq: + # d - other -> date or rational + # + # If the other is a date object, returns a Rational + # whose value is the difference between the two dates in days. + # If the other is a numeric value, returns a date object + # pointing +other+ days before self. + # If the other is a fractional number, + # assumes its precision is at most nanosecond. + # + # Date.new(2001,2,3) - 1 #=> # + # DateTime.new(2001,2,3) - Rational(1,2) + # #=> # + # Date.new(2001,2,3) - Date.new(2001) + # #=> (33/1) + # DateTime.new(2001,2,3) - DateTime.new(2001,2,2,12) + # #=> (1/2) + def -(other) + case other + when Date + Rational(@jd - other.jd) + day_fraction - other.day_fraction + when Integer + self.class.__send__(:new_from_jd, @jd - other, @start, @day_fraction) + when Numeric + r = other.to_r + raise TypeError, "#{other.class} can't be coerced into Integer" unless r.is_a?(Rational) + total = day_fraction - r + days = total.floor + frac = total - days + self.class.__send__(:new_from_jd, @jd + days, @start, frac == 0 ? nil : frac) + else + raise TypeError, "expected numeric" + end + end + + # call-seq: + # d >> n -> new_date + # + # Returns a new \Date object representing the date + # +n+ months later; +n+ should be a numeric: + # + # (Date.new(2001, 2, 3) >> 1).to_s # => "2001-03-03" + # (Date.new(2001, 2, 3) >> -2).to_s # => "2000-12-03" + # + # When the same day does not exist for the new month, + # the last day of that month is used instead: + # + # (Date.new(2001, 1, 31) >> 1).to_s # => "2001-02-28" + # (Date.new(2001, 1, 31) >> -4).to_s # => "2000-09-30" + # + # This results in the following, possibly unexpected, behaviors: + # + # d0 = Date.new(2001, 1, 31) + # d1 = d0 >> 1 # => # + # d2 = d1 >> 1 # => # + # + # d0 = Date.new(2001, 1, 31) + # d1 = d0 >> 1 # => # + # d2 = d1 >> -1 # => # + # + def >>(n) + self.class.__send__(:new_from_jd, month_shifted_jd(n), @start) + end + + # Julian Day number of the date +n+ months from self, clamping the day to the + # last day of the target month (using the calendar that applies under the + # cutover). Shared with DateTime#>> so the time of day can be preserved. + private def month_shifted_jd(n) + internal_civil unless @year + m2 = @month + n.to_i + y2 = @year + (m2 - 1).div(12) + m2 = (m2 - 1) % 12 + 1 + klass = self.class + sg = @start + julian = if sg == Float::INFINITY + true + elsif sg == -Float::INFINITY + false + else + klass.__send__(:gregorian_to_jd, y2, m2, 1) < sg + end + dim = julian ? klass.__send__(:days_in_month_julian, y2, m2) + : klass.__send__(:days_in_month_gregorian, y2, m2) + d2 = @day < dim ? @day : dim + klass.__send__(:civil_to_jd, y2, m2, d2, sg) + end + + # call-seq: + # d << n -> date + # + # Returns a new \Date object representing the date + # +n+ months earlier; +n+ should be a numeric: + # + # (Date.new(2001, 2, 3) << 1).to_s # => "2001-01-03" + # (Date.new(2001, 2, 3) << -2).to_s # => "2001-04-03" + # + # When the same day does not exist for the new month, + # the last day of that month is used instead: + # + # (Date.new(2001, 3, 31) << 1).to_s # => "2001-02-28" + # (Date.new(2001, 3, 31) << -6).to_s # => "2001-09-30" + # + # This results in the following, possibly unexpected, behaviors: + # + # d0 = Date.new(2001, 3, 31) + # d0 << 2 # => # + # d0 << 1 << 1 # => # + # + # d0 = Date.new(2001, 3, 31) + # d1 = d0 << 1 # => # + # d2 = d1 << -1 # => # + # + def <<(n) + self >> -n + end + + # call-seq: + # next_day(n = 1) -> new_date + # + # Equivalent to Date#+ with argument +n+. + def next_day(n = 1) + self + n + end + + # call-seq: + # prev_day(n = 1) -> new_date + # + # Equivalent to Date#- with argument +n+. + def prev_day(n = 1) + self - n + end + + # call-seq: + # d.next -> new_date + # + # Returns a new \Date object representing the following day: + # + # d = Date.new(2001, 2, 3) + # d.to_s # => "2001-02-03" + # d.next.to_s # => "2001-02-04" + # + def next + self + 1 + end + alias_method :succ, :next + + # call-seq: + # next_year(n = 1) -> new_date + # + # Equivalent to #>> with argument n * 12. + def next_year(n = 1) + self >> n * 12 + end + + # call-seq: + # prev_year(n = 1) -> new_date + # + # Equivalent to #<< with argument n * 12. + def prev_year(n = 1) + self << n * 12 + end + + # call-seq: + # next_month(n = 1) -> new_date + # + # Equivalent to #>> with argument +n+. + def next_month(n = 1) + self >> n + end + + # call-seq: + # prev_month(n = 1) -> new_date + # + # Equivalent to #<< with argument +n+. + def prev_month(n = 1) + self << n + end + + # call-seq: + # step(limit, step = 1){|date| ... } -> self + # + # Calls the block with specified dates; + # returns +self+. + # + # - The first +date+ is +self+. + # - Each successive +date+ is date + step, + # where +step+ is the numeric step size in days. + # - The last date is the last one that is before or equal to +limit+, + # which should be a \Date object. + # + # Example: + # + # limit = Date.new(2001, 12, 31) + # Date.new(2001).step(limit){|date| p date.to_s if date.mday == 31 } + # + # Output: + # + # "2001-01-31" + # "2001-03-31" + # "2001-05-31" + # "2001-07-31" + # "2001-08-31" + # "2001-10-31" + # "2001-12-31" + # + # Returns an Enumerator if no block is given. + def step(limit, step = 1) + return to_enum(:step, limit, step) unless block_given? + if Integer === step && instance_of?(Date) && limit.instance_of?(Date) + raise ArgumentError, "step can't be 0" if step == 0 + limit_jd = limit.jd + sg = @start + if step > 0 + jd = @jd + while jd <= limit_jd + obj = Date.allocate + obj.instance_variable_set(:@jd, jd) + obj.instance_variable_set(:@start, sg) + yield obj + jd += step + end + else + jd = @jd + while jd >= limit_jd + obj = Date.allocate + obj.instance_variable_set(:@jd, jd) + obj.instance_variable_set(:@start, sg) + yield obj + jd += step + end + end + return self + end + d = self + cmp = step <=> 0 + raise ArgumentError, "comparison of #{step.class} with 0 failed" if cmp.nil? + if cmp > 0 + while d <= limit + yield d + d = d + step + end + elsif cmp < 0 + while d >= limit + yield d + d = d + step + end + else + raise ArgumentError, "step can't be 0" + end + self + end + + # call-seq: + # upto(max){|date| ... } -> self + # + # Equivalent to #step with arguments +max+ and +1+. + def upto(max, &block) + return to_enum(:upto, max) unless block_given? + if instance_of?(Date) && max.instance_of?(Date) + jd = @jd + max_jd = max.jd + sg = @start + while jd <= max_jd + obj = Date.allocate + obj.instance_variable_set(:@jd, jd) + obj.instance_variable_set(:@start, sg) + yield obj + jd += 1 + end + return self + end + step(max, 1, &block) + end + + # call-seq: + # downto(min){|date| ... } -> self + # + # Equivalent to #step with arguments +min+ and -1. + def downto(min, &block) + return to_enum(:downto, min) unless block_given? + if instance_of?(Date) && min.instance_of?(Date) + jd = @jd + min_jd = min.jd + sg = @start + while jd >= min_jd + obj = Date.allocate + obj.instance_variable_set(:@jd, jd) + obj.instance_variable_set(:@start, sg) + yield obj + jd -= 1 + end + return self + end + step(min, -1, &block) + end + + # --------------------------------------------------------------------------- + # Calendar conversion + # --------------------------------------------------------------------------- + + # call-seq: + # to_date -> self + # + # Returns +self+. + def to_date + self + end + + # call-seq: + # d.to_datetime -> datetime + # + # Returns a DateTime whose value is the same as +self+: + # + # Date.new(2001, 2, 3).to_datetime # => # + # + def to_datetime + DateTime.new(year, month, day, 0, 0, 0, 0, @start) + end + + # call-seq: + # to_time -> time + # + # Returns a new Time object with the same value as +self+; + # if +self+ is a Julian date, derives its Gregorian date + # for conversion to the \Time object: + # + # Date.new(2001, 2, 3).to_time # => 2001-02-03 00:00:00 -0600 + # Date.new(2001, 2, 3, Date::JULIAN).to_time # => 2001-02-16 00:00:00 -0600 + # + def to_time + year, month, day = self.class.__send__(:jd_to_gregorian, @jd) + Time.local(year, month, day) + end + + # --------------------------------------------------------------------------- + # Serialization + # --------------------------------------------------------------------------- + + # :nodoc: + def initialize_copy(other) + # Assign every ivar in the canonical order so copies share the same shape + # as freshly constructed instances (see init_from_jd). + @jd = other.instance_variable_get(:@jd) + @start = other.instance_variable_get(:@start) + @day_fraction = other.instance_variable_get(:@day_fraction) + @year = other.instance_variable_get(:@year) + @month = other.instance_variable_get(:@month) + @day = other.instance_variable_get(:@day) + end + + # :nodoc: + def marshal_dump + # 6-element format: [nth, jd, df, sf, of, sg] + # df = seconds into day (Integer), sf = sub-second fraction (Rational) + if @day_fraction + total_sec = @day_fraction * 86400 + df_int = total_sec.floor + sf = total_sec - df_int + [0, @jd, df_int, sf, 0, @start] + else + [0, @jd, 0, 0, 0, @start] + end + end + + # :nodoc: + def marshal_load(array) + case array.length + when 2 + # Format 1.4/1.6: [jd_like, sg_or_bool] + jd_like, sg_or_bool = array + sg = sg_or_bool == true ? GREGORIAN : (sg_or_bool == false ? JULIAN : sg_or_bool.to_f) + init_from_jd(jd_like.to_i, sg) + when 3 + # Format 1.8: [ajd, of, sg] + ajd, _of, sg = array + sg = normalize_start(sg) + raw_jd = ajd + 0.5r + jd = raw_jd.floor + df = raw_jd - jd + init_from_jd(jd, sg, df == 0 ? nil : df) + when 6 + # Current format: [nth, jd, df, sf, of, sg] + _nth, jd, df, sf, _of, sg = array + sg = normalize_start(sg) + if df != 0 || sf != 0 + day_frac = (Rational(df) + sf) / 86400 + init_from_jd(jd, sg, day_frac) + else + init_from_jd(jd, sg) + end + else + raise TypeError, "invalid marshal data" + end + end + + # call-seq: + # deconstruct_keys(array_of_names_or_nil) -> hash + # + # Returns a hash of the name/value pairs, to use in pattern matching. + # Possible keys are: :year, :month, :day, + # :wday, :yday. + # + # Possible usages: + # + # d = Date.new(2022, 10, 5) + # + # if d in wday: 3, day: ..7 # uses deconstruct_keys underneath + # puts "first Wednesday of the month" + # end + # #=> prints "first Wednesday of the month" + # + # case d + # in year: ...2022 + # puts "too old" + # in month: ..9 + # puts "quarter 1-3" + # in wday: 1..5, month: + # puts "working day in month #{month}" + # end + # #=> prints "working day in month 10" + # + # Note that deconstruction by pattern can also be combined with class check: + # + # if d in Date(wday: 3, day: ..7) + # puts "first Wednesday of the month" + # end + # + def deconstruct_keys(keys) + if keys + if keys.size == 1 + case keys[0] + when :year then { year: year } + when :month then { month: month } + when :day then { day: day } + when :wday then { wday: wday } + when :yday then { yday: yday } + else {} + end + else + h = {} + keys.each do |k| + case k + when :year then h[:year] = year + when :month then h[:month] = month + when :day then h[:day] = day + when :wday then h[:wday] = wday + when :yday then h[:yday] = yday + end + end + h + end + else + { year: year, month: month, day: day, wday: wday, yday: yday } + end + end + + # --------------------------------------------------------------------------- + # String formatting (delegated to strftime.rb) + # --------------------------------------------------------------------------- + + # call-seq: + # asctime -> string + # + # Equivalent to #strftime with argument '%a %b %e %T %Y' + # (or its {shorthand form}[rdoc-ref:language/strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] + # '%c'): + # + # Date.new(2001, 2, 3).asctime # => "Sat Feb 3 00:00:00 2001" + # + # See {asctime}[https://linux.die.net/man/3/asctime]. + # + def asctime + internal_civil unless @year + d = @day + d_s = d < 10 ? " #{d}" : d.to_s + y = @year + y_s = y >= 1000 ? y.to_s : (y >= 0 ? format('%04d', y) : format('-%04d', -y)) + w = (@jd + 1) % 7 + if instance_of?(Date) + "#{ASCTIME_DAYS[w]} #{ASCTIME_MONS[@month]} #{d_s} 00:00:00 #{y_s}".force_encoding(Encoding::US_ASCII) + else + "#{ASCTIME_DAYS[w]} #{ASCTIME_MONS[@month]} #{d_s} #{PAD2[internal_hour]}:#{PAD2[internal_min]}:#{PAD2[internal_sec]} #{y_s}".force_encoding(Encoding::US_ASCII) + end + end + alias_method :ctime, :asctime + + # call-seq: + # iso8601 -> string + # + # Equivalent to #strftime with argument '%Y-%m-%d' + # (or its {shorthand form}[rdoc-ref:language/strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] + # '%F'); + # + # Date.new(2001, 2, 3).iso8601 # => "2001-02-03" + # + def iso8601 + to_s + end + alias_method :xmlschema, :iso8601 + + # call-seq: + # rfc3339 -> string + # + # Equivalent to #strftime with argument '%FT%T%:z'; + # see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: + # + # Date.new(2001, 2, 3).rfc3339 # => "2001-02-03T00:00:00+00:00" + # + def rfc3339 + (to_s << 'T00:00:00+00:00').force_encoding(Encoding::US_ASCII) + end + + # call-seq: + # rfc2822 -> string + # + # Equivalent to #strftime with argument '%a, %-d %b %Y %T %z'; + # see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: + # + # Date.new(2001, 2, 3).rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" + # + def rfc2822 + internal_civil unless @year + w = (@jd + 1) % 7 + y = @year + y_s = y >= 1000 ? y.to_s : (y >= 0 ? format('%04d', y) : format('-%04d', -y)) + if instance_of?(Date) + "#{RFC2822_DAYS[w]}, #{@day}#{RFC_MON_SPACE[@month]}#{y_s} 00:00:00 +0000".force_encoding(Encoding::US_ASCII) + else + "#{RFC2822_DAYS[w]}, #{@day}#{RFC_MON_SPACE[@month]}#{y_s} #{PAD2[internal_hour]}:#{PAD2[internal_min]}:#{PAD2[internal_sec]} +0000".force_encoding(Encoding::US_ASCII) + end + end + alias_method :rfc822, :rfc2822 + + # call-seq: + # httpdate -> string + # + # Equivalent to #strftime with argument '%a, %d %b %Y %T GMT'; + # see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: + # + # Date.new(2001, 2, 3).httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" + # + def httpdate + internal_civil unless @year + w = (@jd + 1) % 7 + y = @year + y_s = y >= 1000 ? y.to_s : (y >= 0 ? format('%04d', y) : format('-%04d', -y)) + if instance_of?(Date) + "#{ASCTIME_DAYS[w]}, #{PAD2[@day]}#{RFC_MON_SPACE[@month]}#{y_s} 00:00:00 GMT".force_encoding(Encoding::US_ASCII) + else + "#{ASCTIME_DAYS[w]}, #{PAD2[@day]}#{RFC_MON_SPACE[@month]}#{y_s} #{PAD2[internal_hour]}:#{PAD2[internal_min]}:#{PAD2[internal_sec]} GMT".force_encoding(Encoding::US_ASCII) + end + end + + # call-seq: + # jisx0301 -> string + # + # Returns a string representation of the date in +self+ + # in JIS X 0301 format. + # + # Date.new(2001, 2, 3).jisx0301 # => "H13.02.03" + # + def jisx0301 + internal_civil unless @year + jd = @jd + m = @month + d = @day + md = "#{PAD2[m]}.#{PAD2[d]}" + if jd >= 2458605 # Reiwa (2019-05-01) + "R#{PAD2[@year - 2018]}.#{md}" + elsif jd >= 2447535 # Heisei (1989-01-08) + "H#{PAD2[@year - 1988]}.#{md}" + elsif jd >= 2424875 # Showa (1926-12-25) + "S#{PAD2[@year - 1925]}.#{md}" + elsif jd >= 2419614 # Taisho (1912-07-30) + "T#{PAD2[@year - 1911]}.#{md}" + elsif jd >= 2405160 # Meiji (1873-01-01) + "M#{PAD2[@year - 1867]}.#{md}" + else + to_s + end + end + + # call-seq: + # to_s -> string + # + # Returns a string representation of the date in +self+ + # in {ISO 8601 extended date format}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications] + # ('%Y-%m-%d'): + # + # Date.new(2001, 2, 3).to_s # => "2001-02-03" + # + def to_s + internal_civil + suffix = MONTH_DAY_SUFFIX[@month][@day] + y = @year + if y >= 1000 + # Fast path: 4-digit year needs no zero-padding (most common case). + (y.to_s << suffix).force_encoding(Encoding::US_ASCII) + elsif y >= 0 + (format('%04d', y) << suffix).force_encoding(Encoding::US_ASCII) + else + (format('-%04d', -y) << suffix).force_encoding(Encoding::US_ASCII) + end + end + + # call-seq: + # inspect -> string + # + # Returns a string representation of +self+: + # + # Date.new(2001, 2, 3).inspect + # # => "#" + # + def inspect + "#".force_encoding(Encoding::US_ASCII) + end + + # Format the start (cutover) for #inspect the way the C extension does: + # "Inf"/"-Inf" for the infinite sentinels and an integer for whole values. + private def inspect_sg + sg = @start + if sg == Float::INFINITY + 'Inf' + elsif sg == -Float::INFINITY + '-Inf' + elsif sg == sg.to_i + sg.to_i.to_s + else + sg.to_s + end + end + + # override + def freeze + internal_civil # compute and cache civil date before freezing + super + end + + # --------------------------------------------------------------------------- + # Private helpers + # --------------------------------------------------------------------------- + + private + + # Normalize a start value coming from old marshal formats: a Date::Infinity + # object becomes the equivalent Float (start is stored as a Float/Integer + # internally, like the C extension's double). + def normalize_start(sg) + sg.is_a?(Infinity) ? sg.to_f : sg + end + + def init_from_jd(jd, sg, df = nil) + @jd = jd + # Store the cutover as a Float, matching the C extension (which keeps it as + # a double). Keeps #start, #hash and marshal output consistent regardless of + # whether the caller passed an Integer (e.g. ITALY) or a Float sentinel. + @start = sg.to_f + @day_fraction = df + # Initialize the lazily-computed civil ivars up front so every Date + # instance shares a single object shape regardless of which accessors + # are called. (yday/cweek/cwyear are not cached; see #yday/#cweek.) + @year = nil + @month = nil + @day = nil + end + + def internal_civil + return if @year + jd = @jd + if @start == Float::INFINITY # always Julian + b = 0 + c = jd + 32082 + elsif jd >= @start # Gregorian (handles -Infinity too) + a = jd + 32044 + b = (4 * a + 3) / 146097 + c = a - (146097 * b) / 4 + else # Julian (before reform date) + b = 0 + c = jd + 32082 + end + d = (4 * c + 3) / 1461 + e = c - (1461 * d) / 4 + m = (5 * e + 2) / 153 + @day = e - (153 * m + 2) / 5 + 1 + @month = m + 3 - 12 * (m / 10) + @year = 100 * b + d - 4800 + m / 10 + end + + # Compute [cwyear, cweek] from the Julian Day. Not cached, matching the + # C extension, which recomputes commercial fields on each access. + def compute_commercial + y, cw, = self.class.__send__(:jd_to_commercial, @jd, @start) + [y, cw] + end + +end diff --git a/lib/date/datetime.rb b/lib/date/datetime.rb new file mode 100644 index 00000000..cefa746c --- /dev/null +++ b/lib/date/datetime.rb @@ -0,0 +1,989 @@ +# frozen_string_literal: true + +class DateTime < Date + + STRFTIME_DATETIME_DEFAULT_FMT = '%FT%T%:z'.encode(Encoding::US_ASCII) + private_constant :STRFTIME_DATETIME_DEFAULT_FMT + + # --------------------------------------------------------------------------- + # Initializer + # --------------------------------------------------------------------------- + + # call-seq: + # DateTime.new(year=-4712, month=1, day=1, hour=0, minute=0, second=0, offset=0, start=Date::ITALY) -> datetime + def initialize(year = -4712, month = 1, day = 1, hour = 0, minute = 0, second = 0, offset = 0, start = ITALY) + year = Integer(year) + month = Integer(month) + of_sec = _str_offset_to_sec(offset) + + raise TypeError, "expected numeric" unless day.is_a?(Numeric) + + # Fractional day/hour/minute: propagate fraction to smaller units + day_r = day.to_r + day_i = day_r.floor + day_frac = day_r - day_i + day = day_i + + jd = self.class.__send__(:internal_valid_civil?, year, month, day, start) + raise Date::Error, "invalid date" unless jd + + raise TypeError, "expected numeric" unless hour.is_a?(Numeric) + raise TypeError, "expected numeric" unless minute.is_a?(Numeric) + raise TypeError, "expected numeric" unless second.is_a?(Numeric) + + # Propagate fractions to smaller units + hour_r = hour.to_r + day_frac * 24 + hour_i = hour_r.floor + hour_frac = hour_r - hour_i + + minute_r = minute.to_r + hour_frac * 60 + minute_i = minute_r.floor + minute_frac = minute_r - minute_i + + second_r = second.to_r + minute_frac * 60 + sec_i = second_r.floor + sec_f = second_r - sec_i + jd, hour, minute, sec_i = self.class.__send__(:_normalize_hms, jd, hour_i, minute_i, sec_i) + + _init_datetime(jd, hour, minute, sec_i, sec_f, of_sec, start) + end + + # --------------------------------------------------------------------------- + # Instance attributes + # --------------------------------------------------------------------------- + + # call-seq: + # hour -> integer + # + # Returns the hour in range (0..23): + # + # DateTime.new(2001, 2, 3, 4, 5, 6).hour # => 4 + def hour + @hour + end + + # call-seq: + # min -> integer + # + # Returns the minute in range (0..59): + # + # DateTime.new(2001, 2, 3, 4, 5, 6).min # => 5 + def min + @min + end + alias minute min + + # call-seq: + # sec -> integer + # + # Returns the second in range (0..59): + # + # DateTime.new(2001, 2, 3, 4, 5, 6).sec # => 6 + def sec + @sec_i + end + alias second sec + + # call-seq: + # sec_fraction -> rational + # + # Returns the fractional part of the second in range + # (Rational(0, 1)...Rational(1, 1)): + # + # DateTime.new(2001, 2, 3, 4, 5, 6.5).sec_fraction # => (1/2) + def sec_fraction + @sec_frac + end + alias second_fraction sec_fraction + + # call-seq: + # d.offset -> rational + # + # Returns the offset. + # + # DateTime.parse('04pm+0730').offset #=> (5/16) + def offset + Rational(@of, 86400) + end + + # call-seq: + # d.zone -> string + # + # Returns the timezone. + # + # DateTime.parse('04pm+0730').zone #=> "+07:30" + def zone + _of2str(@of) + end + + # call-seq: + # day_fraction -> rational + # + # Returns the fractional part of the day in range (Rational(0, 1)...Rational(1, 1)): + # + # DateTime.new(2001,2,3,12).day_fraction # => (1/2) + def day_fraction + Rational(@hour * 3600 + @min * 60 + @sec_i, 86400) + + Rational(@sec_frac.numerator, @sec_frac.denominator * 86400) + end + + # call-seq: + # d.ajd -> rational + # + # Returns the astronomical Julian day number. This is a fractional + # number, which is not adjusted by the offset. + # + # DateTime.new(2001,2,3,4,5,6,'+7').ajd #=> (11769328217/4800) + # DateTime.new(2001,2,2,14,5,6,'-7').ajd #=> (11769328217/4800) + def ajd + jd_r = Rational(@jd) + time_r = Rational(@hour * 3600 + @min * 60 + @sec_i, 86400) + + Rational(@sec_frac.numerator, @sec_frac.denominator * 86400) + of_r = Rational(@of, 86400) + jd_r + time_r - of_r - 0.5r + end + + # --------------------------------------------------------------------------- + # Arithmetic (override for fractional day support) + # --------------------------------------------------------------------------- + + # call-seq: + # d + other -> date + # + # Returns a date object pointing +other+ days after self. The other + # should be a numeric value. If the other is a fractional number, + # assumes its precision is at most nanosecond. + # + # Date.new(2001,2,3) + 1 #=> # + # DateTime.new(2001,2,3) + Rational(1,2) + # #=> # + # DateTime.new(2001,2,3) + Rational(-1,2) + # #=> # + # DateTime.jd(0,12) + DateTime.new(2001,2,3).ajd + # #=> # + def +(other) + case other + when Integer + self.class.__send__(:_new_dt_from_jd_time,@jd + other, @hour, @min, @sec_i, @sec_frac, @of, @start) + when Rational, Float + # other is days (may be fractional) — add as seconds + extra_sec = other.to_r * 86400 + total_r = Rational(@jd) * 86400 + @hour * 3600 + @min * 60 + @sec_i + @sec_frac + extra_sec + _from_total_sec_r(total_r) + when Numeric + r = other.to_r + raise TypeError, "#{other.class} can't be coerced into Integer" unless r.is_a?(Rational) + extra_sec = r * 86400 + total_r = Rational(@jd) * 86400 + @hour * 3600 + @min * 60 + @sec_i + @sec_frac + extra_sec + _from_total_sec_r(total_r) + else + raise TypeError, "expected numeric" + end + end + + # call-seq: + # d - other -> date or rational + # + # If the other is a date object, returns a Rational + # whose value is the difference between the two dates in days. + # If the other is a numeric value, returns a date object + # pointing +other+ days before self. + # If the other is a fractional number, + # assumes its precision is at most nanosecond. + # + # Date.new(2001,2,3) - 1 #=> # + # DateTime.new(2001,2,3) - Rational(1,2) + # #=> # + # Date.new(2001,2,3) - Date.new(2001) + # #=> (33/1) + # DateTime.new(2001,2,3) - DateTime.new(2001,2,2,12) + # #=> (1/2) + def -(other) + case other + when Date + ajd - other.ajd + when Integer + self.class.__send__(:_new_dt_from_jd_time,@jd - other, @hour, @min, @sec_i, @sec_frac, @of, @start) + when Rational, Float + self + (-other) + when Numeric + r = other.to_r + raise TypeError, "#{other.class} can't be coerced into Integer" unless r.is_a?(Rational) + self + (-r) + else + raise TypeError, "expected numeric" + end + end + + # call-seq: + # dt >> n -> date_time + # + # Returns a new DateTime object representing the time +n+ months later; + # the time of day and offset are preserved. (Date#<< delegates to this.) + def >>(n) + self.class.__send__(:_new_dt_from_jd_time, month_shifted_jd(n), @hour, @min, @sec_i, @sec_frac, @of, @start) + end + + # call-seq: + # new_start(start = Date::ITALY]) -> new_date + # + # Returns a copy of +self+ with the given +start+ value: + # + # d0 = Date.new(2000, 2, 3) + # d0.julian? # => false + # d1 = d0.new_start(Date::JULIAN) + # d1.julian? # => true + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + def new_start(start = Date::ITALY) + self.class.__send__(:_new_dt_from_jd_time, @jd, @hour, @min, @sec_i, @sec_frac, @of, start) + end + + # call-seq: + # d.new_offset([offset=0]) -> date + # + # Duplicates self and resets its offset. + # + # d = DateTime.new(2001,2,3,4,5,6,'-02:00') + # #=> # + # d.new_offset('+09:00') #=> # + def new_offset(of = 0) + of_sec = _str_offset_to_sec(of) + # Keep the same absolute instant, re-expressed in the new offset: shift the + # locally stored wall-clock fields by the change in offset. + total = @jd * 86400 + @hour * 3600 + @min * 60 + @sec_i + (of_sec - @of) + jd2, rem = total.divmod(86400) + h, rem = rem.divmod(3600) + m, s = rem.divmod(60) + self.class.__send__(:_new_dt_from_jd_time, jd2, h, m, s, @sec_frac, of_sec, @start) + end + + # --------------------------------------------------------------------------- + # String formatting + # --------------------------------------------------------------------------- + + # call-seq: + # strftime(format = '%FT%T%:z') -> string + # + # Returns a string representation of +self+, + # formatted according the given +format: + # + # DateTime.now.strftime # => "2022-07-01T11:03:19-05:00" + # + # For other formats, + # see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: + def strftime(format = STRFTIME_DATETIME_DEFAULT_FMT) + super(format) + end + + # call-seq: + # dt.jisx0301([n=0]) -> string + # + # Returns a string in a JIS X 0301 format. + # The optional argument +n+ is the number of digits for fractional seconds. + # + # DateTime.parse('2001-02-03T04:05:06.123456789+07:00').jisx0301(9) + # #=> "H13.02.03T04:05:06.123456789+07:00" + def jisx0301(n = 0) + n = n.to_i + ERA_TABLE.each do |start_jd, era, base_year| + if @jd >= start_jd + era_year = year - base_year + if n == 0 + return format('%s%02d.%02d.%02dT%02d:%02d:%02d%s', + era, era_year, month, day, hour, min, sec, zone) + else + sf = sec_fraction + frac = '.' + (sf * (10**n)).to_i.to_s.rjust(n, '0') + return format('%s%02d.%02d.%02dT%02d:%02d:%02d%s%s', + era, era_year, month, day, hour, min, sec, frac, zone) + end + end + end + iso8601(n) + end + + # call-seq: + # dt.iso8601([n=0]) -> string + # dt.xmlschema([n=0]) -> string + # + # This method is equivalent to strftime('%FT%T%:z'). + # The optional argument +n+ is the number of digits for fractional seconds. + # + # DateTime.parse('2001-02-03T04:05:06.123456789+07:00').iso8601(9) + # #=> "2001-02-03T04:05:06.123456789+07:00" + def iso8601(n = 0) + n = n.to_i + if n == 0 + strftime('%Y-%m-%dT%H:%M:%S%:z') + else + sf = sec_fraction + frac = '.' + (sf * (10**n)).to_i.to_s.rjust(n, '0') + strftime("%Y-%m-%dT%H:%M:%S#{frac}%:z") + end + end + alias_method :xmlschema, :iso8601 + + # call-seq: + # dt.rfc3339([n=0]) -> string + # + # This method is equivalent to strftime('%FT%T%:z'). + # The optional argument +n+ is the number of digits for fractional seconds. + # + # DateTime.parse('2001-02-03T04:05:06.123456789+07:00').rfc3339(9) + # #=> "2001-02-03T04:05:06.123456789+07:00" + alias_method :rfc3339, :iso8601 + + # call-seq: + # deconstruct_keys(array_of_names_or_nil) -> hash + # + # Returns a hash of the name/value pairs, to use in pattern matching. + # Possible keys are: :year, :month, :day, + # :wday, :yday, :hour, :min, + # :sec, :sec_fraction, :zone. + # + # Possible usages: + # + # dt = DateTime.new(2022, 10, 5, 13, 30) + # + # if d in wday: 1..5, hour: 10..18 # uses deconstruct_keys underneath + # puts "Working time" + # end + # #=> prints "Working time" + # + # case dt + # in year: ...2022 + # puts "too old" + # in month: ..9 + # puts "quarter 1-3" + # in wday: 1..5, month: + # puts "working day in month #{month}" + # end + # #=> prints "working day in month 10" + # + # Note that deconstruction by pattern can also be combined with class check: + # + # if d in DateTime(wday: 1..5, hour: 10..18, day: ..7) + # puts "Working time, first week of the month" + # end + def deconstruct_keys(keys) + if keys + if keys.size == 1 + case keys[0] + when :year then { year: year } + when :month then { month: month } + when :day then { day: day } + when :wday then { wday: wday } + when :yday then { yday: yday } + when :hour then { hour: hour } + when :min then { min: min } + when :sec then { sec: sec } + when :sec_fraction then { sec_fraction: sec_fraction } + when :zone then { zone: zone } + else {} + end + else + h = {} + keys.each do |k| + case k + when :year then h[:year] = year + when :month then h[:month] = month + when :day then h[:day] = day + when :wday then h[:wday] = wday + when :yday then h[:yday] = yday + when :hour then h[:hour] = hour + when :min then h[:min] = min + when :sec then h[:sec] = sec + when :sec_fraction then h[:sec_fraction] = sec_fraction + when :zone then h[:zone] = zone + end + end + h + end + else + { year: year, month: month, day: day, wday: wday, yday: yday, + hour: hour, min: min, sec: sec, sec_fraction: sec_fraction, zone: zone } + end + end + + DATETIME_TO_S_FMT = '%Y-%m-%dT%H:%M:%S%:z'.encode(Encoding::US_ASCII).freeze + private_constant :DATETIME_TO_S_FMT + + # call-seq: + # dt.to_s -> string + # + # Returns a string in an ISO 8601 format. (This method doesn't use the + # expanded representations.) + # + # DateTime.new(2001,2,3,4,5,6,'-7').to_s + # #=> "2001-02-03T04:05:06-07:00" + def to_s + strftime(DATETIME_TO_S_FMT) + end + + # call-seq: + # inspect -> string + # + # Returns a string representation of +self+: + # + # DateTime.new(2001,2,3,4,5,6,'+7').inspect + # # => "#" + # + # The Julian Day number and seconds are given in UTC; the offset and start + # (cutover) follow, matching the C extension's format. + def inspect + utc_total = @jd * 86400 + @hour * 3600 + @min * 60 + @sec_i - @of + utc_jd, utc_sec = utc_total.divmod(86400) + nano = @sec_frac * 1_000_000_000 + nano_s = nano.denominator == 1 ? nano.numerator.to_s : "(#{nano.numerator}/#{nano.denominator})" + of_s = @of < 0 ? "-#{-@of}" : "+#{@of}" + "#".force_encoding(Encoding::US_ASCII) + end + + def hash + if @hour == 0 && @min == 0 && @sec_i == 0 + [@jd, @start].hash + else + [@jd, @hour, @min, @sec_i, @start].hash + end + end + + # --------------------------------------------------------------------------- + # Serialization override + # --------------------------------------------------------------------------- + + # :nodoc: + def marshal_dump + # 6-element format: [nth, jd, df, sf, of, sg] + df = @hour * 3600 + @min * 60 + @sec_i + sf = (@sec_frac * 1_000_000_000).to_r # nanoseconds as Rational + [0, @jd, df, sf, @of, @start] + end + + # :nodoc: + def marshal_load(array) + case array.length + when 2 + jd_like, sg_or_bool = array + sg = sg_or_bool == true ? ITALY : (sg_or_bool == false ? JULIAN : sg_or_bool.to_f) + _init_datetime(jd_like.to_i, 0, 0, 0, 0r, 0, sg) + when 3 + ajd, of_r, sg = array + sg = normalize_start(sg) + of_sec = (of_r * 86400).to_i + # Reconstruct local JD and time from AJD + local_r = ajd + 0.5r + of_r + jd = local_r.floor + rem_r = (local_r - jd) * 86400 + h = rem_r.to_i / 3600 + rem_r -= h * 3600 + m = rem_r.to_i / 60 + s_r = rem_r - m * 60 + s_i, s_f = _split_second(s_r) + _init_datetime(jd, h, m, s_i, s_f, of_sec, sg) + when 6 + _nth, jd, df, sf, of, sg = array + sg = normalize_start(sg) + h = df / 3600 + df -= h * 3600 + m = df / 60 + s = df % 60 + sf_r = sf.is_a?(Rational) ? (sf / 1_000_000_000) : Rational(sf.to_i, 1_000_000_000) + _init_datetime(jd, h, m, s, sf_r, of, sg) + else + raise TypeError, "invalid marshal data" + end + end + + # --------------------------------------------------------------------------- + # Type conversions + # --------------------------------------------------------------------------- + + # call-seq: + # dt.to_date -> date + # + # Returns a Date object which denotes self. + def to_date + Date.__send__(:new_from_jd, @jd, @start) + end + + # call-seq: + # dt.to_datetime -> self + # + # Returns self. + def to_datetime + self + end + + # call-seq: + # dt.to_time -> time + # + # Returns a Time object which denotes self. + def to_time + y, m, d = self.class.__send__(:jd_to_gregorian, @jd) + if @of == 0 + Time.utc(y, m, d, @hour, @min, @sec_i + @sec_frac) + else + Time.new(y, m, d, @hour, @min, @sec_i + @sec_frac, @of) + end + end + + # --------------------------------------------------------------------------- + # Class methods + # --------------------------------------------------------------------------- + + class << self + def new(year = -4712, month = 1, day = 1, hour = 0, minute = 0, second = 0, offset = 0, start = Date::ITALY) + instance = allocate + instance.__send__(:initialize, year, month, day, hour, minute, second, offset, start) + instance + end + alias_method :civil, :new + + undef_method :today + + # call-seq: + # DateTime._strptime(string[, format='%FT%T%z']) -> hash + # + # Parses the given representation of date and time with the given + # template, and returns a hash of parsed elements. _strptime does + # not support specification of flags and width unlike strftime. + # + # See also strptime(3) and #strftime. + def _strptime(string = JULIAN_EPOCH_DATETIME, format = '%FT%T%z') + Date._strptime(string, format) + end + + # call-seq: + # DateTime.strptime([string='-4712-01-01T00:00:00+00:00'[, format='%FT%T%z'[ ,start=Date::ITALY]]]) -> datetime + # + # Parses the given representation of date and time with the given + # template, and creates a DateTime object. strptime does not support + # specification of flags and width unlike strftime. + # + # DateTime.strptime('2001-02-03T04:05:06+07:00', '%Y-%m-%dT%H:%M:%S%z') + # #=> # + # DateTime.strptime('03-02-2001 04:05:06 PM', '%d-%m-%Y %I:%M:%S %p') + # #=> # + # DateTime.strptime('2001-W05-6T04:05:06+07:00', '%G-W%V-%uT%H:%M:%S%z') + # #=> # + # DateTime.strptime('2001 04 6 04 05 06 +7', '%Y %U %w %H %M %S %z') + # #=> # + # DateTime.strptime('2001 05 6 04 05 06 +7', '%Y %W %u %H %M %S %z') + # #=> # + # DateTime.strptime('-1', '%s') + # #=> # + # DateTime.strptime('-1000', '%Q') + # #=> # + # DateTime.strptime('sat3feb014pm+7', '%a%d%b%y%H%p%z') + # #=> # + # + # See also strptime(3) and #strftime. + def strptime(string = JULIAN_EPOCH_DATETIME, format = '%FT%T%z', start = Date::ITALY) + hash = _strptime(string, format) + _dt_new_by_frags(hash, start) + end + + # call-seq: + # DateTime.jd([jd=0[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]) -> datetime + # + # Creates a DateTime object denoting the given chronological Julian + # day number. + # + # DateTime.jd(2451944) #=> # + # DateTime.jd(2451945) #=> # + # DateTime.jd(Rational('0.5')) + # #=> # + def jd(jd = 0, hour = 0, minute = 0, second = 0, offset = 0, start = Date::ITALY) + raise TypeError, "no implicit conversion of #{jd.class} into Integer" unless jd.is_a?(Numeric) + jd_r = jd.to_r + jd_i = jd_r.floor + h = Integer(hour) + m = Integer(minute) + of_sec = _parse_of(offset) + if jd_i != jd_r + # Fractional JD: convert fraction to extra seconds and handle overflow + frac_sec = (jd_r - jd_i) * 86400 + second = second.to_r + frac_sec + sec_i, sec_f = _split_sec(second) + if sec_i >= 60 + carry_m, sec_i = sec_i.divmod(60) + m += carry_m + end + if m >= 60 + carry_h, m = m.divmod(60) + h += carry_h + end + if h >= 24 + carry_d, h = h.divmod(24) + jd_i += carry_d + end + else + # Integer JD: pass raw values to _normalize_hms (non-cascading) + sec_i, sec_f = _split_sec(second) + end + _new_dt_from_jd_time(jd_i, h, m, sec_i, sec_f, of_sec, start) + end + + # call-seq: + # DateTime.ordinal([year=-4712[, yday=1[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]]) -> datetime + # + # Creates a DateTime object denoting the given ordinal date. + # + # DateTime.ordinal(2001,34) #=> # + # DateTime.ordinal(2001,34,4,5,6,'+7') + # #=> # + # DateTime.ordinal(2001,-332,-20,-55,-54,'+7') + # #=> # + def ordinal(year = -4712, yday = 1, hour = 0, minute = 0, second = 0, offset = 0, start = Date::ITALY) + jd_v = internal_valid_ordinal?(Integer(year), Integer(yday), start) + raise Date::Error, "invalid date" unless jd_v + of_sec = _parse_of(offset) + sec_i, sec_f = _split_sec(second) + _new_dt_from_jd_time(jd_v, Integer(hour), Integer(minute), sec_i, sec_f, of_sec, start) + end + + # call-seq: + # DateTime.commercial([cwyear=-4712[, cweek=1[, cwday=1[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]]]) -> datetime + # + # Creates a DateTime object denoting the given week date. + # + # DateTime.commercial(2001) #=> # + # DateTime.commercial(2002) #=> # + # DateTime.commercial(2001,5,6,4,5,6,'+7') + # #=> # + def commercial(cwyear = -4712, cweek = 1, cwday = 1, hour = 0, minute = 0, second = 0, offset = 0, start = Date::ITALY) + jd_v = internal_valid_commercial?(Integer(cwyear), Integer(cweek), Integer(cwday), start) + raise Date::Error, "invalid date" unless jd_v + of_sec = _parse_of(offset) + sec_i, sec_f = _split_sec(second) + _new_dt_from_jd_time(jd_v, Integer(hour), Integer(minute), sec_i, sec_f, of_sec, start) + end + + # call-seq: + # DateTime.now([start=Date::ITALY]) -> datetime + # + # Creates a DateTime object denoting the present time. + # + # DateTime.now #=> # + def now(start = Date::ITALY) + t = Time.now + jd = civil_to_jd(t.year, t.mon, t.mday, start) + sec_f = Rational(t.subsec) + _new_dt_from_jd_time(jd, t.hour, t.min, t.sec, sec_f, t.utc_offset, start) + end + + # :nodoc: + def _new_dt_from_jd_time(jd, h, m, s, sf, of, sg) + jd, h, m, s = _normalize_hms(jd, h, m, s) + obj = allocate + obj.__send__(:_init_datetime, jd, h, m, s, sf, of, sg) + obj + end + + # Normalize hour/min/sec. + # Negative values: add one period (non-cascading, matching C's c_valid_time_f?). + # After normalization, validate ranges and raise Date::Error if out of range. + # 24:00:00 is valid (normalizes to next day 00:00:00). + def _normalize_hms(jd, h, m, s) + s += 60 if s < 0 + m += 60 if m < 0 + h += 24 if h < 0 + raise Date::Error, "invalid date" if s >= 60 + raise Date::Error, "invalid date" if m >= 60 + raise Date::Error, "invalid date" if h > 24 || h < 0 + raise Date::Error, "invalid date" if h == 24 && (m != 0 || s != 0) + if h == 24 + jd += 1 + h = 0 + end + [jd, h, m, s] + end + + # call-seq: + # DateTime.parse(string='-4712-01-01T00:00:00+00:00'[, comp=true[, start=Date::ITALY]], limit: 128) -> datetime + # + # Parses the given representation of date and time, and creates a + # DateTime object. + # + # This method *does* *not* function as a validator. If the input + # string does not match valid formats strictly, you may get a cryptic + # result. Should consider to use DateTime.strptime instead of this + # method as possible. + # + # If the optional second argument is true and the detected year is in + # the range "00" to "99", makes it full. + # + # DateTime.parse('2001-02-03T04:05:06+07:00') + # #=> # + # DateTime.parse('20010203T040506+0700') + # #=> # + # DateTime.parse('3rd Feb 2001 04:05:06 PM') + # #=> # + # + # Raise an ArgumentError when the string length is longer than _limit_. + # You can stop this check by passing limit: nil, but note + # that it may take a long time to parse. + def parse(string = '-4712-01-01T00:00:00+00:00', comp = true, start = Date::ITALY, limit: 128) + hash = Date._parse(string, comp, limit: limit) + _dt_new_by_frags(hash, start) + end + + # call-seq: + # DateTime.iso8601(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime + # + # Creates a new DateTime object by parsing from a string according to + # some typical ISO 8601 formats. + # + # DateTime.iso8601('2001-02-03T04:05:06+07:00') + # #=> # + # DateTime.iso8601('20010203T040506+0700') + # #=> # + # DateTime.iso8601('2001-W05-6T04:05:06+07:00') + # #=> # + # + # Raise an ArgumentError when the string length is longer than _limit_. + # You can stop this check by passing limit: nil, but note + # that it may take a long time to parse. + def iso8601(string = '-4712-01-01T00:00:00+00:00', start = Date::ITALY, limit: 128) + hash = Date._iso8601(string, limit: limit) + _dt_new_by_frags(hash, start) + end + + # call-seq: + # DateTime.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime + # + # Creates a new DateTime object by parsing from a string according to + # some typical RFC 3339 formats. + # + # DateTime.rfc3339('2001-02-03T04:05:06+07:00') + # #=> # + # + # Raise an ArgumentError when the string length is longer than _limit_. + # You can stop this check by passing limit: nil, but note + # that it may take a long time to parse. + def rfc3339(string = '-4712-01-01T00:00:00+00:00', start = Date::ITALY, limit: 128) + hash = Date._rfc3339(string, limit: limit) + _dt_new_by_frags(hash, start) + end + + # call-seq: + # DateTime.xmlschema(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime + # + # Creates a new DateTime object by parsing from a string according to + # some typical XML Schema formats. + # + # DateTime.xmlschema('2001-02-03T04:05:06+07:00') + # #=> # + # + # Raise an ArgumentError when the string length is longer than _limit_. + # You can stop this check by passing limit: nil, but note + # that it may take a long time to parse. + def xmlschema(string = '-4712-01-01T00:00:00+00:00', start = Date::ITALY, limit: 128) + hash = Date._xmlschema(string, limit: limit) + _dt_new_by_frags(hash, start) + end + + # call-seq: + # DateTime.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime + # DateTime.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime + # + # Creates a new DateTime object by parsing from a string according to + # some typical RFC 2822 formats. + # + # DateTime.rfc2822('Sat, 3 Feb 2001 04:05:06 +0700') + # #=> # + # + # Raise an ArgumentError when the string length is longer than _limit_. + # You can stop this check by passing limit: nil, but note + # that it may take a long time to parse. + def rfc2822(string = 'Mon, 1 Jan -4712 00:00:00 +0000', start = Date::ITALY, limit: 128) + hash = Date._rfc2822(string, limit: limit) + _dt_new_by_frags(hash, start) + end + alias rfc822 rfc2822 + + # call-seq: + # DateTime.httpdate(string='Mon, 01 Jan -4712 00:00:00 GMT'[, start=Date::ITALY]) -> datetime + # + # Creates a new DateTime object by parsing from a string according to + # some RFC 2616 format. + # + # DateTime.httpdate('Sat, 03 Feb 2001 04:05:06 GMT') + # #=> # + # + # Raise an ArgumentError when the string length is longer than _limit_. + # You can stop this check by passing limit: nil, but note + # that it may take a long time to parse. + def httpdate(string = 'Mon, 01 Jan -4712 00:00:00 GMT', start = Date::ITALY, limit: 128) + hash = Date._httpdate(string, limit: limit) + _dt_new_by_frags(hash, start) + end + + # call-seq: + # DateTime.jisx0301(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime + # + # Creates a new DateTime object by parsing from a string according to + # some typical JIS X 0301 formats. + # + # DateTime.jisx0301('H13.02.03T04:05:06+07:00') + # #=> # + # + # For no-era year, legacy format, Heisei is assumed. + # + # DateTime.jisx0301('13.02.03T04:05:06+07:00') + # #=> # + # + # Raise an ArgumentError when the string length is longer than _limit_. + # You can stop this check by passing limit: nil, but note + # that it may take a long time to parse. + def jisx0301(string = '-4712-01-01T00:00:00+00:00', start = Date::ITALY, limit: 128) + hash = Date._jisx0301(string, limit: limit) + _dt_new_by_frags(hash, start) + end + + private + + # Create a DateTime object from parsed fragment hash. + # Uses the same fragment rewrite/complete/validate logic as Date._new_by_frags + # but additionally extracts time components (hour, min, sec, offset, sec_fraction). + def _dt_new_by_frags(hash, sg) + raise Date::Error, 'invalid date' if hash.nil? + hash = sp_rewrite_frags(hash) + orig_sec = hash[:sec] + hash = sp_complete_frags(DateTime, hash) + jd = sp_valid_date_frags_p(hash, sg) + raise Date::Error, 'invalid date' if jd.nil? + + h = hash[:hour] || 0 + m = hash[:min] || 0 + s = hash[:sec] || 0 + raise Date::Error, 'invalid date' if orig_sec && orig_sec > 60 + s = 59 if s > 59 + of = hash[:offset] || 0 + if of.is_a?(Numeric) && (of < -86400 || of > 86400) + warn("invalid offset is ignored: #{of}", uplevel: 0) + of = 0 + end + sf = hash[:sec_fraction] || 0r + _new_dt_from_jd_time(jd, h, m, s, sf, of, sg) + end + + def _parse_of(offset) + case offset + when String + Date.__send__(:offset_str_to_sec, offset) + when Rational + (offset * 86400).to_i + when Numeric + (offset * 86400).to_i + else + 0 + end + end + + def _split_sec(second) + if second.is_a?(Rational) || second.is_a?(Float) + s_r = second.to_r + s_i = s_r.floor + [s_i, s_r - s_i] + else + [Integer(second), 0r] + end + end + + end + + # --------------------------------------------------------------------------- + # Private helpers (strftime overrides) + # --------------------------------------------------------------------------- + + private + + def internal_hour + @hour + end + + def internal_min + @min + end + + def internal_sec + @sec_i + end + + def sec_frac + @sec_frac + end + + def of_seconds + @of + end + + def zone_str + _of2str(@of) + end + + def _init_datetime(jd, h, m, s, sf, of, sg) + @jd = jd + # Store the cutover as a Float, matching the C extension (see init_from_jd). + @start = sg.to_f + @day_fraction = nil + @hour = h + @min = m + @sec_i = s + @sec_frac = sf.is_a?(Rational) ? sf : Rational(sf) + @of = of.to_i + # Initialize lazily-computed (inherited from Date) civil ivars up front so + # every DateTime instance shares a single object shape. + @year = nil + @month = nil + @day = nil + end + + def _split_second(second) + if second.is_a?(Rational) || second.is_a?(Float) + s_r = second.to_r + s_i = s_r.floor + [s_i, s_r - s_i] + else + [Integer(second), 0r] + end + end + + def _str_offset_to_sec(offset) + case offset + when String + self.class.__send__(:_parse_of, offset) + when Rational + (offset * 86400).to_i + when Numeric + r = offset.to_r + raise TypeError, "#{offset.class} can't be used as offset" unless r.is_a?(Rational) + (r * 86400).to_i + else + 0 + end + end + + def _of2str(of) + sign = of < 0 ? '-' : '+' + abs = of.abs + h = abs / 3600 + m = (abs % 3600) / 60 + format('%s%02d:%02d'.encode(Encoding::US_ASCII), sign, h, m) + end + + def _from_total_sec_r(total_r) + jd = (total_r / 86400).floor + rem = total_r - jd * 86400 + h = rem.to_i / 3600 + rem -= h * 3600 + m = rem.to_i / 60 + s_r = rem - m * 60 + s_i = s_r.floor + s_f = s_r - s_i + self.class.__send__(:_new_dt_from_jd_time,jd, h, m, s_i, s_f, @of, @start) + end + +end diff --git a/lib/date/parse.rb b/lib/date/parse.rb new file mode 100644 index 00000000..8f96a7ac --- /dev/null +++ b/lib/date/parse.rb @@ -0,0 +1,1532 @@ +# frozen_string_literal: true + +require_relative "zonetab" + +class Date + # Character class flags used by _parse + HAVE_ALPHA = 1 + HAVE_DIGIT = 2 + HAVE_DASH = 4 + HAVE_DOT = 8 + HAVE_SLASH = 16 + HAVE_COLON = 32 + private_constant :HAVE_ALPHA, :HAVE_DIGIT, :HAVE_DASH, :HAVE_DOT, :HAVE_SLASH, :HAVE_COLON + + class << self + + # ------------------------------------------------------------------ + # _rfc3339 + # ------------------------------------------------------------------ + + # call-seq: + # Date._rfc3339(string, limit: 128) -> hash + # + # Returns a hash of values parsed from +string+, which should be a valid + # {RFC 3339 format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+3339+Format]: + # + # d = Date.new(2001, 2, 3) + # s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" + # Date._rfc3339(s) + # # => {:year=>2001, :mon=>2, :mday=>3, :hour=>0, :min=>0, :sec=>0, :zone=>"+00:00", :offset=>0} + # + # See argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date.rfc3339 (returns a \Date object). + def _rfc3339(string, limit: 128) + unless String === string + raise TypeError if string.is_a?(Symbol) + return {} if string.nil? + string = string.to_str + end + return {} if string.empty? + raise ArgumentError, "string length (#{string.length}) exceeds the limit #{limit}" if limit && string.length > limit + + # Fast path: YYYY-MM-DDTHH:MM:SS+HH:MM (25 bytes) or YYYY-MM-DDTHH:MM:SSZ (20 bytes) + len = string.length + if len == 25 || len == 20 + sc = StringScanner.new(string) + if sc.scan(/(\d{4})-(\d{2})-(\d{2})[Tt](\d{2}):(\d{2}):(\d{2})/) + h = { + year: sc[1].to_i, mon: sc[2].to_i, mday: sc[3].to_i, + hour: sc[4].to_i, min: sc[5].to_i, sec: sc[6].to_i + } + if sc.scan(/[Zz]\z/) + h[:zone] = sc.matched + h[:offset] = 0 + return h + elsif sc.scan(/([+-])(\d{2}):(\d{2})\z/) + zone = sc.matched + h[:zone] = zone + h[:offset] = (sc[1] == '-' ? -1 : 1) * (sc[2].to_i * 3600 + sc[3].to_i * 60) + return h + end + end + end + + h = {} + if (m = RFC3339_RE.match(string)) + h[:year] = m[1].to_i + h[:mon] = m[2].to_i + h[:mday] = m[3].to_i + h[:hour] = m[4].to_i + h[:min] = m[5].to_i + h[:sec] = m[6].to_i + h[:sec_fraction] = Rational(m[7].to_i, 10 ** m[7].length) if m[7] + zone = m[8] + h[:zone] = zone + if zone[0] == 'Z' || zone[0] == 'z' + h[:offset] = 0 + else + h[:offset] = (zone[0] == '-' ? -1 : 1) * (zone[1, 2].to_i * 3600 + zone[4, 2].to_i * 60) + end + end + h + end + + # ------------------------------------------------------------------ + # _httpdate + # ------------------------------------------------------------------ + + # call-seq: + # Date._httpdate(string, limit: 128) -> hash + # + # Returns a hash of values parsed from +string+, which should be a valid + # {HTTP date format}[rdoc-ref:language/strftime_formatting.rdoc@HTTP+Format]: + # + # d = Date.new(2001, 2, 3) + # s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" + # Date._httpdate(s) + # # => {:wday=>6, :mday=>3, :mon=>2, :year=>2001, :hour=>0, :min=>0, :sec=>0, :zone=>"GMT", :offset=>0} + # + # Related: Date.httpdate (returns a \Date object). + def _httpdate(string, limit: 128) + unless String === string + raise TypeError if string.is_a?(Symbol) + return {} if string.nil? + string = string.to_str + end + return {} if string.empty? + raise ArgumentError, "string length (#{string.length}) exceeds the limit #{limit}" if limit && string.length > limit + + # Fast path for Type 1: "Dow, DD Mon YYYY HH:MM:SS GMT" (29 bytes) + len = string.length + if len == 29 + sc = StringScanner.new(string) + if sc.scan(/([A-Za-z]{3}), (\d{2}) ([A-Za-z]{3}) (\d{4}) (\d{2}):(\d{2}):(\d{2}) (GMT)\z/i) + wkey = compute_3key(sc[1]) + wday_info = ABBR_DAY_3KEY[wkey] + if wday_info + mkey = compute_3key(sc[3]) + mon_info = ABBR_MONTH_3KEY[mkey] + if mon_info + return { + wday: wday_info[0], mday: sc[2].to_i, mon: mon_info[0], + year: sc[4].to_i, hour: sc[5].to_i, min: sc[6].to_i, sec: sc[7].to_i, + zone: 'GMT', offset: 0 + } + end + end + end + end + + h = {} + if (m = HTTPDATE_TYPE1_RE.match(string)) + h[:wday] = HTTPDATE_WDAY[m[1].downcase] + h[:mday] = m[2].to_i + h[:mon] = ABBR_MONTH_NUM[m[3].downcase] + h[:year] = m[4].to_i + h[:hour] = m[5].to_i + h[:min] = m[6].to_i + h[:sec] = m[7].to_i + h[:zone] = m[8] + h[:offset] = 0 + elsif (m = HTTPDATE_TYPE2_RE.match(string)) + h[:wday] = HTTPDATE_FULL_WDAY[m[1].downcase] + h[:mday] = m[2].to_i + h[:mon] = ABBR_MONTH_NUM[m[3].downcase] + y = m[4].to_i + h[:year] = y >= 69 ? y + 1900 : y + 2000 + h[:hour] = m[5].to_i + h[:min] = m[6].to_i + h[:sec] = m[7].to_i + h[:zone] = m[8] + h[:offset] = 0 + elsif (m = HTTPDATE_TYPE3_RE.match(string)) + h[:wday] = HTTPDATE_WDAY[m[1].downcase] + h[:mon] = ABBR_MONTH_NUM[m[2].downcase] + h[:mday] = m[3].to_i + h[:hour] = m[4].to_i + h[:min] = m[5].to_i + h[:sec] = m[6].to_i + h[:year] = m[7].to_i + end + h + end + + # ------------------------------------------------------------------ + # _rfc2822 + # ------------------------------------------------------------------ + + # call-seq: + # Date._rfc2822(string, limit: 128) -> hash + # + # Returns a hash of values parsed from +string+, which should be a valid + # {RFC 2822 date format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+2822+Format]: + # + # d = Date.new(2001, 2, 3) + # s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" + # Date._rfc2822(s) + # # => {:wday=>6, :mday=>3, :mon=>2, :year=>2001, :hour=>0, :min=>0, :sec=>0, :zone=>"+0000", :offset=>0} + # + # See argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date.rfc2822 (returns a \Date object). + def _rfc2822(string, limit: 128) + unless String === string + raise TypeError if string.is_a?(Symbol) + return {} if string.nil? + string = string.to_str + end + return {} if string.empty? + raise ArgumentError, "string length (#{string.length}) exceeds the limit #{limit}" if limit && string.length > limit + + # Fast path: "Dow, DD Mon YYYY HH:MM:SS +ZZZZ" (31 bytes, 2-digit day) + len = string.length + if len == 31 + sc = StringScanner.new(string) + if sc.scan(/([A-Za-z]{3}), (\d{2}) ([A-Za-z]{3}) (\d{4}) (\d{2}):(\d{2}):(\d{2}) ([+-]\d{4})\z/) + wkey = compute_3key(sc[1]) + wday_info = ABBR_DAY_3KEY[wkey] + if wday_info + mkey = compute_3key(sc[3]) + mon_info = ABBR_MONTH_3KEY[mkey] + if mon_info + zone = sc[8] + sign = zone[0] == '-' ? -1 : 1 + offset_val = sign * (zone[1, 2].to_i * 3600 + zone[3, 2].to_i * 60) + zone = '+0000' if zone == '+0000' + return { + wday: wday_info[0], mday: sc[2].to_i, mon: mon_info[0], + year: sc[4].to_i, hour: sc[5].to_i, min: sc[6].to_i, sec: sc[7].to_i, + zone: zone, offset: offset_val + } + end + end + end + end + + # Preprocess: remove obs-FWS (\r\n and \0) - skip if not needed + s = (string.include?("\r") || string.include?("\n") || string.include?("\0")) ? + string.gsub(/[\r\n\0]+/, ' ') : string + h = {} + if (m = RFC2822_RE.match(s)) + h[:wday] = HTTPDATE_WDAY[m[1].downcase] if m[1] + h[:mday] = m[2].to_i + h[:mon] = ABBR_MONTH_NUM[m[3].downcase] + y_s = m[4] + y = y_s.to_i + ylen = y_s[0] == '-' ? y_s.length - 1 : y_s.length + if ylen < 4 + if ylen == 3 + h[:year] = y + 1900 + else + h[:year] = y >= 50 ? y + 1900 : y + 2000 + end + else + h[:year] = y + end + h[:hour] = m[5].to_i + h[:min] = m[6].to_i + h[:sec] = m[7].to_i if m[7] + h[:zone] = m[8] + h[:offset] = fast_zone_offset(m[8]) + end + h + end + alias _rfc822 _rfc2822 + + # ------------------------------------------------------------------ + # _xmlschema + # ------------------------------------------------------------------ + + # call-seq: + # Date._xmlschema(string, limit: 128) -> hash + # + # Returns a hash of values parsed from +string+, which should be a valid + # XML date format: + # + # d = Date.new(2001, 2, 3) + # s = d.xmlschema # => "2001-02-03" + # Date._xmlschema(s) # => {:year=>2001, :mon=>2, :mday=>3} + # + # See argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date.xmlschema (returns a \Date object). + def _xmlschema(string, limit: 128) + unless String === string + raise TypeError if string.is_a?(Symbol) + return {} if string.nil? + string = string.to_str + end + return {} if string.empty? + raise ArgumentError, "string length (#{string.length}) exceeds the limit #{limit}" if limit && string.length > limit + + # Fast path: YYYY-MM-DD (exactly 10 bytes, all ASCII) + if string.length == 10 + sc = StringScanner.new(string) + if sc.scan(/(\d{4})-(\d{2})-(\d{2})\z/) + return { year: sc[1].to_i, mon: sc[2].to_i, mday: sc[3].to_i } + end + end + + h = {} + if (m = XMLSCHEMA_DATETIME_RE.match(string)) + h[:year] = m[1].to_i + h[:mon] = m[2].to_i if m[2] + h[:mday] = m[3].to_i if m[3] + h[:hour] = m[4].to_i if m[4] + h[:min] = m[5].to_i if m[5] + h[:sec] = m[6].to_i if m[6] + h[:sec_fraction] = parse_sec_fraction(m[7]) if m[7] + parse_zone_and_offset(m[8], h) if m[8] + elsif (m = XMLSCHEMA_TIME_RE.match(string)) + h[:hour] = m[1].to_i + h[:min] = m[2].to_i + h[:sec] = m[3].to_i + h[:sec_fraction] = parse_sec_fraction(m[4]) if m[4] + parse_zone_and_offset(m[5], h) if m[5] + elsif (m = XMLSCHEMA_TRUNC_RE.match(string)) + if m[3] + h[:mday] = m[3].to_i + else + h[:mon] = m[1].to_i if m[1] + h[:mday] = m[2].to_i if m[2] + end + parse_zone_and_offset(m[4], h) if m[4] + end + h + end + + # ------------------------------------------------------------------ + # _iso8601 + # ------------------------------------------------------------------ + + # call-seq: + # Date._iso8601(string, limit: 128) -> hash + # + # Returns a hash of values parsed from +string+, which should contain + # an {ISO 8601 formatted date}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications]: + # + # d = Date.new(2001, 2, 3) + # s = d.iso8601 # => "2001-02-03" + # Date._iso8601(s) # => {:mday=>3, :year=>2001, :mon=>2} + # + # See argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date.iso8601 (returns a \Date object). + def _iso8601(string, limit: 128) + unless String === string + raise TypeError if string.is_a?(Symbol) + return {} if string.nil? + string = string.to_str + end + return {} if string.empty? + raise ArgumentError, "string length (#{string.length}) exceeds the limit #{limit}" if limit && string.length > limit + + # Fast path: YYYY-MM-DD (exactly 10 bytes, all ASCII) + if string.length == 10 + sc = StringScanner.new(string) + if sc.scan(/(\d{4})-(\d{2})-(\d{2})\z/) + return { mday: sc[3].to_i, year: sc[1].to_i, mon: sc[2].to_i } + end + end + + h = {} + + if (m = ISO8601_EXT_DATETIME_RE.match(string)) + iso8601_ext_datetime(m, h) + elsif (m = ISO8601_BAS_DATETIME_RE.match(string)) + iso8601_bas_datetime(m, h) + elsif (m = ISO8601_EXT_TIME_RE.match(string)) + h[:hour] = m[1].to_i + h[:min] = m[2].to_i + h[:sec] = m[3].to_i if m[3] + h[:sec_fraction] = parse_sec_fraction(m[4]) if m[4] + parse_zone_and_offset(m[5], h) if m[5] + elsif (m = ISO8601_BAS_TIME_RE.match(string)) + h[:hour] = m[1].to_i + h[:min] = m[2].to_i + h[:sec] = m[3].to_i if m[3] + h[:sec_fraction] = parse_sec_fraction(m[4]) if m[4] + parse_zone_and_offset(m[5], h) if m[5] + end + h + end + + # ------------------------------------------------------------------ + # _jisx0301 + # ------------------------------------------------------------------ + + # call-seq: + # Date._jisx0301(string, limit: 128) -> hash + # + # Returns a hash of values parsed from +string+, which should be a valid + # {JIS X 0301 date format}[rdoc-ref:language/strftime_formatting.rdoc@JIS+X+0301+Format]: + # + # d = Date.new(2001, 2, 3) + # s = d.jisx0301 # => "H13.02.03" + # Date._jisx0301(s) # => {:year=>2001, :mon=>2, :mday=>3} + # + # See argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date.jisx0301 (returns a \Date object). + def _jisx0301(string, limit: 128) + unless String === string + raise TypeError if string.is_a?(Symbol) + return {} if string.nil? + string = string.to_str + end + return {} if string.empty? + raise ArgumentError, "string length (#{string.length}) exceeds the limit #{limit}" if limit && string.length > limit + + # Fast path: X##.##.## (9 bytes: era + YY.MM.DD) + if string.length == 9 + sc = StringScanner.new(string) + if sc.scan(/([A-Za-z])(\d{2})\.(\d{2})\.(\d{2})\z/) + era_offset = JISX0301_ERA[sc[1].downcase] + if era_offset + return { + year: sc[2].to_i + era_offset, + mon: sc[3].to_i, + mday: sc[4].to_i + } + end + end + end + + h = {} + if (m = JISX0301_RE.match(string)) + era_char = m[1] ? m[1].downcase : 'h' + era_offset = JISX0301_ERA[era_char] + h[:year] = m[2].to_i + era_offset + h[:mon] = m[3].to_i + h[:mday] = m[4].to_i + h[:hour] = m[5].to_i if m[5] + h[:min] = m[6].to_i if m[6] + h[:sec] = m[7].to_i if m[7] + h[:sec_fraction] = parse_sec_fraction(m[8]) if m[8] && !m[8].empty? + parse_zone_and_offset(m[9], h) if m[9] + else + h = _iso8601(string, limit: limit) + end + h + end + + # ------------------------------------------------------------------ + # _parse + # ------------------------------------------------------------------ + + # call-seq: + # Date._parse(string, comp = true, limit: 128) -> hash + # + # Note: + # This method recognizes many forms in +string+, + # but it is not a validator. + # For formats, see + # {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] + # + # If +string+ does not specify a valid date, + # the result is unpredictable; + # consider using Date._strptime instead. + # + # Returns a hash of values parsed from +string+: + # + # Date._parse('2001-02-03') # => {:year=>2001, :mon=>2, :mday=>3} + # + # If +comp+ is +true+ and the given year is in the range (0..99), + # the current century is supplied; + # otherwise, the year is taken as given: + # + # Date._parse('01-02-03', true) # => {:year=>2001, :mon=>2, :mday=>3} + # Date._parse('01-02-03', false) # => {:year=>1, :mon=>2, :mday=>3} + # + # See argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date.parse(returns a \Date object). + def _parse(string, comp = true, limit: 128) + unless String === string + raise TypeError, "no implicit conversion of #{string.class} into String" if string.is_a?(Symbol) || string.nil? + string = string.to_str + end + return {} if string.empty? + raise ArgumentError, "string length (#{string.length}) exceeds limit (#{limit})" if limit && string.length > limit + + # === Fast paths for common date formats === + len = string.length + + # Fast ISO: YYYY-MM-DD (exactly 10 ASCII bytes) + if len == 10 + sc = StringScanner.new(string) + if sc.scan(/(\d{4})-(\d{2})-(\d{2})\z/) + return { year: sc[1].to_i, mon: sc[2].to_i, mday: sc[3].to_i } + end + end + + # Fast compact: YYYYMMDD (exactly 8 ASCII digit bytes) + if len == 8 + sc = StringScanner.new(string) + if sc.scan(/(\d{4})(\d{2})(\d{2})\z/) + return { year: sc[1].to_i, mon: sc[2].to_i, mday: sc[3].to_i } + end + end + + # Fast US: "Month DD, YYYY" + if (m = FAST_PARSE_US_RE.match(string)) + mon = ABBR_MONTH_NUM[m[1].downcase] + return {year: m[3].to_i, mon: mon, mday: m[2].to_i} if mon + end + + # Fast EU: "DD Month YYYY" + if (m = FAST_PARSE_EU_RE.match(string)) + mon = ABBR_MONTH_NUM[m[2].downcase] + return {year: m[3].to_i, mon: mon, mday: m[1].to_i} if mon + end + + # Fast RFC2822-like: "Dow, DD Mon YYYY HH:MM:SS +ZZZZ" + if (m = FAST_PARSE_RFC2822_RE.match(string)) + wday = ABBR_DAY_NUM[m[1].downcase] + mon = ABBR_MONTH_NUM[m[3].downcase] + if wday && mon + zone = m[8] + return { + wday: wday, mday: m[2].to_i, mon: mon, year: m[4].to_i, + hour: m[5].to_i, min: m[6].to_i, sec: m[7].to_i, + zone: zone, offset: fast_zone_offset(zone) + } + end + end + + # Preprocessing: replace non-date chars with space + str = string.dup + str.gsub!(/[^-+',.\/:\@\[\][:alnum:]]+/, ' ') + + # check_class (byte-level for speed) + cc = 0 + str.each_byte do |b| + if b >= 65 && b <= 90 || b >= 97 && b <= 122 || b > 127 + cc |= HAVE_ALPHA + elsif b >= 48 && b <= 57 + cc |= HAVE_DIGIT + elsif b == 45 + cc |= HAVE_DASH + elsif b == 46 + cc |= HAVE_DOT + elsif b == 47 + cc |= HAVE_SLASH + elsif b == 58 + cc |= HAVE_COLON + end + end + + h = {} + + # parse_day (always runs) + if (cc & HAVE_ALPHA) != 0 + parse_day(str, h) + end + + # parse_time (needs colon or alpha for h/am/pm patterns) + if (cc & HAVE_DIGIT) != 0 && (cc & (HAVE_COLON | HAVE_ALPHA)) != 0 + parse_time(str, h) + end + + # Date parsers: first match wins (goto ok) + matched = false + + if !matched && (cc & (HAVE_ALPHA | HAVE_DIGIT)) == (HAVE_ALPHA | HAVE_DIGIT) + matched = parse_eu(str, h) + end + + if !matched && (cc & (HAVE_ALPHA | HAVE_DIGIT)) == (HAVE_ALPHA | HAVE_DIGIT) + matched = parse_us(str, h) + end + + if !matched && (cc & (HAVE_DIGIT | HAVE_DASH)) == (HAVE_DIGIT | HAVE_DASH) + matched = parse_iso(str, h) + end + + if !matched && (cc & (HAVE_DIGIT | HAVE_DOT)) == (HAVE_DIGIT | HAVE_DOT) + matched = parse_jis(str, h) + end + + if !matched && (cc & (HAVE_ALPHA | HAVE_DIGIT | HAVE_DASH)) == (HAVE_ALPHA | HAVE_DIGIT | HAVE_DASH) + matched = parse_vms(str, h) + end + + if !matched && (cc & (HAVE_DIGIT | HAVE_SLASH)) == (HAVE_DIGIT | HAVE_SLASH) + matched = parse_sla(str, h) + end + + if !matched && (cc & (HAVE_DIGIT | HAVE_DOT)) == (HAVE_DIGIT | HAVE_DOT) + matched = parse_dot(str, h) + end + + if !matched && (cc & HAVE_DIGIT) != 0 + matched = parse_iso2(str, h) + end + + if !matched && (cc & HAVE_DIGIT) != 0 + matched = parse_year(str, h) + end + + if !matched && (cc & HAVE_ALPHA) != 0 + matched = parse_mon(str, h) + end + + if !matched && (cc & HAVE_DIGIT) != 0 + matched = parse_mday(str, h) + end + + if !matched && (cc & HAVE_DIGIT) != 0 + parse_ddd(str, h) + end + + # Post-processing (always runs after ok label) + # parse_bc + if (cc & HAVE_ALPHA) != 0 + parse_bc_post(str, h) + end + + # parse_frag + if (cc & HAVE_DIGIT) != 0 + parse_frag(str, h) + end + + # BC handling + if h.delete(:_bc) + h[:cwyear] = -h[:cwyear] + 1 if h[:cwyear] + h[:year] = -h[:year] + 1 if h[:year] + end + + # comp (century completion) + if comp && h.delete(:_comp) != false + [:cwyear, :year].each do |key| + y = h[key] + if y && y >= 0 && y <= 99 + h[key] = y >= 69 ? y + 1900 : y + 2000 + end + end + end + + # zone -> offset + if h[:zone] && !h.key?(:offset) + h[:offset] = fast_zone_offset(h[:zone]) + end + + h + end + + # ------------------------------------------------------------------ + # parse constructor + # ------------------------------------------------------------------ + + # call-seq: + # Date.parse(string = '-4712-01-01', comp = true, start = Date::ITALY, limit: 128) -> date + # + # Note: + # This method recognizes many forms in +string+, + # but it is not a validator. + # For formats, see + # {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] + # If +string+ does not specify a valid date, + # the result is unpredictable; + # consider using Date._strptime instead. + # + # Returns a new \Date object with values parsed from +string+: + # + # Date.parse('2001-02-03') # => # + # Date.parse('20010203') # => # + # Date.parse('3rd Feb 2001') # => # + # + # If +comp+ is +true+ and the given year is in the range (0..99), + # the current century is supplied; + # otherwise, the year is taken as given: + # + # Date.parse('01-02-03', true) # => # + # Date.parse('01-02-03', false) # => # + # + # See: + # + # - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # - Argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date._parse (returns a hash). + def parse(string = '-4712-01-01', comp = true, start = DEFAULT_SG, limit: 128) + hash = _parse(string, comp, limit: limit) + fast_new_date(hash, start) + end + + # ------------------------------------------------------------------ + # Specialized constructors + # ------------------------------------------------------------------ + + # call-seq: + # Date.iso8601(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date + # + # Returns a new \Date object with values parsed from +string+, + # which should contain + # an {ISO 8601 formatted date}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications]: + # + # d = Date.new(2001, 2, 3) + # s = d.iso8601 # => "2001-02-03" + # Date.iso8601(s) # => # + # + # See: + # + # - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # - Argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date._iso8601 (returns a hash). + def iso8601(string = JULIAN_EPOCH_DATE, start = DEFAULT_SG, limit: 128) + hash = _iso8601(string, limit: limit) + fast_new_date(hash, start) + end + + # call-seq: + # Date.rfc3339(string = '-4712-01-01T00:00:00+00:00', start = Date::ITALY, limit: 128) -> date + # + # Returns a new \Date object with values parsed from +string+, + # which should be a valid + # {RFC 3339 format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+3339+Format]: + # + # d = Date.new(2001, 2, 3) + # s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" + # Date.rfc3339(s) # => # + # + # See: + # + # - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # - Argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date._rfc3339 (returns a hash). + def rfc3339(string = JULIAN_EPOCH_DATETIME, start = DEFAULT_SG, limit: 128) + hash = _rfc3339(string, limit: limit) + fast_new_date(hash, start) + end + + # call-seq: + # Date.xmlschema(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date + # + # Returns a new \Date object with values parsed from +string+, + # which should be a valid XML date format: + # + # d = Date.new(2001, 2, 3) + # s = d.xmlschema # => "2001-02-03" + # Date.xmlschema(s) # => # + # + # See: + # + # - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # - Argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date._xmlschema (returns a hash). + def xmlschema(string = JULIAN_EPOCH_DATE, start = DEFAULT_SG, limit: 128) + hash = _xmlschema(string, limit: limit) + fast_new_date(hash, start) + end + + # call-seq: + # Date.rfc2822(string = 'Mon, 1 Jan -4712 00:00:00 +0000', start = Date::ITALY, limit: 128) -> date + # + # Returns a new \Date object with values parsed from +string+, + # which should be a valid + # {RFC 2822 date format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+2822+Format]: + # + # d = Date.new(2001, 2, 3) + # s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" + # Date.rfc2822(s) # => # + # + # See: + # + # - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # - Argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date._rfc2822 (returns a hash). + def rfc2822(string = JULIAN_EPOCH_DATETIME_RFC2822, start = DEFAULT_SG, limit: 128) + hash = _rfc2822(string, limit: limit) + fast_new_date(hash, start) + end + alias rfc822 rfc2822 + + # call-seq: + # Date.httpdate(string = 'Mon, 01 Jan -4712 00:00:00 GMT', start = Date::ITALY, limit: 128) -> date + # + # Returns a new \Date object with values parsed from +string+, + # which should be a valid + # {HTTP date format}[rdoc-ref:language/strftime_formatting.rdoc@HTTP+Format]: + # + # d = Date.new(2001, 2, 3) + # s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" + # Date.httpdate(s) # => # + # + # See: + # + # - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # - Argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date._httpdate (returns a hash). + def httpdate(string = JULIAN_EPOCH_DATETIME_HTTPDATE, start = DEFAULT_SG, limit: 128) + hash = _httpdate(string, limit: limit) + fast_new_date(hash, start) + end + + # call-seq: + # Date.jisx0301(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date + # + # Returns a new \Date object with values parsed from +string+, + # which should be a valid {JIS X 0301 format}[rdoc-ref:language/strftime_formatting.rdoc@JIS+X+0301+Format]: + # + # d = Date.new(2001, 2, 3) + # s = d.jisx0301 # => "H13.02.03" + # Date.jisx0301(s) # => # + # + # For no-era year, legacy format, Heisei is assumed. + # + # Date.jisx0301('13.02.03') # => # + # + # See: + # + # - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # - Argument {limit}[rdoc-ref:Date@Argument+limit]. + # + # Related: Date._jisx0301 (returns a hash). + def jisx0301(string = JULIAN_EPOCH_DATE, start = DEFAULT_SG, limit: 128) + hash = _jisx0301(string, limit: limit) + fast_new_date(hash, start) + end + + private + + # ------------------------------------------------------------------ + # Shared infrastructure + # ------------------------------------------------------------------ + + def parse_check_limit(str, limit) + raise ArgumentError, "string length (#{str.length}) exceeds limit (#{limit})" if limit && str.length > limit + end + + def parse_to_str(obj) + return nil if obj.nil? + raise TypeError, "no implicit conversion of #{obj.class} into String" if obj.is_a?(Symbol) + String === obj ? obj : obj.to_str + end + + def parse_zone_and_offset(zone_str, hash) + return unless zone_str + hash[:zone] = zone_str + hash[:offset] = fast_zone_offset(zone_str) + end + + def parse_sec_fraction(frac_str) + Rational(frac_str.to_i, 10 ** frac_str.length) + end + + # Fast zone offset calculation for common patterns. + # Handles: Z/z, +HH:MM/-HH:MM, +HHMM/-HHMM, short named zones. + # Falls back to sp_zone_to_diff for complex cases. + def fast_zone_offset(zone_str) + len = zone_str.length + + # Z/z + return 0 if len == 1 && (zone_str[0] == 'Z' || zone_str[0] == 'z') + + if zone_str[0] == '+' || zone_str[0] == '-' + sc = StringScanner.new(zone_str) + if sc.scan(/([+-])(\d{2}):?(\d{2})\z/) + sign = sc[1] == '-' ? -1 : 1 + return sign * (sc[2].to_i * 3600 + sc[3].to_i * 60) + end + end + + # Short named zones: gmt, utc, est, etc. + if len <= 3 + off = ZONE_TABLE[zone_str.downcase] + return off if off + end + + # Fall back to full parser + sp_zone_to_diff(zone_str) + end + + # ------------------------------------------------------------------ + # _iso8601 helpers + # ------------------------------------------------------------------ + + def comp_year69(y) + y >= 69 ? y + 1900 : y + 2000 + end + + def iso8601_ext_datetime(m, h) + if m[1] + # year-mon-mday or truncated + unless m[1] == '-' + y = m[1].to_i + h[:year] = (m[1].length <= 2 && !m[1].start_with?('+') && !m[1].start_with?('-')) ? comp_year69(y) : y + end + h[:mon] = m[2].to_i if m[2] + h[:mday] = m[3].to_i if m[3] + elsif m[4] || m[5] + # year-yday + if m[4] + y = m[4].to_i + h[:year] = (m[4].length <= 2 && !m[4].start_with?('+') && !m[4].start_with?('-')) ? comp_year69(y) : y + end + h[:yday] = m[5].to_i + elsif m[6] || m[7] + # cwyear-wNN-D + if m[6] + y = m[6].to_i + h[:cwyear] = m[6].length <= 2 ? comp_year69(y) : y + end + h[:cweek] = m[7].to_i + h[:cwday] = m[8].to_i if m[8] + elsif m[9] + # -w-D + h[:cwday] = m[9].to_i + end + # time part + if m[10] + h[:hour] = m[10].to_i + h[:min] = m[11].to_i if m[11] + h[:sec] = m[12].to_i if m[12] + h[:sec_fraction] = parse_sec_fraction(m[13]) if m[13] + parse_zone_and_offset(m[14], h) if m[14] + end + end + + def iso8601_bas_datetime(m, h) + if m[1] + # yyyymmdd / --mmdd / ----dd + unless m[1] == '--' + y_s = m[1] + y = y_s.to_i + ylen = y_s.sub(/\A[-+]/, '').length + h[:year] = (ylen <= 2 && !y_s.start_with?('+') && !y_s.start_with?('-')) ? comp_year69(y) : y + end + h[:mon] = m[2].to_i unless m[2] == '-' + h[:mday] = m[3].to_i + elsif m[4] + # yyyyddd + y_s = m[4] + y = y_s.to_i + ylen = y_s.sub(/\A[-+]/, '').length + h[:year] = (ylen <= 2 && !y_s.start_with?('+') && !y_s.start_with?('-')) ? comp_year69(y) : y + h[:yday] = m[5].to_i + elsif m[6] + # -ddd + h[:yday] = m[6].to_i + elsif m[7] + # yyyywwwd + y = m[7].to_i + h[:cwyear] = m[7].length <= 2 ? comp_year69(y) : y + h[:cweek] = m[8].to_i + h[:cwday] = m[9].to_i + elsif m[10] + # -wNN-D + h[:cweek] = m[10].to_i + h[:cwday] = m[11].to_i + elsif m[12] + # -w-D + h[:cwday] = m[12].to_i + end + # time part + if m[13] + h[:hour] = m[13].to_i + h[:min] = m[14].to_i if m[14] + h[:sec] = m[15].to_i if m[15] + h[:sec_fraction] = parse_sec_fraction(m[16]) if m[16] + parse_zone_and_offset(m[17], h) if m[17] + end + end + + # ------------------------------------------------------------------ + # _parse sub-parsers (private) + # ------------------------------------------------------------------ + + def parse_day(str, h) + if (m = PARSE_DAYS_RE.match(str)) + h[:wday] = ABBR_DAY_NUM[m[1].downcase] + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + end + end + + def parse_time(str, h) + if (m = PARSE_TIME_RE.match(str)) + time_part = m[1] + zone_part = m[2] + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + + if (tm = PARSE_TIME_CB_RE.match(time_part)) + hour = tm[1].to_i + if tm[5] + ampm = tm[5].downcase + if ampm == 'p' + hour = (hour % 12) + 12 + else + hour = hour % 12 + end + end + h[:hour] = hour + h[:min] = tm[2].to_i if tm[2] + h[:sec] = tm[3].to_i if tm[3] + h[:sec_fraction] = parse_sec_fraction(tm[4]) if tm[4] + end + + if zone_part + h[:zone] = zone_part + end + end + end + + def parse_eu(str, h) + if (m = PARSE_EU_RE.match(str)) + mon = ABBR_MONTH_NUM[m[2].downcase] + return false unless mon + bc = m[3] && m[3] =~ /\Ab/i ? true : false + s3e(h, m[4], mon, m[1], bc) + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + true + else + false + end + end + + def parse_us(str, h) + if (m = PARSE_US_RE.match(str)) + mon = ABBR_MONTH_NUM[m[1].downcase] + return false unless mon + bc = m[3] && m[3] =~ /\Ab/i ? true : false + s3e(h, m[4], mon, m[2], bc) + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + true + else + false + end + end + + def parse_iso(str, h) + if (m = PARSE_ISO_RE.match(str)) + y_s = m[1] + m_s = m[2] + d_s = m[3] + # Fast path: y is unambiguous year (3+ digits or signed), d is short + if y_s =~ /\A[-+]?\d{3,}\z/ && d_s =~ /\A\d{1,2}\z/ + h[:year] = y_s.to_i + h[:_comp] = false + h[:mon] = m_s.to_i + h[:mday] = d_s.to_i + else + s3e(h, y_s, m_s, d_s, false) + end + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + true + else + false + end + end + + def parse_jis(str, h) + if (m = PARSE_JIS_RE.match(str)) + era_char = m[1].downcase + era_offset = JISX0301_ERA[era_char] + return false unless era_offset + h[:year] = m[2].to_i + era_offset + h[:mon] = m[3].to_i + h[:mday] = m[4].to_i + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + true + else + false + end + end + + def parse_vms(str, h) + if (m = PARSE_VMS11_RE.match(str)) + mon = ABBR_MONTH_NUM[m[2].downcase] + return false unless mon + s3e(h, m[3], mon, m[1], false) + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + true + elsif (m = PARSE_VMS12_RE.match(str)) + mon = ABBR_MONTH_NUM[m[1].downcase] + return false unless mon + s3e(h, m[3], mon, m[2], false) + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + true + else + false + end + end + + def parse_sla(str, h) + if (m = PARSE_SLA_RE.match(str)) + s3e(h, m[1], m[2], m[3], false) + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + true + else + false + end + end + + def parse_dot(str, h) + if (m = PARSE_DOT_RE.match(str)) + s3e(h, m[1], m[2], m[3], false) + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + true + else + false + end + end + + def parse_iso2(str, h) + # iso21: week date + if (m = PARSE_ISO21_RE.match(str)) + if m[1] + y = m[1].to_i + h[:cwyear] = y + end + h[:cweek] = m[2].to_i + h[:cwday] = m[3].to_i if m[3] + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + return true + end + + # iso22: -w-D + if (m = PARSE_ISO22_RE.match(str)) + h[:cwday] = m[1].to_i + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + return true + end + + # iso23: --MM-DD + if (m = PARSE_ISO23_RE.match(str)) + h[:mon] = m[1].to_i if m[1] + h[:mday] = m[2].to_i + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + return true + end + + # iso24: --MMDD + if (m = PARSE_ISO24_RE.match(str)) + h[:mon] = m[1].to_i + h[:mday] = m[2].to_i if m[2] + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + return true + end + + # iso25: YYYY-DDD (guard against fraction match) + unless str =~ /[,.]\d{2,4}-\d{3}\b/ + if (m = PARSE_ISO25_RE.match(str)) + h[:year] = m[1].to_i + h[:yday] = m[2].to_i + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + return true + end + end + + # iso26: -DDD (guard against digit-DDD) + unless str =~ /\d-\d{3}\b/ + if (m = PARSE_ISO26_RE.match(str)) + h[:yday] = m[1].to_i + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + return true + end + end + + false + end + + def parse_year(str, h) + if (m = PARSE_YEAR_RE.match(str)) + h[:year] = m[1].to_i + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + true + else + false + end + end + + def parse_mon(str, h) + if (m = PARSE_MON_RE.match(str)) + mon = ABBR_MONTH_NUM[m[1].downcase] + if mon + h[:mon] = mon + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + return true + end + end + false + end + + def parse_mday(str, h) + if (m = PARSE_MDAY_RE.match(str)) + h[:mday] = m[1].to_i + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + true + else + false + end + end + + def parse_ddd(str, h) + if (m = PARSE_DDD_RE.match(str)) + sign = m[1] + s2 = m[2] + s3 = m[3] + s4 = m[4] + s5 = m[5] + + l2 = s2.length + case l2 + when 2 + if s3.nil? && s4 + h[:sec] = s2.to_i + else + h[:mday] = s2.to_i + end + when 4 + if s3.nil? && s4 + h[:sec] = s2[2, 2].to_i + h[:min] = s2[0, 2].to_i + else + h[:mon] = s2[0, 2].to_i + h[:mday] = s2[2, 2].to_i + end + when 6 + if s3.nil? && s4 + h[:sec] = s2[4, 2].to_i + h[:min] = s2[2, 2].to_i + h[:hour] = s2[0, 2].to_i + else + h[:year] = (sign.to_s + s2[0, 2]).to_i + h[:mon] = s2[2, 2].to_i + h[:mday] = s2[4, 2].to_i + end + when 8, 10, 12, 14 + if s3.nil? && s4 + # read from end: sec,min,hour,mday,mon,year + pos = l2 + h[:sec] = s2[pos - 2, 2].to_i + pos -= 2 + h[:min] = s2[pos - 2, 2].to_i + pos -= 2 + h[:hour] = s2[pos - 2, 2].to_i + pos -= 2 + if pos >= 2 + h[:mday] = s2[pos - 2, 2].to_i + pos -= 2 + if pos >= 2 + h[:mon] = s2[pos - 2, 2].to_i + pos -= 2 + h[:year] = (sign.to_s + s2[0, pos]).to_i if pos > 0 + end + end + else + h[:year] = (sign.to_s + s2[0, 4]).to_i + h[:mon] = s2[4, 2].to_i if l2 >= 6 + h[:mday] = s2[6, 2].to_i if l2 >= 8 + h[:hour] = s2[8, 2].to_i if l2 >= 10 + h[:min] = s2[10, 2].to_i if l2 >= 12 + h[:sec] = s2[12, 2].to_i if l2 >= 14 + h[:_comp] = false + end + when 3 + if s3.nil? && s4 + h[:sec] = s2[1, 2].to_i + h[:min] = s2[0, 1].to_i + else + h[:yday] = s2.to_i + end + when 5 + if s3.nil? && s4 + h[:sec] = s2[3, 2].to_i + h[:min] = s2[1, 2].to_i + h[:hour] = s2[0, 1].to_i + else + h[:year] = (sign.to_s + s2[0, 2]).to_i + h[:yday] = s2[2, 3].to_i + end + when 7 + if s3.nil? && s4 + h[:sec] = s2[5, 2].to_i + h[:min] = s2[3, 2].to_i + h[:hour] = s2[1, 2].to_i + h[:mday] = s2[0, 1].to_i + else + h[:year] = (sign.to_s + s2[0, 4]).to_i + h[:yday] = s2[4, 3].to_i + h[:_comp] = false + end + end + + # s3 (time portion from continuous digits after separator) + if s3 && !s3.empty? + l3 = s3.length + if s4 + # read from end + case l3 + when 2 + h[:sec] = s3[0, 2].to_i + when 4 + h[:sec] = s3[2, 2].to_i + h[:min] = s3[0, 2].to_i + when 6 + h[:sec] = s3[4, 2].to_i + h[:min] = s3[2, 2].to_i + h[:hour] = s3[0, 2].to_i + end + else + # read from start + h[:hour] = s3[0, 2].to_i if l3 >= 2 + h[:min] = s3[2, 2].to_i if l3 >= 4 + h[:sec] = s3[4, 2].to_i if l3 >= 6 + end + end + + # s4: sec_fraction + if s4 && !s4.empty? + h[:sec_fraction] = parse_sec_fraction(s4) + end + + # s5: zone + if s5 && !s5.empty? + zone = s5 + if zone.start_with?('[') + zone = zone[1...-1] # strip brackets + # Format: [offset:zonename] or [offset zonename] or [offset] or [zonename] + if (zm = zone.match(/\A([-+]?\d+(?:[,.]\d+)?):(.+)/)) + # +9:JST, -5:EST, +12:XXX YYY ZZZ + h[:zone] = zm[2].strip + off_s = zm[1] + off_s = "+#{off_s}" unless off_s.start_with?('+') || off_s.start_with?('-') + h[:offset] = fast_zone_offset(off_s) + elsif (zm = zone.match(/\A([-+]?\d+(?:[,.]\d+)?)\s+(\S.+)/)) + # Number followed by space and non-empty name + h[:zone] = zm[2] + off_s = zm[1] + off_s = "+#{off_s}" unless off_s.start_with?('+') || off_s.start_with?('-') + h[:offset] = fast_zone_offset(off_s) + else + # Could be just a number with optional trailing space: [9], [-9], [9 ] + h[:zone] = zone + stripped = zone.strip + if stripped =~ /\A([-+]?\d+(?:[,.]\d+)?)\z/ + off_s = $1 + off_s = "+#{off_s}" unless off_s.start_with?('+') || off_s.start_with?('-') + h[:offset] = fast_zone_offset(off_s) + end + end + else + h[:zone] = zone + end + end + + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + true + else + false + end + end + + def parse_bc_post(str, h) + if (m = PARSE_BC_RE.match(str)) + h[:_bc] = true + str[m.begin(0)...m.end(0)] = ' ' * (m.end(0) - m.begin(0)) + end + end + + def parse_frag(str, h) + if (m = PARSE_FRAG_RE.match(str)) + v = m[1].to_i + if h.key?(:hour) && !h.key?(:mday) + h[:mday] = v if v >= 1 && v <= 31 + elsif h.key?(:mday) && !h.key?(:hour) + h[:hour] = v if v >= 0 && v <= 24 + end + end + end + + # s3e: 3-element (year, month, day) disambiguation + # Faithfully mirrors the C implementation's s3e() logic. + # Arguments: y, m, d are strings (or Integer for m when month name was parsed) + def s3e(h, y, m, d, bc) + # Fast path: y is unambiguously a year (3+ digits or signed), m and d are simple digits. + # This covers ISO "2001-02-03" but not ambiguous cases like "23/5/1999". + if y && m && d && !bc && y.is_a?(String) && m.is_a?(String) && d.is_a?(String) && + !y.start_with?("'") && !d.start_with?("'") && + y =~ /\A([-+])?\d{3,}\z/ && m =~ /\A\d+\z/ && d =~ /\A\d{1,2}\z/ + h[:year] = y.to_i + h[:_comp] = false + h[:mon] = m.to_i + h[:mday] = d.to_i + return + end + + m = m.to_s if m.is_a?(Integer) + + # Step 1: If y && m are present but d is nil, rotate: d=m, m=y, y=nil + if y && m && d.nil? + d = m + m = y + y = nil + end + + # Step 2: If y is nil but d is present, check if d looks like a year + if y.nil? && d + ds = d.to_s + digits = ds.sub(/\A'?-?/, '').sub(/(?:st|nd|rd|th)\z/i, '') + if digits.length > 2 || ds.start_with?("'") + y = d + d = nil + end + end + + # Step 3: Parse y - extract numeric value and determine _comp flag + year_val = nil + comp_flag = nil + if y + ys_raw = y.to_s + if ys_raw.start_with?("'") + year_val = ys_raw[1..].to_i + comp_flag = true + else + # Match digits (with optional leading sign), check for trailing non-digit + if ys_raw =~ /\A[^-+\d]*([-+]?\d+)/ + num_s = $1 + rest = ys_raw[$~.end(0)..] + if rest && !rest.empty? && rest =~ /[^\d]/ + # trailing non-digit (like "st" in "1st"): this y becomes d, old d becomes y + old_d = d + d = num_s + year_val = nil + comp_flag = nil + if old_d + s3eparse_year(old_d.to_s)&.then do |v, c| + year_val = v + comp_flag = c + end + end + else + year_val = num_s.to_i + if num_s.start_with?('-') || num_s.start_with?('+') || num_s.sub(/\A[-+]/, '').length > 2 + comp_flag = false + end + end + end + end + end + + # Step 4: Check m - if it looks like a year (apostrophe or > 2 digits), swap US→BE + if m.is_a?(String) + ms_digits = m.sub(/\A'?-?/, '').sub(/(?:st|nd|rd|th)\z/i, '') + if m.start_with?("'") || ms_digits.length > 2 + # Rotate: old_y=y, y=m, m=d, d=old_y_string + old_y = y + y = m + m = d + d = old_y + + # Re-parse y + ys = y.to_s.sub(/(?:st|nd|rd|th)\z/i, '') + if ys.start_with?("'") + year_val = ys[1..].to_i + comp_flag = true + elsif ys =~ /([-+]?\d+)/ + num_s = $1 + year_val = num_s.to_i + comp_flag = (num_s.start_with?('-') || num_s.start_with?('+') || num_s.sub(/\A[-+]/, '').length > 2) ? false : nil + end + end + end + + # Step 5: Check d - if it looks like a year, swap with y + if d.is_a?(String) + ds_digits = d.sub(/\A'?-?/, '').sub(/(?:st|nd|rd|th)\z/i, '') + if d.start_with?("'") || ds_digits.length > 2 + old_y = y + # d becomes year + ys = d.sub(/(?:st|nd|rd|th)\z/i, '') + if ys.start_with?("'") + year_val = ys[1..].to_i + comp_flag = true + elsif ys =~ /([-+]?\d+)/ + num_s = $1 + year_val = num_s.to_i + comp_flag = (num_s.start_with?('-') || num_s.start_with?('+') || num_s.sub(/\A[-+]/, '').length > 2) ? false : nil + end + d = old_y + end + end + + # Set year + h[:year] = year_val if year_val + h[:_comp] = comp_flag unless comp_flag.nil? + h[:_bc] = true if bc + + # Set mon + if m + if m.is_a?(String) + ms = m.sub(/(?:st|nd|rd|th)\z/i, '') + h[:mon] = $1.to_i if ms =~ /(\d+)/ + else + h[:mon] = m.to_i + end + end + + # Set mday + if d + if d.is_a?(String) + ds = d.sub(/(?:st|nd|rd|th)\z/i, '') + h[:mday] = $1.to_i if ds =~ /(\d+)/ + else + h[:mday] = d.to_i + end + end + end + + # Helper: parse a string as a year value, returning [year_val, comp_flag] or nil + def s3eparse_year(s) + if s.start_with?("'") + [s[1..].to_i, true] + elsif s =~ /\A[^-+\d]*([-+]?\d+)/ + num_s = $1 + cf = (num_s.start_with?('-') || num_s.start_with?('+') || num_s.sub(/\A[-+]/, '').length > 2) ? false : nil + [num_s.to_i, cf] + end + end + + # ------------------------------------------------------------------ + # Fast Date construction + # ------------------------------------------------------------------ + + # Fast Date construction: when year/mon/mday are all present and no + # complex keys (jd, yday, cwyear, wday, wnum, seconds) exist, skip + # the full _sp_complete_frags pipeline and directly validate civil date. + def fast_new_date(hash, sg) + raise Error, 'invalid date' if hash.nil? || hash.empty? + y = hash[:year] + m = hash[:mon] + d = hash[:mday] + if y && m && d && + !hash.key?(:jd) && !hash.key?(:yday) && !hash.key?(:cwyear) && + !hash.key?(:wnum0) && !hash.key?(:wnum1) && !hash.key?(:seconds) + jd = internal_valid_civil?(y, m, d, sg) + raise Error, 'invalid date' if jd.nil? + new_from_jd(jd, sg) + else + internal_new_by_frags(hash, sg) + end + end + + end +end diff --git a/lib/date/shared.rb b/lib/date/shared.rb new file mode 100644 index 00000000..24dd65f3 --- /dev/null +++ b/lib/date/shared.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# Shared methods used across multiple Date implementation files. +class Date + # Generate a case-insensitive 3-byte integer key from a 3-character string. + # Used for O(1) abbreviated day/month name lookup in parse.rb and strptime.rb. + # Key = (byte0_lower << 16) | (byte1_lower << 8) | byte2_lower + def self.compute_3key(s) + b = s.bytes + ((b[0] | 0x20) << 16) | ((b[1] | 0x20) << 8) | (b[2] | 0x20) + end + private_class_method :compute_3key +end diff --git a/lib/date/strftime.rb b/lib/date/strftime.rb new file mode 100644 index 00000000..280d224d --- /dev/null +++ b/lib/date/strftime.rb @@ -0,0 +1,544 @@ +# frozen_string_literal: true + +class Date + # Bitmask flag constants for strftime parsing + FL_LEFT = 0x01 # '-' flag + FL_SPACE = 0x02 # '_' flag + FL_ZERO = 0x04 # '0' flag + FL_UPPER = 0x08 # '^' flag + FL_CHCASE = 0x10 # '#' flag + private_constant :FL_LEFT, :FL_SPACE, :FL_ZERO, :FL_UPPER, :FL_CHCASE + + # call-seq: + # strftime(format = '%F') -> string + # + # Returns a string representation of the date in +self+, + # formatted according the given +format+: + # + # Date.new(2001, 2, 3).strftime # => "2001-02-03" + # + # For other formats, see + # {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. + def strftime(format = DEFAULT_STRFTIME_FMT) + if format.equal?(DEFAULT_STRFTIME_FMT) + internal_civil unless @year + return fast_ymd.force_encoding(Encoding::US_ASCII) + end + fmt = format.to_str + if fmt == YMD_FMT + internal_civil unless @year + return fast_ymd.force_encoding(Encoding::US_ASCII) + end + internal_strftime(fmt).force_encoding(fmt.encoding) + end + + private + + def internal_strftime(fmt) + # Fast path for common format strings (whole-string match) + case fmt + when '%Y-%m-%d', '%F' + internal_civil unless @year + return fast_ymd + when '%H:%M:%S', '%T', '%X' + return instance_of?(Date) ? +'00:00:00' : "#{PAD2[internal_hour]}:#{PAD2[internal_min]}:#{PAD2[internal_sec]}" + when '%m/%d/%y', '%D', '%x' + internal_civil unless @year + return "#{PAD2[@month]}/#{PAD2[@day]}/#{PAD2[@year % 100]}" + when '%Y-%m-%dT%H:%M:%S%z' + internal_civil unless @year + if instance_of?(Date) + return fast_ymd << 'T00:00:00+0000' + else + of = of_seconds + sign = of < 0 ? '-' : '+' + abs = of.abs + return "#{fast_ymd}T#{PAD2[internal_hour]}:#{PAD2[internal_min]}:#{PAD2[internal_sec]}#{sign}#{PAD2[abs / 3600]}#{PAD2[(abs % 3600) / 60]}" + end + when '%a %b %e %H:%M:%S %Y', '%c' + internal_civil unless @year + return fmt_asctime_str + when '%A, %B %d, %Y' + internal_civil unless @year + w = (@jd + 1) % 7 + y = @year + y_s = y >= 1000 ? y.to_s : (y >= 0 ? format('%04d', y) : format('-%04d', -y)) + return "#{DAY_FULL_COMMA[w]}#{MONTH_FULL_SPACE[@month]}#{PAD2[@day]}, #{y_s}" + end + + result = +''.encode(fmt.encoding) + sc = StringScanner.new(fmt) + + until sc.eos? + # Batch collect literal (non-%) characters + if (lit = sc.scan(/[^%]+/)) + result << lit + next + end + + # Must be '%' or something unexpected + unless sc.skip(/%/) + result << sc.getch + next + end + + if sc.eos? + result << '%' + break + end + + # Quick dispatch: if next char is a simple letter spec (A-Z excluding E/O, + # or a-z), skip flag/width/prec parsing entirely. + if (quick = sc.scan(/[A-DFG-NP-Za-z]/)) + fast = fast_spec(quick.ord) + if fast + result << fast + next + end + sc.unscan + end + + # Parse flags (bitmask) + flags = 0 + colons = 0 + while (f = sc.scan(/[-_0^#]/)) + case f + when '-' then flags |= FL_LEFT + when '_' then flags |= FL_SPACE + when '0' then flags |= FL_ZERO + when '^' then flags |= FL_UPPER + when '#' then flags |= FL_CHCASE + end + end + if (c = sc.scan(/:+/)) + colons = c.length + end + + # Parse width + width = sc.scan(/\d+/)&.to_i + raise Errno::ERANGE, "strftime" if width && width > STRFTIME_MAX_WIDTH + + # Parse precision (after '.') + prec = sc.skip(/\./) ? (sc.scan(/\d+/)&.to_i || 0) : nil + + # Post-width colons (for %8:z, %11::z etc.) + if (c = sc.scan(/:+/)) + colons += c.length + end + + # Locale modifier (%E or %O) + locale_mod = sc.scan(/[EO]/)&.ord + + spec = sc.getch&.ord + + # Inline fast path: no flags, no width, no prec, no locale mod, no colons + if flags == 0 && width.nil? && prec.nil? && locale_mod.nil? && colons == 0 + fast = fast_spec(spec) + if fast + result << fast + next + end + end + + result << format_spec_b(spec, flags, colons, width, prec, locale_mod) + end + + result + end + + # Format "%a %b %e %H:%M:%S %Y" directly (used by %c expansion and asctime) + def fmt_asctime_str + w = (@jd + 1) % 7 + d = @day + d_s = d < 10 ? " #{d}" : d.to_s + y = @year + y_s = y >= 1000 ? y.to_s : (y >= 0 ? format('%04d', y) : format('-%04d', -y)) + if instance_of?(Date) + "#{ASCTIME_PREFIX[w][@month]}#{d_s} 00:00:00 #{y_s}" + else + "#{ASCTIME_PREFIX[w][@month]}#{d_s} #{PAD2[internal_hour]}:#{PAD2[internal_min]}:#{PAD2[internal_sec]} #{y_s}" + end + end + + # Inline fast path for common specs with default formatting (no flags/width/prec) + def fast_spec(spec) + case spec + when 89 # 'Y' + internal_civil unless @year + y = @year + if y >= 1000 + s = y.to_s + raise Errno::ERANGE, "strftime" if s.length >= STRFTIME_MAX_COPY_LEN + s + elsif y >= 0 + format('%04d', y) + else + format('-%04d', -y) + end + when 109 # 'm' + internal_civil unless @year + PAD2[@month] + when 100 # 'd' + internal_civil unless @year + PAD2[@day] + when 101 # 'e' + internal_civil unless @year + d = @day + d < 10 ? " #{d}" : d.to_s + when 72 # 'H' + PAD2[internal_hour] + when 77 # 'M' + PAD2[internal_min] + when 83 # 'S' + PAD2[internal_sec] + when 97 # 'a' + STRFTIME_DAYS_ABBR[(@jd + 1) % 7] + when 65 # 'A' + STRFTIME_DAYS_FULL[(@jd + 1) % 7] + when 98, 104 # 'b', 'h' + internal_civil unless @year + STRFTIME_MONTHS_ABBR[@month] + when 66 # 'B' + internal_civil unless @year + STRFTIME_MONTHS_FULL[@month] + when 112 # 'p' + internal_hour < 12 ? 'AM' : 'PM' + when 80 # 'P' + internal_hour < 12 ? 'am' : 'pm' + when 37 # '%' + '%' + when 110 # 'n' + "\n" + when 116 # 't' + "\t" + when 106 # 'j' + yd = yday + if yd < 10 + "00#{yd}" + elsif yd < 100 + "0#{yd}" + else + yd.to_s + end + when 119 # 'w' + ((@jd + 1) % 7).to_s + when 117 # 'u' + cwday.to_s + when 121 # 'y' + internal_civil unless @year + PAD2[@year % 100] + when 90 # 'Z' + zone_str + when 122 # 'z' + of = of_seconds + sign = of < 0 ? '-' : '+' + abs = of.abs + "#{sign}#{PAD2[abs / 3600]}#{PAD2[(abs % 3600) / 60]}" + when 115 # 's' + ((@jd - 2440588) * 86400 - of_seconds + internal_hour * 3600 + internal_min * 60 + internal_sec).to_s + # Composite specs — inline expansion + when 99 # 'c' + internal_civil unless @year + fmt_asctime_str + when 70 # 'F' + internal_civil unless @year + fast_ymd + when 84, 88 # 'T', 'X' + instance_of?(Date) ? '00:00:00' : "#{PAD2[internal_hour]}:#{PAD2[internal_min]}:#{PAD2[internal_sec]}" + when 68, 120 # 'D', 'x' + internal_civil unless @year + "#{PAD2[@month]}/#{PAD2[@day]}/#{PAD2[@year % 100]}" + when 82 # 'R' + instance_of?(Date) ? '00:00' : "#{PAD2[internal_hour]}:#{PAD2[internal_min]}" + when 114 # 'r' + if instance_of?(Date) + '12:00:00 AM' + else + h = internal_hour % 12 + h = 12 if h == 0 + "#{PAD2[h]}:#{PAD2[internal_min]}:#{PAD2[internal_sec]} #{internal_hour < 12 ? 'AM' : 'PM'}" + end + else + nil # fall through to format_spec_b + end + end + + # Full spec handling with bitmask flags (called when fast_spec returns nil or flags/width/prec present) + def format_spec_b(spec, flags, colons, width, prec, locale_mod) + # Handle %E/%O locale modifiers + if locale_mod + valid = locale_mod == 69 ? STRFTIME_E_VALID_BYTES : STRFTIME_O_VALID_BYTES # 69='E' + unless valid.include?(spec) + mod_chr = locale_mod == 69 ? 'E' : 'O' + return "%#{mod_chr}#{spec&.chr}" + end + end + + case spec + when 89, 71 # 'Y', 'G' + y = spec == 89 ? year : cwyear + fmt_year(y, width, prec, flags) + when 67 # 'C' + cent = year.div(100) + pad_num(cent, width || 2, flags) + when 121 # 'y' + pad_num(year % 100, width || 2, flags, zero: true) + when 103 # 'g' + pad_num(cwyear % 100, width || 2, flags, zero: true) + when 109 # 'm' + pad_num(month, width || 2, flags, zero: true) + when 100 # 'd' + pad_num(day, width || 2, flags, zero: true) + when 101 # 'e' + pad_num(day, width || 2, flags, zero: false) + when 106 # 'j' + pad_num(yday, width || 3, flags, zero: true) + when 72 # 'H' + pad_num(internal_hour, width || 2, flags, zero: true) + when 107 # 'k' + pad_num(internal_hour, width || 2, flags, zero: false) + when 73 # 'I' + h = internal_hour % 12 + h = 12 if h == 0 + pad_num(h, width || 2, flags, zero: true) + when 108 # 'l' + h = internal_hour % 12 + h = 12 if h == 0 + pad_num(h, width || 2, flags, zero: false) + when 77 # 'M' + pad_num(internal_min, width || 2, flags, zero: true) + when 83 # 'S' + pad_num(internal_sec, width || 2, flags, zero: true) + when 76 # 'L' + w = width || 3 + ms = (sec_frac * (10**w)).floor + ms.to_s.rjust(w, '0') + when 78 # 'N' + w = width || prec || 9 + ns = (sec_frac * (10**w)).floor + ns.to_s.rjust(w, '0') + when 115 # 's' + unix = (@jd - 2440588) * 86400 - of_seconds + + internal_hour * 3600 + internal_min * 60 + internal_sec + pad_num(unix, width || 1, flags) + when 81 # 'Q' + ms = ((@jd - 2440588) * 86400 - of_seconds + + internal_hour * 3600 + internal_min * 60 + internal_sec) * 1000 + + (sec_frac * 1000).floor + pad_num(ms, width || 1, flags) + when 65 # 'A' + fmt_str(STRFTIME_DAYS_FULL[wday], width, flags) + when 97 # 'a' + fmt_str(STRFTIME_DAYS_ABBR[wday], width, flags) + when 66 # 'B' + fmt_str(STRFTIME_MONTHS_FULL[month], width, flags) + when 98, 104 # 'b', 'h' + fmt_str(STRFTIME_MONTHS_ABBR[month], width, flags) + when 112 # 'p' + fmt_str(internal_hour < 12 ? 'AM' : 'PM', width, flags) + when 80 # 'P' + fmt_str(internal_hour < 12 ? 'am' : 'pm', width, flags) + when 90 # 'Z' + fmt_str(zone_str, width, flags) + when 122 # 'z' + fmt_z(colons, width, prec, flags) + when 117 # 'u' + pad_num(cwday, width || 1, flags) + when 119 # 'w' + pad_num(wday, width || 1, flags) + when 85 # 'U' + pad_num(week_number(0), width || 2, flags, zero: true) + when 87 # 'W' + pad_num(week_number(1), width || 2, flags, zero: true) + when 86 # 'V' + pad_num(cweek, width || 2, flags, zero: true) + when 37 # '%' + '%' + when 43 # '+' + s = internal_strftime('%a %b %e %H:%M:%S %Z %Y') + fmt_str(s, width, flags) + else + # Try composite + expansion = STRFTIME_COMPOSITE_BYTE[spec] + if expansion + s = internal_strftime(expansion) + fmt_str(s, width, flags) + else + "%#{spec&.chr}" + end + end + end + + # Format year (handles negative years, precision, and all flag variants) + def fmt_year(y, width, prec, flags) + if prec + s = y.abs.to_s.rjust(prec, '0') + s = (y < 0 ? '-' : '') + s + elsif flags & FL_LEFT != 0 + s = (y < 0 ? '-' : '') + y.abs.to_s + else + default_w = y < 0 ? 5 : 4 + w = width || default_w + if flags & FL_SPACE != 0 + raw = (y < 0 ? '-' : '') + y.abs.to_s + s = raw.rjust(w, ' ') + else + s = y.abs.to_s.rjust(w - (y < 0 ? 1 : 0), '0') + s = (y < 0 ? '-' : '') + s + end + end + raise Errno::ERANGE, "strftime" if s.length >= STRFTIME_MAX_COPY_LEN + if flags & (FL_UPPER | FL_CHCASE) != 0 + s.upcase + else + s + end + end + + def pad_num(n, default_w, flags, zero: nil) + sign = n < 0 ? '-' : '' + abs = n.abs.to_s + w = default_w + + if flags & FL_LEFT != 0 + sign + abs + elsif flags & FL_SPACE != 0 || (flags & FL_ZERO == 0 && zero == false) + sign + abs.rjust(w - sign.length, ' ') + else + pad = (flags & FL_ZERO != 0 || zero) ? '0' : ' ' + sign + abs.rjust([w - sign.length, abs.length].max, pad) + end + end + + def fmt_str(s, width, flags) + s = s.dup + if flags & FL_CHCASE != 0 + s = (s == s.upcase) ? s.downcase : s.upcase + elsif flags & FL_UPPER != 0 + s = s.upcase + end + if flags & FL_LEFT != 0 + s + elsif width + pad = flags & FL_ZERO != 0 ? '0' : ' ' + s.rjust(width, pad) + else + s + end + end + + # Week number (0=first partial week) + def week_number(ws) + yd = yday + wd = wday # 0=Sun + if ws == 1 + # Monday-based + wd = wd == 0 ? 6 : wd - 1 + end + (yd - wd + 6).div(7) + end + + # Format %z with colons variant and GNU extension flag support + def fmt_z(colons, width, _prec, flags) + of = of_seconds + sign = of < 0 ? '-' : '+' + abs = of.abs + hh = abs / 3600 + mm = (abs % 3600) / 60 + ss = abs % 60 + + no_lead = flags & (FL_LEFT | FL_SPACE) != 0 + + if no_lead + case colons + when 0 + s = format('%s%d%02d', sign, hh, mm) + when 1 + s = format('%s%d:%02d', sign, hh, mm) + when 2 + s = format('%s%d:%02d:%02d', sign, hh, mm, ss) + when 3 + if ss != 0 + s = format('%s%d:%02d:%02d', sign, hh, mm, ss) + elsif mm != 0 + s = format('%s%d:%02d', sign, hh, mm) + else + s = format('%s%d', sign, hh) + end + else + s = format('%s%d:%02d', sign, hh, mm) + end + else + case colons + when 0 + s = format('%s%02d%02d', sign, hh, mm) + when 1 + s = format('%s%02d:%02d', sign, hh, mm) + when 2 + s = format('%s%02d:%02d:%02d', sign, hh, mm, ss) + when 3 + if ss != 0 + s = format('%s%02d:%02d:%02d', sign, hh, mm, ss) + elsif mm != 0 + s = format('%s%02d:%02d', sign, hh, mm) + else + s = format('%s%02d', sign, hh) + end + else + s = format('%s%02d:%02d', sign, hh, mm) + end + end + + if width + if flags & FL_LEFT != 0 + s + elsif flags & FL_SPACE != 0 + s.rjust(width, ' ') + else + digits = s[1..] + sign + digits.rjust(width - 1, '0') + end + else + s + end + end + + # Fast path helper: format '%Y-%m-%d' without allocation overhead + def fast_ymd + y = @year + suffix = MONTH_DAY_SUFFIX[@month][@day] + if y >= 1000 + y.to_s << suffix + elsif y >= 0 + format('%04d', y) << suffix + else + format('-%04d', -y) << suffix + end + end + + # Helpers for DateTime override + def internal_hour + 0 + end + + def internal_min + 0 + end + + def internal_sec + 0 + end + + def sec_frac + 0r + end + + def of_seconds + 0 + end + + def zone_str + '+00:00' + end + +end diff --git a/lib/date/strptime.rb b/lib/date/strptime.rb new file mode 100644 index 00000000..2cfef558 --- /dev/null +++ b/lib/date/strptime.rb @@ -0,0 +1,924 @@ +# frozen_string_literal: true + +class Date + class << self + # call-seq: + # Date._strptime(string, format = '%F') -> hash + # + # Returns a hash of values parsed from +string+ + # according to the given +format+: + # + # Date._strptime('2001-02-03', '%Y-%m-%d') # => {:year=>2001, :mon=>2, :mday=>3} + # + # For other formats, see + # {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. + # (Unlike Date.strftime, does not support flags and width.) + # + # See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. + # + # Related: Date.strptime (returns a \Date object). + def _strptime(string, format = '%F') + string = String === string ? string : string.to_str + format = String === format ? format : format.to_str + if format == '%F' || format == '%Y-%m-%d' + return internal_strptime_ymd(string) + end + if format == '%a %b %d %Y' + return internal_strptime_abdy(string) + end + hash = {} + si = catch(:sp_fail) { sp_run(string, 0, format, hash) } + return nil unless si + hash[:leftover] = string[si..] if si < string.length + if (cent = hash.delete(:_cent)) + hash[:year] = hash[:year] + cent * 100 if hash.key?(:year) + hash[:cwyear] = hash[:cwyear] + cent * 100 if hash.key?(:cwyear) + end + if (merid = hash.delete(:_merid)) + hash[:hour] = hash[:hour] % 12 + merid if hash.key?(:hour) + end + hash + end + + # call-seq: + # Date.strptime(string = '-4712-01-01', format = '%F', start = Date::ITALY) -> date + # + # Returns a new \Date object with values parsed from +string+, + # according to the given +format+: + # + # Date.strptime('2001-02-03', '%Y-%m-%d') # => # + # Date.strptime('03-02-2001', '%d-%m-%Y') # => # + # Date.strptime('2001-034', '%Y-%j') # => # + # Date.strptime('2001-W05-6', '%G-W%V-%u') # => # + # Date.strptime('2001 04 6', '%Y %U %w') # => # + # Date.strptime('2001 05 6', '%Y %W %u') # => # + # Date.strptime('sat3feb01', '%a%d%b%y') # => # + # + # For other formats, see + # {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. + # (Unlike Date.strftime, does not support flags and width.) + # + # See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + # + # See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. + # + # Related: Date._strptime (returns a hash). + def strptime(string = JULIAN_EPOCH_DATE, format = '%F', start = DEFAULT_SG) + str = String === string ? string : string.to_str + if format == '%F' || format == '%Y-%m-%d' + result = internalinternal_strptime_ymd_to_date(str, start) + return result if result + raise Error, 'invalid date' + end + if format == '%a %b %d %Y' + result = internalinternal_strptime_abdy_to_date(str, start) + return result if result + raise Error, 'invalid date' + end + hash = _strptime(string, format) + raise Error, 'invalid date' if hash.nil? + if !hash.key?(:seconds) && !hash.key?(:leftover) && + !hash.key?(:_cent) && !hash.key?(:_merid) && + (year = hash[:year]) && (mon = hash[:mon]) && (mday = hash[:mday]) + jd = internal_valid_civil?(year, mon, mday, start) + raise Error, 'invalid date' if jd.nil? + return new_from_jd(jd, start) + end + internal_new_by_frags(hash, start) + end + + + private + + # Pre-compiled regex constants for sp_run + SP_WHITESPACE = /[ \t\n\r\v\f]+/ + SP_COLONS = /:+/ + SP_EO_CHECK = /[EO]/ + SP_E_COMBO = /E[cCxXyY]/ + SP_O_COMBO = /O[deHImMSuUVwWy]/ + SP_ALPHA3 = /[A-Za-z]{3}/ + SP_SIGN = /[+-]/ + SP_SPACE_OR_DIGIT = / \d|\d{1,2}/ + SP_AMPM_DOT = /[AaPp]\.[Mm]\./ + SP_AMPM = /[AaPp][Mm]/ + SP_NUM_CHECK = /\d|%[EO]?[CDdeFGgHIjkLlMmNQRrSsTUuVvWwXxYy0-9]/ + SP_DIGITS_1 = /\d/ + SP_DIGITS_2 = /\d{1,2}/ + SP_DIGITS_3 = /\d{1,3}/ + SP_DIGITS_4 = /\d{1,4}/ + SP_DIGITS_9 = /\d{1,9}/ + SP_DIGITS_MAX = /\d+/ + private_constant :SP_WHITESPACE, :SP_COLONS, :SP_EO_CHECK, :SP_E_COMBO, + :SP_O_COMBO, :SP_ALPHA3, :SP_SIGN, :SP_SPACE_OR_DIGIT, + :SP_AMPM_DOT, :SP_AMPM, :SP_NUM_CHECK, + :SP_DIGITS_1, :SP_DIGITS_2, :SP_DIGITS_3, :SP_DIGITS_4, + :SP_DIGITS_9, :SP_DIGITS_MAX + + # Core scanner: walks format string and string simultaneously using StringScanner. + # Returns new string position on success; throws :sp_fail on failure. + def sp_run(str, si, fmt, hash) + fmt_sc = StringScanner.new(fmt) + str_sc = StringScanner.new(str) + str_sc.pos = si + + until fmt_sc.eos? + # Whitespace in format: skip any whitespace in both format and string + if fmt_sc.skip(SP_WHITESPACE) + str_sc.skip(SP_WHITESPACE) + next + end + + # Non-% literal: must match exactly + unless fmt_sc.check(/%/) + fc = fmt_sc.getch + throw(:sp_fail) if str_sc.eos? + throw(:sp_fail) if str_sc.getch != fc + next + end + + fmt_sc.skip(/%/) # skip '%' + + # Handle colon modifiers: %:z, %::z, %:::z + if fmt_sc.scan(SP_COLONS) + throw(:sp_fail) unless fmt_sc.skip(/z/) + str_sc.pos = sp_zone(str, str_sc.pos, str.bytesize, hash) + next + end + + # Handle E/O locale modifiers + if fmt_sc.check(SP_EO_CHECK) + if fmt_sc.check(SP_E_COMBO) || fmt_sc.check(SP_O_COMBO) + fmt_sc.skip(SP_EO_CHECK) # skip modifier, fall through to spec + else + # Invalid combo: match '%' literally in string + throw(:sp_fail) if str_sc.eos? || str_sc.peek(1) != '%' + str_sc.skip(/%/) + next + end + end + + spec_ch = fmt_sc.getch + spec = spec_ch&.ord + + case spec + when 65, 97 # 'A', 'a' + s3 = str_sc.scan(SP_ALPHA3) + throw(:sp_fail) unless s3 + key = compute_3key(s3) + entry = ABBR_DAY_3KEY[key] + throw(:sp_fail) unless entry + wday_i = entry[0] + remaining = entry[1] - 3 + if remaining > 0 + tail = str_sc.peek(remaining) + if tail.length == remaining && tail.downcase == DAY_LOWER_STRS[wday_i][3..] + str_sc.pos += remaining + end + end + hash[:wday] = wday_i + + when 66, 98, 104 # 'B', 'b', 'h' + s3 = str_sc.scan(SP_ALPHA3) + throw(:sp_fail) unless s3 + key = compute_3key(s3) + entry = ABBR_MONTH_3KEY[key] + throw(:sp_fail) unless entry + mon_i = entry[0] + remaining = entry[1] - 3 + if remaining > 0 + tail = str_sc.peek(remaining) + if tail.length == remaining && tail.downcase == MONTH_LOWER_STRS[mon_i][3..] + str_sc.pos += remaining + end + end + hash[:mon] = mon_i + + when 67 # 'C' + num_next = !fmt_sc.eos? && fmt_sc.check(SP_NUM_CHECK) + if str_sc.scan(SP_SIGN) + sign = str_sc.matched == '-' ? -1 : 1 + else + sign = 1 + end + s = str_sc.scan(num_next ? SP_DIGITS_2 : SP_DIGITS_MAX) + throw(:sp_fail) unless s + hash[:_cent] = sign * s.to_i + + when 99 # 'c' + str_sc.pos = sp_run(str, str_sc.pos, '%a %b %e %H:%M:%S %Y', hash) + + when 68 # 'D' + str_sc.pos = sp_run(str, str_sc.pos, '%m/%d/%y', hash) + + when 100, 101 # 'd', 'e' + s = str_sc.scan(SP_SPACE_OR_DIGIT) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n < 1 || n > 31 + hash[:mday] = n + + when 70 # 'F' + str_sc.pos = sp_run(str, str_sc.pos, '%Y-%m-%d', hash) + + when 71 # 'G' + if str_sc.scan(SP_SIGN) + sign = str_sc.matched == '-' ? -1 : 1 + else + sign = 1 + end + num_next = !fmt_sc.eos? && fmt_sc.check(SP_NUM_CHECK) + s = str_sc.scan(num_next ? SP_DIGITS_4 : SP_DIGITS_MAX) + throw(:sp_fail) unless s + hash[:cwyear] = sign * s.to_i + + when 103 # 'g' + s = str_sc.scan(SP_DIGITS_2) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n > 99 + hash[:cwyear] = n + hash[:_cent] ||= n >= 69 ? 19 : 20 + + when 72, 107 # 'H', 'k' + s = str_sc.scan(SP_SPACE_OR_DIGIT) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n > 24 + hash[:hour] = n + + when 73, 108 # 'I', 'l' + s = str_sc.scan(SP_SPACE_OR_DIGIT) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n < 1 || n > 12 + hash[:hour] = n + + when 106 # 'j' + s = str_sc.scan(SP_DIGITS_3) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n < 1 || n > 366 + hash[:yday] = n + + when 76 # 'L' + if str_sc.scan(SP_SIGN) + sign = str_sc.matched == '-' ? -1 : 1 + else + sign = 1 + end + osi = str_sc.pos + num_next = !fmt_sc.eos? && fmt_sc.check(SP_NUM_CHECK) + s = str_sc.scan(num_next ? SP_DIGITS_3 : SP_DIGITS_MAX) + throw(:sp_fail) unless s + n = s.to_i + n = -n if sign == -1 + hash[:sec_fraction] = Rational(n, 10**(str_sc.pos - osi)) + + when 77 # 'M' + s = str_sc.scan(SP_DIGITS_2) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n > 59 + hash[:min] = n + + when 109 # 'm' + s = str_sc.scan(SP_DIGITS_2) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n < 1 || n > 12 + hash[:mon] = n + + when 78 # 'N' + if str_sc.scan(SP_SIGN) + sign = str_sc.matched == '-' ? -1 : 1 + else + sign = 1 + end + osi = str_sc.pos + num_next = !fmt_sc.eos? && fmt_sc.check(SP_NUM_CHECK) + s = str_sc.scan(num_next ? SP_DIGITS_9 : SP_DIGITS_MAX) + throw(:sp_fail) unless s + n = s.to_i + n = -n if sign == -1 + hash[:sec_fraction] = Rational(n, 10**(str_sc.pos - osi)) + + when 110, 116 # 'n', 't' + str_sc.pos = sp_run(str, str_sc.pos, ' ', hash) + + when 80, 112 # 'P', 'p' + throw(:sp_fail) if str_sc.eos? + c0 = str_sc.peek(1) + if c0 == 'P' || c0 == 'p' + merid = 12 + elsif c0 == 'A' || c0 == 'a' + merid = 0 + else + throw(:sp_fail) + end + unless str_sc.scan(SP_AMPM_DOT) || str_sc.scan(SP_AMPM) + throw(:sp_fail) + end + hash[:_merid] = merid + + when 81 # 'Q' + sign = 1 + if str_sc.skip(/-/) + sign = -1 + end + s = str_sc.scan(SP_DIGITS_MAX) + throw(:sp_fail) unless s + n = s.to_i + n = -n if sign == -1 + hash[:seconds] = Rational(n, 1000) + + when 82 # 'R' + str_sc.pos = sp_run(str, str_sc.pos, '%H:%M', hash) + + when 114 # 'r' + str_sc.pos = sp_run(str, str_sc.pos, '%I:%M:%S %p', hash) + + when 83 # 'S' + s = str_sc.scan(SP_DIGITS_2) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n > 60 + hash[:sec] = n + + when 115 # 's' + sign = 1 + if str_sc.skip(/-/) + sign = -1 + end + s = str_sc.scan(SP_DIGITS_MAX) + throw(:sp_fail) unless s + n = s.to_i + n = -n if sign == -1 + hash[:seconds] = n + + when 84 # 'T' + str_sc.pos = sp_run(str, str_sc.pos, '%H:%M:%S', hash) + + when 85 # 'U' + s = str_sc.scan(SP_DIGITS_2) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n > 53 + hash[:wnum0] = n + + when 117 # 'u' + s = str_sc.scan(SP_DIGITS_1) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n < 1 || n > 7 + hash[:cwday] = n + + when 86 # 'V' + s = str_sc.scan(SP_DIGITS_2) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n < 1 || n > 53 + hash[:cweek] = n + + when 118 # 'v' + str_sc.pos = sp_run(str, str_sc.pos, '%e-%b-%Y', hash) + + when 87 # 'W' + s = str_sc.scan(SP_DIGITS_2) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n > 53 + hash[:wnum1] = n + + when 119 # 'w' + s = str_sc.scan(SP_DIGITS_1) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n > 6 + hash[:wday] = n + + when 88 # 'X' + str_sc.pos = sp_run(str, str_sc.pos, '%H:%M:%S', hash) + + when 120 # 'x' + str_sc.pos = sp_run(str, str_sc.pos, '%m/%d/%y', hash) + + when 89 # 'Y' + if str_sc.scan(SP_SIGN) + sign = str_sc.matched == '-' ? -1 : 1 + else + sign = 1 + end + num_next = !fmt_sc.eos? && fmt_sc.check(SP_NUM_CHECK) + s = str_sc.scan(num_next ? SP_DIGITS_4 : SP_DIGITS_MAX) + throw(:sp_fail) unless s + hash[:year] = sign * s.to_i + + when 121 # 'y' + s = str_sc.scan(SP_DIGITS_2) + throw(:sp_fail) unless s + n = s.to_i + throw(:sp_fail) if n > 99 + hash[:year] = n + hash[:_cent] ||= n >= 69 ? 19 : 20 + + when 90, 122 # 'Z', 'z' + str_sc.pos = sp_zone(str, str_sc.pos, str.bytesize, hash) + + when 37 # '%' + throw(:sp_fail) if str_sc.eos? || str_sc.peek(1) != '%' + str_sc.skip(/%/) + + when 43 # '+' + str_sc.pos = sp_run(str, str_sc.pos, '%a %b %e %H:%M:%S %Z %Y', hash) + + else + # Unknown spec: match '%' then spec literally + throw(:sp_fail) if str_sc.eos? || str_sc.peek(1) != '%' + str_sc.skip(/%/) + if spec_ch + throw(:sp_fail) if str_sc.eos? || str_sc.peek(1) != spec_ch + str_sc.getch + end + end + end + + str_sc.pos + end + + # Fast path for %Y-%m-%d / %F format. + # Uses match? + byteslice to avoid StringScanner allocation overhead. + STRPTIME_YMD_EXACT = /\A\d{4}-\d{2}-\d{2}\z/ + STRPTIME_YMD_PREFIX = /\A\d{4}-\d{2}-\d{2}/ + STRPTIME_YMD_GENERAL = /\A([+-]?\d+)-(\d{1,2})-(\d{1,2})(.*)\z/m + private_constant :STRPTIME_YMD_EXACT, :STRPTIME_YMD_PREFIX, :STRPTIME_YMD_GENERAL + + def internal_strptime_ymd(str) + slen = str.bytesize + + # Fast path for "YYYY-MM-DD" (exactly 10 chars) + if slen == 10 && STRPTIME_YMD_EXACT.match?(str) + year = str.byteslice(0, 4).to_i + mon = str.byteslice(5, 2).to_i + mday = str.byteslice(8, 2).to_i + return nil if mon < 1 || mon > 12 || mday < 1 || mday > 31 + return { year: year, mon: mon, mday: mday } + end + + # Medium path for "YYYY-MM-DD..." (10+ chars, standard 4-digit year with leftover) + if slen > 10 && STRPTIME_YMD_PREFIX.match?(str) + year = str.byteslice(0, 4).to_i + mon = str.byteslice(5, 2).to_i + mday = str.byteslice(8, 2).to_i + if mon >= 1 && mon <= 12 && mday >= 1 && mday <= 31 + hash = { year: year, mon: mon, mday: mday } + hash[:leftover] = str.byteslice(10..) + return hash + end + end + + # General path for signed years, short years, etc. + m = STRPTIME_YMD_GENERAL.match(str) + return nil unless m + year = m[1].to_i + mon = m[2].to_i + mday = m[3].to_i + return nil if mon < 1 || mon > 12 || mday < 1 || mday > 31 + hash = { year: year, mon: mon, mday: mday } + rest = m[4] + hash[:leftover] = rest unless rest.empty? + hash + end + + # Parse %Y-%m-%d and directly create Date object. + # Returns Date object on success, nil on failure. + # Uses match? + byteslice to avoid StringScanner allocation overhead. + STRPTIME_YMD_GENERAL_EXACT = /\A([+-]?\d+)-(\d{1,2})-(\d{1,2})\z/ + private_constant :STRPTIME_YMD_GENERAL_EXACT + + def internalinternal_strptime_ymd_to_date(str, sg) + slen = str.bytesize + + # Fast path for exactly "YYYY-MM-DD" (10 chars, positive 4-digit year) + if slen == 10 && STRPTIME_YMD_EXACT.match?(str) + year = str.byteslice(0, 4).to_i + mon = str.byteslice(5, 2).to_i + mday = str.byteslice(8, 2).to_i + if mon >= 1 && mon <= 12 && mday >= 1 && mday <= 31 + jd = internal_valid_civil?(year, mon, mday, sg) + return jd ? new_from_jd(jd, sg) : nil + end + return nil + end + + # General path for signed years, non-standard lengths + m = STRPTIME_YMD_GENERAL_EXACT.match(str) + return nil unless m + year = m[1].to_i + mon = m[2].to_i + mday = m[3].to_i + return nil if mon < 1 || mon > 12 || mday < 1 || mday > 31 + + jd = internal_valid_civil?(year, mon, mday, sg) + return nil unless jd + new_from_jd(jd, sg) + end + + # Fast path for "%a %b %d %Y" format. + # Uses single regex match to avoid StringScanner allocation. + STRPTIME_ABDY_PAT = /\A([A-Za-z]{3})([A-Za-z]*) +([A-Za-z]{3})([A-Za-z]*) +(\d{1,2}) +([+-]?\d+)/ + private_constant :STRPTIME_ABDY_PAT + + def internal_strptime_abdy(str) + m = STRPTIME_ABDY_PAT.match(str) + return nil unless m + + # Validate weekday via 3-byte key lookup + key = compute_3key(m[1]) + entry = ABBR_DAY_3KEY[key] + return nil unless entry + wday = entry[0] + day_rest = m[2] + unless day_rest.empty? + return nil unless day_rest.length == entry[1] - 3 && day_rest.downcase == DAY_LOWER_STRS[wday][3..] + end + + # Validate month via 3-byte key lookup + key = compute_3key(m[3]) + entry = ABBR_MONTH_3KEY[key] + return nil unless entry + mon = entry[0] + mon_rest = m[4] + unless mon_rest.empty? + return nil unless mon_rest.length == entry[1] - 3 && mon_rest.downcase == MONTH_LOWER_STRS[mon][3..] + end + + mday = m[5].to_i + return nil if mday < 1 || mday > 31 + year = m[6].to_i + + hash = { year: year, mon: mon, mday: mday, wday: wday } + post = m.post_match + hash[:leftover] = post unless post.empty? + hash + end + + # Parse "%a %b %d %Y" and directly create Date object. + # Uses single regex match to avoid StringScanner allocation. + def internalinternal_strptime_abdy_to_date(str, sg) + m = STRPTIME_ABDY_PAT.match(str) + return nil unless m + return nil unless m.post_match.empty? + + # Validate weekday via 3-byte key lookup + key = compute_3key(m[1]) + entry = ABBR_DAY_3KEY[key] + return nil unless entry + day_rest = m[2] + unless day_rest.empty? + return nil unless day_rest.length == entry[1] - 3 && day_rest.downcase == DAY_LOWER_STRS[entry[0]][3..] + end + + # Validate month via 3-byte key lookup + key = compute_3key(m[3]) + entry = ABBR_MONTH_3KEY[key] + return nil unless entry + mon = entry[0] + mon_rest = m[4] + unless mon_rest.empty? + return nil unless mon_rest.length == entry[1] - 3 && mon_rest.downcase == MONTH_LOWER_STRS[mon][3..] + end + + mday = m[5].to_i + return nil if mday < 1 || mday > 31 + year = m[6].to_i + + jd = internal_valid_civil?(year, mon, mday, sg) + return nil unless jd + new_from_jd(jd, sg) + end + + # Parse zone from string at position si; update hash[:zone] and hash[:offset]. + # Returns new si on success; throws :sp_fail on failure. + def sp_zone(str, si, slen, hash) + m = STRPTIME_ZONE_PAT.match(str[si..]) + throw(:sp_fail) unless m + zone_str = m[1] + hash[:zone] = zone_str + hash[:offset] = sp_zone_to_diff(zone_str) + si + m[0].length + end + + # Convert a zone string to seconds offset from UTC. + # Returns Integer (seconds) or Rational, or nil if unparseable. + # Ported from the original C date_zone_to_diff(). + def sp_zone_to_diff(zone_str) + # Fast path for common numeric zones + len = zone_str.length + c0 = zone_str[0] + if c0 == '+' || c0 == '-' + sc = StringScanner.new(zone_str) + if sc.scan(/([+-])(\d{2}):?(\d{2})\z/) + sign = sc[1] == '-' ? -1 : 1 + h = sc[2].to_i + m = sc[3].to_i + return nil if h > 23 || m > 59 + return sign * (h * 3600 + m * 60) + end + elsif len == 1 && (c0 == 'Z' || c0 == 'z') + return 0 + elsif len <= 3 + off = ZONE_TABLE[zone_str.downcase] + return off if off + end + + s = zone_str.dup + dst = false + + # Strip trailing " time" (optionally preceded by "standard" or "daylight") + strip_word = lambda do |str, len, word| + n = word.length + return nil unless len > n + return nil unless str[len - n - 1] =~ /[[:space:]]/ + return nil unless str[len - n, n].casecmp(word) == 0 + n += 1 + n += 1 while len > n && str[len - n - 1] =~ /[[:space:]]/ + n + end + + l = s.length + if (w = strip_word.call(s, l, 'time')) + l -= w + if (w2 = strip_word.call(s, l, 'standard')) + l -= w2 + elsif (w2 = strip_word.call(s, l, 'daylight')) + l -= w2 + dst = true + else + l += w # revert + end + elsif (w = strip_word.call(s, l, 'dst')) + l -= w + dst = true + end + + shrunk = s[0, l].gsub(/[[:space:]]+/, ' ').strip + if (offset = ZONE_TABLE[shrunk.downcase]) + return dst ? offset + 3600 : offset + end + + # Numeric parsing + t = s[0, l].strip + t = t[3..] if t =~ /\Agmt/i + t = t[$&.length..] if t =~ /\Autc?/i + return nil unless t && t.length > 0 && (t[0] == '+' || t[0] == '-') + + sign = t[0] == '-' ? -1 : 1 + t = t[1..] + + if (m = t.match(/\A(\d+):(\d+)(?::(\d+))?\z/)) + h = m[1].to_i + mn = m[2].to_i + sc = m[3] ? m[3].to_i : 0 + return nil if h > 23 || mn > 59 || sc > 59 + sign * (h * 3600 + mn * 60 + sc) + elsif (m = t.match(/\A(\d+)[,.](\d*)/)) + h = m[1].to_i + return nil if h > 23 + frac_s = m[2] + n = [frac_s.length, 7].min + digits = frac_s[0, n].to_i + digits += 1 if frac_s.length > n && frac_s[n].to_i >= 5 + sec = digits * 36 + os = if n == 0 + h * 3600 + elsif n == 1 + sec * 10 + h * 3600 + elsif n == 2 + sec + h * 3600 + else + denom = 10**(n - 2) + r = Rational(sec, denom) + h * 3600 + r.denominator == 1 ? r.numerator : r + end + sign == -1 ? -os : os + elsif t =~ /\A(\d+)\z/ + digits = $1 + dlen = digits.length + h = digits[0, 2 - dlen % 2].to_i + mn = dlen >= 3 ? digits[2 - dlen % 2, 2].to_i : 0 + sc = dlen >= 5 ? digits[4 - dlen % 2, 2].to_i : 0 + sign * (h * 3600 + mn * 60 + sc) + else + nil + end + end + + # Rewrite :seconds (from %s/%Q) into jd + time components. + # Offset is applied first (converts UTC epoch to local time). + def sp_rewrite_frags(hash) + seconds = hash.delete(:seconds) + return hash unless seconds + + offset = hash[:offset] || 0 + seconds = seconds + offset if offset != 0 + + d, fr = seconds.divmod(86400) + h, fr = fr.divmod(3600) + m, fr = fr.divmod(60) + s, fr = fr.divmod(1) + + hash[:jd] = 2440588 + d + hash[:hour] = h + hash[:min] = m + hash[:sec] = s + hash[:sec_fraction] = fr + hash + end + + # Complete partial date fragments by filling defaults from today's date. + # Mirrors rt_complete_frags() in C. + def sp_complete_frags(klass, hash) + # Fast path: detect :civil case (most common) without iterating all entries + k = nil + a = nil + if hash.key?(:year) || hash.key?(:mon) || hash.key?(:mday) + civil_n = 0 + civil_n += 1 if hash.key?(:year) + civil_n += 1 if hash.key?(:mon) + civil_n += 1 if hash.key?(:mday) + civil_n += 1 if hash.key?(:hour) + civil_n += 1 if hash.key?(:min) + civil_n += 1 if hash.key?(:sec) + # Check if any other pattern matches better + best_n = civil_n + skip_civil = false + if hash.key?(:jd) # jd entry has 1 element + skip_civil = true if 1 > best_n + end + if hash.key?(:yday) # ordinal [:year, :yday, :hour, :min, :sec] + ord_n = (hash.key?(:year) ? 1 : 0) + 1 + (hash.key?(:hour) ? 1 : 0) + (hash.key?(:min) ? 1 : 0) + (hash.key?(:sec) ? 1 : 0) + skip_civil = true if ord_n > best_n + end + if hash.key?(:cwyear) || hash.key?(:cweek) || hash.key?(:cwday) + com_n = (hash.key?(:cwyear) ? 1 : 0) + (hash.key?(:cweek) ? 1 : 0) + (hash.key?(:cwday) ? 1 : 0) + (hash.key?(:hour) ? 1 : 0) + (hash.key?(:min) ? 1 : 0) + (hash.key?(:sec) ? 1 : 0) + skip_civil = true if com_n > best_n + end + if hash.key?(:wnum0) + wn0_n = (hash.key?(:year) ? 1 : 0) + 1 + (hash.key?(:wday) ? 1 : 0) + (hash.key?(:hour) ? 1 : 0) + (hash.key?(:min) ? 1 : 0) + (hash.key?(:sec) ? 1 : 0) + skip_civil = true if wn0_n > best_n + end + if hash.key?(:wnum1) + wn1_n = (hash.key?(:year) ? 1 : 0) + 1 + (hash.key?(:wday) ? 1 : 0) + (hash.key?(:hour) ? 1 : 0) + (hash.key?(:min) ? 1 : 0) + (hash.key?(:sec) ? 1 : 0) + skip_civil = true if wn1_n > best_n + end + unless skip_civil + k = :civil + a = [:year, :mon, :mday, :hour, :min, :sec] + end + end + + unless k + best_k = nil + best_a = nil + best_n = 0 + COMPLETE_FRAGS_TAB.each do |ek, ea| + n = ea.count { |sym| hash.key?(sym) } + if n > best_n + best_k = ek + best_a = ea + best_n = n + end + end + k = best_k + a = best_a + end + + if k && best_n < a.length + today = nil + case k + when :ordinal + hash[:year] ||= (today ||= Date.today).year + hash[:yday] ||= 1 + when :civil + a.each do |sym| + break unless hash[sym].nil? + hash[sym] = (today ||= Date.today).__send__(sym) + end + hash[:mon] ||= 1 + hash[:mday] ||= 1 + when :commercial + a.each do |sym| + break unless hash[sym].nil? + hash[sym] = (today ||= Date.today).__send__(sym) + end + hash[:cweek] ||= 1 + hash[:cwday] ||= 1 + when :wday + today ||= Date.today + hash[:jd] = (today - today.wday + hash[:wday]).jd + when :wnum0 + a.each do |sym| + break unless hash[sym].nil? + hash[sym] = (today ||= Date.today).year + end + hash[:wnum0] ||= 0 + hash[:wday] ||= 0 + when :wnum1 + a.each do |sym| + break unless hash[sym].nil? + hash[sym] = (today ||= Date.today).year + end + hash[:wnum1] ||= 0 + hash[:wday] ||= 1 + end + end + + if k == :time && klass <= DateTime + hash[:jd] ||= Date.today.jd + end + + hash[:hour] ||= 0 + hash[:min] ||= 0 + hash[:sec] = if hash[:sec].nil? + 0 + elsif hash[:sec] > 59 + 59 + else + hash[:sec] + end + hash + end + + # Convert year/week/wday to Julian Day number. + # f=0: Sunday-based (%U), d=0=Sun..6=Sat + # f=1: Monday-based (%W), d=0=Mon..6=Sun (Mon-based) + # Ported from the original C c_weeknum_to_jd(): + # rjd2 = JD(Jan 1) + 6 (= JD of Jan 7) + # return (rjd2 - MOD((rjd2 - f + 1), 7) - 7) + 7*w + d + def sp_weeknum_to_jd(y, w, d, f, sg) + jd_jan7 = civil_to_jd(y, 1, 1, sg) + 6 + (jd_jan7 - (jd_jan7 - f + 1) % 7 - 7) + 7 * w + d + end + + # Find the Julian Day number from the fragment hash. + # Tries jd, ordinal, civil, commercial, wnum0, wnum1 in order. + # Returns jd integer or nil. + def sp_valid_date_frags_p(hash, sg) + return hash[:jd] if hash[:jd] + + if (yday = hash[:yday]) && (year = hash[:year]) + jd = internal_valid_ordinal?(year, yday, sg) + return jd if jd + end + + if (mday = hash[:mday]) && (mon = hash[:mon]) && (year = hash[:year]) + jd = internal_valid_civil?(year, mon, mday, sg) + return jd if jd + end + + # Commercial (ISO week): prefer cwday, else wday (treating 0 as 7) + wday = hash[:cwday] + if wday.nil? + wday = hash[:wday] + wday = 7 if !wday.nil? && wday == 0 + end + if wday && (week = hash[:cweek]) && (year = hash[:cwyear]) + jd = internal_valid_commercial?(year, week, wday, sg) + return jd if jd + end + + # wnum0 (Sunday-start): prefer wday (0=Sun), else cwday (converting 7→0) + wday = hash[:wday] + if wday.nil? + wday = hash[:cwday] + wday = 0 if !wday.nil? && wday == 7 + end + if wday && (week = hash[:wnum0]) && (year = hash[:year]) + jd = sp_weeknum_to_jd(year, week, wday, 0, sg) + return jd if jd + end + + # wnum1 (Monday-start): convert to Mon-based 0=Mon using (wday-1)%7 + # Uses original wday (0=Sun) or cwday (1=Mon..7=Sun) — no 7→0 conversion here + wday = hash[:wday] + wday = hash[:cwday] if wday.nil? + wday = (wday - 1) % 7 if wday + if wday && (week = hash[:wnum1]) && (year = hash[:year]) + jd = sp_weeknum_to_jd(year, week, wday, 1, sg) + return jd if jd + end + + nil + end + + # Create a Date object from parsed fragment hash. + def internal_new_by_frags(hash, sg) + raise Error, 'invalid date' if hash.nil? + hash = sp_rewrite_frags(hash) + hash = sp_complete_frags(Date, hash) + jd = sp_valid_date_frags_p(hash, sg) + raise Error, 'invalid date' if jd.nil? + new_from_jd(jd, sg) + end + end +end diff --git a/lib/date/time.rb b/lib/date/time.rb new file mode 100644 index 00000000..cc463b5c --- /dev/null +++ b/lib/date/time.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class Time + # call-seq: + # t.to_time -> time + # + # Returns self. + def to_time + self + end unless method_defined?(:to_time) + + # call-seq: + # t.to_date -> date + # + # Returns a Date object which denotes self. + def to_date + jd = Date.__send__(:gregorian_to_jd, year, mon, mday) + Date.__send__(:new_from_jd, jd, Date::ITALY) + end unless method_defined?(:to_date) + + # call-seq: + # t.to_datetime -> datetime + # + # Returns a DateTime object which denotes self. + def to_datetime + jd = Date.__send__(:gregorian_to_jd, year, mon, mday) + dt = DateTime.allocate + dt.__send__(:_init_datetime, jd, hour, min, sec, subsec, utc_offset, Date::ITALY) + dt + end unless method_defined?(:to_datetime) +end diff --git a/lib/date/version.rb b/lib/date/version.rb new file mode 100644 index 00000000..063fe245 --- /dev/null +++ b/lib/date/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Date + VERSION = "3.5.1" # :nodoc: +end diff --git a/lib/date/zonetab.rb b/lib/date/zonetab.rb new file mode 100644 index 00000000..b7e22c37 --- /dev/null +++ b/lib/date/zonetab.rb @@ -0,0 +1,325 @@ +# frozen_string_literal: true + +# Timezone name => UTC offset (seconds) mapping table. +# Auto-generated from ext/date/zonetab.list by ext/date/generate-zonetab-rb. +# Do not edit manually. +class Date + ZONE_TABLE = { + "a" => 3600, + "acdt" => 37800, + "acst" => 34200, + "act" => -18000, + "acwst" => 31500, + "adt" => -10800, + "aedt" => 39600, + "aest" => 36000, + "afghanistan" => 16200, + "aft" => 16200, + "ahst" => -36000, + "akdt" => -28800, + "akst" => -32400, + "alaskan" => -32400, + "almt" => 21600, + "anast" => 43200, + "anat" => 43200, + "aoe" => -43200, + "aqtt" => 18000, + "arab" => 10800, + "arabian" => 14400, + "arabic" => 10800, + "art" => -10800, + "ast" => -14400, + "at" => -7200, + "atlantic" => -14400, + "aus central" => 34200, + "aus eastern" => 36000, + "awdt" => 32400, + "awst" => 28800, + "azores" => -3600, + "azost" => 0, + "azot" => -3600, + "azst" => 18000, + "azt" => 14400, + "b" => 7200, + "bnt" => 28800, + "bot" => -14400, + "brst" => -7200, + "brt" => -10800, + "bst" => 3600, + "bt" => 10800, + "btt" => 21600, + "c" => 10800, + "canada central" => -21600, + "cape verde" => -3600, + "cast" => 28800, + "cat" => 7200, + "caucasus" => 14400, + "cct" => 23400, + "cdt" => -18000, + "cen. australia" => 34200, + "central" => -21600, + "central america" => -21600, + "central asia" => 21600, + "central europe" => 3600, + "central european" => 3600, + "central pacific" => 39600, + "cest" => 7200, + "cet" => 3600, + "chadt" => 49500, + "chast" => 45900, + "china" => 28800, + "chost" => 32400, + "chot" => 28800, + "chst" => 36000, + "chut" => 36000, + "cidst" => -14400, + "cist" => -18000, + "ckt" => -36000, + "clst" => -10800, + "clt" => -14400, + "cot" => -18000, + "cst" => -21600, + "cvt" => -3600, + "cxt" => 25200, + "d" => 14400, + "dateline" => -43200, + "davt" => 25200, + "ddut" => 36000, + "e" => 18000, + "e. africa" => 10800, + "e. australia" => 36000, + "e. europe" => 7200, + "e. south america" => -10800, + "eadt" => 39600, + "easst" => -18000, + "east" => -21600, + "eastern" => -18000, + "eat" => 10800, + "ect" => -18000, + "edt" => -14400, + "eest" => 10800, + "eet" => 7200, + "egst" => 0, + "egt" => -3600, + "egypt" => 7200, + "ekaterinburg" => 18000, + "est" => -18000, + "f" => 21600, + "fet" => 10800, + "fiji" => 43200, + "fjst" => 46800, + "fjt" => 43200, + "fkst" => -10800, + "fkt" => -14400, + "fle" => 7200, + "fnt" => -7200, + "fst" => 7200, + "fwt" => 3600, + "g" => 25200, + "galt" => -21600, + "gamt" => -32400, + "get" => 14400, + "gft" => -10800, + "gilt" => 43200, + "gmt" => 0, + "greenland" => -10800, + "greenwich" => 0, + "gst" => 36000, + "gtb" => 7200, + "gyt" => -14400, + "h" => 28800, + "hadt" => -32400, + "hast" => -36000, + "hawaiian" => -36000, + "hdt" => -32400, + "hkt" => 28800, + "hovst" => 28800, + "hovt" => 25200, + "hst" => -36000, + "i" => 32400, + "ict" => 25200, + "idle" => 43200, + "idlw" => -43200, + "idt" => 10800, + "india" => 19800, + "iot" => 21600, + "iran" => 12600, + "irdt" => 16200, + "irkst" => 32400, + "irkt" => 28800, + "irst" => 12600, + "ist" => 19800, + "jerusalem" => 7200, + "jst" => 32400, + "k" => 36000, + "kgt" => 21600, + "korea" => 32400, + "kost" => 39600, + "krast" => 28800, + "krat" => 25200, + "kst" => 32400, + "kuyt" => 14400, + "l" => 39600, + "lhdt" => 39600, + "lhst" => 37800, + "lint" => 50400, + "m" => 43200, + "magst" => 43200, + "magt" => 39600, + "malay peninsula" => 28800, + "mart" => -30600, + "mawt" => 18000, + "mdt" => -21600, + "mest" => 7200, + "mesz" => 7200, + "met" => 3600, + "mewt" => 3600, + "mexico" => -21600, + "mez" => 3600, + "mht" => 43200, + "mid-atlantic" => -7200, + "mmt" => 23400, + "mountain" => -25200, + "msd" => 14400, + "msk" => 10800, + "mst" => -25200, + "mut" => 14400, + "mvt" => 18000, + "myanmar" => 23400, + "myt" => 28800, + "n" => -3600, + "n. central asia" => 21600, + "nct" => 39600, + "ndt" => -5400, + "nepal" => 20700, + "new zealand" => 43200, + "newfoundland" => -12600, + "nfdt" => 43200, + "nft" => 39600, + "north asia" => 25200, + "north asia east" => 28800, + "novst" => 25200, + "novt" => 25200, + "npt" => 20700, + "nrt" => 43200, + "nst" => -9000, + "nt" => -39600, + "nut" => -39600, + "nzdt" => 46800, + "nzst" => 43200, + "nzt" => 43200, + "o" => -7200, + "omsst" => 25200, + "omst" => 21600, + "orat" => 18000, + "p" => -10800, + "pacific" => -28800, + "pacific sa" => -14400, + "pdt" => -25200, + "pet" => -18000, + "petst" => 43200, + "pett" => 43200, + "pgt" => 36000, + "phot" => 46800, + "pht" => 28800, + "pkt" => 18000, + "pmdt" => -7200, + "pmst" => -10800, + "pont" => 39600, + "pst" => -28800, + "pwt" => 32400, + "pyst" => -10800, + "q" => -14400, + "qyzt" => 21600, + "r" => -18000, + "ret" => 14400, + "romance" => 3600, + "rott" => -10800, + "russian" => 10800, + "s" => -21600, + "sa eastern" => -10800, + "sa pacific" => -18000, + "sa western" => -14400, + "sakt" => 39600, + "samoa" => -39600, + "samt" => 14400, + "sast" => 7200, + "sbt" => 39600, + "sct" => 14400, + "se asia" => 25200, + "sgt" => 28800, + "south africa" => 7200, + "sret" => 39600, + "sri lanka" => 21600, + "srt" => -10800, + "sst" => -39600, + "swt" => 3600, + "syot" => 10800, + "t" => -25200, + "taht" => -36000, + "taipei" => 28800, + "tasmania" => 36000, + "tft" => 18000, + "tjt" => 18000, + "tkt" => 46800, + "tlt" => 32400, + "tmt" => 18000, + "tokyo" => 32400, + "tonga" => 46800, + "tost" => 50400, + "tot" => 46800, + "trt" => 10800, + "tvt" => 43200, + "u" => -28800, + "ulast" => 32400, + "ulat" => 28800, + "us eastern" => -18000, + "us mountain" => -25200, + "ut" => 0, + "utc" => 0, + "uyst" => -7200, + "uyt" => -10800, + "uzt" => 18000, + "v" => -32400, + "vet" => -14400, + "vladivostok" => 36000, + "vlast" => 39600, + "vlat" => 36000, + "vost" => 21600, + "vut" => 39600, + "w" => -36000, + "w. australia" => 28800, + "w. central africa" => 3600, + "w. europe" => 3600, + "wadt" => 28800, + "wakt" => 43200, + "warst" => -10800, + "wast" => 7200, + "wat" => 3600, + "west" => 3600, + "west asia" => 18000, + "west pacific" => 36000, + "wet" => 0, + "wft" => 43200, + "wgst" => -3600, + "wgt" => -7200, + "wib" => 25200, + "wit" => 32400, + "wita" => 28800, + "wt" => 0, + "x" => -39600, + "y" => -43200, + "yakst" => 36000, + "yakt" => 32400, + "yakutsk" => 32400, + "yapt" => 36000, + "ydt" => -28800, + "yekst" => 21600, + "yekt" => 18000, + "yst" => -32400, + "z" => 0, + "zp4" => 14400, + "zp5" => 18000, + "zp6" => 21600, + }.freeze +end