Documentation Index
Fetch the complete documentation index at: https://mintlify.com/dmaman86/shiftly/llms.txt
Use this file to discover all available pages before exploring further.
Israeli labor law prescribes elevated pay rates for work performed on Shabbat and on Jewish public holidays. Shiftly models three distinct day types — regular, partial-special, and full-special — and routes each shift through a different segment map accordingly. Day-type classification happens automatically by cross-referencing the calendar date against the Hebcal API and checking the day of the week.
The WorkDayType Enum
enum WorkDayType {
Regular = "Regular",
SpecialPartialStart = "SpecialPartialStart",
SpecialFull = "SpecialFull",
}
| Value | When it applies |
|---|
Regular | Sunday through Thursday with no holiday |
SpecialPartialStart | Friday, or any day listed as a holiday eve / partial observance |
SpecialFull | Saturday (Shabbat), or any paid public holiday |
Holiday Detection: HolidayResolverService
The HolidayResolverService receives the ISO weekday number and a list of event titles returned by the Hebcal API for that date. It resolves the final WorkDayType:
class HolidayResolverService implements HolidayResolver {
private readonly paidHolidays = [
"Rosh Hashana",
"Rosh Hashana II",
"Yom Kippur",
"Sukkot I",
"Shmini Atzeret",
"Pesach I",
"Pesach VII",
"Yom HaAtzma'ut",
"Shavuot I",
];
private readonly partialStartEvents = [
"Erev Rosh Hashana",
"Erev Yom Kippur",
"Erev Sukkot",
"Erev Pesach",
"Erev Shavuot",
"Yom HaZikaron",
"Sukkot VII (Hoshana Rabba)",
"Pesach VI (CH'M)",
];
resolve(params: { weekday: number; eventTitles: string[] }): WorkDayType {
const { weekday, eventTitles } = params;
if (weekday === Weekend.SATURDAY || this.isPaidHoliday(eventTitles))
return WorkDayType.SpecialFull;
if (this.isPartialStart(eventTitles) || weekday === Weekend.FRIDAY)
return WorkDayType.SpecialPartialStart;
return WorkDayType.Regular;
}
}
The holidayKeyMap
Hebcal API event titles are normalized to internal HolidayKey constants used across the app for i18n and display purposes:
const holidayKeyMap = {
"Rosh Hashana" : "rosh_hashana",
"Rosh Hashana II" : "rosh_hashana_2",
"Yom Kippur" : "yom_kippur",
"Sukkot I" : "sukkot",
"Shmini Atzeret" : "shmini_atzeret",
"Pesach I" : "pesach",
"Pesach VI (CH'M)" : "pesach_6",
"Pesach VII" : "pesach_7",
"Yom HaAtzma'ut" : "yom_haatzmaut",
"Shavuot I" : "shavuot",
"Erev Rosh Hashana" : "erev_rosh_hashana",
"Erev Yom Kippur" : "erev_yom_kippur",
"Erev Sukkot" : "erev_sukkot",
"Erev Pesach" : "erev_pesach",
"Erev Shavuot" : "erev_shavuot",
"Yom HaZikaron" : "yom_hazikaron",
"Sukkot VII (Hoshana Rabba)": "hoshana_rabba",
} as const;
type HolidayKey = (typeof holidayKeyMap)[keyof typeof holidayKeyMap];
The SpecialBreakdown Type
Special-day hours are tracked separately from regular hours in their own breakdown:
interface Segment {
percent: number; // 1.5 or 2.0
hours: number;
}
interface SpecialBreakdown {
shabbat150: Segment; // hours at 150% on a special day
shabbat200: Segment; // hours at 200% on a special day
}
SpecialCalculator populates this from LabeledSegmentRange[] entries whose key is "shabbat150" or "shabbat200". Because these keys only appear in the SpecialPartialStart and SpecialFull segment maps, SpecialBreakdown is always zero for Regular days.
Pay Windows by Day Type
Full Special Day (SpecialFull)
On Shabbat and paid public holidays, the entire day is covered by the special segment map. No regular brackets or evening/night bonuses apply.
// SpecialFull segment map
[
{ point: { start: 0, end: 360 }, percent: 2.0, key: "shabbat200" }, // 00:00–06:00
{ point: { start: 360, end: 1320 }, percent: 1.5, key: "shabbat150" }, // 06:00–22:00
{ point: { start: 1320, end: 1800 }, percent: 2.0, key: "shabbat200" }, // 22:00–30:00*
]
// * 1800 min = 30:00 = next-day 06:00, covering any overnight continuation
| Time window | Rate | Key |
|---|
| 00:00 – 06:00 | 200% | shabbat200 |
| 06:00 – 22:00 | 150% | shabbat150 |
| 22:00 – 24:00 | 200% | shabbat200 |
Partial Special Day (SpecialPartialStart)
On Fridays and holiday eves, the day begins as a regular day and transitions to special rates at the special-start time. The exact transition hour is DST-aware.
// SpecialPartialStart segment map (example for winter, specialStart = 17:00 = 1020 min)
[
{ point: { start: 0, end: 360 }, percent: 0.5, key: "hours50" }, // 00:00–06:00 night bonus
{ point: { start: 360, end: 1019 }, percent: 1.0, key: "hours100" }, // 06:00–17:00 regular
{ point: { start: 840, end: 1019 }, percent: 0.2, key: "hours20" }, // 14:00–17:00 evening bonus
{ point: { start: 1020, end: 1320 }, percent: 1.5, key: "shabbat150" }, // 17:00–22:00 special
{ point: { start: 1320, end: 1800 }, percent: 2.0, key: "shabbat200" }, // 22:00–30:00 special
]
| Time window (winter) | Rate | Notes |
|---|
| 00:00 – 06:00 | +50% night bonus | Same as regular day |
| 06:00 – 17:00 | 100% + optional 20% (14:00–17:00) | Regular and evening bonus |
| 17:00 – 22:00 | 150% | shabbat150 — special starts |
| 22:00 – 24:00 | 200% | shabbat200 |
Regular Day
On regular weekdays, no special segments exist. The day uses only the regular segment map with hours100, hours20, and hours50 windows:
| Time window | Applies to | Key |
|---|
| 00:00 – 06:00 | Night bonus | hours50 (+50%) |
| 06:00 – 14:00 | Base rate only | hours100 |
| 14:00 – 22:00 | Evening bonus | hours20 (+20%) |
| 22:00 – 24:00 | Night bonus | hours50 (+50%) |
Regular brackets (100%/125%/150%) are still calculated on top by RegularByDayCalculator.
DST-Aware Special-Start Time
The ShiftSegmentResolver detects Israel Standard Time (UTC+2, winter) vs. Israel Daylight Time (UTC+3, summer) to set the special-start minute:
private getSpecialStartMinutes(date: string): number {
const offsetMinutes = new Date(date).getTimezoneOffset();
const specialStart = -offsetMinutes / 60 === 3 ? 18 : 17;
return specialStart * 60; // returns 1080 (18:00) or 1020 (17:00)
}
| Israel timezone | UTC offset | Special start |
|---|
| Standard (winter) | UTC+2 | 17:00 (1020 min) |
| Daylight (summer) | UTC+3 | 18:00 (1080 min) |
The DST check uses the browser’s Date.getTimezoneOffset() at the specific date string, so the transition is applied correctly to each individual date rather than using a fixed calendar rule.
Summary: Pay Rate Comparison by Day Type
| Day type | 00:00–06:00 | 06:00–14:00 | 14:00–17:00 (winter) | 17:00–22:00 | 22:00–24:00 |
|---|
| Regular | 100% + 50% | 100% | 100% + 20% | 100% + 20% | 100% + 50% |
| Partial Special | 100% + 50% | 100% | 100% + 20% | 150% | 200% |
| Full Special | 200% | 150% | 150% | 150% | 200% |
Regular brackets (125% and 150% overtime) from RegularBreakdown are calculated independently from the special brackets in SpecialBreakdown. The RegularByDayCalculator.handleSpecial() method ensures that on a SpecialFull day (and where crossDayContinuation is false), regular-bracket hours are allocated entirely to the 150% bucket — not split across 100%/125% — because even “base” hours earn 150% on those days.