summaryrefslogtreecommitdiff
path: root/docs/handbook/corebinutils/date.md
blob: d498f406a58f5592e0b795767f4b779734ca019c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# date — Display and Set System Date

## Overview

`date` displays the current date and time, or sets the system clock. It
supports strftime-based format strings, ISO 8601 output, RFC 2822 output,
timezone overrides, date arithmetic via "vary" adjustments, and input
date parsing.

**Source**: `date/date.c`, `date/vary.c`, `date/vary.h`
**Origin**: BSD 4.4, University of California, Berkeley
**License**: BSD-3-Clause / BSD-2-Clause (vary.c)

## Synopsis

```
date [-jnRu] [-r seconds | filename] [-I[date|hours|minutes|seconds|ns]]
     [-f input_fmt] [-v [+|-]val[ymwdHMS]] [-z output_zone]
     [+output_format] [[[[[cc]yy]mm]dd]HH]MM[.ss]]
```

## Options

| Flag | Description |
|------|-------------|
| `-j` | Do not try to set the system clock |
| `-n` | Same as `-j` (compatibility) |
| `-R` | RFC 2822 format output |
| `-u` | Use UTC instead of local time |
| `-r seconds` | Display time from epoch seconds |
| `-r filename` | Display modification time of file |
| `-I[precision]` | ISO 8601 format output |
| `-f input_fmt` | Parse input date using strptime format |
| `-v adjustment` | Adjust date components (can be repeated) |
| `-z timezone` | Use specified timezone for output |

## Source Analysis

### date.c — Main Implementation

#### Key Data Structures

```c
struct iso8601_fmt {
    const char *refname;        /* "date", "hours", "minutes", etc. */
    const char *format_string;  /* strftime format */
    bool include_zone;          /* Whether to append timezone */
};

struct strbuf {
    char *data;
    size_t len;
    size_t cap;
};

struct options {
    const char *input_format;       /* -f format string */
    const char *output_zone;        /* -z timezone */
    const char *reference_arg;      /* -r argument */
    const char *time_operand;       /* MMDDhhmm... or parsed date */
    const char *format_operand;     /* +format string */
    struct vary *vary_chain;        /* -v adjustments */
    const struct iso8601_fmt *iso8601_selected;
    bool no_set;                    /* -j flag */
    bool rfc2822;                   /* -R flag */
    bool use_utc;                   /* -u flag */
};
```

#### ISO 8601 Formats

```c
static const struct iso8601_fmt iso8601_fmts[] = {
    { "date",    "%Y-%m-%d",              false },
    { "hours",   "%Y-%m-%dT%H",           true  },
    { "minutes", "%Y-%m-%dT%H:%M",        true  },
    { "seconds", "%Y-%m-%dT%H:%M:%S",     true  },
    { "ns",      "%Y-%m-%dT%H:%M:%S,%N",  true  },
};
```

#### Key Functions

| Function | Purpose |
|----------|---------|
| `main()` | Parse options, resolve time, format output |
| `parse_args()` | Option-by-option argument processing |
| `validate_options()` | Check for conflicting options |
| `set_timezone_or_die()` | Apply timezone via `setenv("TZ", ...)` |
| `read_reference_time()` | Get time from `-r` argument (epoch or file mtime) |
| `read_current_time()` | Get current time via `clock_gettime(2)` |
| `parse_legacy_time()` | Parse `[[[[cc]yy]mm]dd]HH]MM[.ss]` format |
| `parse_formatted_time()` | Parse via `strptime(3)` with `-f` format |
| `parse_time_operand()` | Dispatch to legacy or formatted parser |
| `set_system_time()` | Set clock via `clock_settime(2)` |
| `apply_variations()` | Apply `-v` adjustments to broken-down time |
| `expand_format_string()` | Expand `%N` (nanoseconds) in format strings |
| `render_format()` | Format via `strftime(3)` with extensions |
| `render_iso8601()` | Generate ISO 8601 output with timezone |
| `render_numeric_timezone()` | Format `+HHMM` timezone offset |
| `print_line_and_exit()` | Write output and exit |

#### Main Flow

```c
int main(int argc, char **argv)
{
    parse_args(argc, argv, &options);
    validate_options(&options);
    setlocale(LC_TIME, "");

    if (options.use_utc)
        set_timezone_or_die("UTC0", "TZ=UTC0");

    if (options.reference_arg != NULL)
        read_reference_time(options.reference_arg, &ts);
    else
        read_current_time(&ts, &resolution);

    if (options.time_operand != NULL) {
        parse_time_operand(&options, &ts, &ts);
        if (!options.no_set)
            set_system_time(&ts);
    }

    localtime_or_die(ts.tv_sec, &tm);
    apply_variations(&options, &tm);

    /* Render output based on -I, -R, or +format */
    output = render_format(format, &tm, ts.tv_nsec, resolution.tv_nsec);
    print_line_and_exit(output);
}
```

#### String Buffer Implementation

`date.c` includes a custom growable string buffer for format expansion:

```c
static void strbuf_init(struct strbuf *buf);
static void strbuf_reserve(struct strbuf *buf, size_t extra);
static void strbuf_append_mem(struct strbuf *buf, const char *data, size_t len);
static void strbuf_append_char(struct strbuf *buf, char ch);
static void strbuf_append_str(struct strbuf *buf, const char *text);
static char *strbuf_finish(struct strbuf *buf);
```

#### Nanosecond Format Extension

The `%N` format specifier (not in standard `strftime`) is expanded
manually before passing to `strftime(3)`:

```c
static void
append_nsec_digits(struct strbuf *buf, const char *pending, size_t len,
    long nsec, long resolution)
{
    /* Format nanoseconds with appropriate precision based on resolution */
}
```

### vary.c — Date Arithmetic

The `-v` flag enables relative date adjustments. Multiple `-v` flags can
be chained to build complex date expressions.

#### Adjustment Types

| Code | Unit | Example |
|------|------|---------|
| `y` | Years | `-v +1y` (next year) |
| `m` | Months | `-v -3m` (3 months ago) |
| `w` | Weeks | `-v +2w` (2 weeks forward) |
| `d` | Days | `-v +1d` (tomorrow) |
| `H` | Hours | `-v -6H` (6 hours ago) |
| `M` | Minutes | `-v +30M` (30 minutes forward) |
| `S` | Seconds | `-v -10S` (10 seconds ago) |

#### Named Values

Month names and weekday names can be used with `=`:

```sh
date -v =monday     # Next Monday
date -v =january    # Set month to January
```

#### Implementation

```c
struct trans {
    int64_t value;
    const char *name;
};

static const struct trans trans_mon[] = {
    { 1, "january" }, { 2, "february" }, { 3, "march" },
    { 4, "april" },   { 5, "may" },      { 6, "june" },
    { 7, "july" },    { 8, "august" },    { 9, "september" },
    { 10, "october" },{ 11, "november" }, { 12, "december" },
    { -1, NULL }
};

static const struct trans trans_wday[] = {
    { 0, "sunday" },   { 1, "monday" },  { 2, "tuesday" },
    { 3, "wednesday" },{ 4, "thursday" },{ 5, "friday" },
    { 6, "saturday" }, { -1, NULL }
};
```

The `vary_apply()` function processes each adjustment in the chain,
calling specific adjuster functions:

```c
static int adjyear(struct tm *tm, char type, int64_t value, bool normalize);
static int adjmon(struct tm *tm, char type, int64_t value, bool is_text, bool normalize);
static int adjday(struct tm *tm, char type, int64_t value, bool normalize);
static int adjwday(struct tm *tm, char type, int64_t value, bool is_text, bool normalize);
static int adjhour(struct tm *tm, char type, int64_t value, bool normalize);
static int adjmin(struct tm *tm, char type, int64_t value, bool normalize);
static int adjsec(struct tm *tm, char type, int64_t value, bool normalize);
```

Each adjuster modifies the broken-down `struct tm` and calls
`normalize_tm()` to fix rolled-over fields via `mktime(3)`.

### Timezone Handling

```c
static void
set_timezone_or_die(const char *tz_value, const char *what)
{
    if (setenv("TZ", tz_value, 1) != 0)
        die_errno("setenv %s", what);
    tzset();
}
```

The `-u` flag sets `TZ=UTC0`. The `-z` flag sets `TZ` to the specified
value only for output formatting (input parsing uses the original timezone).

### Legacy Time Format

The BSD legacy format `[[[[cc]yy]mm]dd]HH]MM[.ss]` is parsed
right-to-left:

```c
static void
parse_legacy_time(const char *text, const struct timespec *base, struct timespec *ts)
{
    /* Parse from rightmost position:
     * 1. [.ss]  - optional seconds
     * 2. MM     - minutes (required)
     * 3. HH     - hours
     * 4. dd     - day
     * 5. mm     - month
     * 6. [cc]yy - year
     */
}
```

## System Calls Used

| Syscall | Purpose |
|---------|---------|
| `clock_gettime(2)` | Read current time with nanosecond precision |
| `clock_settime(2)` | Set system clock (requires root) |
| `stat(2)` | Get file modification time for `-r filename` |
| `setenv(3)` | Set `TZ` environment variable |
| `strftime(3)` | Format broken-down time |
| `strptime(3)` | Parse time from formatted string |
| `mktime(3)` | Normalize broken-down time |
| `localtime_r(3)` | Thread-safe time conversion |

## Format Strings

`date` supports all `strftime(3)` format specifiers plus:

| Specifier | Meaning |
|-----------|---------|
| `%N` | Nanoseconds (extension, expanded before strftime) |
| `%+` | Default format (equivalent to `%a %b %e %T %Z %Y`) |

Common `strftime` specifiers:

| Specifier | Output |
|-----------|--------|
| `%Y` | 4-digit year (2026) |
| `%m` | Month (01-12) |
| `%d` | Day (01-31) |
| `%H` | Hour (00-23) |
| `%M` | Minute (00-59) |
| `%S` | Second (00-60) |
| `%T` | Time as `%H:%M:%S` |
| `%Z` | Timezone abbreviation |
| `%z` | Numeric timezone (`+0000`) |
| `%s` | Epoch seconds |
| `%a` | Abbreviated weekday |
| `%b` | Abbreviated month |

## Examples

```sh
# Default output
date
# → Sat Apr  5 14:30:00 UTC 2026

# Custom format
date "+%Y-%m-%d %H:%M:%S"
# → 2026-04-05 14:30:00

# ISO 8601
date -Iseconds
# → 2026-04-05T14:30:00+00:00

# RFC 2822
date -R
# → Sat, 05 Apr 2026 14:30:00 +0000

# UTC
date -u

# Epoch seconds
date +%s
# → 1775578200

# Date arithmetic: tomorrow
date -v +1d

# Date arithmetic: last Monday
date -v -monday

# Date arithmetic: 3 months from now, at midnight
date -v +3m -v 0H -v 0M -v 0S

# Parse input format
date -f "%Y%m%d" "20260405" "+%A, %B %d"
# → Sunday, April 05

# Display file modification time
date -r /etc/passwd

# Display epoch time
date -r 1775578200
```

## Exit Codes

| Code | Meaning |
|------|---------|
| 0    | Success |
| 1    | Error (invalid format, failed to set time, etc.) |