Tiempo del Servidor vs Tiempo del Cliente: ¿En Cuál Confiar?

The Fundamental Problem

Every user's device has a clock, and that clock might be wrong. Users may have incorrect system time, a different timezone than expected, or — in adversarial scenarios — deliberately manipulate their clock to game time-based features like flash sales, session timeouts, or free trial periods. The core question: which clock do you trust for what purpose?

Use Server Time For:

  • Record creation timestamps: When was this order placed? When was this account created? Always use server UTC.
  • Authorization decisions: Has this session expired? Is this coupon still valid? Is the flash sale active? Server time only.
  • Audit logs: Any time you need an immutable, trusted record of when something happened.
  • Rate limiting: How many requests has this user made in the last 60 seconds?
  • Scheduled jobs: Cron jobs, task queues, email scheduling — all server-side.

Use Client Time For:

  • Local display: Showing the user their local time, date pickers in their timezone.
  • Low-stakes UI features: A "relative time" display ("2 minutes ago") where slight inaccuracy doesn't matter.
  • Timezone detection: Reading Intl.DateTimeFormat().resolvedOptions().timeZone to pre-fill timezone preferences.
  • Performance timing: Measuring how long a user interaction takes on their device.

Never Trust the Client For:

# BAD: Using client-supplied timestamp for record creation
@app.route("/purchase", methods=["POST"])
def purchase():
    timestamp = request.json["timestamp"]  # NEVER trust this!
    order = Order(created_at=timestamp)    # An attacker can set any value

# GOOD: Generate timestamp on the server
@app.route("/purchase", methods=["POST"])
def purchase():
    order = Order(created_at=datetime.now(timezone.utc))

Handling Clock Skew Between Server and Client

A common pattern is to include the server's current timestamp in API responses, allowing clients to calculate the offset between server and client time:

// Server response includes server_time
const { data, server_time } = await fetch("/api/status").then(r => r.json());
const skew = Date.now() - new Date(server_time).getTime();

// Adjust client-side countdowns using the skew
function serverNow() {
  return Date.now() - skew;
}

Flash Sales and Time-Based Features

For features like flash sales where timing matters: render the sale's start/end time from the server in UTC, and do all validation server-side. On the client, display a countdown using performance.now() for accuracy, but always re-validate on every server request.

# Server: always validate timing server-side
def process_flash_sale_order(sale_id, user_id):
    sale = Sale.objects.get(id=sale_id)
    now = datetime.now(timezone.utc)
    if not (sale.starts_at <= now <= sale.ends_at):
        raise ValidationError("Sale is not currently active")