nepdate 1.0.0.rc.1

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.
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NepDate
4
+ # The deterministic human-input parser of PARSING.md section 2. It tries the strict grammar
5
+ # first, then a closed token grammar with a fixed month-name list. It never guesses.
6
+ # @api private
7
+ module Lenient
8
+ SEPARATORS = ["-", "/", "\\", "_", " ", "|", "।", ","].freeze
9
+ MAX_DIGITS = 4
10
+ KELVIN = "\u212A"
11
+
12
+ DEVA_DIGITS = {
13
+ "०" => "0", "१" => "1", "२" => "2", "३" => "3", "४" => "4",
14
+ "५" => "5", "६" => "6", "७" => "7", "८" => "8", "९" => "9"
15
+ }.freeze
16
+
17
+ ERA_MARKERS = ["bs", "b.s.", "b.s", "vs", "v.s.", "v.s", "बि.सं.", "वि.सं.", "बि.सं", "वि.सं"].freeze
18
+ IGNORED_WORDS = ["गते", "मिति"].freeze
19
+
20
+ MONTH_WORDS = {
21
+ "baishakh" => 1, "baisakh" => 1, "baishak" => 1, "vaishakh" => 1, "vaisakh" => 1, "bai" => 1,
22
+ "बैशाख" => 1, "वैशाख" => 1, "बैसाख" => 1,
23
+ "jestha" => 2, "jeth" => 2, "jyestha" => 2, "jes" => 2,
24
+ "जेठ" => 2, "जेष्ठ" => 2, "ज्येष्ठ" => 2,
25
+ "ashad" => 3, "ashadh" => 3, "asar" => 3, "asadh" => 3, "asa" => 3,
26
+ "असार" => 3, "आषाढ" => 3, "असाढ" => 3,
27
+ "shrawan" => 4, "shravan" => 4, "srawan" => 4, "sawan" => 4, "saun" => 4, "shr" => 4,
28
+ "साउन" => 4, "श्रावण" => 4, "सावन" => 4,
29
+ "bhadra" => 5, "bhadau" => 5, "bha" => 5,
30
+ "भदौ" => 5, "भाद्र" => 5, "भाद्रपद" => 5,
31
+ "ashoj" => 6, "asoj" => 6, "ashwin" => 6, "aso" => 6,
32
+ "असोज" => 6, "आश्विन" => 6, "असौज" => 6,
33
+ "kartik" => 7, "kattik" => 7, "kar" => 7,
34
+ "कार्तिक" => 7, "कात्तिक" => 7,
35
+ "mangsir" => 8, "mansir" => 8, "margashirsha" => 8, "man" => 8,
36
+ "मंसिर" => 8, "मङ्सिर" => 8, "मार्गशीर्ष" => 8,
37
+ "poush" => 9, "paush" => 9, "push" => 9, "pus" => 9, "pou" => 9,
38
+ "पुष" => 9, "पौष" => 9, "पुस" => 9,
39
+ "magh" => 10, "mag" => 10, "माघ" => 10,
40
+ "falgun" => 11, "phalgun" => 11, "fagun" => 11, "fal" => 11,
41
+ "फागुन" => 11, "फाल्गुन" => 11,
42
+ "chaitra" => 12, "chait" => 12, "cha" => 12,
43
+ "चैत" => 12, "चैत्र" => 12
44
+ }.freeze
45
+
46
+ module_function
47
+
48
+ def parse(text)
49
+ begin
50
+ strict = Parse.strict(text)
51
+ Calendar.validate(*strict)
52
+ return strict
53
+ rescue Error
54
+ # The strict grammar or the date it reads is unusable: try the token grammar.
55
+ end
56
+
57
+ normalized = text.each_char.map { |char| DEVA_DIGITS.fetch(char, char) }.join
58
+ numbers, digit_counts, months, unknown = tokens(normalized)
59
+
60
+ if unknown
61
+ raise ParseError.new(ErrorKind::UNRECOGNIZED, "unknown word")
62
+ end
63
+ if months.length >= 2
64
+ raise ParseError.new(ErrorKind::UNRECOGNIZED, "more than one month name")
65
+ end
66
+ if months.length == 1 && numbers.length == 2
67
+ return month_form(months.first, numbers, digit_counts)
68
+ end
69
+ if months.empty? && numbers.length == 3
70
+ return numeric_form(numbers, digit_counts)
71
+ end
72
+
73
+ raise ParseError.new(ErrorKind::UNRECOGNIZED, "unrecognized token count")
74
+ end
75
+
76
+ # [numbers, digit counts, month numbers, saw an unknown word]
77
+ def tokens(text)
78
+ numbers = []
79
+ digit_counts = []
80
+ months = []
81
+ unknown = false
82
+
83
+ index = 0
84
+ chars = text.chars
85
+ while index < chars.length
86
+ char = chars[index]
87
+ if SEPARATORS.include?(char)
88
+ index += 1
89
+ next
90
+ end
91
+
92
+ chunk = +""
93
+ while index < chars.length
94
+ char = chars[index]
95
+ break if SEPARATORS.include?(char)
96
+
97
+ chunk << char
98
+ index += 1
99
+ end
100
+
101
+ if numeric?(chunk)
102
+ chunk.split(".").each do |part|
103
+ next if part.empty?
104
+ if part.length > MAX_DIGITS
105
+ raise ParseError.new(ErrorKind::NUMBER_TOO_LONG, "a number has more than 4 digits")
106
+ end
107
+
108
+ numbers << part.to_i
109
+ digit_counts << part.length
110
+ end
111
+ next
112
+ end
113
+
114
+ value = word_value(chunk)
115
+ if value.positive?
116
+ months << value
117
+ elsif value.negative?
118
+ unknown = true
119
+ end
120
+ end
121
+
122
+ [numbers, digit_counts, months, unknown]
123
+ end
124
+
125
+ def numeric?(chunk)
126
+ chunk.each_char.all? { |char| char.between?("0", "9") || char == "." }
127
+ end
128
+
129
+ # A month number, 0 for a dropped marker, or -1 for an unknown word.
130
+ def word_value(token)
131
+ folded = fold(token)
132
+ month = MONTH_WORDS[folded]
133
+ return month if month
134
+ return 0 if ERA_MARKERS.include?(folded) || IGNORED_WORDS.include?(folded)
135
+
136
+ -1
137
+ end
138
+
139
+ # Exactly ASCII A-Z plus the Kelvin sign fold; no other character changes (PARSING.md).
140
+ def fold(token)
141
+ token.each_char.map { |char|
142
+ if char.between?("A", "Z")
143
+ (char.ord + 32).chr(Encoding::UTF_8)
144
+ elsif char == KELVIN
145
+ "k"
146
+ else
147
+ char
148
+ end
149
+ }.join
150
+ end
151
+
152
+ def month_form(month, numbers, digit_counts)
153
+ four = []
154
+ digit_counts.each_with_index { |count, index| four << index if count == MAX_DIGITS }
155
+ unless four.length == 1
156
+ raise ParseError.new(ErrorKind::AMBIGUOUS, "no single four-digit year")
157
+ end
158
+
159
+ year_index = four.first
160
+ other_index = 1 - year_index
161
+ if digit_counts[other_index] > 2
162
+ raise ParseError.new(ErrorKind::AMBIGUOUS, "the day has more than two digits")
163
+ end
164
+
165
+ [numbers[year_index], month, numbers[other_index]]
166
+ end
167
+
168
+ def numeric_form(numbers, digit_counts)
169
+ return [numbers[0], numbers[1], numbers[2]] if digit_counts[0] == MAX_DIGITS
170
+ return [numbers[2], numbers[1], numbers[0]] if digit_counts[2] == MAX_DIGITS
171
+
172
+ raise ParseError.new(ErrorKind::AMBIGUOUS, "no four-digit year first or last")
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NepDate
4
+ # A Bikram Sambat month, 1 = Baishakh through 12 = Chaitra. Ruby has no enum, so this is a
5
+ # small immutable value with the twelve members as constants.
6
+ class Month
7
+ include Comparable
8
+
9
+ NAMES_EN = Names::MONTH_EN
10
+ NAMES_NE = Names::MONTH_NE
11
+ SHORT_EN = Names::MONTH_SHORT_EN
12
+
13
+ attr_reader :number
14
+
15
+ def initialize(number)
16
+ number = Integer(number)
17
+ unless (1..12).cover?(number)
18
+ raise InvalidMonthError, "month #{number} is outside 1..12"
19
+ end
20
+
21
+ @number = number
22
+ freeze
23
+ end
24
+
25
+ def self.from(value)
26
+ return value if value.is_a?(Month)
27
+
28
+ new(value)
29
+ end
30
+
31
+ BAISHAKH = new(1)
32
+ JESTHA = new(2)
33
+ ASHAD = new(3)
34
+ SHRAWAN = new(4)
35
+ BHADRA = new(5)
36
+ ASHOJ = new(6)
37
+ KARTIK = new(7)
38
+ MANGSIR = new(8)
39
+ POUSH = new(9)
40
+ MAGH = new(10)
41
+ FALGUN = new(11)
42
+ CHAITRA = new(12)
43
+
44
+ ALL = [
45
+ BAISHAKH, JESTHA, ASHAD, SHRAWAN, BHADRA, ASHOJ,
46
+ KARTIK, MANGSIR, POUSH, MAGH, FALGUN, CHAITRA
47
+ ].freeze
48
+
49
+ def label(lang: :en)
50
+ Names.month_name(number, Lang.coerce(lang))
51
+ end
52
+
53
+ def short_label(lang: :en)
54
+ Names.month_short(number, Lang.coerce(lang))
55
+ end
56
+
57
+ def to_i
58
+ number
59
+ end
60
+
61
+ def to_s
62
+ label
63
+ end
64
+
65
+ def <=>(other)
66
+ return nil unless other.is_a?(Month)
67
+
68
+ number <=> other.number
69
+ end
70
+
71
+ def ==(other)
72
+ other.is_a?(Month) && number == other.number
73
+ end
74
+ alias_method :eql?, :==
75
+
76
+ def hash
77
+ [Month, number].hash
78
+ end
79
+
80
+ def inspect
81
+ "#<NepDate::Month #{number} #{label}>"
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NepDate
4
+ # What whole-month arithmetic does when the day does not exist in the target month (D-02).
5
+ # +:clamp+ keeps the last day of the target month; +:spill+ moves the excess into the next
6
+ # month. There is no fractional month arithmetic.
7
+ module MonthOverflow
8
+ CLAMP = :clamp
9
+ SPILL = :spill
10
+ ALL = [CLAMP, SPILL].freeze
11
+
12
+ def self.coerce(value)
13
+ return value if value == CLAMP || value == SPILL
14
+
15
+ symbol = value.to_s.downcase.to_sym
16
+ return symbol if ALL.include?(symbol)
17
+
18
+ raise ArgumentError, "overflow must be :clamp or :spill, got #{value.inspect}"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NepDate
4
+ # The month, weekday and digit names of FORMATTING.md section 1.
5
+ # @api private
6
+ module Names
7
+ MONTH_EN = [
8
+ "", "Baishakh", "Jestha", "Ashad", "Shrawan", "Bhadra", "Ashoj",
9
+ "Kartik", "Mangsir", "Poush", "Magh", "Falgun", "Chaitra"
10
+ ].freeze
11
+
12
+ MONTH_NE = [
13
+ "", "बैशाख", "जेठ", "असार", "साउन", "भदौ", "असोज",
14
+ "कार्तिक", "मंसिर", "पुष", "माघ", "फागुन", "चैत"
15
+ ].freeze
16
+
17
+ # D-04: Ashad and Ashoj are Asa and Aso, not the C# "Ash" shared by both.
18
+ MONTH_SHORT_EN = [
19
+ "", "Bai", "Jes", "Asa", "Shr", "Bha", "Aso",
20
+ "Kar", "Man", "Pou", "Mag", "Fal", "Cha"
21
+ ].freeze
22
+
23
+ WEEKDAY_EN = %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday].freeze
24
+ WEEKDAY_NE = %w[आइतवार सोमवार मङ्गलवार बुधवार बिहिवार शुक्रवार शनिवार].freeze
25
+ WEEKDAY_SHORT_EN = %w[Sun Mon Tue Wed Thu Fri Sat].freeze
26
+ WEEKDAY_SHORT_NE = %w[आइत सोम मङ्गल बुध बिहि शुक्र शनि].freeze
27
+
28
+ DEVANAGARI_DIGITS = %w[० १ २ ३ ४ ५ ६ ७ ८ ९].freeze
29
+ ASCII_DIGITS = %w[0 1 2 3 4 5 6 7 8 9].freeze
30
+
31
+ def self.month_name(number, lang = :en)
32
+ (lang == :ne) ? MONTH_NE.fetch(number) : MONTH_EN.fetch(number)
33
+ end
34
+
35
+ def self.month_short(number, lang = :en)
36
+ return MONTH_NE.fetch(number) if lang == :ne
37
+
38
+ MONTH_SHORT_EN.fetch(number)
39
+ end
40
+
41
+ def self.weekday_name(number, lang = :en)
42
+ (lang == :ne) ? WEEKDAY_NE.fetch(number) : WEEKDAY_EN.fetch(number)
43
+ end
44
+
45
+ def self.weekday_short(number, lang = :en)
46
+ (lang == :ne) ? WEEKDAY_SHORT_NE.fetch(number) : WEEKDAY_SHORT_EN.fetch(number)
47
+ end
48
+
49
+ # Replaces every ASCII digit with its Devanagari counterpart and leaves every other character
50
+ # alone (FORMATTING.md section 1).
51
+ def self.nepali_digits(text)
52
+ text.tr("0123456789", "०१२३४५६७८९")
53
+ end
54
+
55
+ def self.digits(number, lang = :en, width: 0)
56
+ if width == 4 && number >= CalendarTable::MIN_YEAR && number <= CalendarTable::MAX_YEAR
57
+ return (lang == :ne) ? YEAR_NE[number - CalendarTable::MIN_YEAR] : YEAR_EN[number - CalendarTable::MIN_YEAR]
58
+ end
59
+ if width == 2 && number >= 0 && number <= 99
60
+ return (lang == :ne) ? TWO_DIGIT_NE[number] : TWO_DIGIT_EN[number]
61
+ end
62
+ if width.zero? && number >= 0 && number <= 99
63
+ return (lang == :ne) ? NUMBER_NE[number] : NUMBER_EN[number]
64
+ end
65
+
66
+ text = number.to_s
67
+ text = text.rjust(width, "0") if width.positive?
68
+ (lang == :ne) ? nepali_digits(text) : text
69
+ end
70
+
71
+ # The short and long forms of every digit string the formatters need, built once at load so a
72
+ # formatting call only reads a String. Years are 1901 through 2199; the two-digit and plain
73
+ # tables cover dates, months and the two-digit year.
74
+ NUMBER_EN = (0..99).map(&:to_s).freeze
75
+ NUMBER_NE = NUMBER_EN.map { |text| nepali_digits(text) }.freeze
76
+ TWO_DIGIT_EN = (0..99).map { |number| number.to_s.rjust(2, "0") }.freeze
77
+ TWO_DIGIT_NE = TWO_DIGIT_EN.map { |text| nepali_digits(text) }.freeze
78
+ YEAR_EN = (CalendarTable::MIN_YEAR..CalendarTable::MAX_YEAR).map(&:to_s).freeze
79
+ YEAR_NE = YEAR_EN.map { |text| nepali_digits(text) }.freeze
80
+ end
81
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NepDate
4
+ # The strict parser of PARSING.md section 1: year, month, day, with ASCII and Devanagari
5
+ # digits mixed freely. The scanner walks UTF-8 bytes; the only multi-byte characters it accepts
6
+ # are the ten Devanagari digits and the danda separator.
7
+ # @api private
8
+ module Parse
9
+ GROUP_COUNT = 3
10
+ MAX_DIGITS = 4
11
+ SEPARATOR_BYTES = {
12
+ 0x2D => true, 0x2F => true, 0x2E => true, 0x5F => true,
13
+ 0x5C => true, 0x20 => true, 0x7C => true
14
+ }.freeze
15
+ DANDA = "।"
16
+
17
+ module_function
18
+
19
+ def strict(text)
20
+ raise TypeError, "date text must be a String, got #{text.class}" unless text.is_a?(String)
21
+
22
+ fast = fast_ascii(text)
23
+ return fast if fast
24
+
25
+ groups = [0, 0, 0]
26
+ count = 0
27
+ in_group = false
28
+ digits = 0
29
+ index = 0
30
+ length = text.bytesize
31
+
32
+ while index < length
33
+ byte = text.getbyte(index)
34
+ digit = nil
35
+ width = 1
36
+
37
+ if byte.between?(0x30, 0x39)
38
+ digit = byte - 0x30
39
+ elsif byte < 0x80
40
+ invalid_character(text, index) unless SEPARATOR_BYTES.key?(byte)
41
+ elsif byte == 0xE0
42
+ second = text.getbyte(index + 1)
43
+ third = text.getbyte(index + 2)
44
+ if second == 0xA5 && third
45
+ if third.between?(0xA6, 0xAF)
46
+ digit = third - 0xA6
47
+ width = 3
48
+ elsif third == 0xA4
49
+ width = 3
50
+ else
51
+ invalid_character(text, index)
52
+ end
53
+ else
54
+ invalid_character(text, index)
55
+ end
56
+ else
57
+ invalid_character(text, index)
58
+ end
59
+
60
+ if digit
61
+ if !in_group
62
+ if count == GROUP_COUNT
63
+ raise ParseError.new(ErrorKind::WRONG_GROUP_COUNT, "more than three groups")
64
+ end
65
+ count += 1
66
+ in_group = true
67
+ digits = 0
68
+ end
69
+ digits += 1
70
+ if digits > MAX_DIGITS
71
+ raise ParseError.new(ErrorKind::NUMBER_TOO_LONG, "a group has more than 4 digits")
72
+ end
73
+ groups[count - 1] = groups[count - 1] * 10 + digit
74
+ index += width
75
+ next
76
+ end
77
+
78
+ in_group = false
79
+ index += width
80
+ end
81
+
82
+ if count < GROUP_COUNT
83
+ raise ParseError.new(ErrorKind::WRONG_GROUP_COUNT, "fewer than three groups")
84
+ end
85
+
86
+ groups
87
+ end
88
+
89
+ # The common YYYY/MM/DD form (and every other one-byte separator) without the byte scanner.
90
+ # Returns nil when the text is not exactly four, two and two ASCII digits, so the scanner can
91
+ # report the precise error or accept Devanagari digits.
92
+ def fast_ascii(text)
93
+ return nil unless text.bytesize == 10
94
+
95
+ first = text.getbyte(0)
96
+ second = text.getbyte(1)
97
+ third = text.getbyte(2)
98
+ fourth = text.getbyte(3)
99
+ fifth = text.getbyte(5)
100
+ sixth = text.getbyte(6)
101
+ eighth = text.getbyte(8)
102
+ ninth = text.getbyte(9)
103
+ return nil unless first.between?(0x30, 0x39) &&
104
+ second.between?(0x30, 0x39) &&
105
+ third.between?(0x30, 0x39) &&
106
+ fourth.between?(0x30, 0x39) &&
107
+ fifth.between?(0x30, 0x39) &&
108
+ sixth.between?(0x30, 0x39) &&
109
+ eighth.between?(0x30, 0x39) &&
110
+ ninth.between?(0x30, 0x39)
111
+ return nil unless SEPARATOR_BYTES.key?(text.getbyte(4)) && SEPARATOR_BYTES.key?(text.getbyte(7))
112
+
113
+ [
114
+ (first - 0x30) * 1000 + (second - 0x30) * 100 + (third - 0x30) * 10 + (fourth - 0x30),
115
+ (fifth - 0x30) * 10 + (sixth - 0x30),
116
+ (eighth - 0x30) * 10 + (ninth - 0x30)
117
+ ]
118
+ end
119
+
120
+ def invalid_character(text, index)
121
+ char = text.byteslice(index, 4).to_s.each_char.first
122
+ raise ParseError.new(ErrorKind::INVALID_CHARACTER, "invalid character #{char.inspect}")
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NepDate
4
+ # A fiscal quarter: Q1 Shrawan-Ashoj through Q4 Baishakh-Ashad of the following year.
5
+ class Quarter
6
+ include Comparable
7
+
8
+ NAMES = {1 => :q1, 2 => :q2, 3 => :q3, 4 => :q4}.freeze
9
+ LABELS = {1 => "Q1", 2 => "Q2", 3 => "Q3", 4 => "Q4"}.freeze
10
+
11
+ attr_reader :number
12
+
13
+ def initialize(number)
14
+ number = Integer(number)
15
+ unless (1..4).cover?(number)
16
+ raise ArgumentError, "quarter #{number} is outside 1..4"
17
+ end
18
+
19
+ @number = number
20
+ freeze
21
+ end
22
+
23
+ def self.from(value)
24
+ return value if value.is_a?(Quarter)
25
+
26
+ case value
27
+ when :q1, "q1", 1 then Q1
28
+ when :q2, "q2", 2 then Q2
29
+ when :q3, "q3", 3 then Q3
30
+ when :q4, "q4", 4 then Q4
31
+ else
32
+ raise ArgumentError, "quarter must be 1..4 or :q1..:q4, got #{value.inspect}"
33
+ end
34
+ end
35
+
36
+ Q1 = new(1)
37
+ Q2 = new(2)
38
+ Q3 = new(3)
39
+ Q4 = new(4)
40
+
41
+ ALL = [Q1, Q2, Q3, Q4].freeze
42
+
43
+ def name
44
+ NAMES.fetch(number)
45
+ end
46
+
47
+ def label
48
+ LABELS.fetch(number)
49
+ end
50
+
51
+ def to_i
52
+ number
53
+ end
54
+
55
+ def to_s
56
+ label
57
+ end
58
+
59
+ def <=>(other)
60
+ return nil unless other.is_a?(Quarter)
61
+
62
+ number <=> other.number
63
+ end
64
+
65
+ def ==(other)
66
+ other.is_a?(Quarter) && number == other.number
67
+ end
68
+ alias_method :eql?, :==
69
+
70
+ def hash
71
+ [Quarter, number].hash
72
+ end
73
+
74
+ def inspect
75
+ "#<NepDate::Quarter #{label}>"
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NepDate
4
+ VERSION = "1.0.0.rc.1"
5
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NepDate
4
+ # A weekday with Sunday = 0, the numbering of ALGORITHM.md section 7 and the shared vectors.
5
+ class Weekday
6
+ include Comparable
7
+
8
+ attr_reader :number
9
+
10
+ def initialize(number)
11
+ number = Integer(number)
12
+ unless (0..6).cover?(number)
13
+ raise ArgumentError, "weekday #{number} is outside 0..6"
14
+ end
15
+
16
+ @number = number
17
+ freeze
18
+ end
19
+
20
+ def self.from(value)
21
+ return value if value.is_a?(Weekday)
22
+
23
+ new(value)
24
+ end
25
+
26
+ SUNDAY = new(0)
27
+ MONDAY = new(1)
28
+ TUESDAY = new(2)
29
+ WEDNESDAY = new(3)
30
+ THURSDAY = new(4)
31
+ FRIDAY = new(5)
32
+ SATURDAY = new(6)
33
+
34
+ ALL = [SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY].freeze
35
+
36
+ def label(lang: :en)
37
+ Names.weekday_name(number, Lang.coerce(lang))
38
+ end
39
+
40
+ def short_label(lang: :en)
41
+ Names.weekday_short(number, Lang.coerce(lang))
42
+ end
43
+
44
+ def sunday? = number.zero?
45
+
46
+ def monday? = number == 1
47
+
48
+ def tuesday? = number == 2
49
+
50
+ def wednesday? = number == 3
51
+
52
+ def thursday? = number == 4
53
+
54
+ def friday? = number == 5
55
+
56
+ def saturday? = number == 6
57
+
58
+ def to_i
59
+ number
60
+ end
61
+
62
+ def to_s
63
+ label
64
+ end
65
+
66
+ def <=>(other)
67
+ return nil unless other.is_a?(Weekday)
68
+
69
+ number <=> other.number
70
+ end
71
+
72
+ def ==(other)
73
+ other.is_a?(Weekday) && number == other.number
74
+ end
75
+ alias_method :eql?, :==
76
+
77
+ def hash
78
+ [Weekday, number].hash
79
+ end
80
+
81
+ def inspect
82
+ "#<NepDate::Weekday #{number} #{label}>"
83
+ end
84
+ end
85
+ end
data/lib/nepdate.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "nepdate/version"
4
+ require_relative "nepdate/errors"
5
+ require_relative "nepdate/calendar_table"
6
+ require_relative "nepdate/calendar"
7
+ require_relative "nepdate/names"
8
+ require_relative "nepdate/lang"
9
+ require_relative "nepdate/month"
10
+ require_relative "nepdate/weekday"
11
+ require_relative "nepdate/quarter"
12
+ require_relative "nepdate/format_options"
13
+ require_relative "nepdate/month_overflow"
14
+ require_relative "nepdate/date_diff"
15
+ require_relative "nepdate/parse"
16
+ require_relative "nepdate/lenient"
17
+ require_relative "nepdate/format"
18
+ require_relative "nepdate/date"
19
+ require_relative "nepdate/date_range"
20
+ require_relative "nepdate/fiscal_year"
21
+
22
+ # Bikram Sambat dates for Ruby. The canonical entry point is +NepDate::Date+;
23
+ # +NepDate::Date.parse+ reads a strict date and +NepDate::Date.today+ reads the clock through an
24
+ # explicit zone and an optional injected clock.
25
+ module NepDate
26
+ class << self
27
+ # The date +text+ names, with the strict grammar.
28
+ def parse(text)
29
+ Date.parse(text)
30
+ end
31
+
32
+ # Today in +zone+, with an optional clock callable.
33
+ def today(zone: Date::DEFAULT_ZONE, clock: nil)
34
+ Date.today(zone: zone, clock: clock)
35
+ end
36
+ end
37
+ end