Created
October 21, 2025 11:28
-
-
Save imcarvalho/4bebaf6930932fe72e0ed6c8edc56c73 to your computer and use it in GitHub Desktop.
Vibe coded Python script to convert from Moodflow backups to Daylio. You'll need to unzip and base64 encode and decode the Daylio backup file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import json | |
| from datetime import datetime | |
| # Load existing Daylio backup | |
| with open('daylio_work/formatted.json', 'r') as f: | |
| daylio_data = json.load(f) | |
| # Load moodflow data | |
| with open('moodflow_backup_2025-10-14T21-25-12-179Z.json', 'r') as f: | |
| moodflow_data = json.load(f) | |
| # Get existing entries and VALIDATE them | |
| existing_entries = daylio_data.get('dayEntries', []) | |
| valid_existing = [] | |
| invalid_existing = [] | |
| for entry in existing_entries: | |
| year = entry.get('year') | |
| month = entry.get('month') | |
| day = entry.get('day') | |
| try: | |
| # Validate date | |
| if year and month and day: | |
| dt = datetime(year, month, day) | |
| valid_existing.append(entry) | |
| else: | |
| raise ValueError('Missing date fields') | |
| except (ValueError, TypeError) as e: | |
| invalid_existing.append(f"{year}-{month:02d}-{day:02d} (ID {entry.get('id')})") | |
| print(f"Original backup: {len(existing_entries)} entries") | |
| print(f" Valid: {len(valid_existing)}") | |
| print(f" Invalid (skipped): {len(invalid_existing)}") | |
| if invalid_existing: | |
| print(f" Skipped dates: {', '.join(invalid_existing)}") | |
| # Create a set of existing valid dates for quick lookup | |
| existing_dates = set() | |
| for entry in valid_existing: | |
| date_key = (entry.get('year'), entry.get('month'), entry.get('day')) | |
| existing_dates.add(date_key) | |
| # Convert moodflow entries with validation | |
| new_entries = [] | |
| invalid_moodflow = [] | |
| moods_data = moodflow_data.get('data', {}).get('moods', {}) | |
| for year_str, year_data in moods_data.items(): | |
| if not isinstance(year_data, dict): | |
| continue | |
| for month_str, month_data in year_data.items(): | |
| if not isinstance(month_data, dict): | |
| continue | |
| for day_str, day_data in month_data.items(): | |
| if not isinstance(day_data, dict): | |
| continue | |
| year = day_data.get("year") | |
| month = day_data.get("month") | |
| day = day_data.get("day") | |
| date_key = (year, month, day) | |
| # Skip if already exists | |
| if date_key in existing_dates: | |
| continue | |
| # Validate date | |
| try: | |
| dt = datetime(year, month, day, 12, 0, 0) | |
| # Convert to Unix timestamp in milliseconds | |
| timestamp_ms = int(dt.timestamp() * 1000) | |
| # Create Daylio entry | |
| entry = { | |
| "id": 0, # Will reassign later | |
| "minute": 0, | |
| "hour": 12, | |
| "day": day, | |
| "month": month, | |
| "year": year, | |
| "datetime": timestamp_ms, | |
| "timeZoneOffset": 0, # UTC | |
| "mood": day_data.get("avgRating", 3), | |
| "note": "", | |
| "note_title": "", | |
| "tags": [], | |
| "assets": [], | |
| "isFavorite": False | |
| } | |
| new_entries.append(entry) | |
| except (ValueError, TypeError) as e: | |
| invalid_moodflow.append(f"{year}-{month:02d}-{day:02d}") | |
| print(f"\nMoodflow data: {len(new_entries)} new valid entries") | |
| if invalid_moodflow: | |
| print(f" Invalid (skipped): {len(invalid_moodflow)}") | |
| print(f" Skipped dates: {', '.join(invalid_moodflow)}") | |
| # Merge valid entries only | |
| all_entries = valid_existing + new_entries | |
| # Sort by datetime (NEWEST FIRST) | |
| all_entries.sort(key=lambda x: x.get('datetime', 0), reverse=True) | |
| # Reassign IDs sequentially from 1 | |
| for idx, entry in enumerate(all_entries, start=1): | |
| entry['id'] = idx | |
| print(f"\n✓ Total VALID entries after merge: {len(all_entries)}") | |
| print(f"✓ IDs reassigned from 1 to {len(all_entries)}") | |
| # Update daylio data | |
| daylio_data['dayEntries'] = all_entries | |
| # Update metadata | |
| if 'metadata' in daylio_data: | |
| daylio_data['metadata']['number_of_entries'] = len(all_entries) | |
| daylio_data['metadata']['created_at'] = int(datetime.now().timestamp() * 1000) | |
| # Save | |
| with open('daylio_work/merged_validated.json', 'w') as f: | |
| json.dump(daylio_data, f, indent=2) | |
| print(f"\n✓ Validated data saved to: daylio_work/merged_validated.json") | |
| print(f"\nVerification:") | |
| print(f" Newest: {all_entries[0]['year']}-{all_entries[0]['month']:02d}-{all_entries[0]['day']:02d} (ID {all_entries[0]['id']})") | |
| print(f" Oldest: {all_entries[-1]['year']}-{all_entries[-1]['month']:02d}-{all_entries[-1]['day']:02d} (ID {all_entries[-1]['id']})") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment