timet 1.6.1.1 → 1.6.3
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/.qlty/qlty.toml +0 -0
- data/.reek.yml +17 -1
- data/.rubocop.yml +6 -1
- data/CHANGELOG.md +37 -4
- data/README.md +40 -24
- data/lib/timet/application.rb +3 -3
- data/lib/timet/database.rb +32 -25
- data/lib/timet/database_syncer.rb +93 -131
- data/lib/timet/discord_notifier.rb +3 -1
- data/lib/timet/s3_supabase.rb +74 -131
- data/lib/timet/table.rb +0 -4
- data/lib/timet/tag_distribution.rb +181 -160
- data/lib/timet/time_helper.rb +18 -3
- data/lib/timet/time_report.rb +17 -3
- data/lib/timet/utils.rb +8 -8
- data/lib/timet/validation_editor.rb +303 -0
- data/lib/timet/version.rb +2 -2
- metadata +6 -9
- data/lib/timet/item_data_helper.rb +0 -59
- data/lib/timet/time_report_helper.rb +0 -36
- data/lib/timet/time_update_helper.rb +0 -75
- data/lib/timet/time_validation_helper.rb +0 -175
- data/lib/timet/validation_edit_helper.rb +0 -160
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
require_relative 'time_helper'
|
|
5
|
+
|
|
6
|
+
module Timet
|
|
7
|
+
# Helper module containing validation logic for time tracking items.
|
|
8
|
+
module TimeValidationHelpers
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def adjust_end_datetime_for_next_day(start_timestamp, new_datetime)
|
|
12
|
+
return new_datetime unless start_timestamp && (new_datetime.to_i <= start_timestamp)
|
|
13
|
+
|
|
14
|
+
new_datetime + (24 * 60 * 60)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create_new_datetime(base_date_time, parsed_time_component)
|
|
18
|
+
Time.new(
|
|
19
|
+
base_date_time.year,
|
|
20
|
+
base_date_time.month,
|
|
21
|
+
base_date_time.day,
|
|
22
|
+
parsed_time_component.hour,
|
|
23
|
+
parsed_time_component.min,
|
|
24
|
+
parsed_time_component.sec,
|
|
25
|
+
base_date_time.utc_offset
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def determine_start_base_date_time(start_timestamp)
|
|
30
|
+
start_timestamp ? Time.at(start_timestamp) : Time.now
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def determine_end_base_date_time(start_timestamp)
|
|
34
|
+
raise ArgumentError, "Cannot set 'end' time because 'start' time is not set." unless start_timestamp
|
|
35
|
+
|
|
36
|
+
Time.at(start_timestamp)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def determine_base_date_time(_item, field, start_timestamp)
|
|
40
|
+
case field
|
|
41
|
+
when 'start'
|
|
42
|
+
determine_start_base_date_time(start_timestamp)
|
|
43
|
+
when 'end'
|
|
44
|
+
determine_end_base_date_time(start_timestamp)
|
|
45
|
+
else
|
|
46
|
+
raise ArgumentError, "Invalid field: #{field}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_time_string(time_str)
|
|
51
|
+
Time.parse(time_str)
|
|
52
|
+
rescue ArgumentError
|
|
53
|
+
raise ArgumentError, "Invalid time format: #{time_str}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def format_time(epoch)
|
|
57
|
+
Time.at(epoch).strftime('%Y-%m-%d %H:%M:%S')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def validate_future_date(new_datetime)
|
|
61
|
+
return unless new_datetime > Time.now.getlocal
|
|
62
|
+
|
|
63
|
+
raise ArgumentError, "Cannot set time to a future date or time: #{new_datetime.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def validate_time_difference(earlier_timestamp, later_timestamp)
|
|
67
|
+
return unless (later_timestamp - earlier_timestamp).abs >= 24 * 60 * 60
|
|
68
|
+
|
|
69
|
+
raise ArgumentError, 'The difference between start and end time must be less than 24 hours.'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def validate_end_after_start(new_epoch, ref_timestamp, new_datetime)
|
|
73
|
+
reference_time = Time.at(ref_timestamp)
|
|
74
|
+
formatted_new = new_datetime.strftime('%Y-%m-%d %H:%M:%S')
|
|
75
|
+
formatted_ref = reference_time.strftime('%Y-%m-%d %H:%M:%S')
|
|
76
|
+
|
|
77
|
+
return unless new_epoch <= ref_timestamp
|
|
78
|
+
|
|
79
|
+
raise ArgumentError, "End time (#{formatted_new}) must be after start time (#{formatted_ref})."
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def validate_start_before_end(new_epoch, ref_timestamp, new_datetime)
|
|
83
|
+
reference_time = Time.at(ref_timestamp)
|
|
84
|
+
formatted_new = new_datetime.strftime('%Y-%m-%d %H:%M:%S')
|
|
85
|
+
formatted_ref = reference_time.strftime('%Y-%m-%d %H:%M:%S')
|
|
86
|
+
|
|
87
|
+
return unless new_epoch >= ref_timestamp
|
|
88
|
+
|
|
89
|
+
raise ArgumentError, "Start time (#{formatted_new}) must be before end time (#{formatted_ref})."
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def validate_end_time(new_epoch, ref_timestamp, new_datetime)
|
|
93
|
+
validate_end_after_start(new_epoch, ref_timestamp, new_datetime)
|
|
94
|
+
validate_time_difference(ref_timestamp, new_epoch)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def check_start_before_end(new_epoch, ref_timestamp, new_datetime)
|
|
98
|
+
validate_start_before_end(new_epoch, ref_timestamp, new_datetime)
|
|
99
|
+
validate_time_difference(new_epoch, ref_timestamp)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def fetch_item_start(item)
|
|
103
|
+
item[Timet::Application::FIELD_INDEX['start']]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def fetch_item_end(item)
|
|
107
|
+
item[Timet::Application::FIELD_INDEX['end']] || TimeHelper.current_timestamp
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def fetch_item_before_end(db, id, item_start)
|
|
111
|
+
db.find_item(id - 1)&.dig(Timet::Application::FIELD_INDEX['end']) || item_start
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def fetch_item_after_start(db, id)
|
|
115
|
+
db.find_item(id + 1)&.dig(Timet::Application::FIELD_INDEX['start']) || TimeHelper.current_timestamp
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Handles validation and editing of time tracking items.
|
|
120
|
+
class ValidationEditor
|
|
121
|
+
include TimeValidationHelpers
|
|
122
|
+
|
|
123
|
+
TIME_FIELDS = %w[start end].freeze
|
|
124
|
+
|
|
125
|
+
def initialize(item, db)
|
|
126
|
+
@item = item
|
|
127
|
+
@db = db
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def update(field, new_value)
|
|
131
|
+
case field
|
|
132
|
+
when 'notes' then update_notes(new_value)
|
|
133
|
+
when 'tag' then update_tag(new_value)
|
|
134
|
+
when 'start' then update_start_time(new_value)
|
|
135
|
+
when 'end' then update_end_time(new_value)
|
|
136
|
+
else raise ArgumentError, "Invalid field: #{field}"
|
|
137
|
+
end
|
|
138
|
+
@item
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def update_notes(value)
|
|
144
|
+
@item[4] = value
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def update_tag(value)
|
|
148
|
+
@item[3] = value
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def update_start_time(time_str)
|
|
152
|
+
@item[1] = process_start_time(time_str)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def update_end_time(time_str)
|
|
156
|
+
@item[2] = process_end_time(time_str)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def process_start_time(time_str)
|
|
160
|
+
return @item[1] if time_str.to_s.strip.empty?
|
|
161
|
+
|
|
162
|
+
build_and_validate_start_time(time_str)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def process_end_time(time_str)
|
|
166
|
+
return @item[2] if time_str.to_s.strip.empty?
|
|
167
|
+
|
|
168
|
+
build_and_validate_end_time(time_str)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def start_timestamp
|
|
172
|
+
@item[1]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def end_timestamp
|
|
176
|
+
@item[2]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def item_id
|
|
180
|
+
@item[0]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def build_and_validate_start_time(time_str)
|
|
184
|
+
new_epoch = parse_to_epoch(time_str, :start)
|
|
185
|
+
run_start_validations(new_epoch)
|
|
186
|
+
new_epoch
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def build_and_validate_end_time(time_str)
|
|
190
|
+
new_epoch = parse_to_epoch(time_str, :end)
|
|
191
|
+
run_end_validations(new_epoch)
|
|
192
|
+
new_epoch
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def parse_to_epoch(time_str, field_type)
|
|
196
|
+
parsed_time = parse_time_string(time_str)
|
|
197
|
+
base = determine_base_date_time(@item, field_type.to_s, start_timestamp)
|
|
198
|
+
new_dt = create_new_datetime(base, parsed_time)
|
|
199
|
+
adjusted_dt = field_type == :end ? adjust_end_datetime_for_next_day(start_timestamp, new_dt) : new_dt
|
|
200
|
+
adjusted_dt.to_i
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def run_start_validations(new_epoch)
|
|
204
|
+
validate_start_not_future(new_epoch)
|
|
205
|
+
validate_start_collision_with_previous(new_epoch)
|
|
206
|
+
validate_start_collision_with_next(new_epoch)
|
|
207
|
+
validate_start_before_item_end(new_epoch)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def run_end_validations(new_epoch)
|
|
211
|
+
validate_end_not_future(new_epoch)
|
|
212
|
+
validate_end_collision_with_previous(new_epoch)
|
|
213
|
+
validate_end_collision_with_next(new_epoch)
|
|
214
|
+
validate_end_after_start_item(new_epoch)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def validate_start_not_future(new_epoch)
|
|
218
|
+
validate_future_date(Time.at(new_epoch))
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def validate_end_not_future(new_epoch)
|
|
222
|
+
validate_future_date(Time.at(new_epoch))
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def validate_start_collision_with_previous(new_epoch)
|
|
226
|
+
prev = @db.find_item(item_id - 1)
|
|
227
|
+
return unless prev
|
|
228
|
+
|
|
229
|
+
prev_end = prev[2]
|
|
230
|
+
return unless new_epoch < prev_end
|
|
231
|
+
|
|
232
|
+
raise ArgumentError, "New start time collides with previous item (ends at #{format_time(prev_end)})."
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def validate_end_collision_with_previous(new_epoch)
|
|
236
|
+
prev = @db.find_item(item_id - 1)
|
|
237
|
+
return unless prev
|
|
238
|
+
|
|
239
|
+
prev_end = prev[2]
|
|
240
|
+
return unless new_epoch < prev_end
|
|
241
|
+
|
|
242
|
+
raise ArgumentError, "New end time collides with previous item (ends at #{format_time(prev_end)})."
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def validate_start_collision_with_next(new_epoch)
|
|
246
|
+
next_item = @db.find_item(item_id + 1)
|
|
247
|
+
return unless next_item
|
|
248
|
+
|
|
249
|
+
next_start = next_item[1]
|
|
250
|
+
return unless new_epoch >= next_start
|
|
251
|
+
|
|
252
|
+
raise ArgumentError, "New start time collides with next item (starts at #{format_time(next_start)})."
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def validate_end_collision_with_next(new_epoch)
|
|
256
|
+
next_item = @db.find_item(item_id + 1)
|
|
257
|
+
return unless next_item
|
|
258
|
+
|
|
259
|
+
next_start = next_item[1]
|
|
260
|
+
return unless new_epoch > next_start
|
|
261
|
+
|
|
262
|
+
raise ArgumentError, "New end time collides with next item (starts at #{format_time(next_start)})."
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def validate_start_before_item_end(new_epoch)
|
|
266
|
+
end_ts = end_timestamp
|
|
267
|
+
return unless end_ts
|
|
268
|
+
|
|
269
|
+
check_start_before_end(new_epoch, end_ts, Time.at(new_epoch))
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def validate_end_after_start_item(new_epoch)
|
|
273
|
+
start_ts = start_timestamp
|
|
274
|
+
validate_end_time(new_epoch, start_ts, Time.at(new_epoch))
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def process_and_update_time_field(item, field, date_value, id)
|
|
278
|
+
formatted_date = TimeHelper.format_time_string(date_value)
|
|
279
|
+
|
|
280
|
+
return print_error(date_value) unless formatted_date
|
|
281
|
+
|
|
282
|
+
new_date = TimeHelper.update_time_field(item, field, formatted_date)
|
|
283
|
+
new_value_epoch = new_date.to_i
|
|
284
|
+
|
|
285
|
+
return @db.update_item(id, field, new_value_epoch) unless invalid_time_value?(item, field, new_value_epoch, id)
|
|
286
|
+
|
|
287
|
+
print_error(new_date)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def print_error(message)
|
|
291
|
+
puts "Invalid date: #{message}".red
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def invalid_time_value?(item, field, new_value_epoch, id)
|
|
295
|
+
is_start = field == 'start'
|
|
296
|
+
item_start = fetch_item_start(item)
|
|
297
|
+
item_end = fetch_item_end(item)
|
|
298
|
+
min_val = is_start ? fetch_item_before_end(@db, id, item_start) : item_start
|
|
299
|
+
max_val = is_start ? item_end : fetch_item_after_start(@db, id)
|
|
300
|
+
!new_value_epoch.between?(min_val, max_val)
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
data/lib/timet/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: timet
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.6.
|
|
4
|
+
version: 1.6.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Frank Vielma
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-02-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: thor
|
|
@@ -106,14 +106,14 @@ dependencies:
|
|
|
106
106
|
requirements:
|
|
107
107
|
- - "~>"
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '1.
|
|
109
|
+
version: '1.209'
|
|
110
110
|
type: :runtime
|
|
111
111
|
prerelease: false
|
|
112
112
|
version_requirements: !ruby/object:Gem::Requirement
|
|
113
113
|
requirements:
|
|
114
114
|
- - "~>"
|
|
115
115
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: '1.
|
|
116
|
+
version: '1.209'
|
|
117
117
|
description: Timet is a command-line time tracker that keeps track of your activities.
|
|
118
118
|
email:
|
|
119
119
|
- frankvielma@gmail.com
|
|
@@ -124,6 +124,7 @@ extensions: []
|
|
|
124
124
|
extra_rdoc_files: []
|
|
125
125
|
files:
|
|
126
126
|
- ".deepsource.toml"
|
|
127
|
+
- ".qlty/qlty.toml"
|
|
127
128
|
- ".reek.yml"
|
|
128
129
|
- ".rspec"
|
|
129
130
|
- ".rubocop.yml"
|
|
@@ -143,19 +144,15 @@ files:
|
|
|
143
144
|
- lib/timet/database_sync_helper.rb
|
|
144
145
|
- lib/timet/database_syncer.rb
|
|
145
146
|
- lib/timet/discord_notifier.rb
|
|
146
|
-
- lib/timet/item_data_helper.rb
|
|
147
147
|
- lib/timet/s3_supabase.rb
|
|
148
148
|
- lib/timet/table.rb
|
|
149
149
|
- lib/timet/tag_distribution.rb
|
|
150
150
|
- lib/timet/time_block_chart.rb
|
|
151
151
|
- lib/timet/time_helper.rb
|
|
152
152
|
- lib/timet/time_report.rb
|
|
153
|
-
- lib/timet/time_report_helper.rb
|
|
154
153
|
- lib/timet/time_statistics.rb
|
|
155
|
-
- lib/timet/time_update_helper.rb
|
|
156
|
-
- lib/timet/time_validation_helper.rb
|
|
157
154
|
- lib/timet/utils.rb
|
|
158
|
-
- lib/timet/
|
|
155
|
+
- lib/timet/validation_editor.rb
|
|
159
156
|
- lib/timet/version.rb
|
|
160
157
|
- lib/timet/week_info.rb
|
|
161
158
|
- sig/timet.rbs
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Timet
|
|
4
|
-
# Helper methods for fetching item data.
|
|
5
|
-
module ItemDataHelper
|
|
6
|
-
module_function
|
|
7
|
-
|
|
8
|
-
# Fetches the start time of a tracking item.
|
|
9
|
-
#
|
|
10
|
-
# @param item [Array] The tracking item.
|
|
11
|
-
#
|
|
12
|
-
# @return [Integer] The start time in epoch format.
|
|
13
|
-
#
|
|
14
|
-
# @example Fetch the start time of a tracking item
|
|
15
|
-
# fetch_item_start(item)
|
|
16
|
-
def fetch_item_start(item)
|
|
17
|
-
item[Timet::Application::FIELD_INDEX['start']]
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Fetches the end time of a tracking item.
|
|
21
|
-
#
|
|
22
|
-
# @param item [Array] The tracking item.
|
|
23
|
-
#
|
|
24
|
-
# @return [Integer] The end time in epoch format.
|
|
25
|
-
#
|
|
26
|
-
# @example Fetch the end time of a tracking item
|
|
27
|
-
# fetch_item_end(item)
|
|
28
|
-
def fetch_item_end(item)
|
|
29
|
-
item[Timet::Application::FIELD_INDEX['end']] || TimeHelper.current_timestamp
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Fetches the end time of the tracking item before the current one.
|
|
33
|
-
#
|
|
34
|
-
# @param db [Timet::Database] The database instance.
|
|
35
|
-
# @param id [Integer] The ID of the current tracking item.
|
|
36
|
-
# @param item_start [Integer] The start time of the current tracking item.
|
|
37
|
-
#
|
|
38
|
-
# @return [Integer] The end time of the previous tracking item in epoch format.
|
|
39
|
-
#
|
|
40
|
-
# @example Fetch the end time of the previous tracking item
|
|
41
|
-
# fetch_item_before_end(db, 1, 1633072800)
|
|
42
|
-
def fetch_item_before_end(db, id, item_start)
|
|
43
|
-
db.find_item(id - 1)&.dig(Timet::Application::FIELD_INDEX['end']) || item_start
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Fetches the start time of the tracking item after the current one.
|
|
47
|
-
#
|
|
48
|
-
# @param db [Timet::Database] The database instance.
|
|
49
|
-
# @param id [Integer] The ID of the current tracking item.
|
|
50
|
-
#
|
|
51
|
-
# @return [Integer] The start time of the next tracking item in epoch format.
|
|
52
|
-
#
|
|
53
|
-
# @example Fetch the start time of the next tracking item
|
|
54
|
-
# fetch_item_after_start(db, id)
|
|
55
|
-
def fetch_item_after_start(db, id)
|
|
56
|
-
db.find_item(id + 1)&.dig(Timet::Application::FIELD_INDEX['start']) || TimeHelper.current_timestamp
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Timet
|
|
4
|
-
# The TimeReportHelper module provides a collection of utility methods for processing and formatting time report data.
|
|
5
|
-
# It includes methods for processing time entries, handling time blocks, formatting items for CSV export,
|
|
6
|
-
# and validating date formats.
|
|
7
|
-
# This module is designed to be included in classes that require time report processing functionalities.
|
|
8
|
-
module TimeReportHelper
|
|
9
|
-
# Exports the report to a CSV file.
|
|
10
|
-
#
|
|
11
|
-
# @return [void] This method does not return a value; it performs side effects such as writing the CSV file.
|
|
12
|
-
#
|
|
13
|
-
# @example Export the report to a CSV file
|
|
14
|
-
# time_report.export_csv
|
|
15
|
-
#
|
|
16
|
-
# @note The method writes the items to a CSV file and prints a confirmation message.
|
|
17
|
-
def export_csv
|
|
18
|
-
file_name = "#{csv_filename}.csv"
|
|
19
|
-
write_csv(file_name)
|
|
20
|
-
|
|
21
|
-
puts "The #{file_name} has been exported."
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# Generates an iCalendar file and writes it to disk.
|
|
25
|
-
#
|
|
26
|
-
# @return [void]
|
|
27
|
-
def export_icalendar
|
|
28
|
-
file_name = "#{ics_filename}.ics"
|
|
29
|
-
cal = Timet::Utils.add_events(items)
|
|
30
|
-
|
|
31
|
-
File.write(file_name, cal.to_ical)
|
|
32
|
-
|
|
33
|
-
puts "The #{file_name} has been generated."
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'time_helper'
|
|
4
|
-
require_relative 'item_data_helper'
|
|
5
|
-
require 'date'
|
|
6
|
-
|
|
7
|
-
module Timet
|
|
8
|
-
# Helper methods for processing and updating time fields.
|
|
9
|
-
module TimeUpdateHelper
|
|
10
|
-
# Processes and updates a time field (start or end) of a tracking item.
|
|
11
|
-
#
|
|
12
|
-
# @param item [Array] The tracking item to be updated.
|
|
13
|
-
# @param field [String] The time field to be updated.
|
|
14
|
-
# @param date_value [String] The new value for the time field.
|
|
15
|
-
# @param id [Integer] The ID of the tracking item.
|
|
16
|
-
#
|
|
17
|
-
# @return [void] This method does not return a value; it performs side effects such as updating the time field.
|
|
18
|
-
#
|
|
19
|
-
# @note The method formats the date value and checks if it is valid.
|
|
20
|
-
# @note If the date value is valid, it updates the time field with the new value.
|
|
21
|
-
# @note If the date value is invalid, it prints an error message.
|
|
22
|
-
def process_and_update_time_field(*args)
|
|
23
|
-
item, field, date_value, id = args
|
|
24
|
-
formatted_date = TimeHelper.format_time_string(date_value)
|
|
25
|
-
|
|
26
|
-
return print_error(date_value) unless formatted_date
|
|
27
|
-
|
|
28
|
-
new_date = TimeHelper.update_time_field(item, field, formatted_date)
|
|
29
|
-
new_value_epoch = new_date.to_i
|
|
30
|
-
|
|
31
|
-
if valid_time_value?(item, field, new_value_epoch, id)
|
|
32
|
-
@db.update_item(id, field, new_value_epoch)
|
|
33
|
-
else
|
|
34
|
-
print_error(new_date)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Prints an error message for an invalid date.
|
|
39
|
-
#
|
|
40
|
-
# @param message [String] The error message to be printed.
|
|
41
|
-
#
|
|
42
|
-
# @return [void] This method does not return a value; it performs side effects such as printing an error message.
|
|
43
|
-
#
|
|
44
|
-
# @example Print an error message for an invalid date
|
|
45
|
-
# print_error('Invalid date: 2023-13-32')
|
|
46
|
-
def print_error(message)
|
|
47
|
-
puts "Invalid date: #{message}".red
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Validates if a new time value is valid for a specific time field (start or end).
|
|
51
|
-
#
|
|
52
|
-
# @param item [Array] The tracking item to be validated.
|
|
53
|
-
# @param field [String] The time field to be validated.
|
|
54
|
-
# @param new_value_epoch [Integer] The new time value in epoch format.
|
|
55
|
-
# @param id [Integer] The ID of the tracking item.
|
|
56
|
-
#
|
|
57
|
-
# @return [Boolean] Returns true if the new time value is valid, otherwise false.
|
|
58
|
-
#
|
|
59
|
-
# @example Validate a new 'start' time value
|
|
60
|
-
# valid_time_value?(item, 'start', 1633072800, 1)
|
|
61
|
-
def valid_time_value?(*args)
|
|
62
|
-
item, field, new_value_epoch, id = args
|
|
63
|
-
item_start = ItemDataHelper.fetch_item_start(item)
|
|
64
|
-
item_end = ItemDataHelper.fetch_item_end(item)
|
|
65
|
-
item_before_end = ItemDataHelper.fetch_item_before_end(@db, id, item_start)
|
|
66
|
-
item_after_start = ItemDataHelper.fetch_item_after_start(@db, id)
|
|
67
|
-
|
|
68
|
-
if field == 'start'
|
|
69
|
-
new_value_epoch.between?(item_before_end, item_end)
|
|
70
|
-
else
|
|
71
|
-
new_value_epoch.between?(item_start, item_after_start)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|