The Oracle RMS Retail Calendar
The retail calendar is the heartbeat of Oracle RMS. Virtually every batch job, every financial report, and every inventory period closes against a retail period — not a calendar month. Understanding how the PERIOD table works and how RMS anchors its batches to fiscal time is foundational knowledge for every developer.
Why Retail Uses Its Own Calendar
Retail businesses don't operate on calendar months. A calendar month has between 28 and 31 days, which makes year-over-year comparisons meaningless — January 2024 had one more trading day than January 2025, and any sales comparison between the two is distorted before you even start.
Retail solves this with a fiscal calendar that divides the year into equal-length periods based on weeks, not arbitrary month boundaries. The most popular structure in global retail is the 4-5-4 calendar — named for the number of weeks in each month within a quarter: 4 weeks, then 5 weeks, then 4 weeks. Every quarter is 13 weeks, every year is exactly 52 weeks (364 days), and year-over-year period comparisons are always like-for-like.
Oracle RMS is built around this retail calendar at every level. The PERIOD table is one of the most referenced tables in all of RMS. Date-based queries in the stock ledger, purchasing, pricing, and replenishment all join to PERIOD rather than using TRUNC(date, 'MM') or calendar month functions.
Oracle RMS maintains its own internal "current date" stored in
SYSTEM_OPTIONS.VDATE (the virtual date). The SYSDATE batch
advances this date each day. Batch jobs that run before the SYSDATE batch
will process against yesterday's date. This is why batch sequencing matters
so much — and why SYSDATE is always the first batch in the daily
schedule.
Calendar Types — 4-5-4, 4-4-5, and Gregorian
RMS supports three calendar structures, configured at system setup and fixed for the life of the instance:
| Column | Type | Description |
|---|---|---|
4-5-4 | 4 wks, 5 wks, 4 wks | Most common in US and large global retailers |
4-4-5 | 4 wks, 4 wks, 5 wks | Common in UK and parts of Europe |
Gregorian | Calendar months | Used by retailers who want month-end closes on the last calendar day. Loses the week-comparability advantage. |
The calendar type determines how the PERIOD table is populated and how period boundaries are calculated. Once chosen, it cannot be changed — the entire history of your stock ledger, purchasing records, and financial closes are anchored to it.
The retail calendar structure is set once during implementation. Migrating from a 4-5-4 to Gregorian calendar requires rebuilding all historical period data and reprocessing all closed stock ledger periods. Treat this decision with the same weight as choosing your primary currency.
The PERIOD Table — The Core Time Dimension
The PERIOD table is the master time dimension for Oracle RMS. It is pre-populated with periods spanning the full implementation history and several years into the future. Every date in RMS can be looked up against PERIOD to find which fiscal week, month, quarter, and year it falls in.
Each row in PERIOD represents one atomic unit of time at the period's granularity level. The most granular level is the week (PERIOD_TYPE = 'WK'). Months, quarters, and fiscal years are all separate rows at their respective period types, linked hierarchically via PARENT_PERIOD_ID.
| Column | Type | Description |
|---|---|---|
PERIOD_IDPK | NUMBER(10) | Surrogate primary key for this period |
PERIOD_TYPE | VARCHAR2(2) | WK = Week · MO = Month · QR = Quarter · YR = Year |
PERIOD_DESC | VARCHAR2(60) | Human-readable label (e.g. 'Week 14, FY2026' or 'Apr 2026') |
START_DATE | DATE | First calendar date of this fiscal period (inclusive) |
END_DATE | DATE | Last calendar date of this fiscal period (inclusive) |
PARENT_PERIOD_IDFK | NUMBER(10) | FK → PERIOD.PERIOD_ID. Week → Month → Quarter → Year hierarchy |
FISCAL_YEAR | NUMBER(4) | The fiscal year this period belongs to |
FISCAL_MONTH | NUMBER(2) | Month number within the fiscal year (1–12) |
FISCAL_WEEK | NUMBER(2) | Week number within the fiscal month (1–5) |
CLOSED_IND | VARCHAR2(1) | Y = this period has been closed (stock ledger finalized) |
Period Hierarchy — Week, Month, Quarter, Year
The PERIOD table is self-referencing via PARENT_PERIOD_ID. The hierarchy flows upward:
The top-level anchor. Parent of all quarters.
Parent of months. Usually defined by the 4-5-4 pattern.
Parent of weeks. Represents a retail fiscal month.
The leaf node. The lowest level of granularity for sales and stock aggregation.
To find which fiscal month a specific date falls in, you join from the week-level period (found by matching START_DATE ≤ date ≤ END_DATE) up to its parent month via PARENT_PERIOD_ID.
The pattern used throughout RMS to find the fiscal week for a date:
WHERE :v_date BETWEEN p.start_date AND p.end_date AND p.period_type =
'WK'. This is more reliable than computing fiscal week numbers manually
— always join to PERIOD for any fiscal time lookups.
The SYSDATE Batch — Advancing RMS Time
Oracle RMS does not use Oracle's built-in SYSDATE function to determine "today" for business operations. Instead, it maintains its own virtual date in SYSTEM_OPTIONS.VDATE. The SYSDATE batch job (also called RMSDATE at some retailers) advances this virtual date by one day each time it runs.
This design has important implications for developers:
1. Batch sequencing is time-sensitive. Any batch that runs before the SYSDATE batch on a given day will process against yesterday's VDATE. Any batch that runs after it will process against today's. If SYSDATE doesn't run, no batch for that day will process against the correct date — every date-based calculation will be one day behind.
2. Custom logic must read VDATE, not SYSDATE. Custom PL/SQL in RMS should always read the current date from SYSTEM_OPTIONS.VDATE rather than calling Oracle's SYSDATE. In production these will agree most of the time, but in test environments, the VDATE is often deliberately set to a past or future date for testing.
3. VDATE advancement triggers period boundaries. When the SYSDATE batch advances VDATE across a period boundary (e.g., from the last day of a fiscal week to the first day of the next), it triggers all period-boundary processing — the stock ledger snapshot for the closing week, replenishment period recalculations, and more.
-- Always read the RMS virtual date this way in custom code
SELECT vdate AS rms_current_date
FROM system_options;
-- DO NOT use this in RMS business logic:
-- SELECT TRUNC(SYSDATE) FROM DUAL;
-- SYSDATE and VDATE may differ in test/staging environments
RC:OMS uses the same period-aware design philosophy as Oracle RMS — every stock movement is timestamped, every period can be closed and re-opened, and historical snapshots are immutable. Without the Oracle licence cost.
See RC:OMS in action →Period Close & the Stock Ledger
At the end of every fiscal week, RMS's period-close batch takes a snapshot of the stock ledger — recording opening stock, sales, purchases, adjustments, and closing stock for every item at every location for that fiscal period. Once a period is closed (CLOSED_IND = 'Y' on the PERIOD row), the historical snapshot is immutable.
This has direct implications for custom development:
- Never query
STKLEDGR(the stock ledger) for a closed period expecting to find the "current" position — useITEM_LOC_SOHfor current stock on hand. - Reprocessing or backdating inventory adjustments into a closed period requires either a period re-open (a controlled DBA operation) or a bridging adjustment in the current open period.
- Financial reporting should always join to
PERIODand filter byCLOSED_IND = 'Y'to ensure only finalized data is included.
Key Tables — Quick Reference
Practical SQL Examples
1. Find the current fiscal week, month, and year
-- Current RMS period context — week, month, quarter, year
WITH current_date_cte AS (
SELECT vdate AS rms_date FROM system_options
)
SELECT wk.period_id week_id,
wk.period_desc fiscal_week,
wk.fiscal_week week_no_in_month,
mo.period_desc fiscal_month,
mo.fiscal_month month_no_in_year,
qr.period_desc fiscal_quarter,
yr.period_desc fiscal_year,
yr.fiscal_year year_number
FROM period wk
JOIN period mo ON mo.period_id = wk.parent_period_id
JOIN period qr ON qr.period_id = mo.parent_period_id
JOIN period yr ON yr.period_id = qr.parent_period_id
WHERE wk.period_type = 'WK'
AND (SELECT rms_date FROM current_date_cte)
BETWEEN wk.start_date AND wk.end_date;
2. Stock ledger summary for a closed fiscal month
-- Stock ledger for a specific fiscal month, by department
SELECT dp.dept,
dp.dept_name,
SUM(sl.opening_stock_cost) opening_cost,
SUM(sl.net_sales_cost) sales_cost,
SUM(sl.purchases_cost) purchases_cost,
SUM(sl.closing_stock_cost) closing_cost
FROM stkledgr sl
JOIN period wk ON wk.period_id = sl.period_id
JOIN period mo ON mo.period_id = wk.parent_period_id
JOIN item_master im ON im.item = sl.item
JOIN deps dp ON dp.dept = im.dept
WHERE mo.fiscal_year = :v_fiscal_year
AND mo.fiscal_month = :v_fiscal_month
AND mo.period_type = 'MO'
AND mo.closed_ind = 'Y'
GROUP BY dp.dept, dp.dept_name
ORDER BY dp.dept;3. Last 13 fiscal weeks — rolling year comparison scaffold
-- Last 13 fiscal weeks with start/end dates (one quarter rolling)
-- Useful for building YoY sales comparison queries
SELECT wk.period_id,
wk.period_desc,
wk.start_date,
wk.end_date,
wk.fiscal_year,
wk.fiscal_month,
wk.fiscal_week,
RANK() OVER (ORDER BY wk.start_date DESC) AS weeks_ago
FROM period wk
JOIN system_options so ON 1=1
WHERE wk.period_type = 'WK'
AND wk.end_date [lt or eq to] so.vdate
ORDER BY wk.start_date DESC
FETCH FIRST 13 ROWS ONLY;
Common Gotchas for Developers
- !
Using Oracle SYSDATE instead of SYSTEM_OPTIONS.VDATE. In production they're the same. In UAT, staging, or when testing future scenarios, VDATE may be set weeks or months different from SYSDATE. Any custom PL/SQL that calls Oracle's
SYSDATEdirectly will behave differently from standard RMS code and break date-sensitive testing. - !
Querying STKLEDGR for current stock instead of ITEM_LOC_SOH.
The stock ledger holds period-close snapshots. The current open period's data in STKLEDGR is partial — it accumulates through the week but isn't finalized until period close. For current stock on hand, always use
ITEM_LOC_SOH. - !
Not joining through PERIOD for fiscal date lookups. Using calendar month functions (
TRUNC(date, 'MM')) in RMS reporting produces wrong groupings because fiscal months don't align with calendar months. Always join toPERIODfor any fiscal time aggregation. - !
Assuming PERIOD rows always exist for future dates. The PERIOD table is pre-populated for a fixed number of future years. If your RMS instance is old and PERIOD rows haven't been extended, future-dated POs, promotions, or replenishment dates will fail validation. Check the maximum
END_DATEin PERIOD periodically and extend when needed. - !
Backdating transactions into a closed period. Once a period is closed (
CLOSED_IND = 'Y'), its stock ledger snapshot is final. You cannot simply insert an inventory adjustment with a date in that closed period and expect the ledger to update — it won't. Any correction to a closed period requires a controlled re-open followed by re-posting, which is a DBA-supervised operation.
Key Takeaways
- ✓Oracle RMS uses a fiscal retail calendar (4-5-4, 4-4-5, or Gregorian) configured at go-live and fixed for the life of the instance.
- ✓The PERIOD table is the master time dimension. Every week, month, quarter, and year is a row with START_DATE and END_DATE. Always join to PERIOD for fiscal time lookups — never use calendar month functions.
- ✓SYSTEM_OPTIONS.VDATE is RMS's internal current date. Custom code must read VDATE, not Oracle's SYSDATE.
- ✓The SYSDATE batch advances VDATE daily and must be the first batch in every day's schedule. All other batches process against the VDATE in effect when they run.
- ✓STKLEDGR holds closed-period stock snapshots. ITEM_LOC_SOH holds current open-period stock. Use the right table for the right question.
- ✓PERIOD rows must exist for future dates to work. Monitor the maximum END_DATE in PERIOD and extend well in advance of expiry.
