Custom Time hierarchies
eazyBI automatically creates two hierarchies in the Time dimension - default and weekly.
For week numbering in the Weekly hierarchy, eazyBI uses ISO standard, which means the first week of the year always has January 4.
You can add additional custom hierarchies to the Time dimension with different rules using the Add custom hierarchy option.
There are three types of custom hierarchies available:
- Fiscal monthly - a standard fiscal year starting in another month;
- Multiple weeks - repeating one- to four-week cycles;
- Calculated - any other period logic (custom numbering, offset periods, seasons, special calendars).
This option is available for users with account Owner, User Admin, or Data Admin user roles.
We recommend using the Custom Time hierarchy Assistant to add new custom Time hierarchies.
On this page:
Fiscal monthly hierarchy
You can create your custom Fiscal hierarchy by selecting the month your fiscal year starts and the Year's name.
- Levels: Year, Quarter, Month, and Day - the same as the default hierarchy.
- Member names: years are named after the selected fiscal year, for example, FY2026; quarters - FQ1 2026. Month members keep their calendar names (for example, Jul 2026) and are only ordered by the fiscal month.
- Fiscal year name: the Fiscal year choice appears only when the first month is not January; a fiscal year starting in January matches the calendar year.
- Limit: only one fiscal hierarchy can be created in the account. If you need several fiscal calendars, define the additional ones as calculated hierarchies.
Multiple weeks hierarchy
The Multiple weeks hierarchy groups days into repeating periods of one to four weeks.
- Periods: choose one-, two-, three-, or four-week periods and a start date of any period. Periods form a continuous grid of N×7 days extending from the start date in both directions, also across year boundaries. Changing the start date renumbers and re-dates every period.
- Year assignment: multiple-weeks periods do not follow the ISO week standard. A period belongs to the next year when that year's January 1 falls within the first half of the period, and it becomes period 1 of the year — so the number of periods per year can vary (for example, 26 or 27 two-week periods).
- Member names: the hierarchy is named automatically by the period length (for example, "2 Weeks"); year members get the period-length prefix (2W 2026), and period members show the period number and start date (2W01, Dec 29 2025).
- Limit: one hierarchy per period length. For longer periods or a custom name, use a calculated hierarchy.
Calculated time hierarchy
You can add calculated hierarchies using JavaScript code that describes how the hierarchy should work.
- Set a unique name for the hierarchy [1].
- Specify the hierarchy's levels- Year, Quarter, Month, Week, and Day [2].
- The custom JavaScript code should calculate a numeric value and a display name value for each level of the date [3].
- Test the custom hierarchy on specific dates [4].
The JavaScript code should include the return statement containing a numeric and a name value for each of the selected levels, and must follow these rules:
- Every date returns values: the code is executed for every date in the Time dimension (timeValue is a JavaScript Date), and every date must return values for all selected levels — there is no way to exclude a date. If the hierarchy should cover only a particular date range, map all dates with the same rules and hide the unwanted periods with a Time dimension filter in your reports.
- Numeric values order and identify members: the numeric value is the member's position within its parent. Dates returning the same value under the same parent are merged into one member. Values must be integers from 0 to 99 for quarter, month, and week, and from 0 to 9999 for year and day.
- Names are display-only: keep them up to 20 characters — longer names are cut off.
- Testing: the date test shows the returned values for one date, while saving validates all dates — so test boundary dates too (year boundaries, period edges).
The example creates a custom hierarchy using the calendar year, month, and day values. The hierarchy excludes the quarter level and will have only the following levels: Year, Month, and Day.
return {
year: timeValue.getFullYear(),
year_name: "" + timeValue.getFullYear(),
month: timeValue.getMonth() + 1,
month_name: strftime("%B %Y", timeValue),
day: timeValue.getDate(),
day_name: strftime("%B %d %Y", timeValue)
}
The strftime function formats dates as strings; in addition to the standard format specifiers, %V (ISO week number) and %G (ISO week year) are supported.
If you would like to apply different rules when calculating the hierarchy, you can override default calendar values by calculating each level's value. Use examples below as guides.
Custom Time hierarchy Assistant
We recommend using the Custom Time hierarchy Assistant to add new custom time hierarchies. The assistant generates a configuration including JavaScript code (*if needed) for a custom hierarchy based on your prompts.
But you are welcome to explore our JavaScript code examples below.
Monthly, with a different start date
The code below creates a hierarchy in which each month starts on a specific day of the previous calendar month - with monthStartDay = 25, the month Jul 2026 covers June 25 – July 24, 2026.
Levels used: Year, Quarter, Month, Day
// Each month starts on the 25th of the previous calendar month:
// for example, "Jul 2026" covers June 25 - July 24, 2026.
const monthStartDay = 25;
const monthName = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
let year = timeValue.getFullYear();
let month = timeValue.getMonth() + 1; // 1-12 calendar month
// From the start day on, the date belongs to the next month's period.
if (timeValue.getDate() >= monthStartDay) {
month++;
if (month > 12) {
month = 1;
year++;
}
}
// The period starts on the monthStartDay of the previous calendar month.
const monthStartDate = new Date(year, month - 2, monthStartDay);
const quarter = Math.ceil(month / 3);
const dayInMonth =
Math.ceil((timeValue.getTime() - monthStartDate.getTime()) / 86400000) + 1;
return {
year: year,
year_name: "" + year,
quarter: quarter,
quarter_name: "Q" + quarter + " " + year,
month: month,
month_name: monthName[month - 1] + " " + year,
day: dayInMonth,
day_name: strftime("%b %d %Y", timeValue)
}
Months can also start on a day in the same calendar month (for example, Jul 2026 covering July 5 – August 4) — the only difference is naming each period after the calendar month it starts in rather than ends in. Ask the Custom Time hierarchy Assistant to adjust the code for that variant.
Fiscal or academic year with a custom start month
For a standard fiscal year with Year, Quarter, Month, and Day levels, use the built-in Fiscal monthly hierarchy type.
Use this calculated version when you need a second fiscal calendar in the account, a hierarchy without the Quarter level, or custom year names such as 2026/27.
The example starts the year in September: 2026/27 covers September 2026 – August 2027.
// Academic/fiscal year starting in September:
// "2026/27" covers September 2026 - August 2027.
const firstMonth = 9;
const year = timeValue.getFullYear();
const month = timeValue.getMonth() + 1; // 1-12 calendar month
// Months before firstMonth belong to the year started the previous year.
const fiscalYear = month >= firstMonth ? year : year - 1;
// Renumber months so the first fiscal month gets number 1.
const fiscalMonth = month >= firstMonth
? month - firstMonth + 1
: month + 12 - firstMonth + 1;
return {
year: fiscalYear,
year_name: fiscalYear + "/" + ("" + (fiscalYear + 1)).slice(2),
month: fiscalMonth,
month_name: strftime("%b %Y", timeValue),
day: timeValue.getDate(),
day_name: strftime("%b %d %Y", timeValue)
}
The year name can follow any convention - for example, name the year by its ending calendar year with year_name: "FY" + (fiscalYear + 1).
Semesters, quadrimesters, and other multi-month periods
The code below splits each year into equal multi-month periods, using the Quarter level for the periods. With monthsPerPeriod = 6, the year has two semesters; use monthsPerPeriod = 4 for three four-month periods (quadrimesters).
Levels used: Year, Quarter, Month, Day
// Split each year into equal multi-month periods: 6 months per period
// gives two semesters, 4 gives three quadrimesters.
const monthsPerPeriod = 6;
const year = timeValue.getFullYear();
const month = timeValue.getMonth() + 1; // 1-12 calendar month
const period = Math.ceil(month / monthsPerPeriod);
return {
year: year,
year_name: "" + year,
quarter: period,
quarter_name: "S" + period + " " + year,
month: month,
month_name: strftime("%b %Y", timeValue),
day: timeValue.getDate(),
day_name: strftime("%b %d %Y", timeValue)
}
Adjust the period name prefix to the period type - for example, S1 2026 for semesters.
A 3-month split matches the default hierarchy's Quarter level - use a calculated hierarchy for that only if you need different quarter names.
ISO weeks with custom names
The built-in Weekly hierarchy already provides Year, Week, and Day levels with ISO week numbering and members like W35, Aug 24 2026.
Use a calculated hierarchy only when you want different week names - the example below produces the shorter W35 2026 - or as a starting point for custom week logic.
Levels used: Year, Week, Day
// ISO week numbering: weeks start on Monday, and week 1 is the week
// containing January 4 - so "W01 2026" covers Dec 29 2025 - Jan 4 2026.
const isoYear = strftime("%G", timeValue);
const isoWeek = strftime("%V", timeValue);
return {
year: +isoYear,
year_name: isoYear,
week: +isoWeek,
week_name: "W" + isoWeek + " " + isoYear,
day: (timeValue.getDay() + 6) % 7 + 1, // Monday = 1 ... Sunday = 7
day_name: strftime("%b %d %Y", timeValue)
}
Note that the year value uses the ISO week-based year (%G), not the calendar year - Dec 29–31, 2025 belong to 2026 in this hierarchy, keeping every week entirely within one year member.
5-4-4 weekly
The hierarchy includes all levels and will be based on a week split by months and quarters using pattern 5-4-4. It will use Sunday as the starting day, and 1st January is always in the first week of the year.
Levels used: Year, Quarter, Month, Week, Day
// 5-4-4 week-based calendar: each quarter has 13 weeks split into
// months of 5, 4, and 4 weeks. Weeks start on Sunday, and January 1
// is always in the first week of the year.
const monthName = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
// Build constructed dates at midnight UTC to match timeValue, so day
// differences are exact whole days in any server timezone.
const tzOffset = timeValue.getTimezoneOffset();
// The Sunday on or before the date - the start of its week.
const weekStartDate = new Date(timeValue);
weekStartDate.setDate(weekStartDate.getDate() - weekStartDate.getDay());
// A week containing January 1 belongs entirely to the new year.
let year = timeValue.getFullYear();
const nextYearStart = new Date(year + 1, 0, 1, 0, -tzOffset);
if ((nextYearStart.getTime() - weekStartDate.getTime()) / 86400000 < 7) {
year++;
}
// The year's first week starts on the Sunday on or before January 1.
const yearStart = new Date(year, 0, 1, 0, -tzOffset);
yearStart.setDate(yearStart.getDate() - yearStart.getDay());
const week = Math.floor(
(weekStartDate.getTime() - yearStart.getTime()) / 86400000 / 7) + 1;
const dayInWeek = Math.floor(
(timeValue.getTime() - weekStartDate.getTime()) / 86400000) + 1;
let quarter, month;
if (week > 52) {
// Week 53 stays in the last month of Q4.
quarter = 4;
month = 12;
} else {
quarter = Math.ceil(week / 13);
const weekInQuarter = week - (quarter - 1) * 13; // 1-13
// Months within a quarter contain 5, 4, and 4 weeks.
const monthInQuarter = weekInQuarter <= 5 ? 1 : (weekInQuarter <= 9 ? 2 : 3);
month = (quarter - 1) * 3 + monthInQuarter;
}
return {
year: year,
year_name: "Y " + year,
quarter: quarter,
quarter_name: "Q" + quarter + " " + year,
month: month,
month_name: monthName[month - 1] + " " + year,
week: week,
week_name: "W" + week,
day: dayInWeek,
day_name: strftime("%b %d %Y", timeValue)
}
Monthly, using a specific start date in each month
The code below uses the zodiac calendar as an example of periods that start on a specific day of each calendar month, following the same pattern every year.
Each "month" is a zodiac sign named after the sign; the sign that crosses the year boundary (Capricorn) stays under the year it started in.
Levels used: Year, Month, Day
// Each "month" is a zodiac sign period starting on a fixed day of its
// calendar month: for example, Aquarius starts on January 20. The sign
// crossing the year boundary (Capricorn) stays under the year it
// started in.
const zodiac = [
{ startDate: 20, name: "♒ Aquarius" }, // starts Jan 20
{ startDate: 19, name: "♓ Pisces" },
{ startDate: 21, name: "♈ Aries" },
{ startDate: 20, name: "♉ Taurus" },
{ startDate: 21, name: "♊ Gemini" },
{ startDate: 22, name: "♋ Cancer" },
{ startDate: 23, name: "♌ Leo" },
{ startDate: 23, name: "♍ Virgo" },
{ startDate: 23, name: "♎ Libra" },
{ startDate: 24, name: "♏ Scorpio" },
{ startDate: 22, name: "♐ Sagittarius" },
{ startDate: 22, name: "♑ Capricorn" } // starts Dec 22
];
// Build constructed dates at midnight UTC to match timeValue, so day
// differences are exact whole days in any server timezone.
const tzOffset = timeValue.getTimezoneOffset();
let year = timeValue.getFullYear();
let month = timeValue.getMonth(); // 0-11 calendar month
// Before this month's sign start day, the date still belongs to the
// sign that started in the previous month.
if (timeValue.getDate() < zodiac[month].startDate) {
month--;
if (month < 0) {
month = 11;
year--;
}
}
const signStart =
new Date(year, month, zodiac[month].startDate, 0, -tzOffset);
const dayInSign = Math.floor(
(timeValue.getTime() - signStart.getTime()) / 86400000) + 1;
return {
year: year,
year_name: "Y " + year,
month: month + 1,
month_name: zodiac[month].name + " " + year,
day: dayInSign,
day_name: strftime("%b %d %Y", timeValue)
}