jalali-date 0.3.7
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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +43 -0
- data/LICENSE +21 -0
- data/README.md +247 -0
- data/Rakefile +2 -0
- data/TODO +7 -0
- data/bin/jcal +6 -0
- data/bin/jdate +6 -0
- data/jalalidate.gemspec +25 -0
- data/lib/jalali_date/cli.rb +36 -0
- data/lib/jalali_date/version.rb +3 -0
- data/lib/jalali_date.rb +372 -0
- data/lib/jalali_date_parser.rb +182 -0
- data/spec/jalali_date_spec.rb +158 -0
- data/spec/spec_helper.rb +9 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a6cf12c626b443c3634bd78cb2e329ddf5b01ef4f907f7b16503a71bdafd4cd4
|
|
4
|
+
data.tar.gz: c9c43f0171b726aa0c6e9fbc3f607c3b6883e01f61a8a9abdda9c0d8636c021a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4b813c10502d8adf45e6cf11ea3bc278187f69acfa19b26ce5590c58c902442396e97a369598d584646056611d6ea3c6c2d696955e04b79bdd0e741ead43fb8b
|
|
7
|
+
data.tar.gz: 68a795aff57af3404af120fc3ea22f256186c2c8781665a8406d30be4410bc4fe375dc06732e2007081248b6f5767090a61a40138f3b320c211db60f2d2d5aa9
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
jalali-date (0.3.7)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: http://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
diff-lcs (1.6.2)
|
|
10
|
+
rspec (3.13.2)
|
|
11
|
+
rspec-core (~> 3.13.0)
|
|
12
|
+
rspec-expectations (~> 3.13.0)
|
|
13
|
+
rspec-mocks (~> 3.13.0)
|
|
14
|
+
rspec-core (3.13.6)
|
|
15
|
+
rspec-support (~> 3.13.0)
|
|
16
|
+
rspec-expectations (3.13.5)
|
|
17
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
18
|
+
rspec-support (~> 3.13.0)
|
|
19
|
+
rspec-mocks (3.13.8)
|
|
20
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
21
|
+
rspec-support (~> 3.13.0)
|
|
22
|
+
rspec-support (3.13.7)
|
|
23
|
+
|
|
24
|
+
PLATFORMS
|
|
25
|
+
ruby
|
|
26
|
+
x86_64-linux
|
|
27
|
+
|
|
28
|
+
DEPENDENCIES
|
|
29
|
+
bundler (>= 1.16)
|
|
30
|
+
jalali-date!
|
|
31
|
+
rspec (~> 3.0)
|
|
32
|
+
|
|
33
|
+
CHECKSUMS
|
|
34
|
+
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
35
|
+
jalali-date (0.3.7)
|
|
36
|
+
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
|
|
37
|
+
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
|
38
|
+
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
|
39
|
+
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
|
|
40
|
+
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
|
41
|
+
|
|
42
|
+
BUNDLED WITH
|
|
43
|
+
4.0.10
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2011 Allen A. Bargi <http://github.com/aziz>
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person ob-
|
|
4
|
+
taining a copy of this software and associated documentation
|
|
5
|
+
files (the "Software"), to deal in the Software without restric-
|
|
6
|
+
tion, including without limitation the rights to use, copy, modi-
|
|
7
|
+
fy, merge, publish, distribute, sublicense, and/or sell copies of
|
|
8
|
+
the Software, and to permit persons to whom the Software is fur-
|
|
9
|
+
nished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
16
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONIN-
|
|
17
|
+
FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
19
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Jalali Date Library
|
|
2
|
+
A lightweight Ruby library for converting, formatting, and parsing Jalali (جلالی/Persian/Shamsi) dates.
|
|
3
|
+
|
|
4
|
+
Jalalidate provides simple APIs for:
|
|
5
|
+
|
|
6
|
+
* Gregorian → Jalali conversion
|
|
7
|
+
* Jalali → Gregorian conversion
|
|
8
|
+
* Jalali date formatting
|
|
9
|
+
* Parsing Persian date strings
|
|
10
|
+
* Working with Persian numerals and month names
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
Add this line to your application's Gemfile:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem 'jalali-date'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
And then execute:
|
|
20
|
+
|
|
21
|
+
$ bundle install
|
|
22
|
+
|
|
23
|
+
Or install it manually:
|
|
24
|
+
|
|
25
|
+
$ gem install jalali-date
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
Date conversion and class methods:
|
|
29
|
+
```ruby
|
|
30
|
+
require 'jalali_date'
|
|
31
|
+
|
|
32
|
+
# Create a jalali date object:
|
|
33
|
+
j_date = JalaliDate.new(1402,02,25)
|
|
34
|
+
#=> <JalaliDate, :year => 1402, :month => 2, :day => 25 >
|
|
35
|
+
|
|
36
|
+
j_date = JalaliDate.new(Date.today)
|
|
37
|
+
#=> <JalaliDate, :year => 1402, :month => 2, :day => 25 >
|
|
38
|
+
|
|
39
|
+
# Initialize with hour, minute and second
|
|
40
|
+
j_date = JalaliDate.new(Time.now)
|
|
41
|
+
#=> <JalaliDate, :year => 1402, :month => 2, :day => 25, :hour => 6, :minute => 01, :second => 30 >
|
|
42
|
+
|
|
43
|
+
# Convert to gregorian date
|
|
44
|
+
g_date = j_date.to_gregorian
|
|
45
|
+
g_date = j_date.to_g
|
|
46
|
+
#=> Mon, 15 May 2023
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Get yesterday, today, or tomorrow in jalali
|
|
50
|
+
j_today = JalaliDate.yesterday
|
|
51
|
+
#=> <JalaliDate, :year => 1402, :month => 2, :day => 24 >
|
|
52
|
+
j_today = JalaliDate.today
|
|
53
|
+
#=> <JalaliDate, :year => 1402, :month => 2, :day => 25 >
|
|
54
|
+
j_tomorrow = JalaliDate.tomorrow
|
|
55
|
+
#=> <JalaliDate, :year => 1402, :month => 2, :day => 26 >
|
|
56
|
+
|
|
57
|
+
# Get year, month, and day
|
|
58
|
+
year = j_date.year #=> 1402
|
|
59
|
+
month = j_date.month #=> 02
|
|
60
|
+
day = j_date.day #=> 25
|
|
61
|
+
|
|
62
|
+
# Get gregorian year, month, and day
|
|
63
|
+
g_year = j_date.g_year #=> 2023
|
|
64
|
+
g_month = j_date.g_month #=> 5
|
|
65
|
+
g_day = j_date.g_day #=> 15
|
|
66
|
+
|
|
67
|
+
# Get day of the year
|
|
68
|
+
dy = j_date.yday #=> 51
|
|
69
|
+
|
|
70
|
+
# Get the next day
|
|
71
|
+
tomorrow = j_date.next
|
|
72
|
+
#=> <JalaliDate, :year => 1402, :month => 2, :day => 26 >
|
|
73
|
+
|
|
74
|
+
# Add and substract days
|
|
75
|
+
j_date + 6.days
|
|
76
|
+
#=> <JalaliDate, :year => 1402, :month => 2, :day => 31 >
|
|
77
|
+
j_date + 1.month
|
|
78
|
+
#=> <JalaliDate, :year => 1402, :month => 3, :day => 25 >
|
|
79
|
+
j_date + 2.years + 5.month + 12.days
|
|
80
|
+
#=> <JalaliDate, :year => 1404, :month => 8, :day => 5 >
|
|
81
|
+
|
|
82
|
+
# Shift the date with number of month
|
|
83
|
+
j_date >> 5
|
|
84
|
+
#=> <JalaliDate, :year => 1402, :month => 7, :day => 25 >
|
|
85
|
+
j_date << 5
|
|
86
|
+
#=> <JalaliDate, :year => 1401, :month => 9, :day => 25 >
|
|
87
|
+
|
|
88
|
+
# Compare two dates
|
|
89
|
+
j_date < j_date.next #=> true
|
|
90
|
+
j_date > j_date.next #=> false
|
|
91
|
+
j_date > j_date.previous #=> true
|
|
92
|
+
j_date <=> j_date.next #=> -1
|
|
93
|
+
j_date <=> j_date #=> 0
|
|
94
|
+
j_date <=> j_date.previous #=> 1
|
|
95
|
+
|
|
96
|
+
# Cycle through dates in different ways, namely, step, upto and downto
|
|
97
|
+
# every 2 days, including j_date, for the next 10 days
|
|
98
|
+
j_date.step( j_date + 10 , 2)
|
|
99
|
+
|
|
100
|
+
# every day, including j_date and the next 5 days
|
|
101
|
+
j_date.upto(j_date + 5)
|
|
102
|
+
|
|
103
|
+
# every day, including j_date and the previous 5 days
|
|
104
|
+
j_date.downto(j_date - 5)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# Get day object as array or hash
|
|
108
|
+
j_date.to_a #=> [1405, 2, 25]
|
|
109
|
+
j_date.to_hash #=> {year: 1405, month: 2, day: 25}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Check for leap year
|
|
113
|
+
JalaliDate.leap? 1402
|
|
114
|
+
#=> false
|
|
115
|
+
|
|
116
|
+
# Check for validity of a jalali date
|
|
117
|
+
JalaliDate.valid?(1402,02,25)
|
|
118
|
+
#=> true
|
|
119
|
+
|
|
120
|
+
JalaliDate.valid?(1402,12,30)
|
|
121
|
+
#=> false
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Date formatting:
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# Initialize with hour, minute and second
|
|
128
|
+
j_date = JalaliDate.new(Time.now)
|
|
129
|
+
#=> <JalaliDate, :year => 1402, :month => 2, :day => 25, :hour => 6, :minute => 01, :second => 30 >
|
|
130
|
+
|
|
131
|
+
j_date.strftime("%a %A %b %B %d %e %j %m %w %y %Y %% %x")
|
|
132
|
+
#=> "۲ش دوشنبه اردیبهشت Ordibehesht 25 25 56 2 1 02 1402 % 02/2/25"
|
|
133
|
+
|
|
134
|
+
j_date.strftime("%X")
|
|
135
|
+
#=> "06:01:30"
|
|
136
|
+
|
|
137
|
+
j_date.strftime("%H:%M - %Y/%n/%d")
|
|
138
|
+
#=> "06:01 - 1402/02/25"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Format meanings:
|
|
142
|
+
|
|
143
|
+
- `[%a]` The abbreviated weekday name (۳ش)
|
|
144
|
+
- `[%A]` The full weekday name (یکشنبه)
|
|
145
|
+
- `[%b]` The month name (اردیبهشت)
|
|
146
|
+
- `[%B]` The month name in pinglish (Ordibehesht)
|
|
147
|
+
- `[%d]` Day of the month (01..31)
|
|
148
|
+
- `[%e]` Day of the month (1..31)
|
|
149
|
+
- `[%j]` Day of the year (1..366)
|
|
150
|
+
- `[%m]` Month of the year (1..12)
|
|
151
|
+
- `[%n]` Month of the year (01..12)
|
|
152
|
+
- `[%w]` Day of the week (Sunday is 0, 0..6)
|
|
153
|
+
- `[%x]` Preferred representation for the date alone, no time in format YY/M/D
|
|
154
|
+
- `[%y]` Year without a century (00..99)
|
|
155
|
+
- `[%Y]` Year with century
|
|
156
|
+
- `[%H]` Hour of the day, 24-hour clock (00..23)
|
|
157
|
+
- `[%I]` Hour of the day, 12-hour clock (01..12)
|
|
158
|
+
- `[%M]` Minute of the hour (00..59)
|
|
159
|
+
- `[%p]` Meridian indicator ("بعد از ظهر" or "قبل از ظهر")
|
|
160
|
+
- `[%P]` Meridian indicator ("ب.ظ" or "ق.ظ")
|
|
161
|
+
- `[%S]` Second of the minute (00..60)
|
|
162
|
+
- `[%X]` Preferred representation for the time alone, no date
|
|
163
|
+
- `[%Z]` Time zone name
|
|
164
|
+
- `[%%]` teral %'' character
|
|
165
|
+
|
|
166
|
+
## Parsing Jalali Dates
|
|
167
|
+
|
|
168
|
+
The parser supports multiple common Persian date formats.
|
|
169
|
+
|
|
170
|
+
### Numeric Formats
|
|
171
|
+
```ruby
|
|
172
|
+
JalaliDate.parse('1402/02/25')
|
|
173
|
+
JalaliDate.parse('1402-02-25')
|
|
174
|
+
JalaliDate.parse('1402.02.25')
|
|
175
|
+
```
|
|
176
|
+
### Persian Month Names
|
|
177
|
+
```ruby
|
|
178
|
+
JalaliDate.parse('25 اردیبهشت 1402')
|
|
179
|
+
JalaliDate.parse('اردیبهشت 25 1402')
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### DateTime Parsing
|
|
183
|
+
```ruby
|
|
184
|
+
JalaliDate.parse('1402/02/25 06:01')
|
|
185
|
+
JalaliDate.parse('1402/02/25 06:01:30')
|
|
186
|
+
JalaliDate.parse('25 اردیبهشت ۱۴۰۲ ساعت ۰۶:۰۱')
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Persian Digits
|
|
190
|
+
|
|
191
|
+
The parser automatically normalizes Persian and Arabic numerals:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
JalaliDate.parse('۱۴۰۲/۰۲/۲۵')
|
|
195
|
+
JalaliDate.parse('١٤٠۲/٠٢/٢٥')
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Tests
|
|
199
|
+
Simply run `rspec` command in the source directory:
|
|
200
|
+
|
|
201
|
+
$ bundle exec rspec
|
|
202
|
+
|
|
203
|
+
## History
|
|
204
|
+
|
|
205
|
+
#### 0.3.3 - 17.SEP.2013
|
|
206
|
+
* added %n formatter for numeric representation of a month, with leading zeros, courtesy of [Mohsen Alizadeh](https://github.com/m0h3n)
|
|
207
|
+
|
|
208
|
+
#### 0.3.2 - 8.APR.2013
|
|
209
|
+
* Making JalaliDate class thread safe, courtesy of [Jahangir Zinedine](https://github.com/jzinedine)
|
|
210
|
+
|
|
211
|
+
#### 0.3.1 - 26.APR.2011
|
|
212
|
+
* Added ruby 1.9 compatibility, courtesy of [Reza](https://github.com/ryco)
|
|
213
|
+
|
|
214
|
+
#### 0.3 - 6.JAN.2011
|
|
215
|
+
* JalaiDate could be initialized with Time and DateTime objects
|
|
216
|
+
* More options for strftime method %H,%M,%S,%X,%Z,%I,%p. read docs for more information
|
|
217
|
+
* Added jdate and jcal binaries to access jcal from the command-line
|
|
218
|
+
* Updated some documentations
|
|
219
|
+
* Now using bundler
|
|
220
|
+
|
|
221
|
+
#### 0.2 - 25.FEB.2010
|
|
222
|
+
* Renamed the gem from JalaliDate to jalalidate
|
|
223
|
+
* Added spec and a full test suite
|
|
224
|
+
* Updated gemspec file for rubygems.org
|
|
225
|
+
* Updated some documentations
|
|
226
|
+
|
|
227
|
+
#### 0.02 - 8.AUG.2008
|
|
228
|
+
* Added jalali to geregorian date convertor.
|
|
229
|
+
* Added JalaliDate class and ported Date class method to JalaliDate
|
|
230
|
+
|
|
231
|
+
#### 0.01 - 7.AUG.2008
|
|
232
|
+
* Planning the project
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
## Note on Patches/Pull Requests
|
|
236
|
+
|
|
237
|
+
* Fork the project.
|
|
238
|
+
* Make your feature addition or bug fix.
|
|
239
|
+
* Add tests for it. This is important so I don't break it in a
|
|
240
|
+
future version unintentionally.
|
|
241
|
+
* Commit, do not mess with rakefile, version, or history.
|
|
242
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
|
243
|
+
* Send me a pull request. Bonus points for topic branches.
|
|
244
|
+
|
|
245
|
+
## Copyright
|
|
246
|
+
|
|
247
|
+
Copyright (c) 2008-2011 Allen A. Bargi. See LICENSE for details.
|
data/Rakefile
ADDED
data/TODO
ADDED
data/bin/jcal
ADDED
data/bin/jdate
ADDED
data/jalalidate.gemspec
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "jalali_date/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "jalali-date"
|
|
7
|
+
s.version = JalaliDate::VERSION
|
|
8
|
+
s.platform = Gem::Platform::RUBY
|
|
9
|
+
s.authors = ["Allen A. Bargi", "Loqman Hakimi"]
|
|
10
|
+
s.email = %q{allen.bargi@gmail.com loqmanhakimi@gmail.com}
|
|
11
|
+
|
|
12
|
+
s.homepage = %q{http://github.com/loqman/jalalidate}
|
|
13
|
+
s.license = "MIT"
|
|
14
|
+
s.summary = %q{A library for working with Jalali Calendar (a.k.a Persian Calendar)}
|
|
15
|
+
s.required_ruby_version = '>= 3.0'
|
|
16
|
+
s.files = `git ls-files`.split("\n")
|
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
s.extra_rdoc_files = [ "LICENSE", "README.md"]
|
|
21
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
|
22
|
+
|
|
23
|
+
s.add_development_dependency 'bundler', '>= 1.16'
|
|
24
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
|
25
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
class JalaliDate
|
|
2
|
+
class Cli
|
|
3
|
+
|
|
4
|
+
# prints today's date in jalali calendar to STDOUT
|
|
5
|
+
#
|
|
6
|
+
def self.jdate(*args)
|
|
7
|
+
jdate = JalaliDate.new(Date.today)
|
|
8
|
+
puts jdate
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# prints current month calendar in jalali calendar to STDOUT
|
|
12
|
+
#
|
|
13
|
+
def self.jcal(*args)
|
|
14
|
+
today = JalaliDate.new(Date.today)
|
|
15
|
+
jdate = JalaliDate.new(today.year,today.month,1)
|
|
16
|
+
# print month and year
|
|
17
|
+
puts jdate.strftime("%b %Y").center(26)
|
|
18
|
+
# print weekdays
|
|
19
|
+
puts JalaliDate::PERSIAN_ABBR_WEEKDAY_NAMES.reverse[1..6].join(" ") + " " + JalaliDate::PERSIAN_ABBR_WEEKDAY_NAMES.reverse[0] + " "
|
|
20
|
+
# print the month days
|
|
21
|
+
padding = true
|
|
22
|
+
JalaliDate::JDaysInMonth[jdate.month - 1].times do |index|
|
|
23
|
+
if padding
|
|
24
|
+
print " " * (jdate.jwday*4)
|
|
25
|
+
padding = false
|
|
26
|
+
end
|
|
27
|
+
print "%2d" % jdate.day + " "
|
|
28
|
+
print "\n" if jdate.jwday == 6
|
|
29
|
+
jdate = jdate.next
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
puts "\n"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/jalali_date.rb
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# :title:Jalali Date #
|
|
3
|
+
if RUBY_VERSION < '1.9'
|
|
4
|
+
require "jcode"
|
|
5
|
+
$KCODE = 'u'
|
|
6
|
+
end
|
|
7
|
+
require "date"
|
|
8
|
+
require "jalali_date_parser"
|
|
9
|
+
|
|
10
|
+
class JalaliDate
|
|
11
|
+
|
|
12
|
+
#:stopdoc:
|
|
13
|
+
GDaysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
14
|
+
JDaysInMonth = [31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29]
|
|
15
|
+
PERSIAN_MONTH_NAMES = [nil, "فروردین","اردیبهشت","خرداد","تیر","مرداد","شهریور","مهر","آبان","آذر","دی","بهمن","اسفند"]
|
|
16
|
+
PERSIAN_MONTH_NAMES_PINGLISH = [nil, "Farvardin", "Ordibehesht", "Khordad", "Tir", "Mordad", "Shahrivar", "Mehr", "Aban", "Azar", "Dey", "Bahman", "Esfand"]
|
|
17
|
+
PERSIAN_WEEKDAY_NAMES = ["یکشنبه","دوشنبه","سهشنبه","چهارشنبه","پنجشنبه","جمعه","شنبه"]
|
|
18
|
+
PERSIAN_ABBR_WEEKDAY_NAMES = ["۱ش ","۲ش","۳ش","۴ش","۵ش","ج ","ش"]
|
|
19
|
+
PERSIAN_MERIDIAN_INDICATOR = ["قبل از ظهر","بعد از ظهر"]
|
|
20
|
+
PERSIAN_ABBR_MERIDIAN_INDICATOR = ["ق.ظ","ب.ظ"]
|
|
21
|
+
#:startdoc:
|
|
22
|
+
|
|
23
|
+
include Comparable
|
|
24
|
+
|
|
25
|
+
attr_accessor :year,:month,:day, :hour, :min, :sec
|
|
26
|
+
attr_reader :g_year, :g_month, :g_day
|
|
27
|
+
|
|
28
|
+
# Can be initialized in two ways:
|
|
29
|
+
# - First by feeding 3 arguments for Jalali Date, year,month and day.
|
|
30
|
+
# - The Second way to initializes is to pass a normal Ruby Date object, it'll be converted to jalali automatically.
|
|
31
|
+
#
|
|
32
|
+
# Example:
|
|
33
|
+
# jdate = JalaliDate.new(Date.today)
|
|
34
|
+
# other_jdate = JalaliDate.new(1388,9,17)
|
|
35
|
+
def initialize *args
|
|
36
|
+
if (args.size == 1) && (args.first.is_a?(Date))
|
|
37
|
+
year,month,day = gregorian_to_jalali(args.first.year, args.first.month, args.first.day)
|
|
38
|
+
elsif (args.size == 1) && (args.first.is_a?(Time) || args.first.is_a?(DateTime))
|
|
39
|
+
year,month,day = gregorian_to_jalali(args.first.year, args.first.month, args.first.day)
|
|
40
|
+
@hour = args.first.hour || 0
|
|
41
|
+
@min = args.first.min || 0
|
|
42
|
+
@sec = args.first.sec || 0
|
|
43
|
+
@zone = args.first.zone || "UTC"
|
|
44
|
+
@utc_offset = args.first.utc_offset || 0
|
|
45
|
+
else
|
|
46
|
+
year,month,day,hour,min,sec,zone,utc_offset = args
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
raise ArgumentError, "invalid arguments or invalid Jalali date" unless self.class.valid?(year,month,day)
|
|
50
|
+
@year = year
|
|
51
|
+
@month = month
|
|
52
|
+
@day = day
|
|
53
|
+
@hour ||= hour || 0
|
|
54
|
+
@min ||= min || 0
|
|
55
|
+
@sec ||= sec || 0
|
|
56
|
+
@zone ||= zone || "UTC"
|
|
57
|
+
@utc_offset ||= utc_offset || 0
|
|
58
|
+
@g_year, @g_month, @g_day = jalali_to_gregorian(year,month,day)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Class Methods -----------------------------------------------------------
|
|
62
|
+
class << self
|
|
63
|
+
# Return a JalaliDate object representing today's date in calendar
|
|
64
|
+
def today
|
|
65
|
+
JalaliDate.new(Date.today)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Return a JalaliDate object representing yesterday's date in calendar
|
|
69
|
+
def yesterday
|
|
70
|
+
JalaliDate.new(Date.today - 1)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Return a JalaliDate object representing tomorrow's date in calendar
|
|
74
|
+
def tomorrow
|
|
75
|
+
JalaliDate.new(Date.today + 1)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Return JalaliDate from a provided string
|
|
79
|
+
def parse(s)
|
|
80
|
+
date = JalaliDateParser.parse(s)
|
|
81
|
+
date[:jalali]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Accpets a four digit number as the jalaliyear and returns true if that particular year
|
|
85
|
+
# is a leap year in jalali calendar otherwise it returns false.
|
|
86
|
+
def leap?(y)
|
|
87
|
+
[6,22,17,13,9,5,1,30].include?(y%33) ? true : false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Accpets three numbers for year (4 digit), month and day in jalali calendar and checks if it's a
|
|
91
|
+
# valid date according to jalali calendar or not.
|
|
92
|
+
def valid?(y,m,d)
|
|
93
|
+
( y.class == Integer && y > 0 &&
|
|
94
|
+
m.class == Integer && (1..12).include?(m) &&
|
|
95
|
+
d.class == Integer &&
|
|
96
|
+
(
|
|
97
|
+
((1..JDaysInMonth[m-1]).include?(d)) || (d == 30 && m == 12 && leap?(y))
|
|
98
|
+
)
|
|
99
|
+
) ? true : false
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Instance Methods --------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
# Converts a JalaiDate object to Ruby Date object
|
|
106
|
+
def to_gregorian
|
|
107
|
+
Date.new(@g_year,@g_month,@g_day)
|
|
108
|
+
end
|
|
109
|
+
alias :to_g :to_gregorian
|
|
110
|
+
|
|
111
|
+
# Returns a string represtation of the JalaliDate object in format like this: y/m/d
|
|
112
|
+
def to_s
|
|
113
|
+
[@year,@month,@day].join("/")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Returns a hash in a format like this: {:year => @year, :month => @month, :day => @day}
|
|
117
|
+
def to_hash
|
|
118
|
+
{:year => @year, :month => @month, :day => @day}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Returns an array in a format like this: [y,m,d]
|
|
122
|
+
def to_a
|
|
123
|
+
[@year,@month,@day]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Return internal object state as a programmer-readable string.
|
|
127
|
+
def inspect
|
|
128
|
+
"#<#{self.class}:#{self.object_id}, :year => #{@year}, :month => #{@month}, :day => #{@day} >"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Adds n days to the current JalaliDate object
|
|
132
|
+
def +(days)
|
|
133
|
+
self.class.new( to_g + days )
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Subtracts n days from the current JalaliDate object
|
|
137
|
+
def -(days)
|
|
138
|
+
self.class.new( to_g - days )
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Return the next day for the current JalaliDate object
|
|
142
|
+
def next(n=1)
|
|
143
|
+
self + n
|
|
144
|
+
end
|
|
145
|
+
alias :succ :next
|
|
146
|
+
|
|
147
|
+
# Return the previous day for the current JalaliDate object
|
|
148
|
+
def previous(n=1)
|
|
149
|
+
self - n
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Compares two JalaliDate objects. acts like Date#<=>
|
|
153
|
+
def <=>(other)
|
|
154
|
+
to_g <=> other.to_g
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Move JalaliDate object forward by n months
|
|
158
|
+
def >>(months)
|
|
159
|
+
y, m = (@year * 12 + (@month - 1) + months).divmod(12)
|
|
160
|
+
m, = (m + 1) .divmod(1)
|
|
161
|
+
d = @day
|
|
162
|
+
d -= 1 until self.class.valid?(y, m, d)
|
|
163
|
+
self.class.new(y,m,d)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Move JalaliDate object backward by n months
|
|
167
|
+
def <<(months)
|
|
168
|
+
self >> -months
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Step the current date forward +step+ days at a time (or backward, if step is negative) until we reach
|
|
172
|
+
# limit (inclusive), yielding the resultant date at each step.
|
|
173
|
+
#
|
|
174
|
+
# Example:
|
|
175
|
+
# jdate = JalaliDate.new(Date.today)
|
|
176
|
+
# jdate.step(Date.today+10, 2) do |jd|
|
|
177
|
+
# puts jd.to_s
|
|
178
|
+
# end
|
|
179
|
+
def step(limit, step=1)
|
|
180
|
+
da = self
|
|
181
|
+
op = %w(- <= >=)[step <=> 0]
|
|
182
|
+
while da.__send__(op, limit)
|
|
183
|
+
yield da
|
|
184
|
+
da += step
|
|
185
|
+
end
|
|
186
|
+
self
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Step forward one day at a time until we reach max (inclusive), yielding each date as we go.
|
|
190
|
+
#
|
|
191
|
+
# Example:
|
|
192
|
+
# jdate = JalaliDate.new(Date.today)
|
|
193
|
+
# days_string = ""
|
|
194
|
+
# jdate.upto(jdate+5) do |jd|
|
|
195
|
+
# days_string += jd.day.to_s
|
|
196
|
+
# end
|
|
197
|
+
def upto(max, &block)
|
|
198
|
+
step(max, +1, &block)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Step backward one day at a time until we reach min (inclusive), yielding each date as we go.
|
|
202
|
+
# See #upto for the example.
|
|
203
|
+
def downto(min, &block)
|
|
204
|
+
step(min, -1, &block)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Is this a leap year?
|
|
208
|
+
def leap?
|
|
209
|
+
self.class.leap?(@year)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Get the week day of this date. Sunday is day-of-week 0; Saturday is day-of-week 6.
|
|
213
|
+
def wday
|
|
214
|
+
to_g.wday
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Get the jalali week day of this date. Saturday is day-of-week 0; Friday is day-of-week 6.
|
|
218
|
+
def jwday
|
|
219
|
+
(to_g.wday + 1) % 7
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Get the day-of-the-year of this date.
|
|
223
|
+
# Farvardin 1 is day-of-the-year 1
|
|
224
|
+
def yday
|
|
225
|
+
m = (@month-2 < 0) ? 0 : @month-2
|
|
226
|
+
(@month==1) ? @day : @day + JDaysInMonth[0..m].inject(0) {|sum, n| sum + n }
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Formats time according to the directives in the given format string. Any text not listed as a directive will be
|
|
230
|
+
# passed through to the output string.
|
|
231
|
+
#
|
|
232
|
+
# Format meanings:
|
|
233
|
+
#
|
|
234
|
+
# [%a] The abbreviated weekday name (۳ش)
|
|
235
|
+
# [%A] The full weekday name (یکشنبه)
|
|
236
|
+
# [%b] The month name (اردیبهشت)
|
|
237
|
+
# [%B] The month name in pinglish (Ordibehesht)
|
|
238
|
+
# [%d] Day of the month (01..31)
|
|
239
|
+
# [%e] Day of the month (1..31)
|
|
240
|
+
# [%j] Day of the year (1..366)
|
|
241
|
+
# [%m] Month of the year (1..12)
|
|
242
|
+
# [%n] Month of the year (01..12)
|
|
243
|
+
# [%w] Day of the week (Sunday is 0, 0..6)
|
|
244
|
+
# [%x] Preferred representation for the date alone, no time in format YY/M/D
|
|
245
|
+
# [%y] Year without a century (00..99)
|
|
246
|
+
# [%Y] Year with century
|
|
247
|
+
# [%H] Hour of the day, 24-hour clock (00..23)
|
|
248
|
+
# [%I] Hour of the day, 12-hour clock (01..12)
|
|
249
|
+
# [%M] Minute of the hour (00..59)
|
|
250
|
+
# [%p] Meridian indicator ("بعد از ظهر" or "قبل از ظهر")
|
|
251
|
+
# [%P] Meridian indicator ("ب.ظ" or "ق.ظ")
|
|
252
|
+
# [%S] Second of the minute (00..60)
|
|
253
|
+
# [%X] Preferred representation for the time alone, no date
|
|
254
|
+
# [%Z] Time zone name
|
|
255
|
+
# [%%] Literal %'' character
|
|
256
|
+
#
|
|
257
|
+
# Example:
|
|
258
|
+
# d = JalaliDate.today
|
|
259
|
+
# d.strftime("Printed on %Y/%m/%d") #=> "Printed on 87/5/26
|
|
260
|
+
def strftime(format_str = '%Y/%m/%d')
|
|
261
|
+
clean_fmt = format_str.gsub(/%{2}/, "SUBSTITUTION_MARKER").
|
|
262
|
+
gsub(/%a/, PERSIAN_ABBR_WEEKDAY_NAMES[wday]).
|
|
263
|
+
gsub(/%A/, PERSIAN_WEEKDAY_NAMES[wday]).
|
|
264
|
+
gsub(/%b/, PERSIAN_MONTH_NAMES[@month]).
|
|
265
|
+
gsub(/%B/, PERSIAN_MONTH_NAMES_PINGLISH[@month]).
|
|
266
|
+
gsub(/%d/, ("%02d" % @day).to_s).
|
|
267
|
+
gsub(/%e/, @day.to_s).
|
|
268
|
+
gsub(/%m/, @month.to_s).
|
|
269
|
+
gsub(/%n/, ("%02d" % @month).to_s).
|
|
270
|
+
gsub(/%Y/, @year.to_s).
|
|
271
|
+
gsub(/%y/, @year.to_s.slice(2,2)).
|
|
272
|
+
gsub(/%j/, yday.to_s).
|
|
273
|
+
gsub(/%H/, ("%02d" % @hour).to_s).
|
|
274
|
+
gsub(/%I/, ("%02d" % ((@hour>=12) ? @hour-12 : @hour)).to_s).
|
|
275
|
+
gsub(/%M/, ("%02d" % @min).to_s).
|
|
276
|
+
gsub(/%S/, ("%02d" % @sec).to_s).
|
|
277
|
+
gsub(/%p/, (@hour>=12 ? "بعد از ظهر" : "قبل از ظهر")).
|
|
278
|
+
gsub(/%P/, (@hour>=12 ? "ب.ظ" : "ق.ظ")).
|
|
279
|
+
gsub(/%w/, wday.to_s).
|
|
280
|
+
gsub(/%Z/, @zone).
|
|
281
|
+
gsub(/%X/, [("%02d" % @hour),("%02d" % @min),("%02d" % @sec)].join(":")).
|
|
282
|
+
gsub(/%x/, [@year.to_s.slice(2,2),@month,@day].join("/")).
|
|
283
|
+
gsub(/#{"SUBSTITUTION_MARKER"}/, '%')
|
|
284
|
+
end
|
|
285
|
+
alias :format :strftime
|
|
286
|
+
|
|
287
|
+
private #-------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
def gregorian_to_jalali(year, month, day) # :nodoc:
|
|
290
|
+
jj=0
|
|
291
|
+
gy = year - 1600
|
|
292
|
+
gm = month - 1
|
|
293
|
+
gd = day - 1
|
|
294
|
+
g_day_no = 365*gy + (gy+3)/4 - (gy+99)/100 + (gy+399)/400
|
|
295
|
+
gm.times { |i| g_day_no += GDaysInMonth[i] }
|
|
296
|
+
g_day_no += 1 if gm > 1 && ((gy%4 == 0 && gy%100 != 0) || (gy%400 == 0))
|
|
297
|
+
g_day_no += gd
|
|
298
|
+
|
|
299
|
+
j_day_no = g_day_no-79
|
|
300
|
+
j_np = j_day_no/12053
|
|
301
|
+
j_day_no %= 12053
|
|
302
|
+
jy = 979 + 33 * j_np + 4*(j_day_no/1461)
|
|
303
|
+
j_day_no %= 1461
|
|
304
|
+
|
|
305
|
+
if (j_day_no >= 366)
|
|
306
|
+
jy += (j_day_no - 1)/365
|
|
307
|
+
j_day_no = (j_day_no - 1) % 365
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
11.times do |i|
|
|
311
|
+
if j_day_no >= JDaysInMonth[i]
|
|
312
|
+
j_day_no -= JDaysInMonth[i]
|
|
313
|
+
jj = i + 1
|
|
314
|
+
else
|
|
315
|
+
jj = i
|
|
316
|
+
break
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
jm = jj + 1
|
|
320
|
+
jd = j_day_no + 1
|
|
321
|
+
|
|
322
|
+
[jy, jm, jd]
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def jalali_to_gregorian(year,month,day) # :nodoc:
|
|
326
|
+
gg=0
|
|
327
|
+
jy = year - 979
|
|
328
|
+
jm = month - 1
|
|
329
|
+
jd = day - 1
|
|
330
|
+
j_day_no = 365*jy + (jy/33)*8 + (jy % 33 + 3)/4
|
|
331
|
+
jm.times { |i| j_day_no += JDaysInMonth[i] }
|
|
332
|
+
j_day_no += jd
|
|
333
|
+
|
|
334
|
+
g_day_no = j_day_no + 79
|
|
335
|
+
gy = 1600 + 400*(g_day_no/146097)
|
|
336
|
+
g_day_no %= 146097
|
|
337
|
+
|
|
338
|
+
leap = true
|
|
339
|
+
if g_day_no >= 36525
|
|
340
|
+
g_day_no -= 1
|
|
341
|
+
gy += 100 * (g_day_no/36524)
|
|
342
|
+
g_day_no %= 36524
|
|
343
|
+
(g_day_no >= 365) ? g_day_no += 1 : leap = false
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
gy += 4 * (g_day_no/1461)
|
|
347
|
+
g_day_no %= 1461
|
|
348
|
+
|
|
349
|
+
if g_day_no >= 366
|
|
350
|
+
leap = false
|
|
351
|
+
g_day_no -= 1
|
|
352
|
+
gy += g_day_no/365
|
|
353
|
+
g_day_no %= 365
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
11.times do |i|
|
|
357
|
+
leap_day = (i==1 && leap) ? 1 : 0
|
|
358
|
+
if g_day_no >= (GDaysInMonth[i] + leap_day )
|
|
359
|
+
g_day_no -= (GDaysInMonth[i] + leap_day )
|
|
360
|
+
gg = i + 1
|
|
361
|
+
else
|
|
362
|
+
gg = i
|
|
363
|
+
break
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
gm = gg + 1
|
|
367
|
+
gd = g_day_no + 1
|
|
368
|
+
|
|
369
|
+
[gy,gm,gd]
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
|
|
4
|
+
# Usage:
|
|
5
|
+
# JalaliDateParser.parse("1402/02/25")
|
|
6
|
+
# JalaliDateParser.parse("۱۴۰۲-۰۲-۲۵ ۰۶:۰۱")
|
|
7
|
+
# JalaliDateParser.parse("25 اردیبهشت 1402")
|
|
8
|
+
# JalaliDateParser.parse("25 اردیبهشت 1402 ساعت 6:01:30")
|
|
9
|
+
# JalaliDateParser.parse("1402.2.25")
|
|
10
|
+
# JalaliDateParser.parse("1402/02/25T06:01:30")
|
|
11
|
+
#
|
|
12
|
+
# Returns:
|
|
13
|
+
# {
|
|
14
|
+
# jalali: #<JalaliDate ...>,
|
|
15
|
+
# gregorian: #<DateTime ...>,
|
|
16
|
+
# components: {
|
|
17
|
+
# year: 1402,
|
|
18
|
+
# month: 2,
|
|
19
|
+
# day: 25,
|
|
20
|
+
# hour: 06,
|
|
21
|
+
# min: 01,
|
|
22
|
+
# sec: 30
|
|
23
|
+
# }
|
|
24
|
+
# }
|
|
25
|
+
|
|
26
|
+
require "date"
|
|
27
|
+
require "jalali_date"
|
|
28
|
+
|
|
29
|
+
module JalaliDateParser
|
|
30
|
+
PERSIAN_DIGITS = "۰۱۲۳۴۵۶۷۸۹"
|
|
31
|
+
ENGLISH_DIGITS = "0123456789"
|
|
32
|
+
ARABIC_DIGITS = "٠١٢٣٤٥٦٧٨٩"
|
|
33
|
+
|
|
34
|
+
MONTHS = {
|
|
35
|
+
"فروردین" => 1,
|
|
36
|
+
"اردیبهشت" => 2,
|
|
37
|
+
"خرداد" => 3,
|
|
38
|
+
"تیر" => 4,
|
|
39
|
+
"مرداد" => 5,
|
|
40
|
+
"شهریور" => 6,
|
|
41
|
+
"مهر" => 7,
|
|
42
|
+
"آبان" => 8,
|
|
43
|
+
"آذر" => 9,
|
|
44
|
+
"دی" => 10,
|
|
45
|
+
"بهمن" => 11,
|
|
46
|
+
"اسفند" => 12
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
COMMON_PATTERNS = [
|
|
50
|
+
# 1403/01/15
|
|
51
|
+
# 1403-01-15
|
|
52
|
+
# 1403.01.15
|
|
53
|
+
{
|
|
54
|
+
regex: /
|
|
55
|
+
(?<year>\d{4})
|
|
56
|
+
[\/\-.]
|
|
57
|
+
(?<month>\d{1,2})
|
|
58
|
+
[\/\-.]
|
|
59
|
+
(?<day>\d{1,2})
|
|
60
|
+
(?:[T\s]+(?<time>\d{1,2}:\d{1,2}(?::\d{1,2})?))?
|
|
61
|
+
/x
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
# 15/01/1403
|
|
65
|
+
# 15-01-1403
|
|
66
|
+
{
|
|
67
|
+
regex: /
|
|
68
|
+
(?<day>\d{1,2})
|
|
69
|
+
[\/\-.]
|
|
70
|
+
(?<month>\d{1,2})
|
|
71
|
+
[\/\-.]
|
|
72
|
+
(?<year>\d{4})
|
|
73
|
+
(?:[T\s]+(?<time>\d{1,2}:\d{1,2}(?::\d{1,2})?))?
|
|
74
|
+
/x
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
# 15 فروردین 1403
|
|
78
|
+
# فروردین 15 1403
|
|
79
|
+
{
|
|
80
|
+
regex: /
|
|
81
|
+
(?:
|
|
82
|
+
(?<day>\d{1,2})\s+
|
|
83
|
+
(?<month_name>[[:word:]\p{Arabic}]+)\s+
|
|
84
|
+
(?<year>\d{4})
|
|
85
|
+
|
|
|
86
|
+
(?<month_name2>[[:word:]\p{Arabic}]+)\s+
|
|
87
|
+
(?<day2>\d{1,2})\s+
|
|
88
|
+
(?<year2>\d{4})
|
|
89
|
+
)
|
|
90
|
+
(?:.*?(?<time>\d{1,2}:\d{1,2}(?::\d{1,2})?))?
|
|
91
|
+
/x
|
|
92
|
+
}
|
|
93
|
+
].freeze
|
|
94
|
+
|
|
95
|
+
class ParseError < StandardError; end
|
|
96
|
+
|
|
97
|
+
module_function
|
|
98
|
+
|
|
99
|
+
def parse(input)
|
|
100
|
+
raise ParseError, "Empty input" if input.nil? || input.strip.empty?
|
|
101
|
+
|
|
102
|
+
str = normalize(input)
|
|
103
|
+
|
|
104
|
+
COMMON_PATTERNS.each do |pattern|
|
|
105
|
+
match = str.match(pattern[:regex])
|
|
106
|
+
next unless match
|
|
107
|
+
|
|
108
|
+
year = (match[:year] || match[:year2]).to_i
|
|
109
|
+
day = (match[:day] || match[:day2]).to_i
|
|
110
|
+
|
|
111
|
+
month =
|
|
112
|
+
if match.names.include?("month")
|
|
113
|
+
match[:month].to_i
|
|
114
|
+
else
|
|
115
|
+
MONTHS[match[:month_name] || match[:month_name2]]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
hour, min, sec = extract_time(match[:time])
|
|
119
|
+
|
|
120
|
+
validate!(year, month, day, hour, min, sec)
|
|
121
|
+
|
|
122
|
+
jdate = JalaliDate.new(year, month, day, hour, min, sec)
|
|
123
|
+
gdate = jdate.to_gregorian.to_datetime
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
jalali: jdate,
|
|
127
|
+
gregorian: gdate,
|
|
128
|
+
components: {
|
|
129
|
+
year: year,
|
|
130
|
+
month: month,
|
|
131
|
+
day: day,
|
|
132
|
+
hour: hour,
|
|
133
|
+
min: min,
|
|
134
|
+
sec: sec
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
raise ParseError, "Unsupported Jalali date format: #{input}"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def normalize(str)
|
|
143
|
+
s = str.dup
|
|
144
|
+
|
|
145
|
+
# Persian/Arabic digits → English
|
|
146
|
+
s.tr!(PERSIAN_DIGITS, ENGLISH_DIGITS)
|
|
147
|
+
s.tr!(ARABIC_DIGITS, ENGLISH_DIGITS)
|
|
148
|
+
|
|
149
|
+
# Normalize Arabic chars
|
|
150
|
+
s.gsub!("ي", "ی")
|
|
151
|
+
s.gsub!("ك", "ک")
|
|
152
|
+
|
|
153
|
+
# Remove extra words
|
|
154
|
+
s.gsub!(/ساعت|در|،/, " ")
|
|
155
|
+
|
|
156
|
+
# Normalize whitespace
|
|
157
|
+
s.gsub!(/\s+/, " ")
|
|
158
|
+
|
|
159
|
+
s.strip
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def extract_time(time_str)
|
|
163
|
+
return [0, 0, 0] unless time_str
|
|
164
|
+
|
|
165
|
+
parts = time_str.split(":").map(&:to_i)
|
|
166
|
+
|
|
167
|
+
[
|
|
168
|
+
parts[0] || 0,
|
|
169
|
+
parts[1] || 0,
|
|
170
|
+
parts[2] || 0
|
|
171
|
+
]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def validate!(year, month, day, hour, min, sec)
|
|
175
|
+
raise ParseError, "Invalid year" unless year > 0
|
|
176
|
+
raise ParseError, "Invalid month" unless month.between?(1, 12)
|
|
177
|
+
raise ParseError, "Invalid day" unless day.between?(1, 31)
|
|
178
|
+
raise ParseError, "Invalid hour" unless hour.between?(0, 23)
|
|
179
|
+
raise ParseError, "Invalid minute" unless min.between?(0, 59)
|
|
180
|
+
raise ParseError, "Invalid second" unless sec.between?(0, 59)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
3
|
+
|
|
4
|
+
describe JalaliDate do
|
|
5
|
+
|
|
6
|
+
it "should initialize with valid year,month,date values" do
|
|
7
|
+
jdate = JalaliDate.new(1388,11,22)
|
|
8
|
+
jdate.should be_instance_of(JalaliDate)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "should raise error for invalid year,month,date combination on initialize" do
|
|
12
|
+
lambda { JalaliDate.new(1388,14,22) }.should raise_error(ArgumentError) # invalid month
|
|
13
|
+
lambda { JalaliDate.new(1388,14,32) }.should raise_error(ArgumentError) # invalid day
|
|
14
|
+
lambda { JalaliDate.new(1388,14,"22") }.should raise_error(ArgumentError) # invalid type
|
|
15
|
+
lambda { JalaliDate.new(1388,14,22,11,0) }.should raise_error(ArgumentError) # invalid arguments
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "should initialize with a ruby date object" do
|
|
19
|
+
jdate = JalaliDate.new(Date.today)
|
|
20
|
+
jdate.should be_instance_of(JalaliDate)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "should populate attr_accessors for jalali year, month and date and attr_reader for gregorian year,month and date" do
|
|
24
|
+
jdate = JalaliDate.new(1388,11,22)
|
|
25
|
+
jdate.year.should eql(1388)
|
|
26
|
+
jdate.month.should eql(11)
|
|
27
|
+
jdate.day.should eql(22)
|
|
28
|
+
jdate.g_year.should eql(2010)
|
|
29
|
+
jdate.g_month.should eql(2)
|
|
30
|
+
jdate.g_day.should eql(11)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should return today, yesterday, and tomorrow date according to jalali calendar" do
|
|
34
|
+
allow(Date).to receive(:today).and_return Date.new(2010,1,1)
|
|
35
|
+
JalaliDate.today.should === JalaliDate.new(1388,10,11)
|
|
36
|
+
JalaliDate.yesterday.should === JalaliDate.new(1388,10,10)
|
|
37
|
+
JalaliDate.tomorrow.should === JalaliDate.new(1388,10,12)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "should distinguish invalid jalali dates" do
|
|
41
|
+
JalaliDate.valid?(1388,13,11).should be_falsey
|
|
42
|
+
JalaliDate.valid?(-1388,11,11).should be_falsey
|
|
43
|
+
JalaliDate.valid?(1388,1,45).should be_falsey
|
|
44
|
+
JalaliDate.valid?(1388,12,30).should be_falsey
|
|
45
|
+
JalaliDate.valid?(1387,12,30).should be_truthy
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "should distinguish leap years" do
|
|
49
|
+
JalaliDate.leap?(1387).should be_truthy
|
|
50
|
+
JalaliDate.leap?(1388).should be_falsey
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "should convert to gregorian date correctly" do
|
|
54
|
+
jdate = JalaliDate.new(1388,10,11)
|
|
55
|
+
jdate.to_g.should == Date.new(2010,1,1)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "should convert to string, array and hash correctly" do
|
|
59
|
+
jdate = JalaliDate.new(1388,10,11)
|
|
60
|
+
jdate.to_s.should === "1388/10/11"
|
|
61
|
+
jdate.to_a.should === [1388,10,11]
|
|
62
|
+
jdate.to_hash.should === {:year => 1388, :month => 10, :day => 11}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "should be able to add and substract days from the currect jalai date object" do
|
|
66
|
+
jdate = JalaliDate.new(1388,10,11)
|
|
67
|
+
five_days_later = jdate + 5
|
|
68
|
+
twenty_days_ago = jdate - 20
|
|
69
|
+
five_days_later.should === JalaliDate.new(1388,10,16)
|
|
70
|
+
twenty_days_ago.should === JalaliDate.new(1388,9,21)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "should be able to compare two jalali dates" do
|
|
74
|
+
jdate = JalaliDate.new(1388,10,11)
|
|
75
|
+
next_month_jdate = JalaliDate.new(1388,11,11)
|
|
76
|
+
next_month_jdate.<=>(jdate).should === 1
|
|
77
|
+
jdate.<=>(next_month_jdate).should === -1
|
|
78
|
+
jdate.<=>(jdate).should == 0
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "should now its next and previous dates" do
|
|
82
|
+
jdate = JalaliDate.new(1388,10,11)
|
|
83
|
+
jdate.next.should === JalaliDate.new(1388,10,12)
|
|
84
|
+
jdate.previous.should === JalaliDate.new(1388,10,10)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "should be able to move the month forward and backward" do
|
|
88
|
+
jdate = JalaliDate.new(1388,10,11)
|
|
89
|
+
five_month_later = jdate >> 5
|
|
90
|
+
five_month_ago = jdate << 5
|
|
91
|
+
five_month_later.should === JalaliDate.new(1389,3,11)
|
|
92
|
+
five_month_ago.should === JalaliDate.new(1388,5,11)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "should be able to cycle through dates in different ways, namely, step, upto and downto" do
|
|
96
|
+
jdate = JalaliDate.new(1388,10,10)
|
|
97
|
+
|
|
98
|
+
days_string = ""
|
|
99
|
+
jdate.step( jdate + 10 , 2) do |jd|
|
|
100
|
+
days_string += jd.day.to_s
|
|
101
|
+
end
|
|
102
|
+
days_string.should === "101214161820"
|
|
103
|
+
|
|
104
|
+
days_string = ""
|
|
105
|
+
jdate.upto(jdate+5) do |jd|
|
|
106
|
+
days_string += jd.day.to_s
|
|
107
|
+
end
|
|
108
|
+
days_string.should === "101112131415"
|
|
109
|
+
|
|
110
|
+
days_string = ""
|
|
111
|
+
jdate.downto(jdate-5) do |jd|
|
|
112
|
+
days_string += jd.day.to_s
|
|
113
|
+
end
|
|
114
|
+
days_string.should == "1098765"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "should return a correct year day based on Jalali Calendar" do
|
|
118
|
+
JalaliDate.new(1388,1,1).yday.should === 1
|
|
119
|
+
JalaliDate.new(1388,12,29).yday.should === 365
|
|
120
|
+
JalaliDate.new(1387,12,30).yday.should === 366
|
|
121
|
+
JalaliDate.new(1388,9,17).yday.should === 263
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it "should be able to print jalali date in different formats" do
|
|
125
|
+
JalaliDate.new(1388,1,7).strftime("%a %A %b %B %d %e %j %m %w %y %Y %% %x").should == "ج جمعه فروردین Farvardin 07 7 7 1 5 88 1388 % 88/1/7"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "should be intialize with a time object" do
|
|
129
|
+
time = Time.now
|
|
130
|
+
JalaliDate.new(time).hour.should == time.hour
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it "should be able for format %H %I %M %p %S %X %Z correctly if initiallized with time" do
|
|
134
|
+
JalaliDate.new(1388,2,15,5,50,10).strftime("%H").should === "05"
|
|
135
|
+
JalaliDate.new(1388,2,15,18,50,10).strftime("%H %I").should === "18 06"
|
|
136
|
+
JalaliDate.new(1388,2,15,12,50,10).strftime("%H %I").should === "12 00"
|
|
137
|
+
JalaliDate.new(1388,2,15,12,50,10).strftime("%H %I %M %S").should === "12 00 50 10"
|
|
138
|
+
JalaliDate.new(1388,2,15,5,50,10).strftime("%p").should === "قبل از ظهر"
|
|
139
|
+
JalaliDate.new(1388,2,15,15,50,10).strftime("%p").should === "بعد از ظهر"
|
|
140
|
+
JalaliDate.new(1388,2,15,15,50,10).strftime("%X").should === "15:50:10"
|
|
141
|
+
JalaliDate.new(1388,2,15,15,50,10).strftime("%X").should === "15:50:10"
|
|
142
|
+
JalaliDate.new(1388,2,15,15,50,10,"CET",3600).strftime("%Z").should === "CET"
|
|
143
|
+
JalaliDate.new(1388,2,2,5,50,10).strftime("%n").should === "02"
|
|
144
|
+
JalaliDate.new(1388,2,2,5,50,10).strftime("%m").should === "2"
|
|
145
|
+
time = Time.now
|
|
146
|
+
JalaliDate.new(time).strftime("%Z").should == time.zone
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "should be able to parse different formats of jalali dates" do
|
|
150
|
+
JalaliDate.parse("1402/02/25").should == JalaliDate.new(1402,2,25)
|
|
151
|
+
JalaliDate.parse("۱۴۰۲-۰۲-۲۵ ۰۶:۰۱").should == JalaliDate.new(1402,2,25)
|
|
152
|
+
JalaliDate.parse("25 اردیبهشت 1402").should == JalaliDate.new(1402,2,25)
|
|
153
|
+
JalaliDate.parse("25 اردیبهشت 1402 ساعت 6:01:30").should == JalaliDate.new(1402,2,25)
|
|
154
|
+
JalaliDate.parse("1402.2.25").should == JalaliDate.new(1402,2,25)
|
|
155
|
+
JalaliDate.parse("1402/02/25T06:01:30").should == JalaliDate.new(1402,2,25)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: jalali-date
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.3.7
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Allen A. Bargi
|
|
8
|
+
- Loqman Hakimi
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.16'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.16'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.0'
|
|
41
|
+
email: allen.bargi@gmail.com loqmanhakimi@gmail.com
|
|
42
|
+
executables:
|
|
43
|
+
- jcal
|
|
44
|
+
- jdate
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files:
|
|
47
|
+
- LICENSE
|
|
48
|
+
- README.md
|
|
49
|
+
files:
|
|
50
|
+
- ".gitignore"
|
|
51
|
+
- ".travis.yml"
|
|
52
|
+
- Gemfile
|
|
53
|
+
- Gemfile.lock
|
|
54
|
+
- LICENSE
|
|
55
|
+
- README.md
|
|
56
|
+
- Rakefile
|
|
57
|
+
- TODO
|
|
58
|
+
- bin/jcal
|
|
59
|
+
- bin/jdate
|
|
60
|
+
- jalalidate.gemspec
|
|
61
|
+
- lib/jalali_date.rb
|
|
62
|
+
- lib/jalali_date/cli.rb
|
|
63
|
+
- lib/jalali_date/version.rb
|
|
64
|
+
- lib/jalali_date_parser.rb
|
|
65
|
+
- spec/jalali_date_spec.rb
|
|
66
|
+
- spec/spec_helper.rb
|
|
67
|
+
homepage: http://github.com/loqman/jalalidate
|
|
68
|
+
licenses:
|
|
69
|
+
- MIT
|
|
70
|
+
metadata: {}
|
|
71
|
+
rdoc_options:
|
|
72
|
+
- "--charset=UTF-8"
|
|
73
|
+
require_paths:
|
|
74
|
+
- lib
|
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '3.0'
|
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '0'
|
|
85
|
+
requirements: []
|
|
86
|
+
rubygems_version: 4.0.10
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: A library for working with Jalali Calendar (a.k.a Persian Calendar)
|
|
89
|
+
test_files: []
|