parsi-date 0.1 → 0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|