whenwords 0.1.0 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +5 -1
- data/lib/whenwords/version.rb +1 -1
- data/lib/whenwords.rb +53 -51
- metadata +8 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3bd67c93384599212636f8539189dec434b1c0e3c0fb78ecf9b31039df9560f4
|
|
4
|
+
data.tar.gz: 577b977de38168eb4863dad6384ed0ffdcfe03a3cc0becee46ac9c61092161c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 150a6aea8bd49cbb182e405ad8d6a2e2bdab29087b4a207d2865ddc9e9447b5babfe45342cc61dcf1390089b7232f8077e4678e460aadefa657b44f16808dde1
|
|
7
|
+
data.tar.gz: fd1481665e27708fc22e4f86c781d7cc2b22193e40fe5c5680871182e214cddbe133645c68a5cdde34d97a79f7b31ea95d68bcb7c1a8f49527d6f8772a685bb4
|
data/README.md
CHANGED
|
@@ -275,7 +275,11 @@ To install this gem onto your local machine, run `bundle exec rake install`.
|
|
|
275
275
|
|
|
276
276
|
## Contributing
|
|
277
277
|
|
|
278
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
278
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ZPVIP/whenwords.
|
|
279
|
+
|
|
280
|
+
## Credits
|
|
281
|
+
|
|
282
|
+
This Ruby gem is an implementation of the [whenwords specification](https://github.com/dbreunig/whenwords) by Drew Breunig — "An Open Source Library Without Code".
|
|
279
283
|
|
|
280
284
|
## License
|
|
281
285
|
|
data/lib/whenwords/version.rb
CHANGED
data/lib/whenwords.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require_relative
|
|
3
|
+
require 'time'
|
|
4
|
+
require_relative 'whenwords/version'
|
|
5
5
|
|
|
6
6
|
module Whenwords
|
|
7
7
|
class Error < StandardError; end
|
|
@@ -9,10 +9,10 @@ module Whenwords
|
|
|
9
9
|
|
|
10
10
|
SECONDS_PER_MINUTE = 60
|
|
11
11
|
SECONDS_PER_HOUR = 3600
|
|
12
|
-
SECONDS_PER_DAY =
|
|
13
|
-
SECONDS_PER_WEEK =
|
|
14
|
-
SECONDS_PER_MONTH =
|
|
15
|
-
SECONDS_PER_YEAR =
|
|
12
|
+
SECONDS_PER_DAY = 86_400
|
|
13
|
+
SECONDS_PER_WEEK = 604_800
|
|
14
|
+
SECONDS_PER_MONTH = 2_592_000 # 30 days
|
|
15
|
+
SECONDS_PER_YEAR = 31_536_000 # 365 days
|
|
16
16
|
|
|
17
17
|
WEEKDAY_NAMES = %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday].freeze
|
|
18
18
|
MONTH_NAMES = %w[January February March April May June July August September October November December].freeze
|
|
@@ -45,12 +45,12 @@ module Whenwords
|
|
|
45
45
|
ref = reference.nil? ? ts : normalize_timestamp(reference)
|
|
46
46
|
|
|
47
47
|
diff = ref - ts
|
|
48
|
-
future = diff
|
|
48
|
+
future = diff.negative?
|
|
49
49
|
diff = diff.abs
|
|
50
50
|
|
|
51
51
|
text = calculate_timeago_text(diff)
|
|
52
52
|
|
|
53
|
-
if text ==
|
|
53
|
+
if text == 'just now'
|
|
54
54
|
text
|
|
55
55
|
elsif future
|
|
56
56
|
"in #{text.sub(/ ago$/, '')}"
|
|
@@ -65,13 +65,13 @@ module Whenwords
|
|
|
65
65
|
# @param max_units [Integer] Maximum number of units to display
|
|
66
66
|
# @return [String] Human-readable duration
|
|
67
67
|
def duration(seconds, compact: false, max_units: 2)
|
|
68
|
-
raise Error,
|
|
69
|
-
raise Error,
|
|
70
|
-
raise Error,
|
|
68
|
+
raise Error, 'Duration cannot be negative' if seconds.negative?
|
|
69
|
+
raise Error, 'Duration cannot be NaN' if seconds.respond_to?(:nan?) && seconds.nan?
|
|
70
|
+
raise Error, 'Duration cannot be infinite' if seconds.respond_to?(:infinite?) && seconds.infinite?
|
|
71
71
|
|
|
72
72
|
seconds = seconds.to_f
|
|
73
73
|
|
|
74
|
-
return compact ?
|
|
74
|
+
return compact ? '0s' : '0 seconds' if seconds.zero?
|
|
75
75
|
|
|
76
76
|
units = calculate_duration_units(seconds)
|
|
77
77
|
non_zero_units = units.select { |_, v| v.positive? }
|
|
@@ -90,20 +90,18 @@ module Whenwords
|
|
|
90
90
|
# @param string [String] The duration string to parse
|
|
91
91
|
# @return [Numeric] Duration in seconds
|
|
92
92
|
def parse_duration(string)
|
|
93
|
-
raise ParseError,
|
|
93
|
+
raise ParseError, 'Duration string cannot be empty' if string.nil? || string.strip.empty?
|
|
94
94
|
|
|
95
95
|
input = string.strip.downcase
|
|
96
96
|
|
|
97
97
|
# Check for negative values
|
|
98
|
-
raise ParseError,
|
|
98
|
+
raise ParseError, 'Negative durations are not allowed' if input.include?('-')
|
|
99
99
|
|
|
100
100
|
# Try colon notation first
|
|
101
|
-
if input.match?(/^\d+:\d{1,2}(:\d{1,2})?$/)
|
|
102
|
-
return parse_colon_notation(input)
|
|
103
|
-
end
|
|
101
|
+
return parse_colon_notation(input) if input.match?(/^\d+:\d{1,2}(:\d{1,2})?$/)
|
|
104
102
|
|
|
105
103
|
total = parse_duration_parts(input)
|
|
106
|
-
raise ParseError,
|
|
104
|
+
raise ParseError, 'No parseable duration units found' if total.zero? && !input.match?(/\b0\s*[a-z]/)
|
|
107
105
|
|
|
108
106
|
total
|
|
109
107
|
end
|
|
@@ -123,11 +121,11 @@ module Whenwords
|
|
|
123
121
|
|
|
124
122
|
case diff_days
|
|
125
123
|
when 0
|
|
126
|
-
|
|
124
|
+
'Today'
|
|
127
125
|
when -1
|
|
128
|
-
|
|
126
|
+
'Yesterday'
|
|
129
127
|
when 1
|
|
130
|
-
|
|
128
|
+
'Tomorrow'
|
|
131
129
|
when -6..-2
|
|
132
130
|
"Last #{WEEKDAY_NAMES[ts_date.wday]}"
|
|
133
131
|
when 2..6
|
|
@@ -159,27 +157,27 @@ module Whenwords
|
|
|
159
157
|
def calculate_timeago_text(diff)
|
|
160
158
|
case diff
|
|
161
159
|
when 0...45
|
|
162
|
-
|
|
160
|
+
'just now'
|
|
163
161
|
when 45...90
|
|
164
|
-
|
|
162
|
+
'1 minute ago'
|
|
165
163
|
when 90...(45 * 60)
|
|
166
164
|
"#{(diff / 60.0).round} minutes ago"
|
|
167
165
|
when (45 * 60)...(90 * 60)
|
|
168
|
-
|
|
166
|
+
'1 hour ago'
|
|
169
167
|
when (90 * 60)...(22 * 3600)
|
|
170
168
|
"#{(diff / 3600.0).round} hours ago"
|
|
171
169
|
when (22 * 3600)...(36 * 3600)
|
|
172
|
-
|
|
173
|
-
when (36 * 3600)...(26 *
|
|
174
|
-
"#{(diff /
|
|
175
|
-
when (26 *
|
|
176
|
-
|
|
177
|
-
when (46 *
|
|
178
|
-
"#{(diff / (30.44 *
|
|
179
|
-
when (320 *
|
|
180
|
-
|
|
170
|
+
'1 day ago'
|
|
171
|
+
when (36 * 3600)...(26 * 86_400)
|
|
172
|
+
"#{(diff / 86_400.0).round} days ago"
|
|
173
|
+
when (26 * 86_400)...(46 * 86_400)
|
|
174
|
+
'1 month ago'
|
|
175
|
+
when (46 * 86_400)...(320 * 86_400)
|
|
176
|
+
"#{(diff / (30.44 * 86_400)).round} months ago"
|
|
177
|
+
when (320 * 86_400)...(548 * 86_400)
|
|
178
|
+
'1 year ago'
|
|
181
179
|
else
|
|
182
|
-
"#{(diff / (365.0 *
|
|
180
|
+
"#{(diff / (365.0 * 86_400)).round} years ago"
|
|
183
181
|
end
|
|
184
182
|
end
|
|
185
183
|
|
|
@@ -213,34 +211,34 @@ module Whenwords
|
|
|
213
211
|
|
|
214
212
|
def format_compact_duration(units)
|
|
215
213
|
abbreviations = {
|
|
216
|
-
years:
|
|
217
|
-
months:
|
|
218
|
-
days:
|
|
219
|
-
hours:
|
|
220
|
-
minutes:
|
|
221
|
-
seconds:
|
|
214
|
+
years: 'y',
|
|
215
|
+
months: 'mo',
|
|
216
|
+
days: 'd',
|
|
217
|
+
hours: 'h',
|
|
218
|
+
minutes: 'm',
|
|
219
|
+
seconds: 's'
|
|
222
220
|
}
|
|
223
221
|
|
|
224
|
-
units.map { |unit, value| "#{value}#{abbreviations[unit]}" }.join(
|
|
222
|
+
units.map { |unit, value| "#{value}#{abbreviations[unit]}" }.join(' ')
|
|
225
223
|
end
|
|
226
224
|
|
|
227
225
|
def format_verbose_duration(units)
|
|
228
226
|
units.map do |unit, value|
|
|
229
227
|
unit_name = unit.to_s
|
|
230
|
-
unit_name = unit_name.chomp(
|
|
228
|
+
unit_name = unit_name.chomp('s') if value == 1
|
|
231
229
|
"#{value} #{unit_name}"
|
|
232
|
-
end.join(
|
|
230
|
+
end.join(', ')
|
|
233
231
|
end
|
|
234
232
|
|
|
235
233
|
def parse_colon_notation(input)
|
|
236
|
-
parts = input.split(
|
|
234
|
+
parts = input.split(':').map(&:to_i)
|
|
237
235
|
|
|
238
236
|
if parts.length == 2
|
|
239
237
|
# h:mm
|
|
240
|
-
parts[0] * SECONDS_PER_HOUR + parts[1] * SECONDS_PER_MINUTE
|
|
238
|
+
(parts[0] * SECONDS_PER_HOUR) + (parts[1] * SECONDS_PER_MINUTE)
|
|
241
239
|
else
|
|
242
240
|
# h:mm:ss
|
|
243
|
-
parts[0] * SECONDS_PER_HOUR + parts[1] * SECONDS_PER_MINUTE + parts[2]
|
|
241
|
+
(parts[0] * SECONDS_PER_HOUR) + (parts[1] * SECONDS_PER_MINUTE) + parts[2]
|
|
244
242
|
end
|
|
245
243
|
end
|
|
246
244
|
|
|
@@ -263,7 +261,7 @@ module Whenwords
|
|
|
263
261
|
end
|
|
264
262
|
end
|
|
265
263
|
|
|
266
|
-
raise ParseError,
|
|
264
|
+
raise ParseError, 'No parseable duration units found' unless found_any
|
|
267
265
|
|
|
268
266
|
total.round
|
|
269
267
|
end
|
|
@@ -277,14 +275,18 @@ module Whenwords
|
|
|
277
275
|
end
|
|
278
276
|
|
|
279
277
|
def format_date_range(start_date, end_date)
|
|
278
|
+
start_month = MONTH_NAMES[start_date.month - 1]
|
|
279
|
+
end_month = MONTH_NAMES[end_date.month - 1]
|
|
280
|
+
|
|
280
281
|
if start_date == end_date
|
|
281
|
-
"#{
|
|
282
|
+
"#{start_month} #{start_date.day}, #{start_date.year}"
|
|
282
283
|
elsif start_date.year == end_date.year && start_date.month == end_date.month
|
|
283
|
-
"#{
|
|
284
|
+
"#{start_month} #{start_date.day}–#{end_date.day}, #{start_date.year}"
|
|
284
285
|
elsif start_date.year == end_date.year
|
|
285
|
-
"#{
|
|
286
|
+
"#{start_month} #{start_date.day} – #{end_month} #{end_date.day}, #{end_date.year}"
|
|
286
287
|
else
|
|
287
|
-
"#{
|
|
288
|
+
"#{start_month} #{start_date.day}, #{start_date.year} – " \
|
|
289
|
+
"#{end_month} #{end_date.day}, #{end_date.year}"
|
|
288
290
|
end
|
|
289
291
|
end
|
|
290
292
|
end
|
metadata
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: whenwords
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Peng Zhang
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
@@ -13,7 +13,7 @@ description: Convert timestamps to readable strings like '3 hours ago' and parse
|
|
|
13
13
|
strings like '2h 30m' into seconds. Includes timeago, duration formatting, duration
|
|
14
14
|
parsing, contextual dates, and date range formatting.
|
|
15
15
|
email:
|
|
16
|
-
-
|
|
16
|
+
- peng@zpvip.com
|
|
17
17
|
executables:
|
|
18
18
|
- whenwords
|
|
19
19
|
extensions: []
|
|
@@ -24,14 +24,14 @@ files:
|
|
|
24
24
|
- exe/whenwords
|
|
25
25
|
- lib/whenwords.rb
|
|
26
26
|
- lib/whenwords/version.rb
|
|
27
|
-
homepage: https://github.com/
|
|
27
|
+
homepage: https://github.com/ZPVIP/whenwords
|
|
28
28
|
licenses:
|
|
29
29
|
- MIT
|
|
30
30
|
metadata:
|
|
31
|
-
homepage_uri: https://github.com/
|
|
32
|
-
source_code_uri: https://github.com/
|
|
33
|
-
changelog_uri: https://github.com/
|
|
34
|
-
documentation_uri: https://github.com/
|
|
31
|
+
homepage_uri: https://github.com/ZPVIP/whenwords
|
|
32
|
+
source_code_uri: https://github.com/ZPVIP/whenwords.git
|
|
33
|
+
changelog_uri: https://github.com/ZPVIP/whenwords/blob/main/CHANGELOG.md
|
|
34
|
+
documentation_uri: https://github.com/ZPVIP/whenwords#readme
|
|
35
35
|
rubygems_mfa_required: 'true'
|
|
36
36
|
rdoc_options: []
|
|
37
37
|
require_paths:
|