Created
January 22, 2024 12:28
Revisions
-
jaimergp created this gist
Jan 22, 2024 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,11 @@ # From Zero To Hero to Hevy I had been using Zero To Hero for years to track my workouts. The app has not seen development in a while. You can backup your ZTH data to a Realm database via the app. This .zth file can be exported to a JSON with the attached Node.js script (you need `npm install realm`). Then the JSON can be post-processed into a CSV file following the Strong app format (I took this [sample](https://github.com/AlexandrosKyriakakis/StrongAppAnalytics/blob/main/Data/strong.csv?plain=1)). The resulting CSV can be imported via the Hevy Import/Export menu. 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,139 @@ import csv import json from datetime import datetime, timedelta from functools import lru_cache def read_json(path): with open(path, "r") as f: return json.load(f) def write_csv(path, data): with open(path, "w") as f: writer = csv.writer(f) writer.writerows(data) def populate_csv_from_json(data): rows = [ [ "Date", "Workout Name", "Duration", "Exercise Name", "Set Order", "Weight", "Reps", "Distance", "Seconds", "Notes", "Workout Notes", "RPE", ] ] class Duration: def __init__(self, *timestamps): self.timestamps = list(timestamps) def append(self, timestamp): if timestamp: self.timestamps.append(timestamp) def __str__(self): dt = timedelta(milliseconds = max(self.timestamps) - min(self.timestamps)) mm, ss = divmod(dt.total_seconds(), 60) hh, mm = divmod(mm, 60) if hh > 1: hh = 0 if hh == 0 and mm == 0: return "1h 00m" return "%dh %02dm" % (hh, mm) for workout in data["Workout"]: timestamp = workout["date"] or 0 date = datetime.fromtimestamp(timestamp / 1000).strftime("%Y-%m-%d %H:%M:%S") workout_name = workout["name"] duration = Duration(timestamp) for exercise in workout["exerciseList"]: for set_order, movement in enumerate(exercise["movementSetList"], 1): row = [] duration.append(movement["date"]) exercise_name = movement_map(movement["movement"]) weight = movement["performedWeight"] reps = movement["reps"] row.extend( [ date, workout_name, duration, exercise_name, set_order, weight, reps, "", "", "", "", "", ] ) rows.append(row) for workout in data["CustomProgramData"]: ... # for exercise in data["Exercise"]: # ... return rows @lru_cache(maxsize=None) def movement_map(key): return { "1653663211431": "Butterfly reverse", "1693643373232": "Push-up", "1693645906260": "Pike push-ups", "BARBELLROW": "Barbell row", "BEHINDTHENECKPRESS": "Behind the neck press", "BENCHPRESS": "Bench press", "BENCHPRESS_DECLINE": "Declined bench press", "BENCHPRESS_INCLINE": "Inclined bench press", "BENCHPRESS_INCLINE_DUMBBELL": "Inclined bench press (dumbbell)", "BICEPS_CURL_HAMMER_DUMBBELL": "Biceps hammer curl (dumbbell)", "CALFRAISE": "Seated calf raise", "CALFRAISE_STANDING": "Standing calf raise", "CURL_BARBELL": "Barbell curl", "CURL_DUMBBELL": "Dumbbell curl", "DEADLIFT": "Deadlift", "DEADLIFT_ROMANIAN": "Romanian deadlift", "FLYS_DUMBBELL": "Dumbbell fly", "FLYS_INCLINE_CABLE": "Incline cable fly", "LEGPRESS": "Leg press", "LEG_CURL": "Leg curl", "LEG_EXTENSION": "Leg extension", "LUNGE_BACKSTEP": "Backstep lunge", "OVERHEADPRESS": "Overhead press", "PEC_FLY_MACHINE": "Pec fly machine", "PULLDOWN_LATERAL": "Lateral pull down", "PULLUP": "Pull-up", "PULL_FACE": "Face pull", "ROW_CABLE_SEATED": "Seated cable row", "ROW_DUMBBELL": "Row (dumbbell)", "ROW_PENDLAY": "Row (pendlay)", "SHOULDER_PRESS_SEATED_DUMBBELL": "Seated shoulder press (dumbbell)", "SHOULDER_RAISE_SIDE_LATERAL": "Lateral side raise", "SKULLCRUSHERS": "Skullcrushers", "SQUAT": "Squat", "SQUAT_FRONT": "Front squat", "TRICEPS_DIPS": "Dips", "TRICEP_EXTENSION_CABLE": "Cable tricep extension", "TRICEP_PUSHDOWNS": "Tricep pushdowns", }.get(key, key) def main(): data = read_json("exported_data.json") rows = populate_csv_from_json(data) write_csv("exported_data.csv", rows) if __name__ == "__main__": main() 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,36 @@ const Realm = require('realm'); const fs = require('fs'); // Function to export entire Realm database to JSON async function exportRealmToJSON() { // Specify the file path for your Realm database const realmFile = './backup.zth'; // Configure Realm with the specified file path const realm = await Realm.open({ path: realmFile, readOnly: true, }); // Get all objects from all available object types const allObjects = {}; realm.schema.forEach((objectType) => { allObjects[objectType.name] = realm.objects(objectType.name); }); // Convert to JSON const jsonData = JSON.stringify(allObjects, null, 2); // Print or save the JSON string as needed console.log(jsonData); // If you want to save to a file const exportFilePath = 'exported_data.json'; fs.writeFileSync(exportFilePath, jsonData, 'utf-8'); // Close the Realm instance realm.close(); } // Call the function to export exportRealmToJSON();