diff --git a/Cargo.lock b/Cargo.lock index 769de77..d9d8e32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,15 +31,15 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", @@ -50,18 +50,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.15.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c764d619ca78fccbf3069b37bd7af92577f044bb15236036662d79b6559f25b7" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" [[package]] name = "cfg-if" @@ -71,9 +68,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -118,32 +115,38 @@ dependencies = [ "cc", ] +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -156,35 +159,36 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "randevu" -version = "1.0.2" +version = "2.0.0" dependencies = [ "blake3", "chrono", + "itoa", ] [[package]] name = "syn" -version = "2.0.50" +version = "2.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" dependencies = [ "proc-macro2", "quote", @@ -199,9 +203,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -209,9 +213,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -224,9 +228,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -234,9 +238,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -247,9 +251,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "windows-core" @@ -262,13 +266,14 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", + "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", @@ -277,42 +282,48 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml index bde5a92..31dac7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "randevu" -version = "1.0.2" +version = "2.0.0" authors = ["TypicalHog"] edition = "2021" description = "The official Rust implementation of the RANDEVU algorithm" @@ -11,5 +11,6 @@ keywords = ["randevu", "rdv", "global", "reminder", "blake3"] categories = ["algorithms", "date-and-time"] [dependencies] -blake3 = "1.5.0" -chrono = "0.4.34" +blake3 = "1.5.1" +chrono = "0.4.37" +itoa = "1.0.11" diff --git a/README.md b/README.md index 77c0edd..ec684c2 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,168 @@ # RANDEVU -Global Probabilistic Daily Reminders for Anything +**Universal Probabilistic Daily Reminder Coordination System for Anything** + +- UNIVERSAL - reminders for the same object are the same for everyone +- PROBABILISTIC - reminders are calculated using a simple probabilistic +algorithm based on powers of 2 +- DAILY - reminders are following a daily schedule/interval (UTC) +- INTRADAY - infinite optional intraday reminder times (RDVT - added in version +2.0 as an extension to the existing system) +- GENERIC - applicable to anything and everything, literally (where it makes +sense, see use cases) +- FOSS - in the public domain (Unlicense or MIT or Apache-2.0) +- OFFLINE - no internet connection required +- PORTABLE - simple code, easy to port anywhere where blake3 is available +- DETERMINISTIC - easily computable and predictable for any object and any date +- PSEUDORANDOM - reminders are spaced randomly and uniformly +- ADJUSTABLE - user can decide how frequently (roughly) they'd like to see +reminders for each object + +**https://github.com/TypicalHog/randevu (Rust - REFERENCE IMPLEMENTATION)** +https://github.com/TypicalHog/randevu-ts (TypeScript)   +https://github.com/TypicalHog/randevu-py (Python) +The above implementations are available on Crates.io, PyPI, and npm as **randevu**. +Feel free to contribute, create an implementation in another (missing) language, +or even an alternative one for 1 (or more) of the 3 languages I've already +created a repository and crate/module/package for. + +## EXAMPLE USAGE +```rust +use chrono::Utc; +use randevu::{rdv, rdvt}; + +fn main() { + let object = "THE_SIMPSONS"; + let date = Utc::now(); + let rdv_value = rdv(object, &date); + let rdvt_value = rdvt(0, object, &date); + + println!("Object {} has RDV{} today with RDVT0 at {:?}", object, rdv_value, rdvt_value); +} -https://github.com/TypicalHog/randevu +``` + +--- -- GLOBAL - reminders for the same object are the same for everyone -- PROBABILISTIC - reminders are calculated using the probabilistic algorithm -- DAILY - reminders are generated daily (UTC) -- GENERIC - applicable to anything and everything, literally -- FOSS - in the public domain (Unlicense or MIT or Apache-2.0) -- OFFLINE - no internet connection required -- DETERMINISTIC - easily computable and predictable for any object and any date -- PSEUDORANDOM - reminders are spaced randomly and uniformly -- ADJUSTABLE - user can decide how frequently they would like to be reminded of each object +**I'm aware of the fact that README.md is a confusing jungle of words, random** +**thoughts, and ideas. (Will work on it)**   +I kinda suck at explaining stuff and structuring text in an organized manner. +I'm sorry about that. :/ +I gave it my best, and I'll keep trying to improve it in the future. +This whole document will probably be revamped entirely. Stuff will be explained +better, irrelevant things, tangents, thoughts and rambles removed, etc. +I would also like to write something like a proper spec for the RANDEVU +algorithm/system. +I'm thinking about creating a FAQ section/document where I'll explain various +aspects of the system and anything else I'd like to clarify or expand on in more +detail. +Also thinking about adding some visual analogies (infographics) with coin +tossing and such which I believe have enormous potential in terms of making the +system easier to explain, as well as understand. --- ## KEY CONCEPTS -- OBJECT - a string representing anything that can be represented with a string of characters (game, movie, person, song, place, video, topic, word, book, number, brand, post, event, item, website, app, quote, action or literally anything else) -- DATE - a string representing a date in ISO 8601 format (YYYY-MM-DD) -- RDV - a positive integer representing the level/significance of a reminder for an OBJECT on a specific DATE +- OBJECT - a string representing anything that is representable with a string of +characters (a game, a movie, a person, a song, a place, a video, a topic, +a word, a book, a number, a brand, a post, an event, an item, a website, an app, +a quote, an action, a movement or **literally** anything else)   +- DATE - a date (UTC) for which we want to calculate the RDV for +- RDV - a positive integer representing the level/significance/rarity of a +reminder for a certain OBJECT on a specific DATE   + +**(New feature of version 2.0)**   +- RDVT - one of an infinite number of random moments in a day (0-24h UTC) when +an object has its reminders for that day, allows for infinite precission   +- RANK - RDVT rank, ranked 0 (most significant) to infinity - RDVT0, RDVT1...   ## RDV CALCULATION -`RDV = number of leading zero bits in blake3(blake3(OBJECT) || blake3(DATE))` +`RDV = number of leading zero bits in blake3::keyed_hash(key: DATE, data: OBJECT)`   +Note: The previous version (1.0) used a different algorithm, so the RDV values +between the two versions have changed and are completely un-correlated.   +By implementing this change, I've eliminated 2/3 blake3 hash calculations and +improved performance (not that it mattered).   +But now I can calculate about 10 million RDV values per second on my PC. +I have strong reasons to believe such major changes won't be happening in the +future and that this is the final version of the algorithm.   +It was still a good time to do such a fundamental change to the algorithm, since +RANDEVU had essentially no adoption apart from myself and a small community I'm +a part of.   ## HOW IT WORKS AND POTENTIAL USE CASES -Imagine a system that assigns a special number (RDV) to every object each day. -The number assigned to each object is different for each object and changes daily. -The number has a 50% chance to be 0, 25% chance to be 1, 12.5% to be 2, and so on (each one being twice as rare). -If an object has RDV4, that also implies RDV3, RDV2, RDV1 and RDV0. -Users can then choose to set a threshold value for each object and if the RDV value for a specific object is greater than or equal to the threshold - the user may decide to do something with that object. -For example, one may decide to watch a certain video once its RDV value hits their desired threshold. -Threshold allows one to decide how often they would like to be "reminded of" a certain object. -0 -> every day, 1 -> every 2 days on average, 2 -> 4 days, 3 -> 8 days, and so on (allows for essentially infinite frequencies of reminders, though ones above 10 happen quite rarely - 2^10 = 1024 days). -If multiple people used this system to get reminded of the same things - they would all get reminded of them on the same days and thus be able to coordinate meetings related to the object in question. -This could allow fans of a "dead" game to all meet and play it on the same day, like once every 256 days let's say. -People could re-watch their favorite movies or videos and discuss them with their fellow fans on the same day. -This system can be applied to anything. -It can be used to assign special appreciation/remembrance days to your favorite books, songs, artists, events, or (as I already said) - ANYTHING. -One could have a huge list of objects they care about and never again risk forgetting any of them - since they will be reminded of them eventually. +Imagine a system that assigns a special number (RDV) to every object each day.   +The number assigned to each object is different for each object and changes +daily.   +The number has a 50% chance to be 0, 25% chance to be 1, 12.5% to be 2, and so +on (each number being twice as rare).   +I've based everything around base-2 (simplest numerical base after unary), it +makes everything fit together so perfectly (you'll probably see why when you +learn more about how the system works). +By making each reminder level twice as rare - we can achive effectively +infinitely infrequent reminders, as well as daily ones, and a pretty good range +of reminders of various frequencies in between. +If an object has a reminder RDV4, that also implies RDV3, RDV2, RDV1 and RDV0.   +Users can then choose to set a threshold value for each object and if the RDV +value for a specific object is greater than or equal to the threshold - the user +may decide to do something with that object.   +For example, one may decide to watch a certain video once its RDV value (RDV +value for that specific video) hits their desired threshold.   +Threshold allows one to decide how often they would like to be "reminded of" a +certain object.   +0 -> every day, 1 -> every 2 days (on average - it's random), 2 -> 4 days, 3 -> +8 days, and so on (allows for essentially infinite frequencies of reminders, +though ones above 10 happen quite rarely - 2^10 = 1024 days).   +If multiple people used this system to get reminded of the same things - they +would all get reminded of them on the same days and thus be able to coordinate +meetings/actions related to the objects in question.   +This could allow fans of a "dead" game (game with no or little players online) +to all meet and play it on the same day, let's say once every 256 days. +People could re-watch their favorite movies or videos and discuss them with +other fellow fans on the same days.   +This system can be applied to anything.   +It can be used to assign special appreciation/remembrance days to your favorite +books, songs, artists, events, or (as I already said) - ANYTHING.   +One could have a huge list of objects they care about and never again risk +forgetting any of them - since they will be reminded of them eventually (for +example - bookmarks).   + +## RDVT CALCULATION AND USE CASES +RDVT is a newly added (version 2.0) feature of the RANDEVU system and an +optional extension of the RDV algorithm.   +We calculate the hash the same way as for RDV, except we append a RANK integer +to the DATE inside the key, separated by an '_'. And we don't count leading zero +bits.   +Instead, we iterate over the bits in the hash and add increments to the RDVT +time (which starts at 00:00h, midnight). For each 1 we add an increment, and do +nothing if the bit is 0. +An increment starts at 12h, and we divide it by half after processing each bit. +For example, if the first 4 bits are 0110 - this will result in a time value of +09:00h (0 * 12h + 1 * 6h + 1 * 3h + 0 * 1.5h...). +Note that there are 256 bits and we keep doing this until we reach 1 ns (or +millisecond/microsecond, depending on the implementation). +This means we get a different uniform pseudorandom time (0-24h) for each RANK.   +If users find the daily RDV reminder too broad and would like a more specific +time for the reminder - they can calculate one or more RDVT times and choose +one. +Since RDVT0 is the most important/main time, users should choose the +lowest-ranked RDVT that works for them - this increases the chances other people +will also choose the same RDVT time as them, thus increasing the chances of an +interaction.   +Let's say a certain YouTube video has an RDV10 today. We could have a browser +extension or a website that would schedule streams (like video premieres) for +that video at the first 10 RDVT times (RDVT0-9). +Fans of that video could all come re-watch it live with others at one of the +RDVT times, and perhaps chat about it in the live chat (assuming the feature +existed) while experiencing it together. +This is just one super specific hypothetical use case I came up with.   + +--- ## OBJECT NAMING CONVENTION (non-exhaustive examples) -OBJECT is a string (preferably uppercase A-Z, 0-9). No spaces allowed. -Spaces and any other characters should be replaced with a single underscore. -Characters outside of this set should only be used for external identifiers that are case-sensitive or contain other symbols, for example, YOUTUBE video IDs. +OBJECT is a string (preferably uppercase A-Z, 0-9). No spaces allowed.   +Spaces and any other characters should be replaced with a single underscore +('_').   +Characters outside of this set should only be used for external identifiers that +are case-sensitive or contain other symbols, for example, YOUTUBE video IDs. ``` XONOTIC (all letters should be uppercase) @@ -51,35 +171,50 @@ STAR_WARS (spaces and dashes in multi-word objects should be replaced with _) THE_MATRIX_1999 (movies should have a year of release at the end) -GRAND_THEFT_AUTO_5 (objects should be referenced by their full name, Roman numerals should be replaced with Arabic ones) +GRAND_THEFT_AUTO_5 (objects should be referenced by their full name, Roman +numerals should be replaced with Arabic ones) ASAP_ROCKY ($ should be replaced with S, same for other similar instances) C_PLUS_PLUS (++ should be replaced with _PLUS_PLUS) -C_SHARP (# should be replaced with _SHARP, in other contexts it could be _HASH or omitted) +C_SHARP (# should be replaced with _SHARP, in other contexts it could be _HASH +or omitted) YEAR_2000 (years should have a YEAR_ prefix) -FRIDAY (weekdays and months should not be abbreviated, same as all other objects) +FRIDAY (weekdays and months should not be abbreviated, same as all other +objects) 2023-08-25 (dates should be in ISO 8601 format, YYYY-MM-DD) NO_MANS_SKY (apostrophe in MAN'S should be dropped) -HARRY_POTTER_SMOKES_WEED_Cdfkq2Nmb3c (video ID should be appended to the video title, double underscore is fine if the ID starts with one, IDs are allowed to use dashes and lowercase letters) +HARRY_POTTER_SMOKES_WEED_Cdfkq2Nmb3c (video ID should be appended to the video +title, double underscore is fine if the ID starts with one, IDs are allowed to +use dashes and lowercase letters) -GETTING_BANGED_BY_GREEN_BOOMERS_MINECRAFT_BETA_1_7_3_SOLO_SURVIVAL_NO_COMMENTARY_OJzsmWBQE3I (brackets, quotation marks, colons, and other punctuation should be dropped, periods in version numbers like 1.7.3 should be replaced with _) +GETTING_BANGED_BY_GREEN_BOOMERS_MINECRAFT_BETA_1_7_3_SOLO_SURVIVAL_NO_COMMENTARY_OJzsmWBQE3I +(brackets, quotation marks, colons, and other punctuation should be dropped, +periods in version numbers like 1.7.3 should be replaced with _) NUMBER_69 (numbers should have a NUMBER_ prefix) + ``` ### WHY THIS VERY SPECIFIC AND STRICT CONVENTION THO? -**TO MAKE SURE EVERYONE GETS THE SAME REMINDERS FOR THE SAME THINGS ON THE SAME DAYS.** - -Due to how the algorithm works - even just a single character difference between two objects causes the system to generate completely different reminders for each. -For example: WEED, weed, Weed, and W33D would all be treated as different and independent objects with completely unrelated reminders. -One can think of objects like passwords - the same password gets you the same reminders as everyone else. -If one wanted to get completely different reminders from other people for a specific object - they could append extra characters to it. -However, this is not the focus of the system, the whole point is to allow people to coordinate getting reminded about stuff at the same time as others. +**TO MAKE SURE EVERYONE GETS THE SAME REMINDERS FOR THE SAME THINGS ON THE SAME** +**DAYS.** + +Due to how the algorithm works - even just a single character difference between +two objects causes the system to generate completely different reminders for +each.   +For example: WEED, weed, Weed, and W33D would all be treated as different and +independent objects with completely unrelated reminders.   +One can think of objects like passwords - the same password gets you the same +reminders as everyone else using said password.   +If one wanted to get completely different reminders from other people for a +specific object - they could append extra characters to it.   +However, this is not the focus of the system. The whole point is to help people +coordinate getting reminded about stuff at the same time as everyone else. diff --git a/src/lib.rs b/src/lib.rs index 4532400..e3cd475 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,38 +2,97 @@ //! //! # Example //! ```rust -//! use randevu::{rdv, utc_date_with_offset}; +//! use chrono::Utc; +//! use randevu::{rdv, rdvt}; //! //! fn main() { //! let object = "THE_SIMPSONS"; -//! let date = utc_date_with_offset(0); +//! let date = Utc::now(); //! let rdv = rdv(object, &date); +//! let rdvt = rdvt(0, object, &date); //! -//! println!("Object {} has RDV{} today", object, rdv); +//! println!("Object {} has RDV{} today with RDVT0 at {:?}", object, rdv, rdvt); //! } //! ``` use blake3; -use chrono::{TimeDelta, Utc}; +use chrono::{DateTime, Datelike, NaiveTime, TimeDelta, Utc}; +use itoa; -/// Returns current UTC DATE `String` in ISO 8601 format (YYYY-MM-DD), with an OFFSET `i64` in days -pub fn utc_date_with_offset(offset: i64) -> String { - (Utc::now() + TimeDelta::days(offset)) - .format("%Y-%m-%d") - .to_string() +/// Returns the 32-byte KEY `[u8; 32]` created from a given DATE `&DateTime` and an optional RANK `Option` +fn create_key(date: &DateTime, rank: Option) -> [u8; 32] { + let mut key = [0u8; 32]; + + let mut year = Datelike::year(date); + let mut month = Datelike::month(date); + let mut day = Datelike::day(date); + + let mut year_len = 4; + let mut prefix_len = 0; + + // Add a prefix (-/+) if the year is not between 0 and 9999 (-YYYY-MM-DD / +YYYY-MM-DD) + if year < 0 { + key[0] = b'-'; + prefix_len = 1; + + year = year.abs(); // Make year positive + } else if year > 9999 { + key[0] = b'+'; + prefix_len = 1; + } + + // Adjust year_len for very large years (both positive and negative) + if year > 9999 { + year_len += 1; + if year > 99999 { + year_len += 1; + } + } + + let full_year_len = prefix_len + year_len; + + // If a rank is provided, write it into the key after the date, separated by an '_' + if rank != None { + let mut buffer = itoa::Buffer::new(); + let rank_str = buffer.format(rank.unwrap()); + key[7 + full_year_len..7 + full_year_len + rank_str.len()] + .copy_from_slice(&rank_str.as_bytes()[..rank_str.len()]); + + key[6 + full_year_len] = b'_'; + } + + // Write the day into the key + key[5 + full_year_len] = b'0' + (day % 10) as u8; + day /= 10; + key[4 + full_year_len] = b'0' + day as u8; + + key[3 + full_year_len] = b'-'; + + // Write the month into the key + key[2 + full_year_len] = b'0' + (month % 10) as u8; + month /= 10; + key[1 + full_year_len] = b'0' + month as u8; + + key[full_year_len] = b'-'; + + // Write the year into the key + for i in (prefix_len..full_year_len).rev() { + key[i] = b'0' + (year % 10) as u8; + year /= 10; + } + + key } -/// Returns RDV level `u32` for an OBJECT `&str` on a specific DATE `&str` +/// Returns the RDV value `u32` for an OBJECT `&str` on a specific DATE `&DateTime` /// -/// **RDV = number of leading zero bits in blake3(blake3(OBJECT) || blake3(DATE))** -pub fn rdv(object: &str, date: &str) -> u32 { - let mut hasher = blake3::Hasher::new(); - hasher.update(blake3::hash(object.as_bytes()).as_bytes()); - hasher.update(blake3::hash(date.as_bytes()).as_bytes()); - let final_hash = hasher.finalize(); +/// **RDV = number of leading zero bits in blake3::keyed_hash(key: DATE, data: OBJECT)** +pub fn rdv(object: &str, date: &DateTime) -> u32 { + let hash = blake3::keyed_hash(&create_key(date, None), object.as_bytes()); + // Count the number of leading zero bits in the hash let mut rdv = 0; - for &byte in final_hash.as_bytes() { + for &byte in hash.as_bytes() { rdv += byte.leading_zeros(); if byte != 0 { @@ -44,27 +103,132 @@ pub fn rdv(object: &str, date: &str) -> u32 { rdv } +/// Returns the RDVT time `DateTime` of a given RANK `u32` for an OBJECT `&str` on a specific DATE `&DateTime` +pub fn rdvt(rank: u32, object: &str, date: &DateTime) -> DateTime { + let hash = blake3::keyed_hash(&create_key(date, Some(rank)), object.as_bytes()); + + // Calculate the time using bits from the hash + let mut total: f64 = 0.0; + let mut increment = 12.0 * 60.0 * 60.0 * 1_000_000_000.0; // 12h in nanoseconds + for (i, byte) in hash.as_bytes().iter().enumerate() { + for j in (0..8).rev() { + let bit = (byte >> j) & 1; + if bit == 1 { + total += increment; + } + increment /= 2.0; + } + // Stop once increments become too small to affect the total + if i > 4 && (2.0 * increment) < (1.0 - total.fract()) { + break; + } + } + + // Construct the RDVT time from total + let rdvt = date.with_time(NaiveTime::MIN).unwrap() + TimeDelta::nanoseconds(total as i64); + + rdvt +} + #[cfg(test)] mod tests { + use chrono::TimeZone; + use super::*; #[test] - fn test_rdv_1() { - assert_eq!(rdv("NO_BOILERPLATE", "2024-01-26"), 11); + fn test_rdv0() { + assert_eq!( + rdv( + "COREJOURNEY", + &Utc.with_ymd_and_hms(2024, 5, 10, 0, 0, 0).unwrap() + ), + 0 + ); + } + + #[test] + fn test_rdv7() { + assert_eq!( + rdv( + "GTA_V_FLYING_MUSIC_Z7RfRLsqECI", + &Utc.with_ymd_and_hms(2024, 5, 10, 0, 0, 0).unwrap() + ), + 7 + ); + } + + #[test] + fn test_rdv8() { + assert_eq!( + rdv( + "THE_COVENANT_2023", + &Utc.with_ymd_and_hms(2024, 5, 10, 0, 0, 0).unwrap() + ), + 8 + ); + } + + #[test] + fn test_rdv9() { + assert_eq!( + rdv( + "NO_BOILERPLATE", + &Utc.with_ymd_and_hms(2024, 5, 10, 0, 0, 0).unwrap() + ), + 9 + ); + } + + #[test] + fn test_rdvt0() { + assert_eq!( + rdvt( + 0, + "COREJOURNEY", + &Utc.with_ymd_and_hms(2024, 5, 10, 0, 0, 0).unwrap() + ), + Utc.with_ymd_and_hms(2024, 5, 10, 8, 34, 51).unwrap() + + TimeDelta::nanoseconds(226747801) + ); } #[test] - fn test_rdv_2() { - assert_eq!(rdv("SHREK_2001", "2024-01-26"), 8); + fn test_rdvt1() { + assert_eq!( + rdvt( + 1, + "GTA_V_FLYING_MUSIC_Z7RfRLsqECI", + &Utc.with_ymd_and_hms(2024, 5, 10, 0, 0, 0).unwrap() + ), + Utc.with_ymd_and_hms(2024, 5, 10, 19, 33, 44).unwrap() + + TimeDelta::nanoseconds(824030471) + ); } #[test] - fn test_rdv_3() { - assert_eq!(rdv("RANDEVU", "2024-01-26"), 1); + fn test_rdvt10() { + assert_eq!( + rdvt( + 10, + "THE_COVENANT_2023", + &Utc.with_ymd_and_hms(2024, 5, 10, 0, 0, 0).unwrap() + ), + Utc.with_ymd_and_hms(2024, 5, 10, 16, 58, 30).unwrap() + + TimeDelta::nanoseconds(927007898) + ); } #[test] - fn test_rdv_4() { - assert_eq!(rdv("RUST", "2024-01-26"), 0); + fn test_rdvt100() { + assert_eq!( + rdvt( + 100, + "NO_BOILERPLATE", + &Utc.with_ymd_and_hms(2024, 5, 10, 0, 0, 0).unwrap() + ), + Utc.with_ymd_and_hms(2024, 5, 10, 0, 27, 37).unwrap() + + TimeDelta::nanoseconds(142724096) + ); } }