alarmdb 0.1.0__tar.gz

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.
alarmdb-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,199 @@
1
+ Metadata-Version: 2.4
2
+ Name: alarmdb
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.13
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: jupyter>=1.1.1
8
+ Requires-Dist: lark>=1.3.1
9
+
10
+ <img src="docs/media/cover.png" alt="AlarmDB cover image" style="width:800px;"/>
11
+
12
+ AlarmDB is a memory-addressed, schema-driven row-based database backed by the iOS Clock app.
13
+
14
+ It can be interfaced via NyQL, a bespoke SQL-like query language.
15
+
16
+ ## Address space
17
+
18
+ We use a 5-bit architecture i.e. there are 32 possible addresses, from `0x00000` to `0x11111`. Each address refers to a word of 32 bytes. Bytes within the word are referenced by their byte offset.
19
+
20
+ Conceptually, one memory address = one word = 32 bytes = one "row" of data in the database.
21
+
22
+ ## Encoding
23
+
24
+ An iOS alarm contains the following parameters:
25
+
26
+ - `Time` (e.g. `"12:15 PM"`)
27
+ - `Repeat Days` (e.g. `["Monday", "Friday"]`)
28
+ - `Is Enabled` (`"Yes"/"No"`)
29
+ - `Allows Snooze` (`"Yes"/"No"`)
30
+ - `Label` (arbitrary string)
31
+
32
+ Note that we _could_ store all our data in the label, but that would be obviously cheating.
33
+
34
+ ### Addressing
35
+
36
+ There are `24*60=1440` possible alarm times, which works out to 10.49 bits. AlarmDB uses the 10 full (i.e. least significant) bits for addressing: the first 5 bits encode the address of the word (`0-31`), and the next 5 bits encode the byte offset within the word (`0-31`).
37
+
38
+ ### Encoding the data byte
39
+
40
+ To construct a single byte of data, we use the `Repeat Days`, `Is Enabled`, and `Allows Snooze` properties of an iOS alarm. The latter two are simple booleans i.e. one bit each.
41
+
42
+ `Repeat Days` would seem to be equally simple, i.e. each weekday is treated as a bit (on or off), which would give us seven bits of data. However, due to a [bug in iOS Shortcuts](https://discussions.apple.com/thread/256008048?sortBy=rank), a Shortcut that tries to do anything with the `Repeat Days` of an alarm which has _exactly one_ repeat day will cause the Shortcut to fail. For this reason, we are forced to sacrifice one bit (I chose the Lord's day, Sunday) to guard against this possibility. Essentially, if the remaining six bits (days) is going to have a popcount of `1`, then the seventh bit (Sunday) flips on.
43
+
44
+ Since the Lord's day is exempt from doing work, we have six bits from `Repeat Days` and two bits from the other two booleans, which gives us one even byte.
45
+
46
+ ### Encoding the schema
47
+
48
+ Since we're only using the bottom 10 full bytes of the alarm address space, `0x10000000000-0x10110100000` i.e. 5:04PM-11:59PM is effectively "reserved" and we can use it to safely store the schema of the database separately from the data. In reality, only `0x10000000000-0x10000011111` (5:04PM-5:36PM) would be used because each record (word) has up to 32 bytes, so there can be at most 32 fields.
49
+
50
+ The bits of a field's byte are broken up into two parts:
51
+
52
+ - Bits 0-2: Data type
53
+ - Bits 3-7: Length in bytes minus 1 (i.e. `0x00000 -> 1` and `0x11111 -> 32`)
54
+
55
+ Despite allowing up to 8 data types, AlarmDB currently only supports five:
56
+
57
+ - `TEXT` (`0x000`): Parsed as UTF-8, truncated by null terminator (`\x00`)
58
+ - `UINT` (`0x001`): Parsed as an unsigned int
59
+ - `INT` (`0x010`): Parsed as a two's complement signed int
60
+ - `TIMESTAMP` (`0x011`): Parsed as an unsigned int and treated as a POSIX timestamp
61
+ - `BOOLEAN` (`0x100`): `false` if every bit is off, `true` otherwise
62
+
63
+ Here I caved slightly and used the alarm's `Label` solely for the purpose of naming the field. There's a way to do it without it (encode the strings in the rest of the reserved address space with a max field name length of 12), but I thought of that too late and no longer have the energy to implement :)
64
+
65
+ Since the schema is user-defined, each record could be interpreted as a single 32-character string, 32 separate one-byte ints, or anything in between.
66
+
67
+ ## Querying: NyQL
68
+
69
+ Interfacing with AlarmDB is simple thanks to NyQL, a SQL-like query language. Below are some examples of NyQL queries (note the absence of a `FROM` clause because there is only one table):
70
+
71
+ ```sql
72
+ -- Clears all rows and sets the schema for the table
73
+ -- Note that the total number of bytes cannot exceed 32
74
+ -- (column name, type, number of bytes)
75
+ SET SCHEMA
76
+ ("alarm_name", TEXT, 8),
77
+ ("repeat_bitmask", UINT, 1),
78
+ ("hours", INT, 1),
79
+ ("minutes", INT, 1),
80
+ ("enabled", BOOLEAN, 1),
81
+ ("allows_snooze", BOOLEAN, 1),
82
+ ("timestamp_added", TIMESTAMP, 4);
83
+
84
+ -- Get the current schema
85
+ GET SCHEMA;
86
+
87
+ """
88
+ Outputs ->
89
+ col_name,type,length_bytes
90
+ alarm_name,TEXT,16
91
+ repeat_bitmask,UINT,1
92
+ hours,INT,1
93
+ minutes,INT,1
94
+ enabled,BOOLEAN,1
95
+ allows_snooze,BOOLEAN,1
96
+ timestamp_added,TIMESTAMP,4
97
+ """
98
+
99
+ -- Add rows
100
+ INSERT VALUES
101
+ ("work", 62, 8, 15, TRUE, FALSE, 1778904247),
102
+ ("do laundry", 8, 18, 0, TRUE, TRUE, 1778904372),
103
+ ("write readme", 0, 19, 45, FALSE, TRUE, 1778905161);
104
+
105
+ -- Get all rows
106
+ SELECT *;
107
+
108
+ """
109
+ Outputs ->
110
+ alarm_name,repeat_bitmask,hours,minutes,enabled,allows_snooze,timestamp_added
111
+ work,62,8,15,True,False,2026-05-16 00:04:07
112
+ do laundry,8,18,0,True,True,2026-05-16 00:06:12
113
+ write readme,0,19,45,False,True,2026-05-16 00:19:21
114
+ """
115
+
116
+ -- More complex SELECT queries
117
+
118
+ SELECT AVG(hours) AS avg_hours, enabled GROUP BY enabled HAVING enabled;
119
+
120
+ """
121
+ Outputs ->
122
+ avg_hours,enabled
123
+ 13.0,True
124
+ """
125
+
126
+ SELECT alarm_name AS morning_alarm_names WHERE hours < 12 AND enabled;
127
+
128
+ """
129
+ Outputs ->
130
+ morning_alarm_names
131
+ work
132
+ """
133
+
134
+ SELECT alarm_name, hours, minutes ORDER BY minutes DESC LIMIT 2;
135
+
136
+ """
137
+ Outputs ->
138
+ alarm_name,hours,minutes
139
+ write readme,19,45
140
+ work,8,15
141
+ """
142
+
143
+ -- Delete by condition
144
+ DELETE WHERE alarm_name = "write readme";
145
+
146
+ SELECT *;
147
+
148
+ """
149
+ Outputs ->
150
+ alarm_name,repeat_bitmask,hours,minutes,enabled,allows_snooze,timestamp_added
151
+ work,62,8,15,True,False,2026-05-16 00:04:07
152
+ do laundry,8,18,0,True,True,2026-05-16 00:06:12
153
+ """
154
+
155
+ -- Update by condition
156
+ UPDATE SET alarm_name = "pick up laundry", hours = 20 WHERE alarm_name = "do laundry";
157
+
158
+ SELECT *;
159
+
160
+ """
161
+ Outputs ->
162
+ alarm_name,repeat_bitmask,hours,minutes,enabled,allows_snooze,timestamp_added
163
+ work,62,8,15,True,False,2026-05-16T00:04:07
164
+ pick up laundry,8,20,0,True,True,2026-05-16T00:06:12
165
+ """
166
+
167
+ -- Set a new schema (this also wipes the database)
168
+ SET SCHEMA ("name", TEXT, 8), ("age", INT, 1);
169
+ ```
170
+
171
+ ## Running AlarmDB
172
+
173
+ AlarmDB is easy-to-use, open-source, and free. To get started, download the following iOS shortcuts:
174
+
175
+ - [AlarmDB write](https://www.icloud.com/shortcuts/8b1ddb45834c4ab4b43f1d358f04ae03) (called internally)
176
+ - [AlarmDB read](https://www.icloud.com/shortcuts/a1d47283e9a34785aed1c658ef81f2fb) (called internally)
177
+ - [AlarmDB](https://www.icloud.com/shortcuts/0b5d7a254ce2484299ae7476e36c45a8) (entrypoint)
178
+
179
+ You will need to update the `cd` command in the `Run Shell Script` action of the `AlarmDB` shortcut so it can navigate to your local copy of this repo.
180
+
181
+ It's convenient to add the `AlarmDB` shortcut to Quick Actions so that you can run NyQL anywhere by selecting the text and going `Services > AlarmDB`.
182
+
183
+ ### Recommended IDEs
184
+
185
+ - MacOS Notes app
186
+
187
+ ## Running on mobile
188
+
189
+ AlarmDB was developed on desktop, but it can be run on mobile if the `Run Shell Script` action of the `AlarmDB` shortcut is replaced with a `Run Script Over SSH` action.
190
+
191
+ ## Other notes
192
+
193
+ ### NULLs
194
+
195
+ There is no concept of a `NULL` value in AlarmDB. As long as there is at least one alarm (byte) in an address, it is assumed that the word at that address represents a full record. If there are no alarms in the byte offsets where the schema expects there to be, then they are assumed to be all zero. In other words, the "default" value for a missing int is `0`, `""` for missing strings, `false` for missing booleans, and `January 1, 1970` for missing timestamps.
196
+
197
+ ### Serialization limitations
198
+
199
+ The `AlarmDB read` shortcut pipe-delimits alarm data, so `|` cannot be used in the name of any field in the schema (since those get written directly into the alarm's label).
@@ -0,0 +1,190 @@
1
+ <img src="docs/media/cover.png" alt="AlarmDB cover image" style="width:800px;"/>
2
+
3
+ AlarmDB is a memory-addressed, schema-driven row-based database backed by the iOS Clock app.
4
+
5
+ It can be interfaced via NyQL, a bespoke SQL-like query language.
6
+
7
+ ## Address space
8
+
9
+ We use a 5-bit architecture i.e. there are 32 possible addresses, from `0x00000` to `0x11111`. Each address refers to a word of 32 bytes. Bytes within the word are referenced by their byte offset.
10
+
11
+ Conceptually, one memory address = one word = 32 bytes = one "row" of data in the database.
12
+
13
+ ## Encoding
14
+
15
+ An iOS alarm contains the following parameters:
16
+
17
+ - `Time` (e.g. `"12:15 PM"`)
18
+ - `Repeat Days` (e.g. `["Monday", "Friday"]`)
19
+ - `Is Enabled` (`"Yes"/"No"`)
20
+ - `Allows Snooze` (`"Yes"/"No"`)
21
+ - `Label` (arbitrary string)
22
+
23
+ Note that we _could_ store all our data in the label, but that would be obviously cheating.
24
+
25
+ ### Addressing
26
+
27
+ There are `24*60=1440` possible alarm times, which works out to 10.49 bits. AlarmDB uses the 10 full (i.e. least significant) bits for addressing: the first 5 bits encode the address of the word (`0-31`), and the next 5 bits encode the byte offset within the word (`0-31`).
28
+
29
+ ### Encoding the data byte
30
+
31
+ To construct a single byte of data, we use the `Repeat Days`, `Is Enabled`, and `Allows Snooze` properties of an iOS alarm. The latter two are simple booleans i.e. one bit each.
32
+
33
+ `Repeat Days` would seem to be equally simple, i.e. each weekday is treated as a bit (on or off), which would give us seven bits of data. However, due to a [bug in iOS Shortcuts](https://discussions.apple.com/thread/256008048?sortBy=rank), a Shortcut that tries to do anything with the `Repeat Days` of an alarm which has _exactly one_ repeat day will cause the Shortcut to fail. For this reason, we are forced to sacrifice one bit (I chose the Lord's day, Sunday) to guard against this possibility. Essentially, if the remaining six bits (days) is going to have a popcount of `1`, then the seventh bit (Sunday) flips on.
34
+
35
+ Since the Lord's day is exempt from doing work, we have six bits from `Repeat Days` and two bits from the other two booleans, which gives us one even byte.
36
+
37
+ ### Encoding the schema
38
+
39
+ Since we're only using the bottom 10 full bytes of the alarm address space, `0x10000000000-0x10110100000` i.e. 5:04PM-11:59PM is effectively "reserved" and we can use it to safely store the schema of the database separately from the data. In reality, only `0x10000000000-0x10000011111` (5:04PM-5:36PM) would be used because each record (word) has up to 32 bytes, so there can be at most 32 fields.
40
+
41
+ The bits of a field's byte are broken up into two parts:
42
+
43
+ - Bits 0-2: Data type
44
+ - Bits 3-7: Length in bytes minus 1 (i.e. `0x00000 -> 1` and `0x11111 -> 32`)
45
+
46
+ Despite allowing up to 8 data types, AlarmDB currently only supports five:
47
+
48
+ - `TEXT` (`0x000`): Parsed as UTF-8, truncated by null terminator (`\x00`)
49
+ - `UINT` (`0x001`): Parsed as an unsigned int
50
+ - `INT` (`0x010`): Parsed as a two's complement signed int
51
+ - `TIMESTAMP` (`0x011`): Parsed as an unsigned int and treated as a POSIX timestamp
52
+ - `BOOLEAN` (`0x100`): `false` if every bit is off, `true` otherwise
53
+
54
+ Here I caved slightly and used the alarm's `Label` solely for the purpose of naming the field. There's a way to do it without it (encode the strings in the rest of the reserved address space with a max field name length of 12), but I thought of that too late and no longer have the energy to implement :)
55
+
56
+ Since the schema is user-defined, each record could be interpreted as a single 32-character string, 32 separate one-byte ints, or anything in between.
57
+
58
+ ## Querying: NyQL
59
+
60
+ Interfacing with AlarmDB is simple thanks to NyQL, a SQL-like query language. Below are some examples of NyQL queries (note the absence of a `FROM` clause because there is only one table):
61
+
62
+ ```sql
63
+ -- Clears all rows and sets the schema for the table
64
+ -- Note that the total number of bytes cannot exceed 32
65
+ -- (column name, type, number of bytes)
66
+ SET SCHEMA
67
+ ("alarm_name", TEXT, 8),
68
+ ("repeat_bitmask", UINT, 1),
69
+ ("hours", INT, 1),
70
+ ("minutes", INT, 1),
71
+ ("enabled", BOOLEAN, 1),
72
+ ("allows_snooze", BOOLEAN, 1),
73
+ ("timestamp_added", TIMESTAMP, 4);
74
+
75
+ -- Get the current schema
76
+ GET SCHEMA;
77
+
78
+ """
79
+ Outputs ->
80
+ col_name,type,length_bytes
81
+ alarm_name,TEXT,16
82
+ repeat_bitmask,UINT,1
83
+ hours,INT,1
84
+ minutes,INT,1
85
+ enabled,BOOLEAN,1
86
+ allows_snooze,BOOLEAN,1
87
+ timestamp_added,TIMESTAMP,4
88
+ """
89
+
90
+ -- Add rows
91
+ INSERT VALUES
92
+ ("work", 62, 8, 15, TRUE, FALSE, 1778904247),
93
+ ("do laundry", 8, 18, 0, TRUE, TRUE, 1778904372),
94
+ ("write readme", 0, 19, 45, FALSE, TRUE, 1778905161);
95
+
96
+ -- Get all rows
97
+ SELECT *;
98
+
99
+ """
100
+ Outputs ->
101
+ alarm_name,repeat_bitmask,hours,minutes,enabled,allows_snooze,timestamp_added
102
+ work,62,8,15,True,False,2026-05-16 00:04:07
103
+ do laundry,8,18,0,True,True,2026-05-16 00:06:12
104
+ write readme,0,19,45,False,True,2026-05-16 00:19:21
105
+ """
106
+
107
+ -- More complex SELECT queries
108
+
109
+ SELECT AVG(hours) AS avg_hours, enabled GROUP BY enabled HAVING enabled;
110
+
111
+ """
112
+ Outputs ->
113
+ avg_hours,enabled
114
+ 13.0,True
115
+ """
116
+
117
+ SELECT alarm_name AS morning_alarm_names WHERE hours < 12 AND enabled;
118
+
119
+ """
120
+ Outputs ->
121
+ morning_alarm_names
122
+ work
123
+ """
124
+
125
+ SELECT alarm_name, hours, minutes ORDER BY minutes DESC LIMIT 2;
126
+
127
+ """
128
+ Outputs ->
129
+ alarm_name,hours,minutes
130
+ write readme,19,45
131
+ work,8,15
132
+ """
133
+
134
+ -- Delete by condition
135
+ DELETE WHERE alarm_name = "write readme";
136
+
137
+ SELECT *;
138
+
139
+ """
140
+ Outputs ->
141
+ alarm_name,repeat_bitmask,hours,minutes,enabled,allows_snooze,timestamp_added
142
+ work,62,8,15,True,False,2026-05-16 00:04:07
143
+ do laundry,8,18,0,True,True,2026-05-16 00:06:12
144
+ """
145
+
146
+ -- Update by condition
147
+ UPDATE SET alarm_name = "pick up laundry", hours = 20 WHERE alarm_name = "do laundry";
148
+
149
+ SELECT *;
150
+
151
+ """
152
+ Outputs ->
153
+ alarm_name,repeat_bitmask,hours,minutes,enabled,allows_snooze,timestamp_added
154
+ work,62,8,15,True,False,2026-05-16T00:04:07
155
+ pick up laundry,8,20,0,True,True,2026-05-16T00:06:12
156
+ """
157
+
158
+ -- Set a new schema (this also wipes the database)
159
+ SET SCHEMA ("name", TEXT, 8), ("age", INT, 1);
160
+ ```
161
+
162
+ ## Running AlarmDB
163
+
164
+ AlarmDB is easy-to-use, open-source, and free. To get started, download the following iOS shortcuts:
165
+
166
+ - [AlarmDB write](https://www.icloud.com/shortcuts/8b1ddb45834c4ab4b43f1d358f04ae03) (called internally)
167
+ - [AlarmDB read](https://www.icloud.com/shortcuts/a1d47283e9a34785aed1c658ef81f2fb) (called internally)
168
+ - [AlarmDB](https://www.icloud.com/shortcuts/0b5d7a254ce2484299ae7476e36c45a8) (entrypoint)
169
+
170
+ You will need to update the `cd` command in the `Run Shell Script` action of the `AlarmDB` shortcut so it can navigate to your local copy of this repo.
171
+
172
+ It's convenient to add the `AlarmDB` shortcut to Quick Actions so that you can run NyQL anywhere by selecting the text and going `Services > AlarmDB`.
173
+
174
+ ### Recommended IDEs
175
+
176
+ - MacOS Notes app
177
+
178
+ ## Running on mobile
179
+
180
+ AlarmDB was developed on desktop, but it can be run on mobile if the `Run Shell Script` action of the `AlarmDB` shortcut is replaced with a `Run Script Over SSH` action.
181
+
182
+ ## Other notes
183
+
184
+ ### NULLs
185
+
186
+ There is no concept of a `NULL` value in AlarmDB. As long as there is at least one alarm (byte) in an address, it is assumed that the word at that address represents a full record. If there are no alarms in the byte offsets where the schema expects there to be, then they are assumed to be all zero. In other words, the "default" value for a missing int is `0`, `""` for missing strings, `false` for missing booleans, and `January 1, 1970` for missing timestamps.
187
+
188
+ ### Serialization limitations
189
+
190
+ The `AlarmDB read` shortcut pipe-delimits alarm data, so `|` cannot be used in the name of any field in the schema (since those get written directly into the alarm's label).
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "alarmdb"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "jupyter>=1.1.1",
9
+ "lark>=1.3.1",
10
+ ]
11
+
12
+ [dependency-groups]
13
+ dev = [
14
+ "pytest>=8.0",
15
+ "pytest-sugar>=1.0",
16
+ ]
17
+
18
+ [tool.pytest.ini_options]
19
+ pythonpath = ["src"]
20
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+