toilet_tracker 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2cd32c62afad4d76b2c46dc4747fd6140c458dd7d5d7ee63d1d4c562761087f2
4
+ data.tar.gz: dc38bc38cd7f5610a6fde62c861e9e38d0c46908f2703098f775ffad8f32ae71
5
+ SHA512:
6
+ metadata.gz: 7f347d353402c1b4c3189ac6fabc9098b03c32caf6c04d729e17320e07693778ff81c698540f47923d24f585f189b02bdb913cc940f89dae98cb3d475c312da6
7
+ data.tar.gz: 7ff87b8075a25a879a96bcdb6c903e03d09419c45e1285b669a48f9184f6995852be152e1cdf98c46c08933e37515898d21818b29fc24dc5d3c3f4dc066ef1e1
data/.overcommit.yml ADDED
@@ -0,0 +1,31 @@
1
+ # Use this file to configure the Overcommit hooks you wish to use in your
2
+ # repository. Hooks are run in the order in which they are listed.
3
+
4
+ PreCommit:
5
+ RuboCop:
6
+ enabled: true
7
+ on_warn: fail # Treat all warnings as failures
8
+
9
+ BundleCheck:
10
+ enabled: true
11
+
12
+ TrailingWhitespace:
13
+ enabled: true
14
+
15
+ YamlSyntax:
16
+ enabled: true
17
+
18
+ CommitMsg:
19
+ CapitalizedSubject:
20
+ enabled: true
21
+
22
+ EmptyMessage:
23
+ enabled: true
24
+
25
+ TrailingPeriod:
26
+ enabled: true
27
+
28
+ PrePush:
29
+ RSpec:
30
+ enabled: true
31
+ command: ['bundle', 'exec', 'rspec']
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ SuggestExtensions: false
4
+
5
+ # Disable metrics cops for now
6
+ Metrics/BlockLength:
7
+ Enabled: false
8
+
9
+ Metrics/MethodLength:
10
+ Enabled: false
11
+
12
+ Metrics/AbcSize:
13
+ Enabled: false
14
+
15
+ Metrics/CyclomaticComplexity:
16
+ Enabled: false
17
+
18
+ Metrics/ClassLength:
19
+ Enabled: false
20
+
21
+ Metrics/PerceivedComplexity:
22
+ Enabled: false
23
+
24
+ # Disable documentation requirement
25
+ Style/Documentation:
26
+ Enabled: false
27
+
28
+ # Increase line length limit or disable
29
+ Layout/LineLength:
30
+ Max: 200
31
+
32
+ # Disable remaining Lint cops that need manual fixes
33
+ Lint/DuplicateBranch:
34
+ Enabled: false
35
+
36
+ Lint/UnreachableLoop:
37
+ Enabled: false
data/.standard.yml ADDED
@@ -0,0 +1,2 @@
1
+ ruby_version: 3.4
2
+ fix: true
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-06-26
4
+
5
+ - Initial release
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 NetherSoul
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/PARSING_SPEC.md ADDED
@@ -0,0 +1,361 @@
1
+ # ToiletTracker Parsing Specification
2
+
3
+ ## Overview
4
+
5
+ ToiletTracker is a Ruby gem that parses WhatsApp message exports to track toilet-related events using emoji-based commands. This specification defines all supported message formats, parsing behaviors, and expected outcomes.
6
+
7
+ ## Core Concepts
8
+
9
+ ### Message Structure
10
+ All WhatsApp messages follow this format:
11
+ ```
12
+ [DD/MM/YY, HH:MM:SS] Sender: Content
13
+ ```
14
+
15
+ ### Command Types
16
+ - **🚽 (Settings)**: Configure timezone shifts and preferences
17
+ - **💩 (Events)**: Log toilet events with timestamps and timezone handling
18
+
19
+ ### Shift Types
20
+ - **Functional Shifts**: Applied to event timestamps (patterns 1, 2, 5)
21
+ - **Decorative Shifts**: For statistics only, do not affect timestamps (patterns 7, 9)
22
+
23
+ ## Supported Parsing Patterns
24
+
25
+ ### WhatsApp Message Format
26
+ **Pattern**: `\[(?<day>\d{2})/(?<month>\d{2})/(?<year>\d{2}),\s*(?<time>\d{2}:\d{2}:\d{2})\]\s*(?<sender>[^:]+):\s*(?<content>.+?)(?:\s*‎<This message was edited>)?$`
27
+
28
+ **Examples**:
29
+ - `[01/01/25, 12:00:00] Alice: 💩`
30
+ - `[25/12/24, 23:59:59] Bob: 🚽 +2`
31
+ - `[15/06/25, 14:30:15] Charlie: 💩 +1 2025-06-15 12:00:00`
32
+
33
+ **Behavior**:
34
+ - Extracts day, month, year (2-digit), time, sender, content
35
+ - Handles edited messages with suffix `‎<This message was edited>`
36
+ - Year is converted to 20XX format (25 → 2025)
37
+
38
+ ### 1. Shift Set (Functional)
39
+ **Pattern**: `🚽\s*([+-]?\d+)$`
40
+ **Type**: `shift_set`
41
+
42
+ **Examples**:
43
+ - `🚽 +2` → Set +2 hour shift effective immediately
44
+ - `🚽 -1` → Set -1 hour shift effective immediately
45
+ - `🚽+3` → Set +3 hour shift (no space required)
46
+ - `🚽 0` → Reset to no shift
47
+
48
+ **Behavior**:
49
+ - Sets timezone shift for the user
50
+ - Effective time is the message timestamp
51
+ - Applies to all future events until changed
52
+ - Validates shift is within configured max_shift_hours
53
+
54
+ ### 2. Shift Set at Time (Functional)
55
+ **Pattern**: `🚽\s*([+-]?\d+)\s+(.+)$`
56
+ **Type**: `shift_set_at_time`
57
+
58
+ **Examples**:
59
+ - `🚽 +2 2025-01-15 14:30` → Set +2 shift effective from Jan 15, 14:30
60
+ - `🚽 -1 2024/12/25 10:00:00` → Set -1 shift effective from Dec 25, 10:00
61
+
62
+ **Behavior**:
63
+ - Sets timezone shift with retroactive effective time
64
+ - If effective_time < message_timestamp, applies retroactively
65
+ - Affects past events after the effective time
66
+ - Status becomes `:alert` if time difference > 30 days
67
+
68
+ ### 3. Timezone Set
69
+ **Pattern**: `🚽\s*([A-Za-z_/][A-Za-z0-9_/+-]*)$`
70
+ **Type**: `timezone_set`
71
+
72
+ **Examples**:
73
+ - `🚽 Europe/Rome` → Set timezone to Europe/Rome
74
+ - `🚽 PST` → Set timezone to PST
75
+ - `🚽 UTC` → Set timezone to UTC
76
+
77
+ **Behavior**:
78
+ - Converts timezone name to appropriate shift
79
+ - Calculates shift relative to default_timezone
80
+ - Creates a ToiletShiftResult with calculated shift
81
+ - Validates timezone exists in ActiveSupport::TimeZone
82
+
83
+ ### 4. Event Now
84
+ **Pattern**: `💩$`
85
+ **Type**: `event_now`
86
+
87
+ **Examples**:
88
+ - `💩` → Log event at message timestamp
89
+
90
+ **Behavior**:
91
+ - Uses message timestamp as event time
92
+ - Applies user's current active shift
93
+ - Most common usage pattern
94
+
95
+ ### 5. Event Now Shifted (Functional)
96
+ **Pattern**: `💩\s*([+-]?\d+)$`
97
+ **Type**: `event_now_shifted`
98
+
99
+ **Examples**:
100
+ - `💩 +2` → Log event at message time + 2 hours
101
+ - `💩 -1` → Log event at message time - 1 hour
102
+
103
+ **Behavior**:
104
+ - Overrides user's active shift for this event only
105
+ - Shift is applied to message timestamp
106
+ - Validates shift is within max_shift_hours
107
+
108
+ ### 6. Event at Time
109
+ **Pattern**: `💩\s+(\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}(?::\d{1,2})?)$`
110
+ **Type**: `event_at_time`
111
+
112
+ **Examples**:
113
+ - `💩 2025-01-15 14:30` → Log event at specific past time
114
+ - `💩 2024/12/25 10:00:00` → Supports both - and / separators
115
+ - `💩 2025-1-1 9:30` → Single digit months/days allowed
116
+
117
+ **Behavior**:
118
+ - Uses explicit timestamp, no shift applied
119
+ - Status becomes `:alert` if |event_time - message_time| > 30 days
120
+ - Records user's active shift for statistics only
121
+
122
+ ### 7. Event at Time Shifted (Decorative)
123
+ **Pattern**: `💩\s*([+-]?\d+)\s+(\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}(?::\d{1,2})?)$`
124
+ **Type**: `event_at_time_shifted`
125
+
126
+ **Examples**:
127
+ - `💩 +2 2025-01-15 14:30` → Log event at 14:30 (shift is decorative)
128
+ - `💩 -1 2024/12/25 10:00:00` → Log event at 10:00 (shift ignored)
129
+
130
+ **Behavior**:
131
+ - **CRITICAL**: Shift is decorative only, does not affect event_time
132
+ - Uses explicit timestamp as-is
133
+ - Shift is stored for statistics/display purposes
134
+ - Status determination same as event_at_time
135
+
136
+ ### 8. Event in Timezone
137
+ **Pattern**: `💩\s*([A-Za-z_/][A-Za-z0-9_/+-]*)\s*$`
138
+ **Type**: `event_in_timezone`
139
+
140
+ **Examples**:
141
+ - `💩 PST` → Log event at message time converted to PST
142
+ - `💩 Europe/Rome` → Log event at message time in Rome timezone
143
+
144
+ **Behavior**:
145
+ - Calculates shift from default_timezone to specified timezone
146
+ - Applies calculated shift to message timestamp
147
+ - Validates timezone exists
148
+
149
+ ### 9. Event in Timezone at Time (Decorative)
150
+ **Pattern**: `💩\s*([A-Za-z_/][A-Za-z0-9_/+-]*)\s+(\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}(?::\d{1,2})?)$`
151
+ **Type**: `event_in_timezone_at_time`
152
+
153
+ **Examples**:
154
+ - `💩 EST 2025-01-15 14:30` → Log event at 14:30 (timezone decorative)
155
+ - `💩 Europe/Rome 2024/12/25 10:00` → Log event at 10:00 (timezone ignored)
156
+
157
+ **Behavior**:
158
+ - **CRITICAL**: Timezone is decorative only, does not affect event_time
159
+ - Uses explicit timestamp as-is
160
+ - Timezone is stored for statistics/display purposes
161
+ - Status determination same as event_at_time
162
+
163
+ ## Date Format Support
164
+
165
+ Supported input formats:
166
+ 1. `YYYY-MM-DD HH:MM:SS` (e.g., `2025-01-15 14:30:00`)
167
+ 2. `YYYY-MM-DD HH:MM` (e.g., `2025-01-15 14:30`)
168
+ 3. `YYYY/MM/DD HH:MM:SS` (e.g., `2025/01/15 14:30:00`)
169
+ 4. `YYYY/MM/DD HH:MM` (e.g., `2025/01/15 14:30`)
170
+
171
+ **Parsing behavior**:
172
+ - Try formats in order, use first successful parse
173
+ - Fallback to ActiveSupport::TimeZone.parse
174
+ - Parse in context of default_timezone
175
+ - Single-digit months/days are accepted
176
+
177
+ ## Status Determination
178
+
179
+ ### Success Status (`:ok`)
180
+ - Valid message format
181
+ - Valid shift/timezone
182
+ - Time difference ≤ 30 days
183
+
184
+ ### Alert Status (`:alert`)
185
+ - Valid parsing but suspicious timing
186
+ - |event_time - message_time| > 30 days
187
+ - User should verify the timestamp
188
+
189
+ ### Error Status (`:error`)
190
+ - Invalid message format
191
+ - Invalid shift (exceeds max_shift_hours)
192
+ - Invalid timezone
193
+ - Invalid datetime format
194
+ - Parsing exceptions
195
+
196
+ ## Error Handling
197
+
198
+ ### Invalid Message Format
199
+ ```ruby
200
+ Core::InvalidMessageFormat.new(content, expected_format)
201
+ ```
202
+
203
+ ### Invalid Shift
204
+ ```ruby
205
+ Core::InvalidShift.new(shift_string)
206
+ # Triggered when:
207
+ # - Shift is not a valid integer
208
+ # - |shift| > max_shift_hours
209
+ ```
210
+
211
+ ### Invalid DateTime
212
+ ```ruby
213
+ Core::InvalidDateTime.new(datetime_string)
214
+ # Triggered when:
215
+ # - None of the date formats match
216
+ # - ActiveSupport can't parse the string
217
+ ```
218
+
219
+ ### Invalid Timezone
220
+ ```ruby
221
+ Core::InvalidTimezone.new(timezone_string)
222
+ # Triggered when:
223
+ # - Timezone not found in ActiveSupport::TimeZone
224
+ ```
225
+
226
+ ## Configuration
227
+
228
+ ### Default Settings
229
+ ```ruby
230
+ default_timezone: "UTC"
231
+ max_shift_hours: 12
232
+ ```
233
+
234
+ ### Validation Rules
235
+ - Shifts must be integers between -max_shift_hours and +max_shift_hours
236
+ - Timezones must exist in ActiveSupport::TimeZone
237
+ - Message timestamps must parse successfully
238
+
239
+ ## Processing Flow
240
+
241
+ 1. **Parse WhatsApp Format**: Extract timestamp, sender, content
242
+ 2. **Pattern Matching**: Try patterns in configuration order
243
+ 3. **Type-Specific Parsing**: Handle based on matched pattern
244
+ 4. **Validation**: Check shifts, timezones, timestamps
245
+ 5. **Status Determination**: Set :ok, :alert, or :error
246
+ 6. **Result Creation**: Return appropriate result object
247
+
248
+ ## Retroactive Shift Application
249
+
250
+ When timezone shifts are set retroactively:
251
+ 1. Process all messages in chronological order
252
+ 2. Track shift changes with effective times
253
+ 3. Apply correct shift to event_now events based on their timestamp
254
+ 4. Only affects functional shifts (patterns 1, 2, 5)
255
+ 5. Decorative shifts (patterns 7, 9) are never modified
256
+
257
+ ## Expected Result Objects
258
+
259
+ ### ToiletShiftResult
260
+ ```ruby
261
+ {
262
+ type: :shift_set | :shift_set_at_time | :timezone_set,
263
+ status: :ok | :alert | :error,
264
+ message: Core::Message,
265
+ shift: Integer,
266
+ effective_time: Time,
267
+ error_details: String | nil
268
+ }
269
+ ```
270
+
271
+ ### PoopEventResult
272
+ ```ruby
273
+ {
274
+ type: :event_now | :event_now_shifted | :event_at_time | :event_at_time_shifted |
275
+ :event_in_timezone | :event_in_timezone_at_time,
276
+ status: :ok | :alert | :error,
277
+ message: Core::Message,
278
+ shift: Integer | nil,
279
+ poop_time: Time,
280
+ timezone: String | nil,
281
+ error_details: String | nil
282
+ }
283
+ ```
284
+
285
+ ### ParseResult (Errors)
286
+ ```ruby
287
+ {
288
+ type: :parse_error,
289
+ status: :error,
290
+ message: nil,
291
+ error_details: String
292
+ }
293
+ ```
294
+
295
+ ## Test Requirements
296
+
297
+ ### Unit Tests
298
+ - Each pattern should be tested individually
299
+ - Edge cases for each pattern
300
+ - Invalid input handling
301
+ - Configuration boundary testing
302
+
303
+ ### Integration Tests
304
+ - Multi-message scenarios with retroactive shifts
305
+ - Complex timezone interactions
306
+ - Large dataset performance
307
+ - CLI functionality
308
+
309
+ ### Performance Benchmarks
310
+ - Process 1000+ message files efficiently
311
+ - Memory usage should remain reasonable
312
+ - Regex compilation should be optimized
313
+
314
+ ## CLI Features
315
+
316
+ ### Commands
317
+ - `parse FILE` - Parse WhatsApp messages from file
318
+ - `interactive` - Interactive mode with TTY::Prompt
319
+ - `stats FILE` - Show parsing statistics
320
+ - `version` - Show version information
321
+
322
+ ### Output Formats
323
+ - **table** - Formatted table with dynamic column widths
324
+ - **json** - Pretty-printed JSON
325
+ - **csv** - CSV format for spreadsheet import
326
+
327
+ ### Statistics
328
+ - Total chat lines vs toilet-tracking messages
329
+ - Success rate for toilet-tracking parsing
330
+ - Message type breakdown
331
+ - Top senders
332
+ - Common errors
333
+
334
+ ## Examples and Edge Cases
335
+
336
+ ### Valid Examples
337
+ ```
338
+ [01/01/25, 10:00:00] Alice: 🚽 +2
339
+ [01/01/25, 12:00:00] Alice: 💩
340
+ [01/01/25, 14:00:00] Alice: 💩 +1 2025-01-01 13:30:00
341
+ [02/01/25, 09:00:00] Bob: 🚽 Europe/Rome
342
+ [02/01/25, 15:00:00] Bob: 💩 PST 2025-01-02 12:00:00
343
+ ```
344
+
345
+ ### Invalid Examples
346
+ ```
347
+ [01/01/25, 10:00:00] Alice: 🚽 +25 # Exceeds max_shift_hours
348
+ [01/01/25, 12:00:00] Alice: 💩 invalid # Invalid shift format
349
+ [01/01/25, 14:00:00] Alice: 💩 2025-13-01 # Invalid date
350
+ [02/01/25, 09:00:00] Bob: 🚽 Mars/Phobos # Invalid timezone
351
+ ```
352
+
353
+ ### Edge Cases
354
+ ```
355
+ [01/01/25, 10:00:00] Alice: 🚽+0 # No space before shift
356
+ [01/01/25, 12:00:00] Alice: 💩 # Simple live event
357
+ [01/01/25, 14:00:00] Alice: 💩 2025-1-1 9:30 # Single digits
358
+ [02/01/25, 09:00:00] Bob: Some regular message # Ignored
359
+ ```
360
+
361
+ This specification serves as the authoritative reference for implementing and testing the ToiletTracker parsing functionality.