ruby-sdn 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,245 @@
1
+ /* $selId: julian.c,v 2.0 1995/10/24 01:13:06 lees Exp $
2
+ * Copyright 1993-1995, Scott E. Lee, all rights reserved.
3
+ * Permission granted to use, copy, modify, distribute and sell so long as
4
+ * the above copyright and this permission statement are retained in all
5
+ * copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
6
+ */
7
+
8
+ /**************************************************************************
9
+ *
10
+ * These are the externally visible components of this file:
11
+ *
12
+ * void
13
+ * SdnToJulian(
14
+ * long int sdn,
15
+ * int *pYear,
16
+ * int *pMonth,
17
+ * int *pDay);
18
+ *
19
+ * Convert a SDN to a Julian calendar date. If the input SDN is less than
20
+ * 1, the three output values will all be set to zero, otherwise *pYear
21
+ * will be >= -4713 and != 0; *pMonth will be in the range 1 to 12
22
+ * inclusive; *pDay will be in the range 1 to 31 inclusive.
23
+ *
24
+ * long int
25
+ * JulianToSdn(
26
+ * int inputYear,
27
+ * int inputMonth,
28
+ * int inputDay);
29
+ *
30
+ * Convert a Julian calendar date to a SDN. Zero is returned when the
31
+ * input date is detected as invalid or out of the supported range. The
32
+ * return value will be > 0 for all valid, supported dates, but there are
33
+ * some invalid dates that will return a positive value. To verify that a
34
+ * date is valid, convert it to SDN and then back and compare with the
35
+ * original.
36
+ *
37
+ * VALID RANGE
38
+ *
39
+ * 4713 B.C. to at least 10000 A.D.
40
+ *
41
+ * Although this software can handle dates all the way back to 4713
42
+ * B.C., such use may not be meaningful. The calendar was created in
43
+ * 46 B.C., but the details did not stabilize until at least 8 A.D.,
44
+ * and perhaps as late at the 4th century. Also, the beginning of a
45
+ * year varied from one culture to another - not all accepted January
46
+ * as the first month.
47
+ *
48
+ * CALENDAR OVERVIEW
49
+ *
50
+ * Julias Ceasar created the calendar in 46 B.C. as a modified form of
51
+ * the old Roman republican calendar which was based on lunar cycles.
52
+ * The new Julian calendar set fixed lengths for the months, abandoning
53
+ * the lunar cycle. It also specified that there would be exactly 12
54
+ * months per year and 365.25 days per year with every 4th year being a
55
+ * leap year.
56
+ *
57
+ * Note that the current accepted value for the tropical year is
58
+ * 365.242199 days, not 365.25. This lead to an 11 day shift in the
59
+ * calendar with respect to the seasons by the 16th century when the
60
+ * Gregorian calendar was created to replace the Julian calendar.
61
+ *
62
+ * The difference between the Julian and today's Gregorian calendar is
63
+ * that the Gregorian does not make centennial years leap years unless
64
+ * they are a multiple of 400, which leads to a year of 365.2425 days.
65
+ * In other words, in the Gregorian calendar, 1700, 1800 and 1900 are
66
+ * not leap years, but 2000 is. All centennial years are leap years in
67
+ * the Julian calendar.
68
+ *
69
+ * The details are unknown, but the lengths of the months were adjusted
70
+ * until they finally stablized in 8 A.D. with their current lengths:
71
+ *
72
+ * January 31
73
+ * February 28/29
74
+ * March 31
75
+ * April 30
76
+ * May 31
77
+ * June 30
78
+ * Quintilis/July 31
79
+ * Sextilis/August 31
80
+ * September 30
81
+ * October 31
82
+ * November 30
83
+ * December 31
84
+ *
85
+ * In the early days of the calendar, the days of the month were not
86
+ * numbered as we do today. The numbers ran backwards (decreasing) and
87
+ * were counted from the Ides (15th of the month - which in the old
88
+ * Roman republican lunar calendar would have been the full moon) or
89
+ * from the Nonae (9th day before the Ides) or from the beginning of
90
+ * the next month.
91
+ *
92
+ * In the early years, the beginning of the year varied, sometimes
93
+ * based on the ascension of rulers. It was not always the first of
94
+ * January.
95
+ *
96
+ * Also, today's epoch, 1 A.D. or the birth of Jesus Christ, did not
97
+ * come into use until several centuries later when Christianity became
98
+ * a dominant religion.
99
+ *
100
+ * ALGORITHMS
101
+ *
102
+ * The calculations are based on two different cycles: a 4 year cycle
103
+ * of leap years and a 5 month cycle of month lengths.
104
+ *
105
+ * The 5 month cycle is used to account for the varying lengths of
106
+ * months. You will notice that the lengths alternate between 30 and
107
+ * 31 days, except for three anomalies: both July and August have 31
108
+ * days, both December and January have 31, and February is less than
109
+ * 30. Starting with March, the lengths are in a cycle of 5 months
110
+ * (31, 30, 31, 30, 31):
111
+ *
112
+ * Mar 31 days \
113
+ * Apr 30 days |
114
+ * May 31 days > First cycle
115
+ * Jun 30 days |
116
+ * Jul 31 days /
117
+ *
118
+ * Aug 31 days \
119
+ * Sep 30 days |
120
+ * Oct 31 days > Second cycle
121
+ * Nov 30 days |
122
+ * Dec 31 days /
123
+ *
124
+ * Jan 31 days \
125
+ * Feb 28/9 days |
126
+ * > Third cycle (incomplete)
127
+ *
128
+ * For this reason the calculations (internally) assume that the year
129
+ * starts with March 1.
130
+ *
131
+ * TESTING
132
+ *
133
+ * This algorithm has been tested from the year 4713 B.C. to 10000 A.D.
134
+ * The source code of the verification program is included in this
135
+ * package.
136
+ *
137
+ * REFERENCES
138
+ *
139
+ * Conversions Between Calendar Date and Julian Day Number by Robert J.
140
+ * Tantzen, Communications of the Association for Computing Machinery
141
+ * August 1963. (Also published in Collected Algorithms from CACM,
142
+ * algorithm number 199). [Note: the published algorithm is for the
143
+ * Gregorian calendar, but was adjusted to use the Julian calendar's
144
+ * simpler leap year rule.]
145
+ *
146
+ **************************************************************************/
147
+
148
+ #include "sdncal.h"
149
+
150
+ #define SDN_OFFSET 32083
151
+ #define DAYS_PER_5_MONTHS 153
152
+ #define DAYS_PER_4_YEARS 1461
153
+
154
+ void
155
+ SdnToJulian(
156
+ long int sdn,
157
+ int *pYear,
158
+ int *pMonth,
159
+ int *pDay)
160
+ {
161
+ int year;
162
+ int month;
163
+ int day;
164
+ long int temp;
165
+ int dayOfYear;
166
+
167
+ if (sdn <= 0) {
168
+ *pYear = 0;
169
+ *pMonth = 0;
170
+ *pDay = 0;
171
+ return;
172
+ }
173
+
174
+ temp = (sdn + SDN_OFFSET) * 4 - 1;
175
+
176
+ /* Calculate the year and day of year (1 <= dayOfYear <= 366). */
177
+ year = temp / DAYS_PER_4_YEARS;
178
+ dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
179
+
180
+ /* Calculate the month and day of month. */
181
+ temp = dayOfYear * 5 - 3;
182
+ month = temp / DAYS_PER_5_MONTHS;
183
+ day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
184
+
185
+ /* Convert to the normal beginning of the year. */
186
+ if (month < 10) {
187
+ month += 3;
188
+ } else {
189
+ year += 1;
190
+ month -= 9;
191
+ }
192
+
193
+ /* Adjust to the B.C./A.D. type numbering. */
194
+ year -= 4800;
195
+ if (year <= 0) year--;
196
+
197
+ *pYear = year;
198
+ *pMonth = month;
199
+ *pDay = day;
200
+ }
201
+
202
+ long int
203
+ JulianToSdn(
204
+ int inputYear,
205
+ int inputMonth,
206
+ int inputDay)
207
+ {
208
+ int year;
209
+ int month;
210
+
211
+ /* check for invalid dates */
212
+ if (inputYear == 0 || inputYear < -4713 ||
213
+ inputMonth <= 0 || inputMonth > 12 ||
214
+ inputDay <= 0 || inputDay > 31)
215
+ {
216
+ return(0);
217
+ }
218
+
219
+ /* check for dates before SDN 1 (Jan 2, 4713 B.C.) */
220
+ if (inputYear == -4713) {
221
+ if (inputMonth == 1 && inputDay == 1) {
222
+ return(0);
223
+ }
224
+ }
225
+
226
+ /* Make year always a positive number. */
227
+ if (inputYear < 0) {
228
+ year = inputYear + 4801;
229
+ } else {
230
+ year = inputYear + 4800;
231
+ }
232
+
233
+ /* Adjust the start of the year. */
234
+ if (inputMonth > 2) {
235
+ month = inputMonth - 3;
236
+ } else {
237
+ month = inputMonth + 9;
238
+ year--;
239
+ }
240
+
241
+ return( (year * DAYS_PER_4_YEARS) / 4
242
+ + (month * DAYS_PER_5_MONTHS + 2) / 5
243
+ + inputDay
244
+ - SDN_OFFSET );
245
+ }
@@ -0,0 +1,90 @@
1
+ #ifndef _INCLUDED_SDNCAL_H
2
+ #define _INCLUDED_SDNCAL_H
3
+
4
+ /* $selId: sdncal.h,v 2.0 1995/10/24 01:13:06 lees Exp $
5
+ * Copyright 1993-1995, Scott E. Lee, all rights reserved.
6
+ * Permission granted to use, copy, modify, distribute and sell so long as
7
+ * the above copyright and this permission statement are retained in all
8
+ * copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
9
+ */
10
+
11
+ /**************************************************************************
12
+ *
13
+ * This package defines a set of routines that convert calendar dates to
14
+ * and from a serial day number (SDN). The SDN is a serial numbering of
15
+ * days where SDN 1 is November 25, 4714 BC in the Gregorian calendar and
16
+ * SDN 2447893 is January 1, 1990. This system of day numbering is
17
+ * sometimes referred to as Julian days, but to avoid confusion with the
18
+ * Julian calendar, it is referred to as serial day numbers here. The term
19
+ * Julian days is also used to mean the number of days since the beginning
20
+ * of the current year.
21
+ *
22
+ * The SDN can be used as an intermediate step in converting from one
23
+ * calendar system to another (such as Gregorian to Jewish). It can also
24
+ * be used for date computations such as easily comparing two dates,
25
+ * determining the day of the week, finding the date of yesterday or
26
+ * calculating the number of days between two dates.
27
+ *
28
+ * When using this software on 16 bit systems, be careful to store SDNs in
29
+ * a long int, because it will not fit in the 16 bits that some systems
30
+ * allocate to an int.
31
+ *
32
+ * For each calendar, there are two routines provided. One converts dates
33
+ * in that calendar to SDN and the other converts SDN to calendar dates.
34
+ * The routines are named SdnTo<CALENDAR>() and <CALENDAR>ToSdn(), where
35
+ * <CALENDAR> is the name of the calendar system.
36
+ *
37
+ * SDN values less than one are not supported. If a conversion routine
38
+ * returns an SDN of zero, this means that the date given is either invalid
39
+ * or is outside the supported range for that calendar.
40
+ *
41
+ * At least some validity checks are performed on input dates. For
42
+ * example, a negative month number will result in the return of zero for
43
+ * the SDN. A returned SDN greater than one does not necessarily mean that
44
+ * the input date was valid. To determine if the date is valid, convert it
45
+ * to SDN, and if the SDN is greater than zero, convert it back to a date
46
+ * and compare to the original. For example:
47
+ *
48
+ * int y1, m1, d1;
49
+ * int y2, m2, d2;
50
+ * long int sdn;
51
+ * ...
52
+ * sdn = GregorianToSdn(y1, m1, d1);
53
+ * if (sdn > 0) {
54
+ * SdnToGregorian(sdn, &y2, &m2, &d2);
55
+ * if (y1 == y2 && m1 == m2 && d1 == d2) {
56
+ * ... date is valid ...
57
+ * }
58
+ * }
59
+ *
60
+ **************************************************************************/
61
+
62
+ /* Gregorian calendar conversions. */
63
+ void SdnToGregorian(long int sdn, int *pYear, int *pMonth, int *pDay);
64
+ long int GregorianToSdn(int year, int month, int day);
65
+ extern const char *MonthNameShort[13];
66
+ extern const char *MonthNameLong[13];
67
+
68
+ /* Julian calendar conversions. */
69
+ void SdnToJulian(long int sdn, int *pYear, int *pMonth, int *pDay);
70
+ long int JulianToSdn(int year, int month, int day);
71
+
72
+ /* Jewish calendar conversions. */
73
+ void SdnToJewish(long int sdn, int *pYear, int *pMonth, int *pDay);
74
+ long int JewishToSdn(int year, int month, int day);
75
+ extern const char *JewishMonthName[14];
76
+
77
+ /* French republic calendar conversions. */
78
+ void SdnToFrench(long int sdn, int *pYear, int *pMonth, int *pDay);
79
+ long int FrenchToSdn(int inputYear, int inputMonth, int inputDay);
80
+ extern const char *FrenchMonthName[14];
81
+
82
+ /* Islamic calendar conversions. */
83
+ /* Not implemented yet. */
84
+
85
+ /* Day of week conversion. 0=Sunday, 6=Saturday */
86
+ int DayOfWeek(long int sdn);
87
+ extern const char *DayNameShort[7];
88
+ extern const char *DayNameLong[7];
89
+
90
+ #endif /* _INCLUDED_SDNCAL_H */
@@ -0,0 +1,212 @@
1
+ /*
2
+ sdnmodule.c: Copyright (C) 1997,1999,2014 Tadayoshi Funaba All rights reserved
3
+ $Id: sdnmodule.c,v 1.7 2014-04-05 16:47:22+09 tadf Exp $
4
+ */
5
+
6
+ #include "ruby.h"
7
+ #ifdef HAVE_RUBY_ENCODING_H
8
+ #include "ruby/encoding.h"
9
+ #endif
10
+ #include "sdncal.h"
11
+
12
+ #define CHECK_RANGE(v, m) \
13
+ { \
14
+ if ((v) < (m)) \
15
+ rb_raise(rb_eArgError, "out of range"); \
16
+ }
17
+
18
+ #define CHECK_DOMAIN(v, m) \
19
+ { \
20
+ if ((v) < (m)) \
21
+ rb_raise(rb_eArgError, "out of domain");\
22
+ }
23
+
24
+ #define CHECK_DOMAIN2(v, m, n) \
25
+ { \
26
+ if ((v) < (m) || (v) > (n)) \
27
+ rb_raise(rb_eArgError, "out of domain");\
28
+ }
29
+
30
+ static VALUE
31
+ sdn_SdnToGregorian(VALUE obj, VALUE sdn)
32
+ {
33
+ int y, m, md;
34
+ long isdn;
35
+
36
+ isdn = NUM2LONG(sdn); CHECK_DOMAIN(isdn, 1);
37
+ SdnToGregorian(isdn, &y, &m, &md);
38
+ return rb_ary_new3(3, INT2NUM(y), INT2NUM(m), INT2NUM(md));
39
+ }
40
+
41
+ static VALUE
42
+ sdn_GregorianToSdn(VALUE obj, VALUE y, VALUE m, VALUE md)
43
+ {
44
+ int iy, im, imd, iy2, im2, imd2;
45
+ long sdn;
46
+
47
+ iy = NUM2INT(y); CHECK_DOMAIN(iy, -4714);
48
+ im = NUM2INT(m); CHECK_DOMAIN2(im, 1, 12);
49
+ imd = NUM2INT(md); CHECK_DOMAIN2(imd, 1, 31);
50
+ sdn = GregorianToSdn(iy, im, imd);
51
+ CHECK_RANGE(sdn, 1);
52
+ SdnToGregorian(sdn, &iy2, &im2, &imd2);
53
+ if (iy != iy2 || im != im2 || imd != imd2)
54
+ rb_raise(rb_eArgError, "invalid date");
55
+ return INT2NUM(sdn);
56
+ }
57
+
58
+ static VALUE
59
+ sdn_SdnToJulian(VALUE obj, VALUE sdn)
60
+ {
61
+ int y, m, md;
62
+ long isdn;
63
+
64
+ isdn = NUM2LONG(sdn); CHECK_DOMAIN(isdn, 1);
65
+ SdnToJulian(isdn, &y, &m, &md);
66
+ return rb_ary_new3(3, INT2NUM(y), INT2NUM(m), INT2NUM(md));
67
+ }
68
+
69
+ static VALUE
70
+ sdn_JulianToSdn(VALUE obj, VALUE y, VALUE m, VALUE md)
71
+ {
72
+ int iy, im, imd, iy2, im2, imd2;
73
+ long sdn;
74
+
75
+ iy = NUM2INT(y); CHECK_DOMAIN(iy, -4713);
76
+ im = NUM2INT(m); CHECK_DOMAIN2(im, 1, 12);
77
+ imd = NUM2INT(md); CHECK_DOMAIN2(imd, 1, 31);
78
+ sdn = JulianToSdn(iy, im, imd);
79
+ CHECK_RANGE(sdn, 1);
80
+ SdnToJulian(sdn, &iy2, &im2, &imd2);
81
+ if (iy != iy2 || im != im2 || imd != imd2)
82
+ rb_raise(rb_eArgError, "invalid date");
83
+ return INT2NUM(sdn);
84
+ }
85
+
86
+ static VALUE
87
+ sdn_SdnToJewish(VALUE obj, VALUE sdn)
88
+ {
89
+ int y, m, md;
90
+ long isdn;
91
+
92
+ isdn = NUM2LONG(sdn); CHECK_DOMAIN(isdn, 347998);
93
+ SdnToJewish(NUM2INT(sdn), &y, &m, &md);
94
+ return rb_ary_new3(3, INT2NUM(y), INT2NUM(m), INT2NUM(md));
95
+ }
96
+
97
+ static VALUE
98
+ sdn_JewishToSdn(VALUE obj, VALUE y, VALUE m, VALUE md)
99
+ {
100
+ int iy, im, imd, iy2, im2, imd2;
101
+ long sdn;
102
+
103
+ iy = NUM2INT(y); CHECK_DOMAIN(iy, 1);
104
+ im = NUM2INT(m); CHECK_DOMAIN2(im, 1, 13);
105
+ imd = NUM2INT(md); CHECK_DOMAIN2(imd, 1, 30);
106
+ sdn = JewishToSdn(iy, im, imd);
107
+ CHECK_RANGE(sdn, 1); /* 347998 */
108
+ SdnToJewish(sdn, &iy2, &im2, &imd2);
109
+ if (iy != iy2 || im != im2 || imd != imd2)
110
+ rb_raise(rb_eArgError, "invalid date");
111
+ return INT2NUM(sdn);
112
+ }
113
+
114
+ static VALUE
115
+ sdn_SdnToFrench(VALUE obj, VALUE sdn)
116
+ {
117
+ int y, m, md;
118
+ long isdn;
119
+
120
+ isdn = NUM2LONG(sdn); CHECK_DOMAIN2(isdn, 2375840, 2380952);
121
+ SdnToFrench(isdn, &y, &m, &md);
122
+ return rb_ary_new3(3, INT2NUM(y), INT2NUM(m), INT2NUM(md));
123
+ }
124
+
125
+ static VALUE
126
+ sdn_FrenchToSdn(VALUE obj, VALUE y, VALUE m, VALUE md)
127
+ {
128
+ int iy, im, imd, iy2, im2, imd2;
129
+ long sdn;
130
+
131
+ iy = NUM2INT(y); CHECK_DOMAIN(iy, 1);
132
+ im = NUM2INT(m); CHECK_DOMAIN2(im, 1, 13);
133
+ imd = NUM2INT(md); CHECK_DOMAIN2(imd, 1, 30);
134
+ sdn = FrenchToSdn(iy, im, imd);
135
+ CHECK_RANGE(sdn, 1);
136
+ SdnToFrench(sdn, &iy2, &im2, &imd2);
137
+ if (iy != iy2 || im != im2 || imd != imd2)
138
+ rb_raise(rb_eArgError, "invalid date");
139
+ return INT2NUM(sdn);
140
+ }
141
+
142
+ static VALUE
143
+ sdn_DayOfWeek(VALUE obj, VALUE sdn)
144
+ {
145
+ long isdn;
146
+ int wd;
147
+
148
+ isdn = NUM2LONG(sdn);
149
+ wd = DayOfWeek(isdn);
150
+ return INT2NUM(wd);
151
+ }
152
+
153
+ static VALUE
154
+ mk_ary_of_str(long len, const char *a[])
155
+ {
156
+ VALUE o;
157
+ long i;
158
+
159
+ o = rb_ary_new2(len);
160
+ for (i = 0; i < len; i++) {
161
+ VALUE e;
162
+
163
+ if (!a[i])
164
+ e = Qnil;
165
+ else {
166
+ e = rb_str_new2(a[i]);
167
+ #ifdef HAVE_RUBY_ENCODING_H
168
+ rb_enc_associate(e, rb_utf8_encoding());
169
+ ENC_CODERANGE_CLEAR(e);
170
+ #endif
171
+ rb_obj_freeze(e);
172
+ }
173
+ rb_ary_push(o, e);
174
+ }
175
+ rb_obj_freeze(o);
176
+ return o;
177
+ }
178
+
179
+ void
180
+ Init_sdn(void)
181
+ {
182
+ VALUE mod;
183
+
184
+ mod = rb_define_module("Sdn");
185
+
186
+ rb_define_module_function(mod, "DayOfWeek", sdn_DayOfWeek, 1);
187
+ rb_define_module_function(mod, "SdnToGregorian", sdn_SdnToGregorian, 1);
188
+ rb_define_module_function(mod, "GregorianToSdn", sdn_GregorianToSdn, 3);
189
+ rb_define_module_function(mod, "SdnToJulian", sdn_SdnToJulian, 1);
190
+ rb_define_module_function(mod, "JulianToSdn", sdn_JulianToSdn, 3);
191
+ rb_define_module_function(mod, "SdnToJewish", sdn_SdnToJewish, 1);
192
+ rb_define_module_function(mod, "JewishToSdn", sdn_JewishToSdn, 3);
193
+ rb_define_module_function(mod, "SdnToFrench", sdn_SdnToFrench, 1);
194
+ rb_define_module_function(mod, "FrenchToSdn", sdn_FrenchToSdn, 3);
195
+
196
+ rb_define_const
197
+ (mod, "VERSION",
198
+ rb_obj_freeze(rb_str_new2("$Revision: 1.7 $")));
199
+
200
+ #define const_array(a) mk_ary_of_str(sizeof a / sizeof a[0], a)
201
+ #define def_const(n, o) rb_define_const(mod, n, o)
202
+ #define def_const_array(n, a) def_const(n, const_array(a))
203
+
204
+ def_const_array("DayNameShort", DayNameShort);
205
+ def_const_array("DayNameLong", DayNameLong);
206
+
207
+ def_const_array("MonthNameShort", MonthNameShort);
208
+ def_const_array("MonthNameLong", MonthNameLong);
209
+
210
+ def_const_array("JewishMonthName", JewishMonthName);
211
+ def_const_array("FrenchMonthName", FrenchMonthName);
212
+ }