From 5271f82ca19a6d531b1277267bac4b6d3ac54bf6 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Wed, 5 Feb 2025 09:58:36 -0800 Subject: [PATCH 1/4] Add timehashes to nip 52 --- 52.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/52.md b/52.md index cc2625af..87738bd8 100644 --- a/52.md +++ b/52.md @@ -29,6 +29,7 @@ The list of tags are as follows: * `title` (required) title of the calendar event * `start` (required) inclusive start date in ISO 8601 format (YYYY-MM-DD). Must be less than `end`, if it exists. * `end` (optional) exclusive end date in ISO 8601 format (YYYY-MM-DD). If omitted, the calendar event ends on the same date as `start`. +* `T` (required) a [timehash](#timehashes) overlapping with the event's scheduled duration. Multiple tags SHOULD be included to cover the event's duration at different resolutions. * `location` (optional, repeated) location of the calendar event. e.g. address, GPS coordinates, meeting room name, link to video call * `g` (optional) [geohash](https://en.wikipedia.org/wiki/Geohash) to associate calendar event with a searchable physical location * `p` (optional, repeated) 32-bytes hex pubkey of a participant, optional recommended relay URL, and participant's role in the meeting @@ -54,6 +55,11 @@ The following tags are deprecated: ["start", ""], ["end", ""], + // Timehashes + ["T", "dy"], + ["T", "dyj"], + ["T", "dyju"], + // Location ["location", ""], ["g", ""], @@ -90,6 +96,7 @@ The list of tags are as follows: * `end` (optional) exclusive end Unix timestamp in seconds. If omitted, the calendar event ends instantaneously. * `start_tzid` (optional) time zone of the start timestamp, as defined by the IANA Time Zone Database. e.g., `America/Costa_Rica` * `end_tzid` (optional) time zone of the end timestamp, as defined by the IANA Time Zone Database. e.g., `America/Costa_Rica`. If omitted and `start_tzid` is provided, the time zone of the end timestamp is the same as the start timestamp. +* `T` (required) a [timehash](#timehashes) overlapping with the event's scheduled duration. Multiple tags SHOULD be included to cover the event's duration at different resolutions. * `summary` (optional) brief description of the calendar event * `image` (optional) url of an image to use for the event * `location` (optional, repeated) location of the calendar event. e.g. address, GPS coordinates, meeting room name, link to video call @@ -123,6 +130,11 @@ The following tags are deprecated: ["start_tzid", ""], ["end_tzid", ""], + // Timehashes + ["T", "dy"], + ["T", "dyj"], + ["T", "dyju"], + // Location ["location", ""], ["g", ""], @@ -223,9 +235,49 @@ The list of tags are as follows: } ``` -## Unsolved Limitations +## Timehashes -* No private events +A timehash is an adaptation of [geohashes](https://en.wikipedia.org/wiki/Geohash) to nostr timestamps. + +Here's some example code in javascript for calculating them: + +```javascript +function timeHash(seconds, precision = 32) { + const alphabet = '0123456789bcdefghjkmnpqrstuvwxyz' + const uint32 = Math.min(seconds >>> 0, 0xFFFFFFFF) + const binary = uint32.toString(2).padStart(32, '0') + const chunks = Math.min(Math.floor(precision / 5), 6) + + let hash = '' + + for (let i = 0; i < chunks * 5; i += 5) { + const chunk = binary.slice(i, i + 5) + const index = parseInt(chunk, 2) + + hash += alphabet[index] + } + + return hash +} +``` + +For the purposes of this NIP, including hashes at precisions of 10 (~month granularity), 15 (~day granularity), and 20 (~hour granularity) should be sufficient. This will result in hashes of between 2 and 4 characters. If an event spans a longer period of time, multiple hashes at the same granularity should be included. + +For example, for an event spanning 3 hours from `1738774800` to `1738785600`, the following hashes would be valid: `dy, dyj, dyjt, dyju, dyjv, dyjw`. Here's a function for calculating that: + +```javascript +function timeHashesBetween(start, end) { + const hashes = new Set() + + for (let seconds = start; seconds <= end; seconds += 1800) { + for (const precision of [10, 15, 20]) { + hashes.add(timeHash(seconds, precision)) + } + } + + return Array.from(hashes).sort() +} +``` ## Intentionally Unsupported Scenarios From ade4e614fed031ebaa10daaee089c233eca2db3a Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Wed, 5 Feb 2025 14:48:19 -0800 Subject: [PATCH 2/4] Tweak language --- 52.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/52.md b/52.md index 87738bd8..3bf64f4d 100644 --- a/52.md +++ b/52.md @@ -29,7 +29,7 @@ The list of tags are as follows: * `title` (required) title of the calendar event * `start` (required) inclusive start date in ISO 8601 format (YYYY-MM-DD). Must be less than `end`, if it exists. * `end` (optional) exclusive end date in ISO 8601 format (YYYY-MM-DD). If omitted, the calendar event ends on the same date as `start`. -* `T` (required) a [timehash](#timehashes) overlapping with the event's scheduled duration. Multiple tags SHOULD be included to cover the event's duration at different resolutions. +* `T` (required) a [timehash](#timehashes) overlapping with the event's scheduled timeframe. Multiple tags SHOULD be included to cover the event's timeframe at different resolutions. * `location` (optional, repeated) location of the calendar event. e.g. address, GPS coordinates, meeting room name, link to video call * `g` (optional) [geohash](https://en.wikipedia.org/wiki/Geohash) to associate calendar event with a searchable physical location * `p` (optional, repeated) 32-bytes hex pubkey of a participant, optional recommended relay URL, and participant's role in the meeting @@ -96,7 +96,7 @@ The list of tags are as follows: * `end` (optional) exclusive end Unix timestamp in seconds. If omitted, the calendar event ends instantaneously. * `start_tzid` (optional) time zone of the start timestamp, as defined by the IANA Time Zone Database. e.g., `America/Costa_Rica` * `end_tzid` (optional) time zone of the end timestamp, as defined by the IANA Time Zone Database. e.g., `America/Costa_Rica`. If omitted and `start_tzid` is provided, the time zone of the end timestamp is the same as the start timestamp. -* `T` (required) a [timehash](#timehashes) overlapping with the event's scheduled duration. Multiple tags SHOULD be included to cover the event's duration at different resolutions. +* `T` (required) a [timehash](#timehashes) overlapping with the event's scheduled timeframe. Multiple tags SHOULD be included to cover the event's timeframe at different resolutions. * `summary` (optional) brief description of the calendar event * `image` (optional) url of an image to use for the event * `location` (optional, repeated) location of the calendar event. e.g. address, GPS coordinates, meeting room name, link to video call From 8405dd7828797c954cf047ee41c4012306dbfa20 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Wed, 5 Feb 2025 14:56:08 -0800 Subject: [PATCH 3/4] Recommend fewer time hashes --- 52.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/52.md b/52.md index 3bf64f4d..b7c5ab0d 100644 --- a/52.md +++ b/52.md @@ -261,16 +261,16 @@ function timeHash(seconds, precision = 32) { } ``` -For the purposes of this NIP, including hashes at precisions of 10 (~month granularity), 15 (~day granularity), and 20 (~hour granularity) should be sufficient. This will result in hashes of between 2 and 4 characters. If an event spans a longer period of time, multiple hashes at the same granularity should be included. +For the purposes of this NIP, including hashes at precisions of 10 (~month granularity) and 15 (~day granularity) should be sufficient. This will result in hashes of 2 or 3 characters. If an event spans a longer period of time, multiple hashes at the same granularity should be included. -For example, for an event spanning 3 hours from `1738774800` to `1738785600`, the following hashes would be valid: `dy, dyj, dyjt, dyju, dyjv, dyjw`. Here's a function for calculating that: +For example, for an event spanning 12 hours from `1738774800` to `1738818000`, the following hashes would be valid: `dy, dyj, dyk`. Here's a function for calculating that: ```javascript function timeHashesBetween(start, end) { const hashes = new Set() - for (let seconds = start; seconds <= end; seconds += 1800) { - for (const precision of [10, 15, 20]) { + for (let seconds = start; seconds <= end; seconds += 12 * 60 * 60) { + for (const precision of [10, 15]) { hashes.add(timeHash(seconds, precision)) } } From 172377329a520301e316039621d795f883ef6100 Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Wed, 5 Feb 2025 15:01:59 -0800 Subject: [PATCH 4/4] Remove timehashes, use D instead --- 52.md | 58 ++----------------------------------------------------- README.md | 1 + 2 files changed, 3 insertions(+), 56 deletions(-) diff --git a/52.md b/52.md index b7c5ab0d..428b6e33 100644 --- a/52.md +++ b/52.md @@ -29,7 +29,6 @@ The list of tags are as follows: * `title` (required) title of the calendar event * `start` (required) inclusive start date in ISO 8601 format (YYYY-MM-DD). Must be less than `end`, if it exists. * `end` (optional) exclusive end date in ISO 8601 format (YYYY-MM-DD). If omitted, the calendar event ends on the same date as `start`. -* `T` (required) a [timehash](#timehashes) overlapping with the event's scheduled timeframe. Multiple tags SHOULD be included to cover the event's timeframe at different resolutions. * `location` (optional, repeated) location of the calendar event. e.g. address, GPS coordinates, meeting room name, link to video call * `g` (optional) [geohash](https://en.wikipedia.org/wiki/Geohash) to associate calendar event with a searchable physical location * `p` (optional, repeated) 32-bytes hex pubkey of a participant, optional recommended relay URL, and participant's role in the meeting @@ -55,11 +54,6 @@ The following tags are deprecated: ["start", ""], ["end", ""], - // Timehashes - ["T", "dy"], - ["T", "dyj"], - ["T", "dyju"], - // Location ["location", ""], ["g", ""], @@ -96,7 +90,7 @@ The list of tags are as follows: * `end` (optional) exclusive end Unix timestamp in seconds. If omitted, the calendar event ends instantaneously. * `start_tzid` (optional) time zone of the start timestamp, as defined by the IANA Time Zone Database. e.g., `America/Costa_Rica` * `end_tzid` (optional) time zone of the end timestamp, as defined by the IANA Time Zone Database. e.g., `America/Costa_Rica`. If omitted and `start_tzid` is provided, the time zone of the end timestamp is the same as the start timestamp. -* `T` (required) a [timehash](#timehashes) overlapping with the event's scheduled timeframe. Multiple tags SHOULD be included to cover the event's timeframe at different resolutions. +* `D` (required) the day-granularity unix timestamp on which the event takes place, calculated as `unix_seconds() / seconds_in_one_day`. Multiple tags SHOULD be included to cover the event's timeframe. * `summary` (optional) brief description of the calendar event * `image` (optional) url of an image to use for the event * `location` (optional, repeated) location of the calendar event. e.g. address, GPS coordinates, meeting room name, link to video call @@ -126,15 +120,11 @@ The following tags are deprecated: // Timestamps ["start", ""], ["end", ""], + ["D", "82549"], ["start_tzid", ""], ["end_tzid", ""], - // Timehashes - ["T", "dy"], - ["T", "dyj"], - ["T", "dyju"], - // Location ["location", ""], ["g", ""], @@ -235,50 +225,6 @@ The list of tags are as follows: } ``` -## Timehashes - -A timehash is an adaptation of [geohashes](https://en.wikipedia.org/wiki/Geohash) to nostr timestamps. - -Here's some example code in javascript for calculating them: - -```javascript -function timeHash(seconds, precision = 32) { - const alphabet = '0123456789bcdefghjkmnpqrstuvwxyz' - const uint32 = Math.min(seconds >>> 0, 0xFFFFFFFF) - const binary = uint32.toString(2).padStart(32, '0') - const chunks = Math.min(Math.floor(precision / 5), 6) - - let hash = '' - - for (let i = 0; i < chunks * 5; i += 5) { - const chunk = binary.slice(i, i + 5) - const index = parseInt(chunk, 2) - - hash += alphabet[index] - } - - return hash -} -``` - -For the purposes of this NIP, including hashes at precisions of 10 (~month granularity) and 15 (~day granularity) should be sufficient. This will result in hashes of 2 or 3 characters. If an event spans a longer period of time, multiple hashes at the same granularity should be included. - -For example, for an event spanning 12 hours from `1738774800` to `1738818000`, the following hashes would be valid: `dy, dyj, dyk`. Here's a function for calculating that: - -```javascript -function timeHashesBetween(start, end) { - const hashes = new Set() - - for (let seconds = start; seconds <= end; seconds += 12 * 60 * 60) { - for (const precision of [10, 15]) { - hashes.add(timeHash(seconds, precision)) - } - } - - return Array.from(hashes).sort() -} -``` - ## Intentionally Unsupported Scenarios ### Recurring Calendar Events diff --git a/README.md b/README.md index dd3c0f74..d0c5a705 100644 --- a/README.md +++ b/README.md @@ -278,6 +278,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos | `a` | coordinates to an event | relay URL | [01](01.md) | | `A` | root address | relay URL | [22](22.md) | | `d` | identifier | -- | [01](01.md) | +| `D` | day | -- | [52](52.md) | | `e` | event id (hex) | relay URL, marker, pubkey (hex) | [01](01.md), [10](10.md) | | `E` | root event id | relay URL | [22](22.md) | | `f` | currency code | -- | [69](69.md) |