parsi-date 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile.lock +1 -1
- data/lib/parsi-date.rb +563 -153
- data/lib/parsi-datetime.rb +160 -59
- data/lib/version.rb +1 -1
- data/parsi-date.gemspec +1 -1
- data/spec/parsi-date/accessor_spec.rb +2 -9
- data/spec/parsi-date/comp_spec.rb +3 -3
- data/spec/parsi-date/constants_spec.rb +5 -5
- data/spec/parsi-date/construction_spec.rb +2 -10
- data/spec/parsi-date/conversion_spec.rb +2 -2
- data/spec/parsi-date/strftime_spec.rb +2 -8
- data/spec/spec_helper.rb +1 -1
- metadata +4 -4
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/lib/parsi-date.rb
CHANGED
@@ -1,67 +1,352 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# parsi-date.rb
|
4
|
+
#
|
5
|
+
# Author: Hassan Zamani 2012
|
6
|
+
#
|
7
|
+
# == Overview
|
8
|
+
#
|
9
|
+
# Date class represent a date in persian (jalali) calendar
|
10
|
+
#
|
11
|
+
# === Ways of calculating the date.
|
12
|
+
#
|
13
|
+
# In common usage, the date is reckoned in years since or before the Common Era
|
14
|
+
# (Hegira of Muhammad from Mecca to Medina in 622 CE), then as a month and
|
15
|
+
# day-of-the-month within the current year. This is known as the *Civil* *Date*,
|
16
|
+
# and abbreviated as +civil+ in the Date class.
|
17
|
+
#
|
18
|
+
# Instead of year, month-of-the-year, and day-of-the-month, the date can also
|
19
|
+
# be reckoned in terms of year and day-of-the-year. This is known as the *Ordinal*
|
20
|
+
# *Date*, and is abbreviated as +ordinal+ in the Date class. (Note that referring
|
21
|
+
# to this as the Julian date is incorrect.)
|
22
|
+
#
|
23
|
+
# For scientific purposes, it is convenient to refer to a date simply as a day
|
24
|
+
# count, counting from an arbitrary initial day. The date first chosen for this
|
25
|
+
# was January 1, 4713 BCE (-5335/9/1 in hijri solar calendar). A count of days from
|
26
|
+
# this date is the *Julian* *Day* *Number* or *Julian* *Date*, which is abbreviated
|
27
|
+
# as +jd+ in the Date class. This is in local time, and counts from midnight on the
|
28
|
+
# initial day. The stricter usage is in UTC, and counts from midday on the initial
|
29
|
+
# day. This is referred to in the Date class as the *Astronomical* *Julian* *Day*
|
30
|
+
# *Number*, and abbreviated as +ajd+. In the Date class, the Astronomical Julian
|
31
|
+
# Day Number includes fractional days.
|
32
|
+
#
|
33
|
+
# Another absolute day count is the *Modified* *Julian* *Day* *Number*, which
|
34
|
+
# takes November 17, 1858 (1237/08/26 in hijri solar calendar) as its initial day.
|
35
|
+
# This is abbreviated as +mjd+ in the Date class. There is also an *Astronomical*
|
36
|
+
# *Modified* *Julian* *Day* *Number*, which is in UTC and includes fractional days.
|
37
|
+
# This is abbreviated as +amjd+ in the Date class. Like the Modified Julian Day
|
38
|
+
# Number (and unlike the Astronomical Julian Day Number), it counts from midnight.
|
39
|
+
#
|
40
|
+
# === Time Zones
|
41
|
+
#
|
42
|
+
# DateTime objects support a simple representation of time zones. Time zones are
|
43
|
+
# represented as an offset from UTC, as a fraction of a day. This offset is the
|
44
|
+
# how much local time is later (or earlier) than UTC. UTC offset 0 is centred on
|
45
|
+
# England (also known as GMT). As you travel east, the offset increases until you
|
46
|
+
# reach the dateline in the middle of the Pacific Ocean; as you travel west, the
|
47
|
+
# offset decreases. This offset is abbreviated as +offset+ in the DateTime class.
|
48
|
+
#
|
49
|
+
# This simple representation of time zones does not take into account the common
|
50
|
+
# practice of Daylight Savings Time or Summer Time.
|
51
|
+
#
|
52
|
+
# Most DateTime methods return the date and the time in local time. The two
|
53
|
+
# exceptions are #ajd() and #amjd(), which return the date and time in UTC time,
|
54
|
+
# including fractional days.
|
55
|
+
#
|
56
|
+
# The Date class does not support time zone offsets, in that there is no way to
|
57
|
+
# create a Date object with a time zone. However, methods of the Date class when
|
58
|
+
# used by a DateTime instance will use the time zone offset of this instance.
|
59
|
+
#
|
2
60
|
|
3
61
|
require 'date'
|
4
62
|
|
5
63
|
module Parsi
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
64
|
+
# Class representing a date.
|
65
|
+
#
|
66
|
+
# See the documentation to the file parsi-date.rb for an overview.
|
67
|
+
#
|
68
|
+
# Internally, the date is represented as an Astronomical Julian Day Number, +ajd+.
|
69
|
+
# (There is also an +offset+ field for a time zone offset, but this is only for
|
70
|
+
# the use of the DateTime subclass.)
|
71
|
+
#
|
72
|
+
# A new Date object is created using one of the object creation class methods
|
73
|
+
# named after the corresponding date format, and the arguments appropriate to
|
74
|
+
# that date format; for instance, Date::civil() (aliased to Date::new()) with
|
75
|
+
# year, month, and day-of-month, or Date::ordinal() with year and day-of-year.
|
76
|
+
#
|
77
|
+
# Date objects are immutable once created.
|
78
|
+
#
|
79
|
+
# Once a Date has been created, date values can be retrieved for the different
|
80
|
+
# date formats supported using instance methods. For instance, #mon() gives the
|
81
|
+
# Civil month and #yday() gives the Ordinal day of the year. Date values can be
|
82
|
+
# retrieved in any format, regardless of what format was used to create the
|
83
|
+
# Date instance.
|
84
|
+
#
|
85
|
+
# The Date class includes the Comparable module, allowing
|
86
|
+
# date objects to be compared and sorted, ranges of dates
|
87
|
+
# to be created, and so forth.
|
12
88
|
class Date
|
89
|
+
|
13
90
|
include Comparable
|
14
91
|
|
15
|
-
|
92
|
+
# Full month names, in Farsi. Months count from 1 to 12; a
|
93
|
+
# month's numerical representation indexed into this array
|
94
|
+
# gives the name of that month (hence the first element is nil).
|
95
|
+
MONTHNAMES = [nil] + %w(فروردین اردیبهشت خرداد تیر مرداد شهریور مهر آبان آذر دی بهمن اسفند)
|
16
96
|
|
17
|
-
|
18
|
-
|
19
|
-
|
97
|
+
# Full names of days of the week, in Farsi. Days of the week
|
98
|
+
# count from 0 to 6 (except in the commercial week); a day's numerical
|
99
|
+
# representation indexed into this array gives the name of that day.
|
100
|
+
DAYNAMES = %w(یکشنده دوشنده سهشنده چهارشنده چنجشنده جمعه شنده)
|
20
101
|
|
21
|
-
|
102
|
+
# Abbreviated month names, in English.
|
103
|
+
ABBR_MONTHNAMES = [nil] + %w(Far Ord Kho Tir Mor Sha Meh Abn Azr Dey Bah Esf)
|
22
104
|
|
105
|
+
# Abbreviated day names, in Farsi.
|
106
|
+
ABBR_DAYNAMES = %w(۱ش ۲ش ۳ش ۴ش ۵ش ج ش)
|
107
|
+
|
108
|
+
[MONTHNAMES, ABBR_MONTHNAMES, DAYNAMES, ABBR_DAYNAMES].each do |xs|
|
109
|
+
xs.each{|x| x.freeze unless x.nil?}.freeze
|
110
|
+
end
|
111
|
+
|
112
|
+
HALF_DAYS_IN_DAY = Rational(1, 2) # :nodoc:
|
113
|
+
HOURS_IN_DAY = Rational(1, 24) # :nodoc:
|
114
|
+
MINUTES_IN_DAY = Rational(1, 1440) # :nodoc:
|
115
|
+
SECONDS_IN_DAY = Rational(1, 86400) # :nodoc:
|
116
|
+
MILLISECONDS_IN_DAY = Rational(1, 86400*10**3) # :nodoc:
|
117
|
+
NANOSECONDS_IN_DAY = Rational(1, 86400*10**9) # :nodoc:
|
118
|
+
MILLISECONDS_IN_SECOND = Rational(1, 10**3) # :nodoc:
|
119
|
+
NANOSECONDS_IN_SECOND = Rational(1, 10**9) # :nodoc:
|
120
|
+
|
121
|
+
JALALI_EPOCH_IN_AJD = Rational(3896641, 2) # :nodoc:
|
122
|
+
MJD_EPOCH_IN_AJD = Rational(4800001, 2) # 1858-11-17 # :nodoc:
|
123
|
+
UNIX_EPOCH_IN_AJD = Rational(4881175, 2) # 1970-01-01 # :nodoc:
|
124
|
+
JALALI_EPOCH_IN_CJD = 1948321 # :nodoc:
|
125
|
+
MJD_EPOCH_IN_CJD = 2400001 # :nodoc:
|
126
|
+
UNIX_EPOCH_IN_CJD = 2440588 # :nodoc:
|
127
|
+
LD_EPOCH_IN_CJD = 2299160 # :nodoc:
|
128
|
+
DAYS_IN_MONTH = [nil, 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29] # :nodoc:
|
129
|
+
|
130
|
+
shared_methods = Module.new do
|
131
|
+
|
132
|
+
# Returns true if the given year is a leap year of the calendar.
|
23
133
|
def leap? year
|
24
134
|
((((((year - ((year > 0) ? 474 : 473)) % 2820) + 474) + 38) * 682) % 2816) < 682
|
25
135
|
end
|
26
|
-
|
136
|
+
alias_method :jalali_leap?, :leap?
|
27
137
|
|
28
|
-
|
29
|
-
return false unless year.is_a?(Fixnum) && month.is_a?(Fixnum) && day.is_a?(Fixnum)
|
30
|
-
return true if leap?(year) && month == 12 && day == 30
|
138
|
+
private
|
31
139
|
|
32
|
-
|
33
|
-
end
|
140
|
+
DAYS_TO_FIRST_OF_MONTH = [nil, 0, 31, 62, 93, 124, 155, 186, 216, 246, 276, 306, 336] # :nodoc:
|
34
141
|
|
35
|
-
|
36
|
-
|
142
|
+
# Convert a Civil Date to a Julian Day Number and returns the corresponding Julian Day Number.
|
143
|
+
def civil_to_jd year, month, day # :nodoc:
|
144
|
+
epbase = year - 474
|
145
|
+
epyear = 474 + (epbase % 2820)
|
37
146
|
|
38
|
-
|
39
|
-
|
40
|
-
|
147
|
+
day + DAYS_TO_FIRST_OF_MONTH[month] +
|
148
|
+
(epyear * 682 - 110) / 2816 +
|
149
|
+
(epyear - 1) * 365 +
|
150
|
+
(epbase / 2820 * 1029983) +
|
151
|
+
(JALALI_EPOCH_IN_CJD - 1)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Convert a Julian Day Number to a Civil Date. +jday+ is the Julian Day Number.
|
155
|
+
#
|
156
|
+
# Returns the corresponding [year, month, day_of_month] as a three-element array.
|
157
|
+
def jd_to_civil jday
|
158
|
+
depoch = (jday - first_day_of_year(475))
|
159
|
+
cycle, cyear = depoch.divmod 1029983
|
41
160
|
|
42
161
|
if cyear == 1029982
|
43
|
-
|
162
|
+
ycycle = 2820
|
163
|
+
else
|
164
|
+
aux1, aux2 = cyear.divmod 366
|
165
|
+
ycycle = (2134 * aux1 + 2816 * aux2 + 2815) / 1028522 + aux1 + 1
|
166
|
+
end
|
167
|
+
year = ycycle + 2820 * cycle + 474
|
168
|
+
yday = jday - first_day_of_year(year) + 1
|
169
|
+
month = ((yday <= 186) ? yday / 31.0 : (yday - 6) / 30.0).ceil
|
170
|
+
day = (jday - first_day_of_month(year, month) + 1)
|
171
|
+
[year, month, day]
|
172
|
+
end
|
173
|
+
|
174
|
+
# Do +year+, +month+, and day-of-month +day+ make a valid Civil Date?
|
175
|
+
# Returns the corresponding Julian Day Number if they do, nil if they don't.
|
176
|
+
# Invalid values cause an ArgumentError to be raised.
|
177
|
+
def _valid_civil? year, month, day # :nodoc:
|
178
|
+
return unless year.is_a?(Fixnum) && month.is_a?(Fixnum) && day.is_a?(Fixnum)
|
179
|
+
return civil_to_jd(year, 12, 30) if leap?(year) && month == 12 && day == 30
|
180
|
+
|
181
|
+
if 1 <= month && month <= 12 && 1 <= day && day <= DAYS_IN_MONTH[month]
|
182
|
+
return civil_to_jd year, month, day
|
44
183
|
else
|
45
|
-
|
46
|
-
aux2 = cyear % 366
|
47
|
-
ycycle = (2134 * aux1 + 2816 * aux2 + 2815) / 1028522 + aux1 + 1
|
184
|
+
nil
|
48
185
|
end
|
49
|
-
|
50
|
-
year -= 1 if year <= 0
|
186
|
+
end
|
51
187
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
188
|
+
# Do the +year+ and day-of-year +yday+ make a valid Ordinal Date?
|
189
|
+
# Returns the corresponding Julian Day Number if they do, or nil if they don't.
|
190
|
+
def _valid_ordinal? year, yday
|
191
|
+
ordinal_to_jd year, yday
|
56
192
|
end
|
57
193
|
|
58
|
-
|
194
|
+
def first_day_of_year year # :nodoc:
|
195
|
+
civil_to_jd year, 1, 1
|
196
|
+
end
|
59
197
|
|
60
|
-
def
|
61
|
-
|
62
|
-
Date.jd jd
|
198
|
+
def last_day_of_year year # :nodoc:
|
199
|
+
_valid_civil?(year, 12, 30) || civil_to_jd(year, 12, 29)
|
63
200
|
end
|
64
201
|
|
202
|
+
def first_day_of_month year, month # :nodoc:
|
203
|
+
civil_to_jd year, month, 1
|
204
|
+
end
|
205
|
+
|
206
|
+
def last_day_of_month year, month # :nodoc:
|
207
|
+
_valid_civil?(year, month, 31) || _valid_civil?(year, month, 30) || _valid_civil?(year, month, 29)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Convert an Ordinal Date to a Julian Day Number.
|
211
|
+
#
|
212
|
+
# +year+ and +yday+ are the year and day-of-year to convert.
|
213
|
+
#
|
214
|
+
# Returns the corresponding Julian Day Number.
|
215
|
+
def ordinal_to_jd year, yday # :nodoc:
|
216
|
+
first_day_of_year(year) + yday - 1
|
217
|
+
end
|
218
|
+
|
219
|
+
# Convert a Julian Day Number to an Ordinal Date.
|
220
|
+
#
|
221
|
+
# +jday+ is the Julian Day Number to convert.
|
222
|
+
#
|
223
|
+
# Returns the corresponding Ordinal Date as [year, day_of_year]
|
224
|
+
def jd_to_ordinal jday # :nodoc:
|
225
|
+
year = jd_to_civil(jd).first
|
226
|
+
yday = jday - first_day_of_year(year) + 1
|
227
|
+
[year, yday]
|
228
|
+
end
|
229
|
+
|
230
|
+
# Convert an Astronomical Julian Day Number to a (civil) Julian Day Number.
|
231
|
+
#
|
232
|
+
# +ajd+ is the Astronomical Julian Day Number to convert. +offset+ is the offset from
|
233
|
+
# UTC as a fraction of a day (defaults to 0).
|
234
|
+
#
|
235
|
+
# Returns the (civil) Julian Day Number as [day_number, fraction] where +fraction+ is
|
236
|
+
# always 1/2.
|
237
|
+
def ajd_to_jd ajd, offset=0 # :nodoc:
|
238
|
+
(ajd + offset + HALF_DAYS_IN_DAY).divmod(1)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Convert a (civil) Julian Day Number to an Astronomical Julian Day Number.
|
242
|
+
#
|
243
|
+
# +jd+ is the Julian Day Number to convert, and +fraction+ is a fraction of day.
|
244
|
+
# +offset+ is the offset from UTC as a fraction of a day (defaults to 0).
|
245
|
+
#
|
246
|
+
# Returns the Astronomical Julian Day Number as a single numeric value.
|
247
|
+
def jd_to_ajd jd, fraction, offset=0 # :nodoc:
|
248
|
+
jd + fraction - offset - HALF_DAYS_IN_DAY
|
249
|
+
end
|
250
|
+
|
251
|
+
# Convert an +hour+, +minute+, +second+s period to a fractional day.
|
252
|
+
def time_to_day_fraction hour, minute, second # :nodoc:
|
253
|
+
Rational(hour * 3600 + minute * 60 + second, 86400)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Convert an Astronomical Modified Julian Day Number to an Astronomical Julian Day Number.
|
257
|
+
def amjd_to_ajd(amjd) amjd + MJD_EPOCH_IN_AJD end # :nodoc:
|
258
|
+
|
259
|
+
# Convert an Astronomical Julian Day Number to an Astronomical Modified Julian Day Number.
|
260
|
+
def ajd_to_amjd(ajd) ajd - MJD_EPOCH_IN_AJD end # :nodoc:
|
261
|
+
|
262
|
+
# Convert a Modified Julian Day Number to a Julian Day Number.
|
263
|
+
def mjd_to_jd(mjd) mjd + MJD_EPOCH_IN_CJD end # :nodoc:
|
264
|
+
|
265
|
+
# Convert a Julian Day Number to a Modified Julian Day Number.
|
266
|
+
def jd_to_mjd(jd) jd - MJD_EPOCH_IN_CJD end # :nodoc:
|
267
|
+
|
268
|
+
# Convert a count of the number of days since the adoption of the Gregorian Calendar
|
269
|
+
# (in Italy) to a Julian Day Number.
|
270
|
+
def ld_to_jd(ld) ld + LD_EPOCH_IN_CJD end # :nodoc:
|
271
|
+
|
272
|
+
# Convert a Julian Day Number to the number of days since the adoption of the
|
273
|
+
# Gregorian Calendar (in Italy).
|
274
|
+
def jd_to_ld(jd) jd - LD_EPOCH_IN_CJD end # :nodoc:
|
275
|
+
|
276
|
+
# Convert a Julian Day Number to the day of the week.
|
277
|
+
#
|
278
|
+
# Sunday is day-of-week 0; Saturday is day-of-week 6.
|
279
|
+
def jd_to_wday(jd) (jd + 1) % 7 end # :nodoc:
|
280
|
+
|
281
|
+
# Is +jd+ a valid Julian Day Number?
|
282
|
+
#
|
283
|
+
# If it is, returns it. In fact, any value is treated as a valid Julian Day Number.
|
284
|
+
def _valid_jd?(jd) jd end # :nodoc:
|
285
|
+
end
|
286
|
+
|
287
|
+
extend shared_methods
|
288
|
+
include shared_methods
|
289
|
+
|
290
|
+
class << self
|
291
|
+
|
292
|
+
alias_method :new!, :new
|
293
|
+
|
294
|
+
def valid_jd? jd
|
295
|
+
!!_valid_jd?(jd)
|
296
|
+
end
|
297
|
+
|
298
|
+
def valid_ordinal? year, yday
|
299
|
+
!!_valid_ordinal?(year, yday)
|
300
|
+
end
|
301
|
+
|
302
|
+
def valid_civil? year, month, day
|
303
|
+
!!_valid_civil?(year, month, day)
|
304
|
+
end
|
305
|
+
alias :valid_date? :valid_civil?
|
306
|
+
alias :valid? :valid_civil?
|
307
|
+
alias :exist? :valid_civil?
|
308
|
+
|
309
|
+
# Create a new Date object from a Julian Day Number.
|
310
|
+
#
|
311
|
+
# +jday+ is the Julian Day Number; if not specified, it defaults to 0.
|
312
|
+
#
|
313
|
+
# examples:
|
314
|
+
# Parsi::Date.jd 2456229 # => #<Parsi::Date: 1391-08-07>
|
315
|
+
# Parsi::Date.jd 2456230 # => #<Parsi::Date: 1391-08-08>
|
316
|
+
# Parsi::Date.jd # => #<Parsi::Date: -5335-09-01>
|
317
|
+
#
|
318
|
+
def jd jday=0
|
319
|
+
jd = _valid_jd? jday
|
320
|
+
new! jd_to_ajd(jday, 0, 0), 0
|
321
|
+
end
|
322
|
+
|
323
|
+
# Create a new Date object from an Ordinal Date, specified by +year+ and day-of-year +yday+.
|
324
|
+
# +yday+ can be negative, in which it counts backwards from the end of the year.
|
325
|
+
#
|
326
|
+
# examples:
|
327
|
+
# Parsi::Date.ordinal 1390 # => #<Parsi::Date: 1390-01-01>
|
328
|
+
# Parsi::Date.ordinal 1391, 120 # => #<Parsi::Date: 1391-04-27>
|
329
|
+
# Parsi::Date.ordinal 1390, -1 # => #<Parsi::Date: 1389-12-29>
|
330
|
+
#
|
331
|
+
def ordinal year=0, yday=1
|
332
|
+
raise ArgumentError, 'invalid date' unless jd = _valid_ordinal?(year, yday)
|
333
|
+
new! jd_to_ajd(jd, 0, 0), 0
|
334
|
+
end
|
335
|
+
|
336
|
+
# Create a new Date object for the Civil Date specified by +year+, +month+, and
|
337
|
+
# day-of-month +day+.
|
338
|
+
def civil year=1, month=1, day=1
|
339
|
+
raise ArgumentError, 'invalid date' unless jd = _valid_civil?(year, month, day)
|
340
|
+
new! jd_to_ajd(jd, 0, 0), 0
|
341
|
+
end
|
342
|
+
alias_method :new, :civil
|
343
|
+
|
344
|
+
# Parses the given representation of date and time, and creates a date object.
|
345
|
+
#
|
346
|
+
# If the optional second argument is true and the detected year is in the range “00” to “99”,
|
347
|
+
# considers the year a 2-digit form and makes it full.
|
348
|
+
#
|
349
|
+
# For
|
65
350
|
def parse string, comp=true
|
66
351
|
# TODO: Add more parse options, for example parse '۴ام فروردین ۱۳۹۱'
|
67
352
|
m = string.match /(?<year>\d+)(\/|-| )(?<month>\d+)(\/|-| )(?<day>\d+)/
|
@@ -74,77 +359,237 @@ module Parsi
|
|
74
359
|
centry = Date.today.year / 100
|
75
360
|
year = (m[:year].prepend centry.to_s).to_i
|
76
361
|
end
|
77
|
-
if
|
78
|
-
|
362
|
+
if jd = _valid_civil?(year, month, day)
|
363
|
+
new! jd_to_ajd(jd, 0, 0), 0
|
79
364
|
else
|
80
365
|
raise ArgumentError.new 'invalid date'
|
81
366
|
end
|
82
367
|
end
|
83
368
|
end
|
84
369
|
|
370
|
+
# Create a new Date object representing today.
|
85
371
|
def today
|
86
|
-
|
372
|
+
::Date.today.to_parsi
|
87
373
|
end
|
88
|
-
|
89
|
-
def tomorrow ; today + 1 end
|
90
|
-
def yesterday; today - 1 end
|
91
374
|
end
|
92
375
|
|
93
|
-
|
94
|
-
|
95
|
-
|
376
|
+
# Create a new Date object.
|
377
|
+
#
|
378
|
+
# +ajd+ is the Astronomical Julian Day Number.
|
379
|
+
# +offset+ is the offset from UTC as a fraction of a day.
|
380
|
+
def initialize ajd=0, offset=0
|
381
|
+
@ajd, @offset = ajd, offset
|
96
382
|
end
|
97
383
|
|
98
|
-
|
99
|
-
|
384
|
+
# Get the date as an Astronomical Julian Day Number.
|
385
|
+
def ajd
|
386
|
+
@ajd
|
387
|
+
end
|
100
388
|
|
101
|
-
def
|
102
|
-
|
389
|
+
def offset
|
390
|
+
@offset
|
103
391
|
end
|
392
|
+
private :offset
|
104
393
|
|
105
|
-
|
106
|
-
|
394
|
+
# Get the date as an Astronomical Modified Julian Day Number.
|
395
|
+
def amjd
|
396
|
+
@amjd ||= ajd_to_amjd ajd
|
107
397
|
end
|
108
398
|
|
399
|
+
# Get the date as a Julian Day Number.
|
109
400
|
def jd
|
110
|
-
@jd ||=
|
111
|
-
|
112
|
-
epyear = 474 + epbase % 2820
|
401
|
+
@jd ||= ajd_to_jd(ajd, offset).first
|
402
|
+
end
|
113
403
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
404
|
+
# Get any fractional day part of the date.
|
405
|
+
def day_fraction
|
406
|
+
@day_fraction ||= ajd_to_jd(ajd, offset).last
|
407
|
+
end
|
408
|
+
|
409
|
+
# Get the date as a Modified Julian Day Number.
|
410
|
+
def mjd
|
411
|
+
@mjd ||= jd_to_mjd jd
|
412
|
+
end
|
413
|
+
|
414
|
+
# Get the date as the number of days since the Day of Calendar
|
415
|
+
# Reform (in Italy and the Catholic countries).
|
416
|
+
def ld
|
417
|
+
@ld ||= jd_to_ld jd
|
418
|
+
end
|
419
|
+
|
420
|
+
# Get the date as a Civil Date, [year, month, day_of_month]
|
421
|
+
def civil # :nodoc:
|
422
|
+
@civil ||= jd_to_civil jd
|
423
|
+
end
|
424
|
+
|
425
|
+
# Get the date as an Ordinal Date, [year, day_of_year]
|
426
|
+
def ordinal # :nodoc:
|
427
|
+
@ordinal ||= jd_to_ordinal jd
|
120
428
|
end
|
121
429
|
|
122
|
-
|
123
|
-
def ld ; (jd - 2299160).floor end
|
124
|
-
def ajd ; gregorian.ajd end
|
125
|
-
def amjd ; gregorian.amjd end
|
430
|
+
private :civil, :ordinal
|
126
431
|
|
432
|
+
# Get the year of this date.
|
433
|
+
def year() civil[0] end
|
434
|
+
|
435
|
+
# Get the month of this date.
|
436
|
+
#
|
437
|
+
# Farvardin is month 1.
|
438
|
+
def mon() civil[1] end
|
439
|
+
alias_method :month, :mon
|
440
|
+
|
441
|
+
# Get the day-of-the-month of this date.
|
442
|
+
def mday() civil[2] end
|
443
|
+
alias_method :day, :mday
|
444
|
+
|
445
|
+
# Get the day-of-the-year of this date.
|
446
|
+
#
|
447
|
+
# January 1 is day-of-the-year 1
|
448
|
+
def yday; ordinal[1] end
|
449
|
+
|
450
|
+
# Get the week day of this date. Sunday is day-of-week 0;
|
451
|
+
# Saturday is day-of-week 6.
|
127
452
|
def wday
|
128
|
-
|
453
|
+
@wday ||= jd_to_wday jd
|
454
|
+
end
|
455
|
+
|
456
|
+
::Date::DAYNAMES.each_with_index do |n, i|
|
457
|
+
define_method(n.downcase + '?'){ wday == i }
|
458
|
+
end
|
459
|
+
|
460
|
+
# Return a new Date object that is +n+ days later than the current one.
|
461
|
+
#
|
462
|
+
# +n+ may be a negative value, in which case the new Date is earlier
|
463
|
+
# than the current one; however, #-() might be more intuitive.
|
464
|
+
#
|
465
|
+
# If +n+ is not a Numeric, a TypeError will be thrown. In particular,
|
466
|
+
# two Dates cannot be added to each other.
|
467
|
+
def + n
|
468
|
+
case n
|
469
|
+
when Numeric
|
470
|
+
return self.class.new!(ajd + n, offset)
|
471
|
+
end
|
472
|
+
raise TypeError, 'expected numeric'
|
129
473
|
end
|
130
474
|
|
131
|
-
|
132
|
-
|
475
|
+
# If +x+ is a Numeric value, create a new Date object that is +x+ days
|
476
|
+
# earlier than the current one.
|
477
|
+
#
|
478
|
+
# If +x+ is a Date, return the number of days between the two dates; or,
|
479
|
+
# more precisely, how many days later the current date is than +x+.
|
480
|
+
#
|
481
|
+
# If +x+ is neither Numeric nor a Date, a TypeError is raised.
|
482
|
+
def - x
|
483
|
+
case x
|
484
|
+
when Numeric
|
485
|
+
return self.class.new!(ajd - x, offset)
|
486
|
+
when Date
|
487
|
+
return ajd - x.ajd
|
488
|
+
end
|
489
|
+
raise TypeError, 'expected numeric or date'
|
133
490
|
end
|
134
491
|
|
135
|
-
|
136
|
-
|
492
|
+
# Compare this date with another date.
|
493
|
+
#
|
494
|
+
# +other+ can also be a Numeric value, in which case it is interpreted as an
|
495
|
+
# Astronomical Julian Day Number.
|
496
|
+
#
|
497
|
+
# Comparison is by Astronomical Julian Day Number, including fractional days.
|
498
|
+
# This means that both the time and the timezone offset are taken into account
|
499
|
+
# when comparing two DateTime instances. When comparing a DateTime instance
|
500
|
+
# with a Date instance, the time of the latter will be considered as falling
|
501
|
+
# on midnight UTC.
|
502
|
+
def <=> other
|
503
|
+
case other
|
504
|
+
when Numeric
|
505
|
+
return ajd <=> other
|
506
|
+
when Date
|
507
|
+
return ajd <=> other.ajd
|
508
|
+
when ::Date
|
509
|
+
return ajd <=> other.ajd
|
510
|
+
else
|
511
|
+
begin
|
512
|
+
left, right = other.coerce(self)
|
513
|
+
return left <=> right
|
514
|
+
rescue NoMethodError
|
515
|
+
end
|
516
|
+
end
|
517
|
+
nil
|
137
518
|
end
|
138
519
|
|
139
|
-
|
140
|
-
|
520
|
+
# The relationship operator for Date.
|
521
|
+
#
|
522
|
+
# Compares dates by Julian Day Number. When comparing two DateTime instances,
|
523
|
+
# or a DateTime with a Date, the instances will be regarded as equivalent if
|
524
|
+
# they fall on the same date in local time.
|
525
|
+
def === (other)
|
526
|
+
case other
|
527
|
+
when Numeric
|
528
|
+
return jd == other
|
529
|
+
when Date; return jd == other.jd
|
530
|
+
else
|
531
|
+
begin
|
532
|
+
l, r = other.coerce(self)
|
533
|
+
return l === r
|
534
|
+
rescue NoMethodError
|
535
|
+
end
|
536
|
+
end
|
537
|
+
false
|
141
538
|
end
|
142
539
|
|
540
|
+
def next_day(n=1) self + n end
|
541
|
+
def prev_day(n=1) self - n end
|
542
|
+
|
543
|
+
# Return a new Date one day after this one.
|
544
|
+
def next() next_day end
|
545
|
+
alias_method :succ, :next
|
546
|
+
|
547
|
+
# Return a new Date object that is +n+ months later than the current one.
|
548
|
+
#
|
549
|
+
# If the day-of-the-month of the current Date is greater than the last day of
|
550
|
+
# the target month, the day-of-the-month of the returned Date will be the last
|
551
|
+
# day of the target month.
|
552
|
+
def >> n
|
553
|
+
y, m = (year * 12 + (mon - 1) + n).divmod(12)
|
554
|
+
m, = (m + 1) .divmod(1)
|
555
|
+
d = mday
|
556
|
+
until jd2 = _valid_civil?(y, m, d)
|
557
|
+
d -= 1
|
558
|
+
raise ArgumentError, 'invalid date' unless d > 0
|
559
|
+
end
|
560
|
+
self + (jd2 - jd)
|
561
|
+
end
|
562
|
+
|
563
|
+
# Return a new Date object that is +n+ months earlier than the current one.
|
564
|
+
#
|
565
|
+
# If the day-of-the-month of the current Date is greater than the last day of
|
566
|
+
# the target month, the day-of-the-month of the returned Date will be the last
|
567
|
+
# day of the target month.
|
568
|
+
def << (n) self >> -n end
|
569
|
+
|
570
|
+
def next_month(n=1) self >> n end
|
571
|
+
def prev_month(n=1) self << n end
|
572
|
+
|
573
|
+
def next_year(n=1) self >> n * 12 end
|
574
|
+
def prev_year(n=1) self << n * 12 end
|
575
|
+
|
143
576
|
def to_gregorian
|
144
577
|
::Date.jd jd
|
145
578
|
end
|
146
579
|
alias :gregorian :to_gregorian
|
147
580
|
|
581
|
+
def to_time
|
582
|
+
gregorian.to_time
|
583
|
+
end
|
584
|
+
|
585
|
+
def to_date
|
586
|
+
self
|
587
|
+
end
|
588
|
+
|
589
|
+
def to_datetime
|
590
|
+
DateTime.new! jd_to_ajd(jd, 0, 0), 0
|
591
|
+
end
|
592
|
+
|
148
593
|
def strftime format='%Y/%m/%d'
|
149
594
|
format.
|
150
595
|
gsub('%%', 'PERCENT_SUBSTITUTION_MARKER').
|
@@ -161,125 +606,90 @@ module Parsi
|
|
161
606
|
gsub('%_m', '% 2d' % month).
|
162
607
|
gsub('%-m', month.to_s).
|
163
608
|
gsub('%^B', '%B').
|
164
|
-
gsub('%B',
|
609
|
+
gsub('%B', MONTHNAMES[month]).
|
165
610
|
gsub('%h', '%b').
|
166
|
-
gsub('%b',
|
167
|
-
gsub('%^b',
|
611
|
+
gsub('%b', ABBR_MONTHNAMES[month]).
|
612
|
+
gsub('%^b', ABBR_MONTHNAMES[month].capitalize).
|
168
613
|
gsub('%d', '%02d' % day).
|
169
614
|
gsub('%e', '% 2d' % day).
|
170
615
|
gsub('%-d', day.to_s).
|
171
616
|
gsub('%j', '%03d' % yday.to_s).
|
172
|
-
gsub('%A',
|
173
|
-
gsub('%a',
|
174
|
-
gsub('%u', cwday.to_s).
|
617
|
+
gsub('%A', DAYNAMES[wday]).
|
618
|
+
gsub('%a', ABBR_DAYNAMES[wday]).
|
175
619
|
gsub('%w', wday.to_s).
|
176
620
|
gsub('%n', "\n").
|
177
621
|
gsub('%t', "\t").
|
178
622
|
gsub('PERCENT_SUBSTITUTION_MARKER', '%')
|
179
623
|
end
|
180
624
|
|
181
|
-
def + days
|
182
|
-
raise TypeError.new 'expected numeric' unless days.is_a? Numeric
|
183
|
-
Date.jd jd + days
|
184
|
-
end
|
185
|
-
|
186
|
-
def - days
|
187
|
-
self + -days
|
188
|
-
end
|
189
625
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
m = monthes % 12
|
195
|
-
y -= 1 and m = 12 if m == 0
|
196
|
-
d = day
|
197
|
-
d -= 1 until Date.valid? y, m, d
|
198
|
-
Date.new y, m, d
|
199
|
-
end
|
626
|
+
# Step the current date forward +step+ days at a time (or backward, if +step+ is
|
627
|
+
# negative) until we reach +limit+ (inclusive), yielding the resultant date at each step.
|
628
|
+
def step limit, step=1
|
629
|
+
return to_enum(:step, limit, step) unless block_given?
|
200
630
|
|
201
|
-
def << monthes
|
202
|
-
self >> -monthes
|
203
|
-
end
|
204
|
-
|
205
|
-
def <=> other
|
206
|
-
if other.respond_to? :jd
|
207
|
-
jd <=> other.jd
|
208
|
-
elsif other.is_a? Numeric
|
209
|
-
jd <=> other
|
210
|
-
else
|
211
|
-
raise ArgumentError.new "comparison of #{self.class} with #{other.class} failed"
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def step limit, by=1
|
216
631
|
date = self
|
217
|
-
comp_op = %w(== <= >=)[
|
632
|
+
comp_op = %w(== <= >=)[step <=> 0]
|
218
633
|
while date.send comp_op, limit
|
219
634
|
yield date
|
220
|
-
date +=
|
635
|
+
date += step
|
221
636
|
end
|
222
637
|
self
|
223
638
|
end
|
224
639
|
|
225
|
-
|
640
|
+
# Step forward one day at a time until we reach +max+
|
641
|
+
# (inclusive), yielding each date as we go.
|
642
|
+
def upto max, &block # :yield: date
|
226
643
|
step max, 1, &block
|
227
644
|
end
|
228
645
|
|
229
|
-
|
646
|
+
# Step backward one day at a time until we reach +min+
|
647
|
+
# (inclusive), yielding each date as we go.
|
648
|
+
def downto min, &block # :yield: date
|
230
649
|
step min, -1, &block
|
231
650
|
end
|
232
651
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
652
|
+
# Is this Date equal to +other+?
|
653
|
+
#
|
654
|
+
# +other+ must both be a Date object, and represent the same date.
|
655
|
+
def eql? (other) self.class === other && self == other end
|
237
656
|
|
238
|
-
|
239
|
-
|
240
|
-
end
|
241
|
-
|
242
|
-
def next_month n=1
|
243
|
-
self >> n
|
244
|
-
end
|
245
|
-
|
246
|
-
def next_year n=1
|
247
|
-
self >> (n * 12)
|
248
|
-
end
|
657
|
+
# Calculate a hash value for this date.
|
658
|
+
def hash() ajd.hash end
|
249
659
|
|
250
|
-
|
251
|
-
|
660
|
+
# Return internal object state as a programmer-readable string.
|
661
|
+
def inspect
|
662
|
+
format('#<%s: %s (%s,%s)>', self.class, to_s, ajd, offset)
|
252
663
|
end
|
253
664
|
|
254
|
-
|
255
|
-
|
256
|
-
|
665
|
+
# Return the date as a human-readable string.
|
666
|
+
#
|
667
|
+
# The format used is YYYY-MM-DD.
|
668
|
+
def to_s() format('%.4d-%02d-%02d', year, mon, mday) end
|
257
669
|
|
258
|
-
|
259
|
-
|
260
|
-
end
|
670
|
+
# Dump to Marshal format.
|
671
|
+
def marshal_dump() [@ajd, @offset] end
|
261
672
|
|
262
|
-
|
263
|
-
def
|
264
|
-
def doshanbe? ; wday == 2 end
|
265
|
-
def seshanbe? ; wday == 3 end
|
266
|
-
def chaharshanbe? ; wday == 4 end
|
267
|
-
def panjshanbe? ; wday == 5 end
|
268
|
-
def jomee? ; wday == 6 end
|
269
|
-
|
270
|
-
private
|
271
|
-
def first_of_year
|
272
|
-
@first_of_year ||= Date.ordinal year
|
273
|
-
end
|
673
|
+
# Load from Marshal format.
|
674
|
+
def marshal_load(a) @ajd, @of, = a end
|
274
675
|
end
|
275
676
|
end
|
276
677
|
|
277
678
|
class Date
|
679
|
+
class << self
|
680
|
+
# Creates a Date object corresponding to the specified Jalali Date +year+, +month+ and
|
681
|
+
# +day+.
|
682
|
+
def parsi year=0, month=1, day=1
|
683
|
+
Parsi::Date.civil year, month, day
|
684
|
+
end
|
685
|
+
alias :jalali :parsi
|
686
|
+
end
|
687
|
+
|
688
|
+
# Returns a Parsi::Date object representing same date in Jalali calendar
|
278
689
|
def to_parsi
|
279
|
-
Parsi::Date.
|
690
|
+
Parsi::Date.new! ajd, offset
|
280
691
|
end
|
281
|
-
alias :to_persian :to_parsi
|
282
|
-
alias :to_jalali :to_parsi
|
283
|
-
alias :parsi :to_parsi
|
284
692
|
alias :jalali :to_parsi
|
693
|
+
alias :to_jalali :to_parsi
|
694
|
+
alias :to_persian :to_parsi
|
285
695
|
end
|
data/lib/parsi-datetime.rb
CHANGED
@@ -1,101 +1,202 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# parsi-date.rb
|
4
|
+
#
|
5
|
+
# Author: Hassan Zamani 2012
|
6
|
+
|
1
7
|
module Parsi
|
8
|
+
# Class representing a date and time.
|
9
|
+
#
|
10
|
+
# See the documentation to the file parsi-date.rb for an overview.
|
11
|
+
#
|
12
|
+
# DateTime objects are immutable once created.
|
2
13
|
class DateTime < Date
|
3
|
-
include Comparable
|
4
14
|
|
5
|
-
|
15
|
+
shared_methods = Module.new do
|
16
|
+
|
17
|
+
def zone_to_offset zone
|
18
|
+
m = zone.match /(?<sign>\+|-)?(?<hour>\d{1,2}):?(?<minute>\d{,2})/
|
19
|
+
return 0 if m.nil?
|
20
|
+
offset = Rational(m[:hour].to_i, 24) + Rational(m[:minute].to_i, 1440)
|
21
|
+
m[:sign] == '-' ? -offset : offset
|
22
|
+
end
|
23
|
+
private :zone_to_offset
|
24
|
+
|
25
|
+
# Convert a fractional day +fraction+ to [hours, minutes, seconds, fraction_of_a_second]
|
26
|
+
def day_fraction_to_time fraction # :nodoc:
|
27
|
+
seconds, fraction = fraction.divmod SECONDS_IN_DAY
|
28
|
+
hours, seconds = seconds.divmod 3600
|
29
|
+
minutes, seconds = seconds.divmod 60
|
30
|
+
[hours, minutes, seconds, fraction * 86400]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Do +hour+, +minute+, and +second+ constitute a valid time?
|
34
|
+
#
|
35
|
+
# If they do, returns their value as a fraction of a day. If not, returns nil.
|
36
|
+
#
|
37
|
+
# The 24-hour clock is used. Negative values of +hour+, +minute+, and +second+ are treating
|
38
|
+
# as counting backwards from the end of the next larger unit (e.g. a +minute+ of -2 is
|
39
|
+
# treated as 58). No wraparound is performed.
|
40
|
+
def _valid_time? hour, minute, second
|
41
|
+
hour += 24 if hour < 0
|
42
|
+
minute += 60 if minute < 0
|
43
|
+
seconds += 60 if second < 0
|
44
|
+
return unless ((0...24) === hour && (0...60) === minute && (0...60) === second) ||
|
45
|
+
(24 == hour && 0 == minute && 0 == second)
|
46
|
+
time_to_day_fraction hour, minute, second
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
extend shared_methods
|
51
|
+
include shared_methods
|
6
52
|
|
7
53
|
class << self
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
54
|
+
|
55
|
+
def valid_time? hour, min, sec
|
56
|
+
!!_valid_time?(hour, min, sec)
|
57
|
+
end
|
58
|
+
private :valid_time?
|
59
|
+
|
60
|
+
# Create a new DateTime object corresponding to the specified Julian Day Number +jd+
|
61
|
+
# and +hour+, +minute+, +second+.
|
62
|
+
#
|
63
|
+
# The 24-hour clock is used. If an invalid time portion is specified, an ArgumentError
|
64
|
+
# is raised.
|
65
|
+
def jd jd=0, hour=0, minute=0, second=0, zone="00:00"
|
66
|
+
fraction = _valid_time? hour, minute, second
|
67
|
+
raise ArgumentError, 'invalid time' if fraction.nil?
|
68
|
+
|
69
|
+
offset = zone_to_offset zone
|
70
|
+
|
71
|
+
new! jd_to_ajd(jd, fraction, offset), offset
|
72
|
+
end
|
73
|
+
|
74
|
+
# Create a new DateTime object corresponding to the specified Astronomical Julian Day
|
75
|
+
# Number +ajd+ in given +offset+.
|
76
|
+
def ajd ajd=0, zone="00:00"
|
77
|
+
new! ajd, zone_to_offset(zone)
|
17
78
|
end
|
18
79
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
80
|
+
# Create a new DateTime object corresponding to the specified Ordinal Date and +hour+,
|
81
|
+
# +minute+, +second+.
|
82
|
+
#
|
83
|
+
# The 24-hour clock is used. If an invalid time portion is specified, an ArgumentError
|
84
|
+
# is raised.
|
85
|
+
def ordinal year=0, yday=1, hour=0, minute=0, second=0, zone="00:00"
|
86
|
+
jd = _valid_ordinal? year, yday
|
87
|
+
fraction = _valid_time?(hour, minute, second)
|
88
|
+
raise ArgumentError, 'invalid date' if jd.nil? or fraction.nil?
|
89
|
+
|
90
|
+
offset = zone_to_offset zone
|
91
|
+
|
92
|
+
new! jd_to_ajd(jd, fr, offset), offset
|
25
93
|
end
|
26
94
|
|
95
|
+
# Create a new DateTime object corresponding to the specified Civil Date and +hour+,
|
96
|
+
# +minute+, +second+.
|
97
|
+
#
|
98
|
+
# The 24-hour clock is used. If an invalid time portion is specified, an ArgumentError is
|
99
|
+
# raised.
|
100
|
+
#
|
101
|
+
# +offset+ is the offset from UTC as a fraction of a day (defaults to 0).
|
102
|
+
def civil year=0, month=1, day=1, hour=0, minute=0, second=0, zone="00:00"
|
103
|
+
jd = _valid_civil? year, month, day
|
104
|
+
fraction = _valid_time? hour, minute, second
|
105
|
+
raise ArgumentError, 'invalid date' if jd.nil? or fraction.nil?
|
106
|
+
|
107
|
+
offset = zone_to_offset zone
|
108
|
+
|
109
|
+
new! jd_to_ajd(jd, fraction, offset), offset
|
110
|
+
end
|
111
|
+
alias_method :new, :civil
|
112
|
+
|
113
|
+
# Create a new DateTime object representing the current time.
|
27
114
|
def now
|
28
|
-
|
29
|
-
today = now.to_date.to_parsi
|
30
|
-
DateTime.new today.year, today.month, today.day, now.hour, now.minute, now.second, now.offset
|
115
|
+
::DateTime.now.to_parsi
|
31
116
|
end
|
32
|
-
end
|
33
117
|
|
34
|
-
|
35
|
-
|
36
|
-
DateTime.valid_time? hour, minute, second
|
118
|
+
private :today
|
119
|
+
end
|
37
120
|
|
38
|
-
|
39
|
-
|
121
|
+
# Get the time of this date as [hours, minutes, seconds,
|
122
|
+
# fraction_of_a_second]
|
123
|
+
def time # :nodoc:
|
124
|
+
@time ||= day_fraction_to_time day_fraction
|
40
125
|
end
|
126
|
+
private :time
|
127
|
+
|
128
|
+
# Get the hour of this date.
|
129
|
+
def hour() time[0] end
|
130
|
+
|
131
|
+
# Get the minute of this date.
|
132
|
+
def min() time[1] end
|
133
|
+
|
134
|
+
# Get the second of this date.
|
135
|
+
def sec() time[2] end
|
136
|
+
|
137
|
+
# Get the fraction-of-a-second of this date.
|
138
|
+
def sec_fraction() time[3] end
|
41
139
|
|
42
|
-
|
43
|
-
|
44
|
-
|
140
|
+
alias_method :minute, :min
|
141
|
+
alias_method :second, :sec
|
142
|
+
alias_method :second_fraction, :sec_fraction
|
143
|
+
|
144
|
+
def to_s
|
145
|
+
format('%.4d-%02d-%02dT%02d:%02d:%02d%s', year, mon, mday, hour, min, sec, zone)
|
45
146
|
end
|
46
147
|
|
47
|
-
|
48
|
-
|
148
|
+
public :offset
|
149
|
+
|
150
|
+
def new_offset zone
|
151
|
+
offset = zone_to_offset zone
|
152
|
+
self.class.new! ajd, offset
|
49
153
|
end
|
50
154
|
|
51
|
-
def
|
52
|
-
|
155
|
+
def zone
|
156
|
+
o = offset * 24
|
157
|
+
format("%s%02d:%02d", (o >= 0 ? '+' : '-'), o.to_i, (o - o.to_i) * 60)
|
53
158
|
end
|
54
159
|
|
55
160
|
def strftime format='%Y/%m/%d %H:%M:%S'
|
56
161
|
gregorian.strftime super format
|
57
162
|
end
|
58
163
|
|
59
|
-
def
|
60
|
-
Date.new year, month, day
|
61
|
-
end
|
62
|
-
|
63
|
-
def to_gregorian
|
164
|
+
def gregorian
|
64
165
|
@gregorian ||= begin
|
65
|
-
::DateTime.
|
166
|
+
::DateTime.jd jd, hour, minute, second, zone
|
66
167
|
end
|
67
168
|
end
|
68
|
-
alias :
|
169
|
+
alias :to_gregorian :gregorian
|
69
170
|
|
70
|
-
def
|
71
|
-
|
72
|
-
DateTime.new date.year, date.month, date.day, hour, minute, second, offset
|
171
|
+
def to_time
|
172
|
+
gregorian.to_time
|
73
173
|
end
|
74
174
|
|
75
|
-
def
|
76
|
-
|
77
|
-
DateTime.new date.year, date.month, date.day, hour, minute, second, offset
|
175
|
+
def to_date
|
176
|
+
Date.new! jd_to_ajd(jd, 0, 0), 0
|
78
177
|
end
|
79
178
|
|
80
|
-
def
|
81
|
-
|
82
|
-
to_gregorian <=> other.to_gregorian
|
83
|
-
elsif other.is_a? ::Date
|
84
|
-
to_gregorian <=> other
|
85
|
-
else
|
86
|
-
raise ArgumentError.new "comparison of #{self.class} with #{other.class} failed"
|
87
|
-
end
|
179
|
+
def to_datetime
|
180
|
+
self
|
88
181
|
end
|
89
182
|
end
|
90
183
|
end
|
91
184
|
|
92
185
|
class DateTime
|
186
|
+
class << self
|
187
|
+
# Creates a DateTime object corresponding to the specified Jalali Date (+year+, +month+ and
|
188
|
+
# +day+) and time (+hour+, +minute+ and +second+) in given +zone+.
|
189
|
+
def parsi year=0, month=1, day=1, hour=0, minute=0, second=0, zone="00:00"
|
190
|
+
Parsi::DateTime.civil year, month, day, hour, minute, second, zone
|
191
|
+
end
|
192
|
+
alias :jalali :parsi
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns a Parsi::DateTime object representing same date in Jalali calendar
|
93
196
|
def to_parsi
|
94
|
-
|
95
|
-
Parsi::DateTime.new date.year, date.month, date.day, hour, minute, second, offset
|
197
|
+
Parsi::DateTime.new! ajd, offset
|
96
198
|
end
|
97
|
-
alias :to_persian :to_parsi
|
98
|
-
alias :to_jalali :to_parsi
|
99
|
-
alias :parsi :to_parsi
|
100
199
|
alias :jalali :to_parsi
|
200
|
+
alias :to_jalali :to_parsi
|
201
|
+
alias :to_persian :to_parsi
|
101
202
|
end
|
data/lib/version.rb
CHANGED
data/parsi-date.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.email = 'hsn.zamani@gmail.com'
|
12
12
|
s.homepage = 'http://github.com/hzamani/parsi-date'
|
13
13
|
s.summary = 'Solar Hijri (Jalali, Persian, Parsi) date library for Ruby'
|
14
|
-
s.description = "A date library for Ruby
|
14
|
+
s.description = "A Solar Hijri (Jalali) date library for Ruby, whitch provides much of Ruby's built-in date class"
|
15
15
|
|
16
16
|
|
17
17
|
# Load Paths...
|
@@ -53,14 +53,7 @@ end
|
|
53
53
|
|
54
54
|
describe "Parsi::Date#wday" do
|
55
55
|
it "determines the week day" do
|
56
|
-
Parsi::Date.civil(1391, 1, 17).wday.should ==
|
57
|
-
Parsi::Date.civil(1391, 8, 6).wday.should ==
|
56
|
+
Parsi::Date.civil(1391, 1, 17).wday.should == 4
|
57
|
+
Parsi::Date.civil(1391, 8, 6).wday.should == 6
|
58
58
|
end
|
59
59
|
end
|
60
|
-
|
61
|
-
describe "Parsi::Date#cwday" do
|
62
|
-
it "determines the commercial week day" do
|
63
|
-
Parsi::Date.civil(1391, 1, 17).cwday.should == 6
|
64
|
-
Parsi::Date.civil(1391, 8, 6).cwday.should == 1
|
65
|
-
end
|
66
|
-
end
|
@@ -18,14 +18,14 @@ describe "Parsi::Date#<=>" do
|
|
18
18
|
end
|
19
19
|
|
20
20
|
it "returns 0 when self is equal to a Numeric" do
|
21
|
-
(Parsi::Date.civil(1391, 4, 6) <=>
|
21
|
+
(Parsi::Date.civil(1391, 4, 6) <=> Rational(4912209,2)).should == 0
|
22
22
|
end
|
23
23
|
|
24
24
|
it "returns -1 when self is less than a Numeric" do
|
25
|
-
(Parsi::Date.civil(1391, 4, 6) <=>
|
25
|
+
(Parsi::Date.civil(1391, 4, 6) <=> Rational(4912210,2)).should == -1
|
26
26
|
end
|
27
27
|
|
28
28
|
it "returns 1 when self is greater than a Numeric" do
|
29
|
-
(Parsi::Date.civil(1391, 4, 6) <=>
|
29
|
+
(Parsi::Date.civil(1391, 4, 6) <=> Rational(4912208,2)).should == 1
|
30
30
|
end
|
31
31
|
end
|
@@ -3,23 +3,23 @@ require File.expand_path('../../spec_helper', __FILE__)
|
|
3
3
|
|
4
4
|
describe "Date constants" do
|
5
5
|
it "defines MONTHNAMES" do
|
6
|
-
Parsi::MONTHNAMES.should == [nil] + %w(فروردین اردیبهشت خرداد تیر مرداد شهریور مهر آبان آذر دی بهمن اسفند)
|
6
|
+
Parsi::Date::MONTHNAMES.should == [nil] + %w(فروردین اردیبهشت خرداد تیر مرداد شهریور مهر آبان آذر دی بهمن اسفند)
|
7
7
|
end
|
8
8
|
|
9
9
|
it "defines ABBR_MONTHNAMES" do
|
10
|
-
Parsi::ABBR_MONTHNAMES.should == [nil] + %w(Far Ord Kho Tir Mor Sha Meh Abn Azr Dey Bah Esf)
|
10
|
+
Parsi::Date::ABBR_MONTHNAMES.should == [nil] + %w(Far Ord Kho Tir Mor Sha Meh Abn Azr Dey Bah Esf)
|
11
11
|
end
|
12
12
|
|
13
13
|
it "defines DAYNAMES" do
|
14
|
-
Parsi::DAYNAMES.should == %w(
|
14
|
+
Parsi::Date::DAYNAMES.should == %w(یکشنده دوشنده سهشنده چهارشنده چنجشنده جمعه شنده )
|
15
15
|
end
|
16
16
|
|
17
17
|
it "defines ABBR_DAYNAMES" do
|
18
|
-
Parsi::ABBR_DAYNAMES.should == %w(
|
18
|
+
Parsi::Date::ABBR_DAYNAMES.should == %w(۱ش ۲ش ۳ش ۴ش ۵ش ج ش)
|
19
19
|
end
|
20
20
|
|
21
21
|
it "freezes MONTHNAMES, DAYNAMES, ABBR_MONTHNAMES, ABBR_DAYSNAMES" do
|
22
|
-
[Parsi::MONTHNAMES, Parsi::DAYNAMES, Parsi::ABBR_MONTHNAMES, Parsi::ABBR_DAYNAMES].each do |ary|
|
22
|
+
[Parsi::Date::MONTHNAMES, Parsi::Date::DAYNAMES, Parsi::Date::ABBR_MONTHNAMES, Parsi::Date::ABBR_DAYNAMES].each do |ary|
|
23
23
|
ary.should be_frozen
|
24
24
|
end
|
25
25
|
end
|
@@ -21,9 +21,9 @@ describe Parsi::Date do
|
|
21
21
|
expect { Parsi::Date.civil 1391, '1', 9 }.to raise_error(ArgumentError, 'invalid date')
|
22
22
|
end
|
23
23
|
|
24
|
-
it "constructs a Date for
|
24
|
+
it "constructs a Date for 1/1/1 by default" do
|
25
25
|
date = Parsi::Date.civil
|
26
|
-
date.year.should ==
|
26
|
+
date.year.should == 1
|
27
27
|
date.month.should == 1
|
28
28
|
date.day.should == 1
|
29
29
|
end
|
@@ -57,12 +57,4 @@ describe Parsi::Date do
|
|
57
57
|
expect { date = Parsi::Date.parse '12-30-1390' }.to raise_error(ArgumentError)
|
58
58
|
end
|
59
59
|
end
|
60
|
-
|
61
|
-
context "#today" do
|
62
|
-
it "initialize a date object with today's attributes" do
|
63
|
-
Date.stub(:today) { Date.new 2012, 10, 26 }
|
64
|
-
date = Parsi::Date.today
|
65
|
-
[date.year, date.month, date.day].should == [1391, 8, 5]
|
66
|
-
end
|
67
|
-
end
|
68
60
|
end
|
@@ -7,11 +7,11 @@ describe "Parsi::Date.jd" do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
it "returns a Date object representing Julian day 0 if no arguments passed"do
|
10
|
-
Parsi::Date.jd.should == Parsi::Date.civil(-
|
10
|
+
Parsi::Date.jd.should == Parsi::Date.civil(-5334, 9, 1)
|
11
11
|
end
|
12
12
|
|
13
13
|
it "constructs a Date object if passed a negative number" do
|
14
|
-
Parsi::Date.jd(-1).should == Parsi::Date.civil(-
|
14
|
+
Parsi::Date.jd(-1).should == Parsi::Date.civil(-5334, 8, 30)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -5,7 +5,6 @@ describe "Parsi::Date#strftime" do
|
|
5
5
|
|
6
6
|
it "should be able to print the date" do
|
7
7
|
Parsi::Date.civil(1390, 4, 6).strftime.should == "1390/04/06"
|
8
|
-
Parsi::Date.civil(1390, 4, 6).strftime.should == Parsi::Date.civil(1390, 4, 6).to_s
|
9
8
|
end
|
10
9
|
|
11
10
|
it "should be able to print the full day name" do
|
@@ -50,14 +49,9 @@ describe "Parsi::Date#strftime" do
|
|
50
49
|
Parsi::Date.civil(1390, 4, 6).strftime("%t").should == "\t"
|
51
50
|
end
|
52
51
|
|
53
|
-
it "should be able to show the commercial week day" do
|
54
|
-
Parsi::Date.civil(1390, 4, 10).strftime("%u").should == "7"
|
55
|
-
Parsi::Date.civil(1390, 4, 11).strftime("%u").should == "1"
|
56
|
-
end
|
57
|
-
|
58
52
|
it "should be able to show the week day" do
|
59
|
-
Parsi::Date.civil(1390, 4,
|
60
|
-
Parsi::Date.civil(1390, 4,
|
53
|
+
Parsi::Date.civil(1390, 4, 11).strftime("%w").should == "6"
|
54
|
+
Parsi::Date.civil(1390, 4, 12).strftime("%w").should == "0"
|
61
55
|
end
|
62
56
|
|
63
57
|
it "should be able to show the year in YYYY format" do
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parsi-date
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.2'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -59,8 +59,8 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
-
description: A date library for Ruby
|
63
|
-
|
62
|
+
description: A Solar Hijri (Jalali) date library for Ruby, whitch provides much of
|
63
|
+
Ruby's built-in date class
|
64
64
|
email: hsn.zamani@gmail.com
|
65
65
|
executables: []
|
66
66
|
extensions: []
|