Конвенция «Стих дня»
На этой странице описано, как выбрать стих дня из набора данных verse-of-the-day , чтобы вы могли реализовать его на любом языке и получить тот же стих, что и в любой другой реализации.
API состоит из статических файлов, поэтому он не выбирает стих за вас. Используется общепринятая практика по умолчанию: приложения, которые её придерживаются, показывают один и тот же стих в один и тот же день. Приложения могут выбирать другие стихи, и начальное значение предоставляет им простой способ сделать это, сохраняя при этом те же гарантии.
Гарантии
- Детерминированный процесс. Одна и та же дата (и начальное значение генератора случайных чисел) всегда выдает один и тот же стих на любом устройстве и при любой реализации.
- В течение года нет повторов. Каждый год имеет свой собственный перемешанный порядок стихов, и дни года следуют этому порядку. В наборе данных всегда содержится не менее 366 стихов для каждого используемого дня, поэтому стих никогда не появляется дважды в одном и том же календарном году.
- Праздники. В христианские праздники (например, Рождество или Воскресение Христово) стих дня — это один из стихов этого праздника. Стихи праздника читаются ежегодно, поэтому каждый год используется новый стих, пока не будут использованы все стихи. В праздники, которые длятся несколько дней (например, три дня после Воскресения Христова), каждый день используется новый стих. В другие дни стихи праздника не используются.
- Каждый год порядок повторяется. После того, как все стихи использованы, порядок не повторяется. Поскольку каждый год перемешивается отдельно, один и тот же стих может иногда появляться 31 декабря и снова 1 января (примерно в 1 из 650 случаев на Новый год).
- Стабильная история. В набор данных добавляются или удаляются стихи только из года, который еще не начался (см.
addedInиremovedIn), поэтому стих дня для даты, которая уже наступила, никогда не меняется. - Независимо от порядка в списке. Место каждого стиха в годовом порядке определяется его собственной ссылкой, поэтому порядок в списке не имеет значения.
Входные данные
| Вход | Описание |
|---|---|
| Стихи | Массив verses из /api/d/verse-of-the-day/verses.json Каждый стих содержит book , chapter , verse и categories , а также может содержать endVerse , addedIn и removedIn . |
| Дата | Календарная дата (год, месяц и день). Для поля «сегодня» используйте локальную дату пользователя, а не дату в формате UTC. |
| Семя | Необязательный параметр. Строка, указывающая на другой порядок. По умолчанию используется пустая строка, которая отображает стандартный стих дня. |
| Праздники | Массив holidays из того же файла. Каждый праздник имеет значение id своей категории, а также либо month и day (праздник, отмечаемый в один и тот же день каждый год), либо список из dates ( YYYY-MM-DD , по порядку) для праздника, дата которого меняется каждый год. Праздник может включать несколько дат в одном году, если он длится несколько дней. |
| Категория | Необязательно. Выбирайте только те стихи, у которых эта категория находится в диапазоне categories , в любой день. |
Алгоритм
Шаг 1: Найдите год и день года.
Возьмите год, месяц и день недели, а также день года, соответствующий этой дате, считая от 0: 1 января — это день 0, 31 декабря — это день 364 (или день 365 в високосном году).
В календаре используется пролептический григорианский календарь: год считается високосным, если он делится на 4, за исключением лет, делящихся на 100, но не делящихся на 400 (2000 год — високосный, 1900 — нет).
Шаг 2: Сохраните используемые стихи.
Сохраните стихи, которые используются в течение года:
addedInотсутствует (или равно нулю), илиaddedIn <= year, иremovedInотсутствует (или равно нулю), илиyear < removedIn.
Шаг 3: Выберите стихи, из которых хотите выбрать.
Выберите стихи для исполнения, ключевой префикс для шага 4 и позицию для шага 7:
- Была задана категория. Сохраняйте используемые стихи, в которых
categoriesсодержит эту категорию. Приставка —{seed}|{year}, а позиция — день года. - Категория не указана, и дата является праздником. Дата считается праздником, если праздник в
holidaysсодержит цифрыmonthиdayдаты, или если дата указана вdates(записывается какYYYY-MM-DD, с четырехзначным годом). Если совпадает более одного праздника, используйте первый из списка (в наборе данных никогда не бывает двух праздников на одну и ту же дату). Сохраняйте используемые стихи,categoriesкоторых содержат цифруidпраздника. Если такие стихи есть, префикс равен{seed}|{id}(например,|christmas), а позиция —year × k + i, гдеk— количество дней, в течение которых праздник отмечается в году (1 для праздника сmonthиday; в противном случае — количество егоdatesв году даты), аi— количество дней, предшествующих дате. Для праздника, который отмечается один день в году, позиция — это просто год. Если таких нет, продолжайте с 3. - В противном случае. Сохраняйте используемые стихи, в которых
categoriesне содержитidкакого-либо праздника изholidaysПрефикс —{seed}|{year}, а позиция — день года.
seed — это начальное значение (по умолчанию пустая строка), а year — это год из шага 1 в десятичном формате без ведущих нулей (например 2026 , 1 ).
Если не осталось ни одного стиха, то нет и стиха дня.
В списке праздников, даты которых меняются каждый год, указаны только предварительно рассчитанные даты (в настоящее время с 2026 по 2125 год), поэтому в другие годы это обычные дни. Не рассчитывайте даты самостоятельно.
Шаг 4: Составьте ключевой тон каждого куплета.
Для каждого оставшегося куплета сформируйте следующую строку:
{prefix}|{book} {chapter}:{verse}prefix— это префикс из шага 3.book— этоbookцифра стиха, точно так, как она указана в наборе данных (заглавная буква идентификатора книги USFM, например,JHNили1JN).chapterиverse— это десятичные числа без ведущих нулей.endVerseне является частью ключа. Отрывок определяется по его первому стиху.
Например, ключ для Иоанна 3:16 в обычный день 2026 года при использовании начального значения генератора случайных чисел равен |2026|JHN 3:16 , а ключ для Луки 2:7 на Рождество равен |christmas|LUK 2:7 .
Шаг 5: Хэшируйте каждый ключ.
Хэшируйте байты ключа в формате UTF-8 с помощью 32-битного хеша FNV-1a , затем смешайте результат с финализатором MurmurHash3 fmix32 Все арифметические операции выполняются над беззнаковыми 32-битными целыми числами, поэтому умножения циклически повторяются (они берутся по модулю 2³²), а сдвиги вправо являются логическими (они сдвигаются на нули).
hash = 0x811C9DC5
for each byte b in UTF-8(key):
hash = hash XOR b
hash = (hash * 0x01000193) mod 2^32
hash = hash XOR (hash >> 16)
hash = (hash * 0x85EBCA6B) mod 2^32
hash = hash XOR (hash >> 13)
hash = (hash * 0xC2B2AE35) mod 2^32
hash = hash XOR (hash >> 16)В результате получается ключ сортировки стиха — число от 0 до 4 294 967 295.
Шаг 6: Распределите стихи по местам.
Отсортируйте стихи по их ключу сортировки, начиная с наименьшего, сравнивая ключи в виде беззнаковых чисел.
Если у двух стихов одинаковый ключ сортировки (что встречается крайне редко), то в порядковом порядке (по коду символа) первым следует тот стих, ключевая строка которого из шага 4 стоит первой. Все ключевые строки начинаются с одного и того же префикса, поэтому они различаются только последующей ASCII-ссылкой.
Шаг 7: Выберите стих
Стих дня — это стих, занимающий позицию p mod n в отсортированном списке (считая с 0), где p — это позиция из шага 3 (день года или year × k + i в праздничный день), а n — количество стихов в отсортированном списке. Результат mod никогда не бывает отрицательным (для отрицательного года добавляйте n , пока он не станет отрицательным).
В обычные дни полный набор данных содержит как минимум 366 стихов, поэтому day mod n равно day Цифра mod имеет значение, когда вы выбираете из категории или собственного списка, в котором меньше стихов, чем дней в году, в этом случае стихи повторяются в течение года. В праздничные дни позиция проходит по стихам праздника за один год (а для праздника, длящегося несколько дней, за один день).
Псевдокод
function verseOfTheDay(verses, holidays, date, seed = "", category = null):
year = date.year
day = dayOfYear(date) // 0 for January 1st
inUse = [v for v in verses
if (v.addedIn == null or v.addedIn <= year)
and (v.removedIn == null or year < v.removedIn)]
if category != null:
return pick([v for v in inUse if category in v.categories], seed + "|" + year, day)
holiday = first h in holidays where (h.month == date.month and h.day == date.day)
or (h.dates contains format(date, "YYYY-MM-DD"))
if holiday != null:
holidayVerses = [v for v in inUse if holiday.id in v.categories]
if holidayVerses is not empty:
if holiday.dates == null:
position = year
else:
datesInYear = [d for d in holiday.dates if d starts with year + "-"]
position = year * length(datesInYear) + count of d in datesInYear where d < date
return pick(holidayVerses, seed + "|" + holiday.id, position)
holidayIds = [h.id for h in holidays]
return pick([v for v in inUse if no category of v is in holidayIds], seed + "|" + year, day)
function pick(candidates, prefix, position):
if candidates is empty: return null
keyed = [(hash(key), key, v) for v in candidates
where key = prefix + "|" + v.book + " " + v.chapter + ":" + v.verse]
sort keyed by (hash, key) // hash as an unsigned number, key in ordinal order
return keyed[position mod length(keyed)].verse // a mod that is never negativeДля каждой обычной даты в году используется один и тот же отсортированный список, поэтому, если вы выбираете стихи для многих дат, отсортируйте их один раз в год и используйте этот список повторно.
Семена
Начальное значение генератора случайных чисел (сид) меняет тональность каждого стиха, поэтому он обеспечивает совершенно другой порядок, но с теми же гарантиями (отсутствие повторов в течение года, новый порядок каждый год, стабильная история). Используйте свой собственный сид, например, название вашего приложения, если хотите, чтобы у вашего приложения был свой собственный стих дня, а не общий. Оставьте это поле пустым, чтобы отображался тот же стих, что и у всех остальных.
Распространенные ошибки
- Знаковые целые числа. В языках, где нет беззнаковых 32-битных целых чисел (Java, Kotlin, JavaScript), арифметические операции выполняются над знаковыми 32-битными целыми числами с использованием логического сдвига вправо (
>>>), затем результат преобразуется в беззнаковое значение (например,hash.toLong() and 0xFFFFFFFFилиhash >>> 0в JavaScript) перед сравнением. В JavaScript для умножения используетсяMath.imul;*приводит к потере точности. - Хэширование символов вместо байтов. Хэшируются байты UTF-8 ключа, а не его кодовые единицы UTF-16 или кодовые точки Unicode. Это имеет значение только для начальных значений, содержащих не-ASCII символы, но они допускаются.
- Для обозначения "сегодня" используется UTC. Стих дня меняется в полночь по местному времени пользователя. Использование даты в формате UTC показывает стих предыдущего или следующего дня для части дня в большинстве часовых поясов.
- Отсчитывая день года от 1, 1 января считается нулевым днем.
- Расчет дат праздников. Используйте значение
datesуказанное в наборе данных. Разные церкви и календари рассчитывают Пасху по-разному, и в наборе данных указаны только предварительно рассчитанные даты. - Если не учитывать праздники. Без
holidaysстихи о праздниках рассматриваются как обычные стихи, что меняет и стихи для каждого обычного дня. - Удалённые стихи исключены. Стихи с
removedInгодом остаются в наборе данных, поскольку они необходимы для более ранних лет. Отфильтруйте их на шаге 2; не удаляйте их при загрузке списка.
Тестовые векторы
Используйте эти значения для проверки реализации.
Хэши
| Ключ (в виде строки JSON) | Ключ сортировки | Шестиугольник |
|---|---|---|
"" | 2872998923 | 0xab3e7c0b |
"a" | 444641715 | 0x1a80b1b3 |
"|2026|JHN 3:16" | 1930190452 | 0x730c5e74 |
"my-app|2026|PSA 23:1" | 2020168843 | 0x7869548b |
"é|1|GEN 1:1" | 3677064616 | 0xdb2b8da8 |
Дни года
| Дата | Год | День года |
|---|---|---|
| 2026-01-01 | 2026 | 0 |
| 2026-12-31 | 2026 | 364 |
| 2028-02-29 | 2028 | 59 |
| 2028-12-31 | 2028 | 365 |
| 1900-12-31 | 1900 | 364 |
| 2000-12-31 | 2000 | 365 |
| 0001-03-01 | 1 | 59 |
| 9999-12-31 | 9999 | 364 |
Выбор из небольшого списка
В них используется следующий список из шестнадцати стихов, восемь из которых посвящены праздникам:
[
{ "book": "GEN", "chapter": 1, "verse": 1, "categories": ["praise"] },
{
"book": "PSA",
"chapter": 23,
"verse": 1,
"categories": ["comfort", "provision"]
},
{
"book": "PRO",
"chapter": 3,
"verse": 5,
"endVerse": 6,
"categories": ["faith", "guidance"]
},
{
"book": "ISA",
"chapter": 40,
"verse": 31,
"categories": ["strength", "hope"]
},
{
"book": "JHN",
"chapter": 3,
"verse": 16,
"categories": ["love", "salvation"]
},
{
"book": "ROM",
"chapter": 8,
"verse": 28,
"categories": ["hope"],
"removedIn": 2027
},
{ "book": "PHP", "chapter": 4, "verse": 13, "categories": ["strength"] },
{
"book": "1JN",
"chapter": 4,
"verse": 19,
"categories": ["love"],
"addedIn": 2027
},
{ "book": "LUK", "chapter": 2, "verse": 7, "categories": ["christmas"] },
{
"book": "LUK",
"chapter": 2,
"verse": 10,
"endVerse": 11,
"categories": ["christmas"]
},
{
"book": "MAT",
"chapter": 1,
"verse": 23,
"categories": ["christmas"],
"addedIn": 2027
},
{
"book": "MAT",
"chapter": 28,
"verse": 6,
"categories": ["resurrection-sunday"]
},
{
"book": "ACT",
"chapter": 2,
"verse": 24,
"categories": ["post-resurrection-sunday"]
},
{
"book": "ROM",
"chapter": 6,
"verse": 9,
"categories": ["post-resurrection-sunday"]
},
{
"book": "1CO",
"chapter": 15,
"verse": 20,
"categories": ["post-resurrection-sunday"]
},
{
"book": "1TH",
"chapter": 4,
"verse": 14,
"categories": ["post-resurrection-sunday"]
}
]И вот эти праздники (в Сочельник нет стихов, поэтому это обычный день):
[
{ "id": "christmas-eve", "month": 12, "day": 24 },
{ "id": "christmas", "month": 12, "day": 25 },
{ "id": "resurrection-sunday", "dates": ["2026-04-05", "2027-03-28"] },
{
"id": "post-resurrection-sunday",
"dates": [
"2026-04-06",
"2026-04-07",
"2026-04-08",
"2027-03-29",
"2027-03-30",
"2027-03-31"
]
}
]Для 1 января 2026 года при использовании стандартного начального значения четыре праздничных стиха и 1 Иоанна 4:19 (который пока не используется) исключены, поэтому осталось семь стихов. Отсортированные по ключам сортировки, они выглядят следующим образом:
| Позиция | Ключ | Ключ сортировки |
|---|---|---|
| 0 | |2026|PSA 23:1 | 638659039 |
| 1 | |2026|ROM 8:28 | 1462075190 |
| 2 | |2026|PRO 3:5 | 1643805236 |
| 3 | |2026|JHN 3:16 | 1930190452 |
| 4 | |2026|PHP 4:13 | 2076082788 |
| 5 | |2026|GEN 1:1 | 2439817197 |
| 6 | |2026|ISA 40:31 | 3044461366 |
1 января — нулевой день, поэтому стих дня — Псалом 23:1. 15 марта — 73-й день, а 73 по модулю 7 = 3, поэтому это Иоанна 3:16.
На Рождество остаются только те рождественские стихи, которые используются в настоящее время. В 2027 году их три, отсортированные по ключам сортировки:
| Позиция | Ключ | Ключ сортировки |
|---|---|---|
| 0 | |christmas|LUK 2:10 | 1698480150 |
| 1 | |christmas|MAT 1:23 | 2563849700 |
| 2 | |christmas|LUK 2:7 | 3160581874 |
2027 mod 3 = 2, поэтому стих дня на Рождество 2027 года — это Лука 2:7. В 2026 году Матфей 1:23 еще не используется, поэтому их всего два, и 2026 mod 2 = 0 дает Лука 2:10-11.
Праздник после Воскресения Христова длится три дня. Четыре стиха, отсортированные по ключам, следующие:
| Позиция | Ключ | Ключ сортировки |
|---|---|---|
| 0 | |post-resurrection-sunday|1TH 4:14 | 80824114 |
| 1 | |post-resurrection-sunday|ROM 6:9 | 665551464 |
| 2 | |post-resurrection-sunday|1CO 15:20 | 820536316 |
| 3 | |post-resurrection-sunday|ACT 2:24 | 3132510482 |
В 2026 году это событие отмечается в течение трёх дней, поэтому k = 3, и позиции 6, 7 и 8 апреля составляют 2026 × 3 + 0 = 6078, 6079 и 6080. 6078 mod 4 = 2, 6079 mod 4 = 3 и 6080 mod 4 = 0 дают 1 Коринфянам 15:20, Деяния 2:24 и 1 Фессалоникийцам 4:14. В 2027 году позиции продолжаются с 6081, поэтому следующий стих — Римлянам 6:9.
| Дата | Семя | Категория | Стих дня |
|---|---|---|---|
| 2026-01-01 | ПСА 23:1 | ||
| 2026-01-02 | Римлянам 8:28 | ||
| 2026-03-15 | Иоанн 3:16 | ||
| 2026-12-31 | ПСА 23:1 | ||
| 2027-01-01 | Иоанн 3:16 | ||
| 2027-06-30 | ПРО 3:5-6 | ||
| 2028-02-29 | ПРО 3:5-6 | ||
| 2028-12-31 | ПСА 23:1 | ||
| 1969-12-31 | ПРО 3:5-6 | ||
| 0001-01-01 | Исаия 40:31 | ||
| 2026-01-01 | my-app | Исаия 40:31 | |
| 2027-01-01 | my-app | PHP 4:13 | |
| 2026-01-01 | hope | Римлянам 8:28 | |
| 2027-01-01 | hope | Исаия 40:31 | |
| 2027-01-01 | joy | Нет стиха дня | |
| 2026-12-25 | ЛК 2:10-11 | ||
| 2027-12-25 | ЛК 2:7 | ||
| 2028-12-25 | ЛК 2:10-11 | ||
| 2029-12-25 | Мф 1:23 | ||
| 2026-12-25 | my-app | ЛК 2:10-11 | |
| 2026-04-05 | Мф 28:6 | ||
| 2027-03-28 | Мф 28:6 | ||
| 2028-04-16 | ПСА 23:1 | ||
| 2026-12-24 | ПСА 23:1 | ||
| 2026-07-01 | christmas | ЛК 2:7 | |
| 2026-04-06 | 1CO 15:20 | ||
| 2026-04-07 | Деяния 2:24 | ||
| 2026-04-08 | 1-е 4:14 | ||
| 2026-04-09 | ПСА 23:1 | ||
| 2027-03-29 | Римлянам 6:9 | ||
| 2027-03-30 | 1CO 15:20 | ||
| 2027-03-31 | Деяния 2:24 |
16 апреля 2028 года — Воскресение Христово, но оно не входит в этот список под номером dates , поэтому это обычный день.
Выбранные фрагменты из набора данных
В наборе данных стихи прошедшего года никогда не меняются, поэтому стихи 2026 года остаются неизменными:
| Дата | Праздничный день | Стих дня |
|---|---|---|
| 2026-01-01 | ПСА 32:1 | |
| 2026-04-03 | Страстная пятница | Лк 23:33 |
| 2026-04-05 | Воскресение Христово | Мф 28:9 |
| 2026-04-06 | После Воскресения Христова | Откр 1:17-18 |
| 2026-06-15 | EST 4:14 | |
| 2026-10-28 | ПСА 136:1 | |
| 2026-10-31 | Канун Дня всех святых | ЕФ 5:8 |
| 2026-11-29 | Первое воскресенье Адвента | МАЛ 3:1 |
| 2026-12-24 | сочельник | Мф 1:21 |
| 2026-12-25 | Рождество | Исаия 9:6 |
| 2026-12-31 | Иезек. 36:26 |
Реализации
Эти реализации следуют общепринятой конвенции, и их тесты проверяют значения, указанные на этой странице:
- JavaScript/TypeScript:
pickVerseOfTheDay()иFreeUseBibleApi.getVerseOfTheDay()в пакетеfree-use-bible-api( источник ). - Kotlin:
pickVerseOfTheDay()иFreeUseBibleApi.getVerseOfTheDay()в Kotlin SDK ( исходный код ). - Swift:
pickVerseOfTheDay(_:date:seed:category:holidays:)иFreeUseBibleAPI.getVerseOfTheDay(date:seed:category:dataset:endpoint:)в Swift SDK ( исходный код ). getVerseOfTheDayинструмент сервера MCP ( источник ).getVerseOfTheDay()из@helloao/tools( источник ), который использует список, встроенный в пакет.