{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://jasonlotito.github.io/daytiles/schema/v1/habits.schema.json",
  "title": "Daytiles habits export, version 1",
  "description": "Habits and their history, as Daytiles exports and imports them. Fields may be added within version 1: readers must ignore properties they don't know, and treat a missing property as its default. Only an incompatible change makes a new version.",
  "type": "object",
  "required": [
    "format",
    "version",
    "habits"
  ],
  "properties": {
    "$schema": {
      "type": "string",
      "description": "This schema's URL. Optional."
    },
    "format": {
      "const": "daytiles-habits"
    },
    "version": {
      "const": 1
    },
    "exportedAt": {
      "type": "string",
      "format": "date-time"
    },
    "habits": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/habit"
      }
    },
    "settings": {
      "$ref": "#/$defs/settings"
    }
  },
  "$defs": {
    "day": {
      "type": "string",
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
      "description": "A calendar day, YYYY-MM-DD, in the person's own time zone. Not a timestamp."
    },
    "habit": {
      "type": "object",
      "required": [
        "name"
      ],
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid",
          "description": "Matches a habit already in the app on import, which is merged into instead of duplicated. Leave it out to always add a new habit."
        },
        "name": {
          "type": "string"
        },
        "kind": {
          "enum": [
            "daily",
            "count",
            "amount",
            "quit"
          ],
          "default": "daily",
          "description": "daily: one tile a day. count: a set of squares filled in any order. amount: how much was done each day. quit: something given up, clean every day except the slips. Unknown values import as daily."
        },
        "color": {
          "enum": [
            "rose",
            "clay",
            "ember",
            "ochre",
            "brass",
            "moss",
            "fern",
            "jade",
            "lagoon",
            "tide",
            "harbor",
            "cornflower",
            "iris",
            "violet",
            "orchid",
            "berry"
          ],
          "description": "Unknown or missing colors get one picked for them."
        },
        "order": {
          "type": "integer",
          "description": "Position on home, lowest first."
        },
        "createdAt": {
          "type": "string",
          "format": "date-time"
        },
        "days": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/day"
          },
          "description": "Daily habits: the days it was done."
        },
        "weeklyGoal": {
          "type": "integer",
          "minimum": 1,
          "maximum": 7,
          "default": 7,
          "description": "Daily habits: days a week that make a full week."
        },
        "schedule": {
          "$ref": "#/$defs/schedule"
        },
        "target": {
          "type": "integer",
          "minimum": 1,
          "description": "Count habits: how many squares."
        },
        "squares": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/square"
          },
          "description": "Count habits: the filled squares."
        },
        "unit": {
          "type": "string",
          "description": "Amount habits: what's counted, as written after a number, like \"oz\" or \"pages\"."
        },
        "step": {
          "type": "number",
          "exclusiveMinimum": 0,
          "default": 1,
          "description": "Amount habits: how much one tap adds."
        },
        "dailyGoal": {
          "type": [
            "number",
            "null"
          ],
          "exclusiveMinimum": 0,
          "description": "Amount habits: how much makes a day done. Missing or null just keeps a record, and any amount counts as done."
        },
        "timerOn": {
          "type": "boolean",
          "default": false,
          "description": "Amount habits counted in minutes or hours: the + button starts and stops a timer instead of adding a step."
        },
        "amounts": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/amount"
          },
          "description": "Amount habits: how much was done each day."
        },
        "slips": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/day"
          },
          "description": "Quit habits: the days slipped. Every other day from createdAt through today counts as clean."
        },
        "checkIns": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/checkIn"
          },
          "description": "When it was checked off, for days checked off on the day itself. Used to learn reminder times."
        },
        "reminders": {
          "$ref": "#/$defs/reminders"
        },
        "appearance": {
          "$ref": "#/$defs/appearance"
        },
        "health": {
          "$ref": "#/$defs/health"
        },
        "pauses": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/pause"
          },
          "description": "Days off, such as a vacation. Paused days don't count against the habit or break its streak."
        },
        "skipped": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/day"
          },
          "description": "Daily and amount habits: single days set aside, such as a sick day. Like paused days, they don't count against the habit or break its streak."
        }
      }
    },
    "square": {
      "type": "object",
      "required": [
        "square"
      ],
      "properties": {
        "square": {
          "type": "integer",
          "minimum": 1
        },
        "day": {
          "$ref": "#/$defs/day",
          "description": "The day it was filled, when known."
        }
      }
    },
    "amount": {
      "type": "object",
      "required": [
        "day",
        "value"
      ],
      "properties": {
        "day": {
          "$ref": "#/$defs/day"
        },
        "value": {
          "type": "number",
          "minimum": 0
        }
      }
    },
    "checkIn": {
      "type": "object",
      "required": [
        "day",
        "time"
      ],
      "properties": {
        "day": {
          "$ref": "#/$defs/day"
        },
        "time": {
          "type": "string",
          "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
          "description": "Local time of day, 24-hour HH:mm."
        }
      }
    },
    "reminders": {
      "type": "object",
      "description": "Which reminders mention the habit, when reminders are chosen habit by habit.",
      "properties": {
        "morning": {
          "type": "boolean",
          "default": false,
          "description": "When the app's morning reminder covers chosen habits, whether this habit is one of them."
        },
        "evening": {
          "type": "boolean",
          "default": false,
          "description": "As morning, for the evening reminder."
        },
        "morningTime": {
          "type": "string",
          "pattern": "^(default|off|([01][0-9]|2[0-3]):[0-5][0-9])$",
          "description": "\"default\" follows the app's morning reminder in settings; \"HH:mm\" is the habit's own time in its place; \"off\" means no morning reminder for this habit. Missing leaves the habit as it is on import."
        },
        "eveningTime": {
          "type": "string",
          "pattern": "^(default|off|([01][0-9]|2[0-3]):[0-5][0-9])$",
          "description": "As morningTime, for the evening reminder."
        },
        "extraTimes": {
          "type": "array",
          "maxItems": 2,
          "items": {
            "type": "string",
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$"
          },
          "description": "Pro: up to two more times of day the habit is reminded, \"HH:mm\", beside the morning and evening reminders. Missing leaves the habit as it is on import."
        }
      }
    },
    "appearance": {
      "type": "object",
      "description": "How the habit looks in Daytiles. Safe to leave out when converting from another app.",
      "properties": {
        "finish": {
          "type": "string",
          "default": "glass"
        },
        "tint": {
          "type": "string",
          "default": "pale"
        },
        "photoTint": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "default": 0.6
        },
        "pageTiles": {
          "enum": [
            "big",
            "small",
            "tiny"
          ],
          "default": "big"
        },
        "galleryID": {
          "type": [
            "string",
            "null"
          ],
          "description": "A piece from the app's gallery behind the habit's page. A photo of your own is in the memories archive instead."
        },
        "homeSize": {
          "type": "integer",
          "minimum": 0,
          "maximum": 4,
          "default": 0,
          "description": "Tiles to a row on home: 4 small, 2 large, 1 wide. 0 follows the app's setting."
        }
      }
    },
    "health": {
      "type": "object",
      "required": [
        "kind"
      ],
      "description": "The Apple Health type the habit counts toward.",
      "properties": {
        "kind": {
          "enum": [
            "water",
            "mindful",
            "toothbrushing",
            "caffeine",
            "steps",
            "exercise",
            "workouts"
          ],
          "description": "Unknown types are left unlinked on import."
        },
        "each": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "How much one check-off counts as, in the type's own measure: milliliters, minutes, milligrams. Missing is the type's usual amount."
        },
        "since": {
          "$ref": "#/$defs/day",
          "description": "The first day that counts toward Health."
        }
      }
    },
    "pause": {
      "type": "object",
      "required": [
        "start"
      ],
      "properties": {
        "start": {
          "$ref": "#/$defs/day",
          "description": "The first day off."
        },
        "end": {
          "$ref": "#/$defs/day",
          "description": "The last day off, included. Missing means paused until resumed."
        }
      }
    },
    "schedule": {
      "type": "object",
      "description": "Daily habits: which days it asks for, in place of weeklyGoal. Give weekdays, or every with from. Days off the schedule don't count against the habit or break its streak. Missing means weeklyGoal days a week.",
      "properties": {
        "weekdays": {
          "type": "array",
          "items": {
            "enum": [
              "sunday",
              "monday",
              "tuesday",
              "wednesday",
              "thursday",
              "friday",
              "saturday"
            ]
          },
          "minItems": 1,
          "uniqueItems": true,
          "description": "The days of the week it's done on."
        },
        "every": {
          "type": "integer",
          "minimum": 2,
          "description": "Done every so many days."
        },
        "from": {
          "$ref": "#/$defs/day",
          "description": "A day it's due. It's due every `every` days before and after it."
        }
      }
    },
    "reminderSetting": {
      "type": "object",
      "required": [
        "mode",
        "time"
      ],
      "properties": {
        "mode": {
          "enum": [
            "off",
            "all",
            "choose"
          ],
          "description": "off, every habit, or only habits that have it turned on in their reminders."
        },
        "time": {
          "type": "string",
          "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
          "description": "Local time of day, 24-hour HH:mm."
        }
      }
    },
    "settings": {
      "type": "object",
      "description": "Settings for the whole app that go with the habits. Missing leaves the app's settings as they are on import. iCloud sync is per device and isn't included.",
      "properties": {
        "weekStart": {
          "enum": [
            "monday",
            "sunday"
          ]
        },
        "weekMarks": {
          "type": "boolean",
          "description": "Marks on the squares where each week begins and ends."
        },
        "morningReminder": {
          "$ref": "#/$defs/reminderSetting"
        },
        "eveningReminder": {
          "$ref": "#/$defs/reminderSetting"
        },
        "learnReminderTimes": {
          "type": "boolean",
          "description": "Reminders move to about when each habit usually gets checked off."
        },
        "homeTiles": {
          "enum": ["small", "large", "wide"],
          "description": "The size home tiles start at: four, two or one to a row."
        },
        "homeText": {
          "type": "boolean",
          "description": "Names and other words on home's tiles. False leaves tiles with only color and squares."
        },
        "appIcon": {
          "enum": ["teal", "rose", "ember", "gold", "lime", "fern", "jade", "sky", "harbor", "iris", "violet", "berry", "sunset", "graphite", "frost"],
          "description": "The icon on the home screen. Every one but teal needs Pro; without it the choice is kept for when Pro is back."
        }
      }
    }
  }
}
