doing 2.1.103 → 2.1.105
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/CHANGELOG.md +11 -0
- data/Gemfile.lock +1 -1
- data/README.md +3 -1
- data/bin/commands/view.rb +5 -0
- data/bin/doing +5 -0
- data/docs/doc/Array.html +1 -1
- data/docs/doc/BooleanTermParser/Clause.html +1 -1
- data/docs/doc/BooleanTermParser/Operator.html +1 -1
- data/docs/doc/BooleanTermParser/Query.html +1 -1
- data/docs/doc/BooleanTermParser/QueryParser.html +1 -1
- data/docs/doc/BooleanTermParser/QueryTransformer.html +1 -1
- data/docs/doc/BooleanTermParser.html +1 -1
- data/docs/doc/Doing/ArrayCleanup.html +1 -1
- data/docs/doc/Doing/ArrayNestedHash.html +1 -1
- data/docs/doc/Doing/ArrayTags.html +1 -1
- data/docs/doc/Doing/ByDayExport.html +1 -1
- data/docs/doc/Doing/CSVExport.html +1 -1
- data/docs/doc/Doing/CalendarImport.html +1 -1
- data/docs/doc/Doing/Change.html +1 -1
- data/docs/doc/Doing/Changes.html +1 -1
- data/docs/doc/Doing/ChronifyArray.html +1 -1
- data/docs/doc/Doing/ChronifyNumeric.html +1 -1
- data/docs/doc/Doing/ChronifyString.html +1 -1
- data/docs/doc/Doing/Color.html +1 -1
- data/docs/doc/Doing/Completion/BashCompletions.html +1 -1
- data/docs/doc/Doing/Completion/FigCompletions.html +1 -1
- data/docs/doc/Doing/Completion/FishCompletions.html +1 -1
- data/docs/doc/Doing/Completion/StringUtils.html +1 -1
- data/docs/doc/Doing/Completion/ZshCompletions.html +1 -1
- data/docs/doc/Doing/Completion.html +1 -1
- data/docs/doc/Doing/Configuration.html +2 -1
- data/docs/doc/Doing/DayOneRenderer.html +1 -1
- data/docs/doc/Doing/DayoneExport.html +1 -1
- data/docs/doc/Doing/DoingExport.html +1 -1
- data/docs/doc/Doing/DoingImport.html +1 -1
- data/docs/doc/Doing/Entry.html +1 -1
- data/docs/doc/Doing/Errors/DoingNoTraceError.html +1 -1
- data/docs/doc/Doing/Errors/DoingRuntimeError.html +1 -1
- data/docs/doc/Doing/Errors/DoingStandardError.html +1 -1
- data/docs/doc/Doing/Errors/EmptyInput.html +1 -1
- data/docs/doc/Doing/Errors/HistoryLimitError.html +1 -1
- data/docs/doc/Doing/Errors/InvalidPlugin.html +1 -1
- data/docs/doc/Doing/Errors/MissingBackupFile.html +1 -1
- data/docs/doc/Doing/Errors/NoResults.html +1 -1
- data/docs/doc/Doing/Errors/PluginException.html +1 -1
- data/docs/doc/Doing/Errors/UserCancelled.html +1 -1
- data/docs/doc/Doing/Errors/WrongCommand.html +1 -1
- data/docs/doc/Doing/Errors.html +1 -1
- data/docs/doc/Doing/HTMLExport.html +1 -1
- data/docs/doc/Doing/Hooks.html +1 -1
- data/docs/doc/Doing/Item.html +1 -1
- data/docs/doc/Doing/ItemDates.html +1 -1
- data/docs/doc/Doing/ItemQuery.html +1 -1
- data/docs/doc/Doing/ItemState.html +1 -1
- data/docs/doc/Doing/ItemTags.html +1 -1
- data/docs/doc/Doing/Items.html +1 -1
- data/docs/doc/Doing/JSONExport.html +1 -1
- data/docs/doc/Doing/JSONImport.html +1 -1
- data/docs/doc/Doing/Logger.html +1 -1
- data/docs/doc/Doing/MarkdownExport.html +1 -1
- data/docs/doc/Doing/Note.html +1 -1
- data/docs/doc/Doing/Pager.html +1 -1
- data/docs/doc/Doing/Plugins.html +1 -1
- data/docs/doc/Doing/Prompt.html +1 -1
- data/docs/doc/Doing/PromptChoose.html +1 -1
- data/docs/doc/Doing/PromptFZF.html +1 -1
- data/docs/doc/Doing/PromptInput.html +1 -1
- data/docs/doc/Doing/PromptSTD.html +1 -1
- data/docs/doc/Doing/PromptYN.html +1 -1
- data/docs/doc/Doing/Section.html +1 -1
- data/docs/doc/Doing/StringHighlight.html +1 -1
- data/docs/doc/Doing/StringNormalize.html +1 -1
- data/docs/doc/Doing/StringQuery.html +1 -1
- data/docs/doc/Doing/StringTags.html +1 -1
- data/docs/doc/Doing/StringTransform.html +1 -1
- data/docs/doc/Doing/StringTruncate.html +1 -1
- data/docs/doc/Doing/StringURL.html +1 -1
- data/docs/doc/Doing/SymbolNormalize.html +1 -1
- data/docs/doc/Doing/TaskPaperExport.html +1 -1
- data/docs/doc/Doing/TemplateExport.html +1 -1
- data/docs/doc/Doing/TemplateString.html +1 -1
- data/docs/doc/Doing/TimingImport.html +1 -1
- data/docs/doc/Doing/Types.html +11 -1
- data/docs/doc/Doing/Util/Backup.html +1 -1
- data/docs/doc/Doing/Util.html +1 -1
- data/docs/doc/Doing/Version.html +1 -1
- data/docs/doc/Doing/WWID.html +3 -3
- data/docs/doc/Doing.html +2 -2
- data/docs/doc/FalseClass.html +1 -1
- data/docs/doc/GLI/Commands/Help.html +1 -1
- data/docs/doc/GLI/Commands/MarkdownDocumentListener.html +1 -1
- data/docs/doc/GLI/Commands.html +1 -1
- data/docs/doc/GLI.html +1 -1
- data/docs/doc/Hash.html +1 -1
- data/docs/doc/Numeric.html +1 -1
- data/docs/doc/Object.html +1 -1
- data/docs/doc/PhraseParser/Operator.html +1 -1
- data/docs/doc/PhraseParser/PhraseClause.html +1 -1
- data/docs/doc/PhraseParser/Query.html +1 -1
- data/docs/doc/PhraseParser/QueryParser.html +1 -1
- data/docs/doc/PhraseParser/QueryTransformer.html +1 -1
- data/docs/doc/PhraseParser/TermClause.html +1 -1
- data/docs/doc/PhraseParser.html +1 -1
- data/docs/doc/Status.html +1 -1
- data/docs/doc/String.html +1 -1
- data/docs/doc/Symbol.html +1 -1
- data/docs/doc/Time.html +1 -1
- data/docs/doc/TrueClass.html +1 -1
- data/docs/doc/_index.html +1 -1
- data/docs/doc/file.README.html +4 -2
- data/docs/doc/index.html +4 -2
- data/docs/doc/top-level-namespace.html +1 -1
- data/doing.rdoc +65 -1
- data/lib/completion/_doing.zsh +9 -9
- data/lib/completion/doing.bash +16 -16
- data/lib/completion/doing.fish +8 -0
- data/lib/completion/doing.ts +90 -0
- data/lib/doing/add_options.rb +4 -0
- data/lib/doing/configuration.rb +1 -0
- data/lib/doing/plugins/export/template_export.rb +3 -1
- data/lib/doing/types.rb +2 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid/timers.rb +46 -8
- metadata +1 -1
data/lib/completion/doing.ts
CHANGED
|
@@ -1518,6 +1518,16 @@ const completionSpec: Fig.Spec = {
|
|
|
1518
1518
|
|
|
1519
1519
|
},
|
|
1520
1520
|
|
|
1521
|
+
{
|
|
1522
|
+
name: ["--totals_format"],
|
|
1523
|
+
description: "Time format for totals output",
|
|
1524
|
+
args: {
|
|
1525
|
+
name: "FORMAT",
|
|
1526
|
+
description: "FORMAT",
|
|
1527
|
+
},
|
|
1528
|
+
|
|
1529
|
+
},
|
|
1530
|
+
|
|
1521
1531
|
{
|
|
1522
1532
|
name: ["--val"],
|
|
1523
1533
|
description: "Perform a tag value query",
|
|
@@ -1746,6 +1756,16 @@ const completionSpec: Fig.Spec = {
|
|
|
1746
1756
|
|
|
1747
1757
|
},
|
|
1748
1758
|
|
|
1759
|
+
{
|
|
1760
|
+
name: ["--totals_format"],
|
|
1761
|
+
description: "Time format for totals output",
|
|
1762
|
+
args: {
|
|
1763
|
+
name: "FORMAT",
|
|
1764
|
+
description: "FORMAT",
|
|
1765
|
+
},
|
|
1766
|
+
|
|
1767
|
+
},
|
|
1768
|
+
|
|
1749
1769
|
{
|
|
1750
1770
|
name: ["--val"],
|
|
1751
1771
|
description: "Perform a tag value query",
|
|
@@ -2801,6 +2821,16 @@ const completionSpec: Fig.Spec = {
|
|
|
2801
2821
|
|
|
2802
2822
|
},
|
|
2803
2823
|
|
|
2824
|
+
{
|
|
2825
|
+
name: ["--totals_format"],
|
|
2826
|
+
description: "Time format for totals output",
|
|
2827
|
+
args: {
|
|
2828
|
+
name: "FORMAT",
|
|
2829
|
+
description: "FORMAT",
|
|
2830
|
+
},
|
|
2831
|
+
|
|
2832
|
+
},
|
|
2833
|
+
|
|
2804
2834
|
{
|
|
2805
2835
|
name: ["--val"],
|
|
2806
2836
|
description: "Perform a tag value query",
|
|
@@ -3007,6 +3037,16 @@ const completionSpec: Fig.Spec = {
|
|
|
3007
3037
|
|
|
3008
3038
|
},
|
|
3009
3039
|
|
|
3040
|
+
{
|
|
3041
|
+
name: ["--totals_format"],
|
|
3042
|
+
description: "Time format for totals output",
|
|
3043
|
+
args: {
|
|
3044
|
+
name: "FORMAT",
|
|
3045
|
+
description: "FORMAT",
|
|
3046
|
+
},
|
|
3047
|
+
|
|
3048
|
+
},
|
|
3049
|
+
|
|
3010
3050
|
],
|
|
3011
3051
|
|
|
3012
3052
|
},
|
|
@@ -3813,6 +3853,16 @@ const completionSpec: Fig.Spec = {
|
|
|
3813
3853
|
|
|
3814
3854
|
},
|
|
3815
3855
|
|
|
3856
|
+
{
|
|
3857
|
+
name: ["--totals_format"],
|
|
3858
|
+
description: "Time format for totals output",
|
|
3859
|
+
args: {
|
|
3860
|
+
name: "FORMAT",
|
|
3861
|
+
description: "FORMAT",
|
|
3862
|
+
},
|
|
3863
|
+
|
|
3864
|
+
},
|
|
3865
|
+
|
|
3816
3866
|
{
|
|
3817
3867
|
name: ["--val"],
|
|
3818
3868
|
description: "Perform a tag value query",
|
|
@@ -3997,6 +4047,16 @@ const completionSpec: Fig.Spec = {
|
|
|
3997
4047
|
|
|
3998
4048
|
},
|
|
3999
4049
|
|
|
4050
|
+
{
|
|
4051
|
+
name: ["--totals_format"],
|
|
4052
|
+
description: "Time format for totals output",
|
|
4053
|
+
args: {
|
|
4054
|
+
name: "FORMAT",
|
|
4055
|
+
description: "FORMAT",
|
|
4056
|
+
},
|
|
4057
|
+
|
|
4058
|
+
},
|
|
4059
|
+
|
|
4000
4060
|
{
|
|
4001
4061
|
name: ["--val"],
|
|
4002
4062
|
description: "Perform a tag value query",
|
|
@@ -4503,6 +4563,16 @@ const completionSpec: Fig.Spec = {
|
|
|
4503
4563
|
|
|
4504
4564
|
},
|
|
4505
4565
|
|
|
4566
|
+
{
|
|
4567
|
+
name: ["--totals_format"],
|
|
4568
|
+
description: "Time format for totals output",
|
|
4569
|
+
args: {
|
|
4570
|
+
name: "FORMAT",
|
|
4571
|
+
description: "FORMAT",
|
|
4572
|
+
},
|
|
4573
|
+
|
|
4574
|
+
},
|
|
4575
|
+
|
|
4506
4576
|
],
|
|
4507
4577
|
|
|
4508
4578
|
},
|
|
@@ -4803,6 +4873,16 @@ const completionSpec: Fig.Spec = {
|
|
|
4803
4873
|
|
|
4804
4874
|
},
|
|
4805
4875
|
|
|
4876
|
+
{
|
|
4877
|
+
name: ["--totals_format"],
|
|
4878
|
+
description: "Time format for totals output",
|
|
4879
|
+
args: {
|
|
4880
|
+
name: "FORMAT",
|
|
4881
|
+
description: "FORMAT",
|
|
4882
|
+
},
|
|
4883
|
+
|
|
4884
|
+
},
|
|
4885
|
+
|
|
4806
4886
|
{
|
|
4807
4887
|
name: ["--val"],
|
|
4808
4888
|
description: "Perform a tag value query",
|
|
@@ -5091,6 +5171,16 @@ const completionSpec: Fig.Spec = {
|
|
|
5091
5171
|
|
|
5092
5172
|
},
|
|
5093
5173
|
|
|
5174
|
+
{
|
|
5175
|
+
name: ["--totals_format"],
|
|
5176
|
+
description: "Time format for totals output",
|
|
5177
|
+
args: {
|
|
5178
|
+
name: "FORMAT",
|
|
5179
|
+
description: "FORMAT",
|
|
5180
|
+
},
|
|
5181
|
+
|
|
5182
|
+
},
|
|
5183
|
+
|
|
5094
5184
|
],
|
|
5095
5185
|
|
|
5096
5186
|
},
|
data/lib/doing/add_options.rb
CHANGED
|
@@ -138,6 +138,10 @@ def add_options(type, cmd, default_template: 'default')
|
|
|
138
138
|
cmd.desc 'Totals grouping (tags|section). Can be repeated, e.g. --by tags --by section'
|
|
139
139
|
cmd.arg_name 'GROUP'
|
|
140
140
|
cmd.flag [:by], must_match: REGEX_TOTALS_BY, multiple: true, type: TotalsBySymbol
|
|
141
|
+
|
|
142
|
+
cmd.desc 'Time format for totals output (clock|hmclock|hm|dhm|ydhm|tight|natural|speech|m|averages)'
|
|
143
|
+
cmd.arg_name 'FORMAT'
|
|
144
|
+
cmd.flag [:totals_format], must_match: REGEX_TOTALS_FORMAT, type: TotalsFormatSymbol
|
|
141
145
|
when :tag_filter
|
|
142
146
|
cmd.desc 'Filter entries by tag. Combine multiple tags with a comma. Wildcards allowed (*, ?)'
|
|
143
147
|
cmd.arg_name 'TAG'
|
data/lib/doing/configuration.rb
CHANGED
|
@@ -141,7 +141,9 @@ module Doing
|
|
|
141
141
|
out += wwid.tag_times(format: Doing.setting('timer_format').to_sym,
|
|
142
142
|
sort_by: opt[:sort_tags],
|
|
143
143
|
sort_order: opt[:tag_order],
|
|
144
|
-
by: opt[:by]
|
|
144
|
+
by: opt[:by],
|
|
145
|
+
totals_format: opt[:totals_format],
|
|
146
|
+
items: items)
|
|
145
147
|
end
|
|
146
148
|
out
|
|
147
149
|
end
|
data/lib/doing/types.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Doing
|
|
|
7
7
|
REGEX_BOOL = /^(?:and|all|any|or|not|none|p(?:at(?:tern)?)?)$/i.freeze
|
|
8
8
|
REGEX_SORT_ORDER = /^(?:a(?:sc)?|d(?:esc)?)$/i.freeze
|
|
9
9
|
REGEX_TOTALS_BY = /^(?:t(?:ag(?:s)?)?|s(?:ec(?:tion|tions)?)?|p(?:roj(?:ect|ects)?)?)$/i.freeze
|
|
10
|
+
REGEX_TOTALS_FORMAT = /^(?:averages|clock|hmclock|hm|dhm|ydhm|tight|natural|speech|m)$/i.freeze
|
|
10
11
|
REGEX_VALUE_QUERY = /^(?:!)?@?(?:\S+) +(?:!?[<>=][=*]?|[$*^]=) +(?:.*?)$/.freeze
|
|
11
12
|
REGEX_CLOCK = '(?:\d{1,2}+(?::\d{1,2}+)?(?: *(?:am|pm))?|midnight|noon)'
|
|
12
13
|
REGEX_TIME = /^#{REGEX_CLOCK}$/i.freeze
|
|
@@ -32,6 +33,7 @@ module Doing
|
|
|
32
33
|
TagArray = Class.new(Array)
|
|
33
34
|
TagSortSymbol = Class.new(Symbol)
|
|
34
35
|
TotalsBySymbol = Class.new(Symbol)
|
|
36
|
+
TotalsFormatSymbol = Class.new(String)
|
|
35
37
|
TemplateName = Class.new(String)
|
|
36
38
|
end
|
|
37
39
|
end
|
data/lib/doing/version.rb
CHANGED
data/lib/doing/wwid/timers.rb
CHANGED
|
@@ -11,10 +11,11 @@ module Doing
|
|
|
11
11
|
## @param sort_by [Symbol] Sort by :name or :time
|
|
12
12
|
## @param sort_order [Symbol] The sort order (:asc or :desc)
|
|
13
13
|
##
|
|
14
|
-
def tag_times(format: :text, sort_by: :time, sort_order: :asc, by: nil)
|
|
14
|
+
def tag_times(format: :text, sort_by: :time, sort_order: :asc, by: nil, totals_format: nil, items: nil)
|
|
15
15
|
groupings = normalize_totals_groupings(by)
|
|
16
16
|
totals = collect_totals(groupings)
|
|
17
17
|
return '' if totals.empty?
|
|
18
|
+
totals_format = normalize_totals_format(totals_format)
|
|
18
19
|
|
|
19
20
|
timers_snapshot = totals[:tags] || {}
|
|
20
21
|
|
|
@@ -54,6 +55,8 @@ module Doing
|
|
|
54
55
|
timer_data: timer_data,
|
|
55
56
|
sort_by: sort_by,
|
|
56
57
|
sort_order: sort_order,
|
|
58
|
+
totals_format: totals_format,
|
|
59
|
+
items: items,
|
|
57
60
|
remaining_map: remaining_map,
|
|
58
61
|
budgets: budgets,
|
|
59
62
|
budgets_total: budgets_total,
|
|
@@ -144,7 +147,7 @@ module Doing
|
|
|
144
147
|
sorted
|
|
145
148
|
end
|
|
146
149
|
|
|
147
|
-
def render_totals_group(format:, group:, timer_data:, sort_by:, sort_order:, remaining_map:, budgets:, budgets_total:, budget_fmt:)
|
|
150
|
+
def render_totals_group(format:, group:, timer_data:, sort_by:, sort_order:, totals_format:, items:, remaining_map:, budgets:, budgets_total:, budget_fmt:)
|
|
148
151
|
timer_data = timer_data.dup
|
|
149
152
|
timer_data.delete('meanwhile')
|
|
150
153
|
|
|
@@ -155,6 +158,7 @@ module Doing
|
|
|
155
158
|
|
|
156
159
|
title = group == :section ? 'Section Totals' : 'Tag Totals'
|
|
157
160
|
label = group == :section ? 'section' : 'tag'
|
|
161
|
+
line_format = totals_format == :averages ? :hmclock : totals_format
|
|
158
162
|
|
|
159
163
|
case format
|
|
160
164
|
when :html
|
|
@@ -180,7 +184,7 @@ EOHEAD
|
|
|
180
184
|
if group == :tags && remaining_map.key?(k) && remaining_map[k].positive?
|
|
181
185
|
budget_str = " (budget left #{budget_fmt.call(remaining_map[k])})"
|
|
182
186
|
end
|
|
183
|
-
output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{v.time_string(format:
|
|
187
|
+
output += "<tr><td style='text-align:left;'>#{k}</td><td style='text-align:left;'>#{v.time_string(format: line_format)}#{budget_str}</td></tr>\n"
|
|
184
188
|
end
|
|
185
189
|
output += <<EOTAIL
|
|
186
190
|
<tr>
|
|
@@ -190,7 +194,7 @@ EOHEAD
|
|
|
190
194
|
<tfoot>
|
|
191
195
|
<tr>
|
|
192
196
|
<td style="text-align:left;"><strong>Total</strong></td>
|
|
193
|
-
<td style="text-align:left;">#{total.time_string(format:
|
|
197
|
+
<td style="text-align:left;">#{total.time_string(format: line_format)}#{group == :tags && budgets_total.positive? ? " (total budgets left #{budget_fmt.call(budgets_total)})" : ''}</td>
|
|
194
198
|
</tr>
|
|
195
199
|
</tfoot>
|
|
196
200
|
</table>
|
|
@@ -210,7 +214,7 @@ EOTAIL
|
|
|
210
214
|
if group == :tags && remaining_map.key?(k) && remaining_map[k].positive?
|
|
211
215
|
budget_str = " (budget left #{budget_fmt.call(remaining_map[k])})"
|
|
212
216
|
end
|
|
213
|
-
output += "| #{' ' * (pad - k.length)}#{k} | #{v.time_string(format:
|
|
217
|
+
output += "| #{' ' * (pad - k.length)}#{k} | #{v.time_string(format: line_format)}#{budget_str} |\n"
|
|
214
218
|
end
|
|
215
219
|
output + "[#{title}]"
|
|
216
220
|
when :json
|
|
@@ -219,7 +223,7 @@ EOTAIL
|
|
|
219
223
|
row = {
|
|
220
224
|
label => k,
|
|
221
225
|
'seconds' => v,
|
|
222
|
-
'formatted' => v.time_string(format:
|
|
226
|
+
'formatted' => v.time_string(format: line_format)
|
|
223
227
|
}
|
|
224
228
|
if group == :tags
|
|
225
229
|
row['budget'] = budgets[k]
|
|
@@ -287,7 +291,7 @@ EOTAIL
|
|
|
287
291
|
(max - k.length).times do
|
|
288
292
|
spacer += ' '
|
|
289
293
|
end
|
|
290
|
-
line = "#{k}:#{spacer}#{v.time_string(format:
|
|
294
|
+
line = "#{k}:#{spacer}#{v.time_string(format: line_format)}"
|
|
291
295
|
if group == :tags && remaining_map.key?(k) && remaining_map[k].positive?
|
|
292
296
|
line += " (budget left #{budget_fmt.call(remaining_map[k])})"
|
|
293
297
|
end
|
|
@@ -295,11 +299,45 @@ EOTAIL
|
|
|
295
299
|
end
|
|
296
300
|
|
|
297
301
|
output = output.empty? ? '' : "\n--- #{title} ---\n#{output.join("\n")}"
|
|
298
|
-
output += "\n\nTotal tracked: #{total.time_string(format:
|
|
302
|
+
output += "\n\nTotal tracked: #{total.time_string(format: line_format)}"
|
|
303
|
+
output += totals_average_suffix(total, items) if totals_format == :averages
|
|
299
304
|
output += " (total budgets left #{budget_fmt.call(budgets_total)})" if group == :tags && budgets_total.positive?
|
|
300
305
|
output += "\n"
|
|
301
306
|
output
|
|
302
307
|
end
|
|
303
308
|
end
|
|
309
|
+
|
|
310
|
+
def normalize_totals_format(value)
|
|
311
|
+
format = (value || Doing.setting('totals_format') || 'clock').to_s.downcase
|
|
312
|
+
format = 'clock' if format.empty?
|
|
313
|
+
return :averages if format == 'averages'
|
|
314
|
+
|
|
315
|
+
format.to_sym
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def totals_average_suffix(total_seconds, items)
|
|
319
|
+
return '' unless items.respond_to?(:empty?) && !items.empty?
|
|
320
|
+
|
|
321
|
+
total_hours = total_seconds.to_f / 3600
|
|
322
|
+
total_hours_floor = total_hours.floor
|
|
323
|
+
total_minutes = ((total_hours - total_hours_floor) * 60).round
|
|
324
|
+
if total_minutes == 60
|
|
325
|
+
total_hours_floor += 1
|
|
326
|
+
total_minutes = 0
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
dates = items.map { |item| item.date.to_date }.compact
|
|
330
|
+
return '' if dates.empty?
|
|
331
|
+
|
|
332
|
+
start_date = dates.min
|
|
333
|
+
end_date = dates.max
|
|
334
|
+
time_span_in_days = [(end_date - start_date).to_i + 1, 1].max
|
|
335
|
+
avg_hours_per_day = total_hours / time_span_in_days
|
|
336
|
+
|
|
337
|
+
format(' (%<hours>dh %<minutes>d min, %<avg>.2fh/day)',
|
|
338
|
+
hours: total_hours_floor,
|
|
339
|
+
minutes: total_minutes,
|
|
340
|
+
avg: avg_hours_per_day)
|
|
341
|
+
end
|
|
304
342
|
end
|
|
305
343
|
end
|