# 13.7 Text, dates and files (/building-ai-workers/programming-fundamentals/text-dates-files)

---
type: Document
title: "13.7 Text, dates and files"
description: "The hidden rules in text and dates: line endings, files, ISO dates, month and year ends, leap days and time zones."
status: stable
order: 313.07
ksor:
  owner: team:panaversity
  audience: [ public ]
  approval:
    by: process:panaversity
    at: 2026-11-10T15:33:14Z
chapter: "13"
part: III
expert_status: builder-only
concepts: [ "13.7" ]
last_verified: 2026-10-30
generated:
  at: 2026-11-10T15:33:14Z
  by: process:panaversity
trust_tier: unverified
build_id: sha256:46fd53c5d7ab1c14f6347908d45c9fdaecda4d914439923ee878bdcecd57803e
dirty: true
ksor_version: 0.0.60
---

**In everyday life.** At 9 p.m. on Monday in Columbus, it is already Tuesday morning in Tokyo.

**What you will learn.** Text and dates carry rules you cannot see, and which of them to test on purpose.

**Why it matters.** Two values can look the same and not match, and a date can come out one day wrong.

### What goes wrong when I read text from a file?

Reading a file gives you its whole content as one string, and the code splits it into lines and values. A missing file stops the program with an error that names the file's path, its address in the folders. [13.8](reading-an-error.md) shows how to read it.

Windows ends each line with two invisible characters, a carriage return `\r` and a newline `\n`. Mac and Linux use only `\n`. A TypeScript script that splits on `\n` alone leaves a `\r` stuck to the last value on each line, so `"open\r"` is not `"open"`. Like a space at the end of a spreadsheet cell, it looks the same, but a lookup does not match it. TypeScript's `trim()` and Python's `strip()` remove blank space from the ends.

Here is a Windows line, with `\r` at its end. `split(",")` cuts text at each comma and gives a list of the pieces. The code cuts the line twice: once as it is, and once after cleaning its ends. Predict both values.

```ts tab="TypeScript" tab-group="language"
const line = "INV-8301,V001,open\r";
const parts = line.split(",");            // cut at each comma
const cleaned = line.trim().split(",");   // clean the ends first, then cut
console.log(parts[2] === "open", cleaned[2] === "open");
```

```python tab="Python" tab-group="language"
line = "INV-8301,V001,open\r"
parts = line.split(",")             # cut at each comma
cleaned = line.strip().split(",")   # clean the ends first, then cut
print(parts[2] == "open", cleaned[2] == "open")
```

```text tab="TypeScript" tab-group="language"
false true
```

```text tab="Python" tab-group="language"
False True
```

### Why are dates hard?

Brightline writes every date as **ISO** text: year, month, day, such as `2026-11-06`. ISO dates sort correctly as text, because each has the same length, zeros included, year first. So a script can compare `due <= cutoff` directly. Adding days is harder.

| Case | Example | Result |
| --- | --- | --- |
| Crossing a month end | `2026-10-20` plus 15 days | `2026-11-04` |
| Crossing a year end | `2026-12-20` plus 30 days | `2027-01-19` |
| A leap year | `2028-02-15` plus 15 days | `2028-03-01`, because February 2028 has 29 days |

### Why can a test pass on one computer and fail on another?

Because of time zones. A date, such as October 20, is the same everywhere. A moment is one instant, and it can fall on different dates in different places: 9 p.m. on Monday in Columbus is Tuesday morning in Tokyo. Code that holds a moment can give a different day on two computers. Code that holds a date cannot.

| Language | What it holds |
| --- | --- |
| TypeScript | `Date` holds a moment, and some ways of making one use the computer's local time zone. TypeScript has no type for a plain calendar day. So Brightline's TypeScript does date math in UTC, the world reference time, and the result is the same on every computer. |
| Python | `date` is a calendar day with no time and no time zone, so it cannot slip this way. |

**At work.** The agent's first due-date function read the date with `new Date("2026-10-07")`, which is midnight UTC. Then it wrote the result in the computer's local time. Ana's tests passed on the team's build server, a shared computer set to UTC. On her computer in Columbus they failed by one day, because midnight UTC on October 7 is still 8 p.m. on October 6 there. The agent had mixed local time and UTC. The fix was to build every date in UTC.

**Find the rule line.** Adding days is counting forward on a wall calendar. You put a finger on the start day, count, and turn the page when the month runs out. You do not need every line of `addDays`, only the line that does the counting. The comments say what each line is for. Its answers should match the table.

```ts tab="TypeScript" tab-group="language"
function addDays(isoDate: string, days: number): string {
  const start = `${isoDate}T00:00:00Z`;      // the date, at midnight UTC
  const date = new Date(start);              // make a moment from it
  const newDay = date.getUTCDate() + days;   // the rule: count forward
  date.setUTCDate(newDay);                   // past the month's end, turn the page
  const fullText = date.toISOString();       // back to text
  return fullText.slice(0, 10);              // keep the date part
}

console.log(addDays("2026-10-20", 15), addDays("2026-12-20", 30), addDays("2028-02-15", 15));
```

```python tab="Python" tab-group="language"
from datetime import date, timedelta


def add_days(iso_date: str, days: int) -> str:
    start = date.fromisoformat(iso_date)   # make a calendar day from the text
    end = start + timedelta(days=days)     # the rule: count forward
    return end.isoformat()                 # back to text


print(add_days("2026-10-20", 15), add_days("2026-12-20", 30), add_days("2028-02-15", 15))
```

| Language | The rule line |
| --- | --- |
| TypeScript | `setUTCDate` sets the day in UTC, and moves on to the next month when the number is too big. In the first line, text between backticks can hold a value: `${isoDate}` puts in what `isoDate` holds. |
| Python | `timedelta(days=days)` is a length of time, added to the date. |

Both print:

```text
2026-11-04 2027-01-19 2028-03-01
```

### Try it

The file in `13-7` adds 15 days to a date, then checks a Windows line. Run it and investigate, as in [13.1](you-read-the-agent-writes.md#try-it).

**Modify.** Send: "Please change the date in the file to 2028-02-20, and clean the Windows line before it is split. Don't run it yet." Work out the new date on a calendar. February 2028 has 29 days. Then ask the agent to run it.

**Make.** Send: "Please make a new file in 13-7 called mine that adds 30 days to [a date of yours, across a year end]. Don't run it yet." Work out the answer yourself, and then ask the agent to run it.

**What you should see.** `2028-03-06`, then true where it was false, because the invisible `\r` is gone.

**The operating rule:** keep dates as ISO text, do date math in UTC or with a calendar-day type, and test month ends, year ends, leap days and Windows line endings on purpose.

![Figure 13.8. A calendar strip crossing a month end: October 20 plus 15 days lands on November 4, and February 15, 2028 plus 15 days lands on March 1 because of the leap day.](img/calendar-strip.png)

*Figure 13.8. Date math crosses edges. The edges are where the bugs are, so they are where the tests go.*

**Next.** [13.8](reading-an-error.md) shows how to read what a program says when it breaks.
