Analytics

Timezones, currencies, and data freshness

The three behind-the-scenes mechanics — store timezone, per-currency totals, and the daily worker — that shape every number on Analytics.

Three quiet mechanics decide what every Analytics number actually means: which calendar day an order lands on (your store timezone), which bucket its money goes into (its currency), and how soon it shows up (the daily worker plus live gap-filling). None of them have a settings screen — they run automatically — but understanding them explains almost every "that number looks off" moment. This page walks through all three, plus one important nuance about how figures are stored, and ends with troubleshooting recipes.

Timezones: every day and month is bucketed in your store's time

Analytics never groups orders by UTC. It groups them by your Shopify store timezone, so that a day on the sales chart matches the same day in your Shopify reports.

How it works

The app reads your store's timezone offset directly from Shopify — specifically the shop's timezoneOffsetMinutes, the number of minutes your store is ahead of or behind UTC. It fetches this from the Shopify Admin API and caches it for 12 hours before fetching again, so a recent timezone change can take up to half a day to be picked up.

When the app needs to know which day an order belongs to, it takes the order's exact placement timestamp, shifts it by your store offset, and reads off the local calendar date (YYYY-MM-DD). That local date is what the order is filed under — in both the daily chart and the monthly Group by view.

Because bucketing uses the same store timezone Shopify uses for its own reports, your Llama sales chart and your Shopify reports line up day-for-day. A $40 upsell on an order placed Tuesday in your local time appears on Tuesday in both places.

Late-night orders bucket by your local day

This is the most visible effect. An order placed at, say, 11:50 PM local time is filed under that local day — not the next UTC day. If you are several hours ahead of or behind UTC, a late-evening or early-morning order in UTC terms can be on a different calendar date than the one you experienced. The app always uses your local day, so the numbers match what you saw happen in your own time.

Changing your store timezone is not retroactive

The store offset is applied at the moment an order's day is computed and stored. The app does not go back and re-file orders that were already counted.

If you change your store timezone in Shopify, only newly processed days follow the new offset. Days that were already aggregated into daily snapshots keep the offset they were stored with — they are not retroactively re-bucketed. You may therefore see a small one-time discontinuity around the change: older days reflect the old timezone, newer days the new one. This is expected and self-corrects going forward. (Remember the offset is also cached for 12 hours, so the switch-over is not instant.)

Edge cases: DST and stores far from UTC

Currencies: separate totals, never converted

Every metric record the app stores is keyed by currency, and the app never converts one currency into another. There is no exchange-rate math anywhere in Analytics.

What this means in practice

BehaviorDetail
Default currencyYour shop's primary currency. When the page first loads, it shows totals in that currency.
Separate totals per currencyEach currency has its own daily snapshots and its own headline cards. A $100 USD order and a €100 EUR order are stored in different buckets and never summed together.
Currency filterIf your store has recorded orders in more than one currency, a Currency filter appears so you can switch which currency you're viewing. With only one currency, there's effectively nothing to switch.
No conversion, everAmounts are always shown in the order's own currency. The app does not add two currencies together or restate one in another.

This is why a multi-currency store shows more than one set of numbers — see the two currency tabs recipe below. It is working as designed: each currency stands alone so totals are always exact and never distorted by a fluctuating exchange rate.

Orders with no currency fall back to USD

When an order is captured without a currency value — a missing or blank currency on the incoming order — the app files it under USD.

If you see a stray USD currency bucket on a store that doesn't normally sell in USD, this fallback is almost certainly the cause: one or more orders arrived without a currency and were defaulted to USD on capture. The numbers in that bucket are real revenue; they're just grouped under USD because the original order didn't specify a currency. Switch the Currency filter to that bucket to inspect the orders behind it.

For a fuller treatment of how per-currency totals appear across the metric cards and orders list, see the Metrics reference.

Data freshness: the daily worker plus live numbers

Analytics is served from two sources at once: pre-aggregated daily snapshots built by a background worker, and live numbers computed on the fly for orders too new to have been aggregated yet. The two are merged so the page always looks complete.

The daily worker (snapshots for yesterday and earlier)

A background job runs once per day and rolls up orders into daily snapshots. For each store it:

Reads your store timezone

It loads your store's timezoneOffsetMinutes so every day is bucketed in your local time (the same offset described above).

Finds where it left off

It looks up the most recent day it already has a snapshot for, and only processes orders newer than that — it never re-aggregates a day it has already stored.

Aggregates through the end of yesterday

It processes all orders from that point through the end of the previous local day, grouping them by currency and then by day. Today's orders are intentionally left out of the snapshot — they're handled live instead (next section).

Writes the daily snapshots

It stores one set of snapshots per day, at three levels: a shop-wide summary, a breakdown by funnel type, and a breakdown by individual campaign.

Live gap-filling (today's orders, no refresh needed)

You don't have to wait until tomorrow's worker run to see today's sales. When you open Analytics, the app:

  1. Finds the latest day it has a stored snapshot for, in the currency you're viewing.
  2. Pulls every order newer than that snapshot — the ones the worker hasn't processed yet.
  3. Computes their metrics live, in memory, using the same rules as the worker.
  4. Merges those live numbers with the stored snapshots and returns the combined result.

The effect: today's orders appear automatically, with no manual refresh, sync button, or waiting for the nightly job.

There's a small, normal lag between an order being placed and it being counted. The order has to reach the app and be captured before it can be live-computed, and Analytics reflects it on the next page load. So a brand-new order shows up within a short while of being placed, not the instant the shopper clicks "Buy."

A brand-new store that hasn't had its first nightly worker run yet has no stored snapshots at all. Until the first run completes, every number you see is live-computed from your recent orders. This is normal — your figures are still correct; they just haven't been written into permanent daily snapshots yet. After the first nightly run, older days come from snapshots and only today stays live.

Important nuance: snapshots are append-only and gross

This one trips up the most people, so it's worth stating plainly. Daily snapshots are append-only, and there is no automatic recount of a day once it has been written.

What that means for you:

  • Refunds are not subtracted. If you refund an order after its day has been snapshotted, Total sales does not go down.
  • Cancellations are not subtracted. A cancelled order that was already counted stays counted in the historical snapshot.
  • Order edits after capture are not re-applied to an already-stored day.

In short, the figures on Analytics are gross — they reflect each order as it was originally captured, not its final state after refunds, cancellations, or edits.

Do not expect Analytics to match a Shopify net sales report. Llama Analytics shows gross campaign revenue with no refund, cancellation, or post-capture-edit adjustments. If reconciling, compare against a gross/line-item view, and account for refunds yourself. The page Why your Analytics totals differ from Shopify reports walks through every reason the two can diverge.

Troubleshooting recipes

On this page