> ## Documentation Index
> Fetch the complete documentation index at: https://docs.finwatch.finance/llms.txt
> Use this file to discover all available pages before exploring further.

# Time Based Rules

Fraud patterns are often time-dependent. Legitimate transactions tend to follow predictable temporal patterns — business hours, weekdays, regular pay cycles. When a transaction breaks these patterns, it's a signal worth investigating. This guide teaches you how to build rules that are aware of *when* a transaction happens.

***

## Why Time Matters

Consider these scenarios:

* A corporate account that normally processes payroll on weekday mornings suddenly initiates a wire transfer at 3 AM on a Saturday.
* A consumer account that's been inactive for months makes a large purchase at 2 AM.
* A series of rapid transactions occur during a national holiday when the account holder is unlikely to be actively spending.

None of these transactions are inherently suspicious based on amount or destination alone. It's the **timing** that makes them unusual. Time functions give you the vocabulary to express these patterns.

***

## `hour_of_day()`

Extracts the hour component from a transaction's timestamp.

**Signature:** `hour_of_day(<timestamp_field>)`

**Return Value:** An integer from `0` to `23` representing the hour in **UTC**.

* `0` = midnight (12:00 AM)
* `12` = noon (12:00 PM)
* `23` = 11:00 PM

### Example: Flagging Transactions During Unusual Hours

```go lines theme={null}
rule UnusualTransactionTime {
    description "Large transactions during unusual hours receive extra scrutiny."

    when hour_of_day(timestamp) >= 1
     and hour_of_day(timestamp) < 5
     and amount > 1000

    then review
         score  0.6
         reason "Large transaction during unusual hours (1 AM - 5 AM)"
}
```

**What this detects:** Any transaction over \$1,000 that occurs between 1:00 AM and 4:59 AM UTC. This is a common window for fraudulent activity — compromised accounts are often exploited when the legitimate owner is asleep.

**Why three conditions?** We use two `hour_of_day()` checks to define a range (>= 1 AND \< 5), and combine them with an amount threshold to avoid flagging every small late-night coffee purchase.

### Example: Late Night OR Early Morning

If you want to flag transactions that happen late at night **or** very early in the morning (spanning midnight), you need to handle the boundary carefully:

```go lines theme={null}
rule LateNightTransactions {
    description "Detects transactions made late at night."

    when hour_of_day(timestamp) >= 23
      or hour_of_day(timestamp) <= 3

    then review
         score  0.4
         reason "Transaction occurred during late night hours"
}
```

This fires for transactions at 11 PM, midnight, 1 AM, 2 AM, or 3 AM.

> **Warning:** Remember the [precedence pitfall](conditions-deep-dive.md#the-precedence-pitfall). If you add an `and amount > 1000` after the `or` clause, the grouping becomes `(hour >= 23 OR hour <= 3) AND amount > 1000`. If your intent is different, consider splitting into two rules.

### Combining with Amount for High-Risk Detection

```go lines theme={null}
rule LateNightHighValue {
    description "High-value transactions during late night hours are high risk."

    when amount > 5000
     and hour_of_day(timestamp) >= 23

    then review
         score  0.7
         reason "High-value transaction during late night hours"
}
```

By putting the cheap `amount > 5000` check first, the `hour_of_day()` function only runs on transactions that pass the amount threshold — an example of the [gate-and-probe pattern](aggregate-functions-guide.md#the-gate-and-probe-pattern).

***

## `day_of_week()`

Extracts the day of the week from a transaction's timestamp.

**Signature:** `day_of_week(<timestamp_field>)`

**Return Value:** An integer from `0` to `6`:

| Value | Day       |
| ----- | --------- |
| `0`   | Sunday    |
| `1`   | Monday    |
| `2`   | Tuesday   |
| `3`   | Wednesday |
| `4`   | Thursday  |
| `5`   | Friday    |
| `6`   | Saturday  |

### Example: Weekend Transaction Monitoring

```go lines theme={null}
rule WeekendTransactionCheck {
    description "Flags high-value transactions on weekends for business accounts."

    when day_of_week(timestamp) == 0
      or day_of_week(timestamp) == 6
     and amount > 5000

    then review
         score  0.4
         reason "High-value transaction on a weekend"
}
```

**What this detects:** Transactions on Sunday (0) or Saturday (6) that exceed \$5,000. Business accounts typically don't process payments on weekends, so weekend activity can indicate unauthorized access.

### Special Feature: String Day Names with `in`

The `day_of_week()` function also supports string comparisons when used with the `in` operator. You can use day names instead of numbers:

```ws theme={null}
when day_of_week(timestamp) in ("Saturday", "Sunday")
```

This is equivalent to checking for `0` or `6`, but more readable.

### Example: Business Hours Only

```go lines theme={null}
rule OutsideBusinessHours {
    description "Flags transactions outside standard business hours for corporate accounts."

    when hour_of_day(timestamp) < 9
      or hour_of_day(timestamp) >= 17
     and metadata.account_type == "corporate"
     and amount > 10000

    then review
         score  0.5
         reason "Corporate account transaction outside business hours"
}
```

***

## Other Time Functions

FinWatch provides a complete set of temporal extraction functions. While `hour_of_day()` and `day_of_week()` are the most commonly used, the others are valuable for specific scenarios.

### `day_of_month()`

**Signature:** `day_of_month(<timestamp_field>)`

**Return Value:** An integer from `1` to `31`.

**Use Case:** Detecting activity on specific calendar days. For example, payroll fraud often targets specific days (1st, 15th, last day of month).

```go lines theme={null}
rule EndOfMonthHighVolume {
    description "Flags unusually large transactions on the last day of the month."

    when day_of_month(timestamp) >= 28
     and amount > 50000

    then review
         score  0.3
         reason "High-value transaction near end of month"
}
```

### `day_of_year()`

**Signature:** `day_of_year(<timestamp_field>)`

**Return Value:** An integer from `1` to `366`.

**Use Case:** Identifying transactions on specific dates, such as holidays or known fraud campaign windows.

```go lines theme={null}
rule ChristmasTransactions {
    description "Review high-value transactions on Christmas Day."

    when day_of_year(timestamp) == 359
     and amount > 5000

    then review
         score  0.3
         reason "High-value transaction on Christmas Day"
}
```

> **Note:** Day 359 is December 25th in a non-leap year. For production rules, consider using `month_of_year()` and `day_of_month()` together for clarity.

### `month_of_year()`

**Signature:** `month_of_year(<timestamp_field>)`

**Return Value:** An integer from `1` (January) to `12` (December).

**Use Case:** Seasonal fraud patterns. Fraud attempts often spike during holiday shopping seasons (November, December) and tax filing periods (January through April).

```go lines theme={null}
rule HolidaySeasonAlert {
    description "Increased scrutiny during peak holiday shopping season."

    when month_of_year(timestamp) == 12
     and amount > 2000

    then alert
         score  0.2
         reason "Transaction during peak holiday season — elevated fraud risk"
}
```

### `week_of_year()`

**Signature:** `week_of_year(<timestamp_field>)`

**Return Value:** An integer from `1` to `53` (ISO week number).

**Use Case:** Weekly pattern analysis or identifying specific weeks with known elevated risk (e.g., Black Friday week, tax deadline week).

### `year()`

**Signature:** `year(<timestamp_field>)`

**Return Value:** The full year as an integer (e.g., `2026`).

**Use Case:** Filtering out stale or future-dated transactions. A transaction with `year(timestamp) < 2020` is almost certainly a data error.

```go lines theme={null}
rule StaleDateTransaction {
    description "Flags transactions with suspiciously old timestamps."

    when year(timestamp) < 2024

    then alert
         score  0.5
         reason "Transaction timestamp is from before 2024 — possible data error"
}
```

***

## Combining Time with Other Conditions

The real power of time functions emerges when you combine them with other condition types — simple comparisons, aggregates, and metadata checks.

### Multi-Factor Time-Based Detection

```go lines theme={null}
rule HighRiskNightActivity {
    description "High-risk: large transaction, unusual hour, from a new account."

    when amount > 10000
     and hour_of_day(timestamp) >= 1
     and hour_of_day(timestamp) < 5
     and metadata.account_age_days < 30

    then block
         score  0.9
         reason "Large late-night transaction from a new account — high risk of fraud"
}
```

This rule layers four conditions:

1. **Amount:** Over \$10,000 (eliminates low-value noise).
2. **Time:** Between 1 AM and 5 AM (unusual hours).
3. **Time:** Same check, upper bound.
4. **Account age:** Less than 30 days (new account).

Each condition alone might not be concerning. Together, they paint a high-risk picture: a brand-new account making large transfers in the middle of the night.

### Time + Aggregates

```go lines theme={null}
rule NightTimeVelocity {
    description "Detects rapid transactions during nighttime hours."

    when hour_of_day(timestamp) >= 22
     and count(when source == $current.source, "PT1H") > 5

    then review
         score  0.7
         reason "Multiple transactions from same source during nighttime hours"
}
```

This combines a time check (after 10 PM) with a velocity check (more than 5 transactions from the same source in the last hour). During daytime, 5 transactions per hour might be normal. At night, it's suspicious.

***

## Time Zone Considerations

All time functions in FinWatch operate on the transaction's `timestamp` field and return values in **UTC**. This is an important detail for global fintechs operating across multiple time zones.

### What This Means in Practice

If your users are in West Africa (UTC+1), a transaction at "2 AM local time" would have a UTC timestamp of 1 AM. A rule checking `hour_of_day(timestamp) >= 2` would **not** match this transaction.

### Recommendations

1. **Store timestamps in UTC.** This is best practice regardless of FinWatch. All API payloads should use RFC 3339 UTC timestamps (e.g., `2026-04-18T14:30:00Z`).
2. **Design rules with UTC offsets in mind.** If your users are primarily in UTC+1, and you want to flag "late night" activity (midnight to 5 AM local), your UTC range would be 11 PM to 4 AM:

```go theme={null}
// UTC+1: Local midnight to 5 AM = UTC 23:00 to 04:00
when hour_of_day(timestamp) >= 23
  or hour_of_day(timestamp) < 4
```

3. **Use metadata for timezone-aware rules.** If you need timezone-specific logic, include the user's timezone or local hour in the transaction metadata:

```json theme={null}
{
  "meta_data": {
    "user_timezone": "Africa/Lagos",
    "local_hour": 2
  }
}
```

Then your rule can check `metadata.local_hour` directly:

```go theme={null}
when metadata.local_hour >= 1
 and metadata.local_hour < 5
```

This approach offloads the timezone conversion to your application, where it belongs.

***

## Next Steps

* [**Previous Transaction Lookups**](previous-transaction-guide.md) — Combine time awareness with sequential pattern detection.
* [**Aggregate Functions Guide**](aggregate-functions-guide.md) — Use time functions alongside aggregate functions for powerful multi-signal detection.
* [**The Rule Cookbook**](rule-cookbook.md) — See production-ready rules that use time functions.
* [**DSL Reference**](../DSL_REFERENCE.md) — Complete time function reference.
