Skip to main content

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.

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

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:
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. 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

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.

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:
ValueDay
0Sunday
1Monday
2Tuesday
3Wednesday
4Thursday
5Friday
6Saturday

Example: Weekend Transaction Monitoring

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:
when day_of_week(timestamp) in ("Saturday", "Sunday")
This is equivalent to checking for 0 or 6, but more readable.

Example: Business Hours Only

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).
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.
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).
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.
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

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

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:
// 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
  1. Use metadata for timezone-aware rules. If you need timezone-specific logic, include the user’s timezone or local hour in the transaction metadata:
{
  "meta_data": {
    "user_timezone": "Africa/Lagos",
    "local_hour": 2
  }
}
Then your rule can check metadata.local_hour directly:
when metadata.local_hour >= 1
 and metadata.local_hour < 5
This approach offloads the timezone conversion to your application, where it belongs.

Next Steps