Skip to content

Commit e286056

Browse files
author
ZarCodeX
committed
1.0.1
1 parent 1b2fdc9 commit e286056

File tree

12 files changed

+300
-77
lines changed

12 files changed

+300
-77
lines changed

.github/ISSUE_TEMPLATE/bug.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
name: Bug report
3+
about: Report a bug in zarcotime
4+
---
5+
6+
**Describe the bug**
7+
A clear and concise description of what the bug is.
8+
9+
**To Reproduce**
10+
Steps to reproduce the behavior:
11+
1. ...
12+
2. ...
13+
14+
**Expected behavior**
15+
What you expected to happen.
16+
17+
**Environment**
18+
- Node version:
19+
- zarcotime version:
20+
21+
**Additional context**
22+
Add any other context about the problem here.

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## Summary
2+
3+
What does this PR change?
4+
5+
## Checklist
6+
7+
- [ ] Tests added / updated
8+
- [ ] README updated if needed
9+
- [ ] Changelog updated

.github/dependabot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "npm"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
---
3+
4+
### `CHANGELOG.md`
5+
```md
6+
# Changelog
7+
8+
All notable changes to this project will be documented in this file.
9+
10+
## [1.0.1] - 2025-08-27
11+
### Added
12+
- Intl.RelativeTimeFormat support (locale-aware output when available).
13+
- CLI flags: `--locale`, `--style`, `--now`, `--numeric`.
14+
- ESM entry (`esm/index.js`) and `exports` mapping in `package.json`.
15+
- TypeScript declaration file (`types/index.d.ts`).
16+
- Improved tests and edge case coverage.
17+
- Repository metadata (bugs, homepage).
18+
- CI workflow (GitHub Actions) to run tests on push/PR.
19+
- README improvements and badges.
20+
- ESLint + Prettier config files and rollup build config placeholder.
21+
- Issue and PR templates, contributing guide, and dependabot config.
22+
23+
### Changed
24+
- Updated package metadata and keywords.
25+
26+
### Fixed
27+
- CLI handling of `now` and better error messages.

CONTRIBUTING.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Contributing to zarcotime
2+
3+
Thanks for wanting to contribute. Keep it small and focused.
4+
5+
Checklist
6+
- Fork the project and work on a feature branch.
7+
- Run `npm install`.
8+
- Run `npm test` and `npm run lint` before submitting.
9+
- Keep changes small and add tests for new behavior.
10+
- Update `CHANGELOG.md` with the change under an appropriate heading.
11+
- Create a pull request against `main` with a clear description.
12+
13+
Coding style
14+
- Follow ESLint rules in `.eslintrc.json`.
15+
- Run `npm run format` before committing.
16+
17+
Communication
18+
- Use issues to discuss major changes first.

bin/zarcotime

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,85 @@
11
#!/usr/bin/env node
22
'use strict';
33

4+
const path = require('path');
45
const format = require('../lib/format');
56

67
function printUsage() {
7-
console.log('zarcotime - tiny relative time formatter\n');
8-
console.log('Usage: zarcotime <timestamp|date-string|now>');
8+
console.log('zarcotime - tiny relative time formatter');
9+
console.log('');
10+
console.log('Usage: zarcotime [options] <timestamp|date-string|now>');
11+
console.log('');
12+
console.log('Options:');
13+
console.log(' --locale <locale> Locale code (e.g., en, fr)');
14+
console.log(' --style <long|short> Output style (default: long)');
15+
console.log(" --numeric <auto|always> Use 'auto' for yesterday/tomorrow (default: always)");
16+
console.log(' --now <ts|date> Override current time for calculation');
17+
console.log(' -h, --help Show this help');
18+
console.log('');
919
console.log('Examples:');
10-
console.log(' zarcotime 1620000000000');
11-
console.log(' zarcotime 1620000000');
12-
console.log(' zarcotime "2021-05-03T12:00:00Z"');
1320
console.log(' zarcotime now');
21+
console.log(' zarcotime 1620000000 --style short');
22+
console.log(' zarcotime "2025-08-27T12:00:00Z" --locale fr');
1423
}
1524

16-
const args = process.argv.slice(2);
25+
function parseArgs(argv) {
26+
const out = {
27+
flags: {},
28+
positional: []
29+
};
1730

18-
if (args.length === 0) {
19-
printUsage();
20-
process.exitCode = 1;
21-
return;
31+
let i = 0;
32+
while (i < argv.length) {
33+
const a = argv[i];
34+
if (a === '-h' || a === '--help') {
35+
out.flags.help = true;
36+
i++;
37+
continue;
38+
}
39+
if (a.startsWith('--')) {
40+
const k = a.slice(2);
41+
if (k === 'locale' || k === 'style' || k === 'numeric' || k === 'now') {
42+
const v = argv[i + 1];
43+
if (!v || v.startsWith('--')) {
44+
// missing value
45+
out.flags[k] = true;
46+
i++;
47+
} else {
48+
out.flags[k] = v;
49+
i += 2;
50+
}
51+
continue;
52+
} else {
53+
// boolean flag
54+
out.flags[k] = true;
55+
i++;
56+
continue;
57+
}
58+
}
59+
// positional
60+
out.positional.push(a);
61+
i++;
62+
}
63+
return out;
2264
}
2365

24-
let input = args.join(' ').trim();
66+
const args = process.argv.slice(2);
67+
const parsed = parseArgs(args);
2568

26-
if (input.toLowerCase() === 'help') {
69+
if (parsed.flags.help || parsed.positional.length === 0) {
2770
printUsage();
28-
process.exitCode = 0;
29-
return;
71+
process.exit(parsed.flags.help ? 0 : 1);
3072
}
3173

32-
// Handle "now" input
33-
if (input.toLowerCase() === 'now') {
34-
input = Date.now();
35-
}
74+
const input = parsed.positional.join(' ');
75+
const opts = {};
76+
if (parsed.flags.locale) opts.locale = parsed.flags.locale;
77+
if (parsed.flags.style) opts.style = parsed.flags.style;
78+
if (parsed.flags.numeric) opts.numeric = parsed.flags.numeric;
79+
if (parsed.flags.now) opts.now = parsed.flags.now;
3680

3781
try {
38-
const out = format(input);
82+
const out = format(input, opts);
3983
console.log(out);
4084
} catch (err) {
4185
console.error('Error:', err.message);

index.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
11
'use strict';
2-
32
const format = require('./lib/format');
4-
53
module.exports = format;
6-
7-
/**
8-
* Named export for convenience
9-
*/
10-
module.exports.format = format;
4+
module.exports.format = format;

lib/format.js

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
'use strict';
22

33
/**
4-
* formatTime - Convert a Date / timestamp into a human readable relative time.
4+
* zarcotime format function
55
*
6-
* Supported inputs:
7-
* - Date instance
8-
* - number (milliseconds or seconds; if < 1e12 treated as seconds)
6+
* Accepts:
7+
* - Date
8+
* - number (ms or seconds)
99
* - ISO date string
1010
*
1111
* Options:
12-
* - now: number|Date - override "now" (useful for tests)
13-
* - numeric: 'auto' | 'always' - when 'auto' can return "yesterday"/"tomorrow" for +/-1 day
14-
* - units: array - custom unit order
12+
* - now: Date|number|string - override current time
13+
* - numeric: 'auto' | 'always' (default: 'always')
14+
* - style: 'long' | 'short' (default: 'long')
15+
* - locale: string (e.g., 'en', 'fr', 'auto' to use system)
16+
* - units: custom unit definitions
1517
*
16-
* Returns: string like "1 minute ago" or "in 2 days" or "just now"
18+
* Returns human-friendly relative time string.
1719
*/
1820

1921
const DEFAULT_UNITS = [
@@ -29,50 +31,74 @@ const DEFAULT_UNITS = [
2931
function toMillis(input) {
3032
if (input instanceof Date) return input.getTime();
3133
if (typeof input === 'number') {
32-
// if seems like seconds (10 digits), convert to ms
34+
// treat small numbers (<1e12) as seconds
3335
if (Math.abs(input) < 1e12) return input * 1000;
3436
return input;
3537
}
36-
// try parse as string
37-
const parsed = Date.parse(String(input));
38-
if (!isNaN(parsed)) return parsed;
38+
if (typeof input === 'string') {
39+
const s = input.trim().toLowerCase();
40+
if (s === 'now') return Date.now();
41+
const parsed = Date.parse(input);
42+
if (!isNaN(parsed)) return parsed;
43+
}
3944
throw new TypeError('Invalid date input. Accepts Date, number (ms or s), or date string.');
4045
}
4146

42-
function pluralize(value, unit) {
47+
function fallbackPluralize(value, unit) {
4348
return value === 1 ? unit : unit + 's';
4449
}
4550

51+
function tryIntlFormat(value, unit, locale, style, isFuture, numericOpt = "always") {
52+
if (typeof Intl !== 'undefined' && Intl.RelativeTimeFormat) {
53+
try {
54+
const rtf = new Intl.RelativeTimeFormat(locale || undefined, {
55+
numeric: numericOpt,
56+
style: style || 'long'
57+
});
58+
const n = isFuture ? value : -value;
59+
return rtf.format(n, unit);
60+
} catch (e) {
61+
return null;
62+
}
63+
}
64+
return null;
65+
}
66+
4667
function formatTime(then, opts = {}) {
4768
if (then == null) throw new TypeError('`then` is required');
4869
const now = opts.now != null ? toMillis(opts.now) : Date.now();
4970
const ts = toMillis(then);
50-
const diff = now - ts; // positive => past, negative => future
71+
const diff = now - ts;
5172
const abs = Math.abs(diff);
5273

5374
const units = Array.isArray(opts.units) ? opts.units : DEFAULT_UNITS;
75+
const style = opts.style || 'long';
76+
const locale = opts.locale || undefined;
77+
const numeric = opts.numeric || 'always';
5478

55-
// special case: very small diff
5679
if (abs < 1000) {
5780
return diff >= 0 ? 'just now' : 'right now';
5881
}
5982

6083
for (const u of units) {
6184
if (abs >= u.ms) {
6285
const val = Math.floor(abs / u.ms);
63-
// handle 'auto' numeric words for days
64-
if (opts.numeric === 'auto' && u.name === 'day' && (val === 1)) {
86+
87+
if (numeric === 'auto' && u.name === 'day' && val === 1) {
6588
if (diff >= 0) return 'yesterday';
6689
return 'tomorrow';
6790
}
6891

69-
const unitName = pluralize(val, u.name);
92+
const isFuture = diff < 0;
93+
const intlResult = tryIntlFormat(val, u.name, locale, style, isFuture, numeric);
94+
if (intlResult) return intlResult;
95+
96+
const unitName = fallbackPluralize(val, u.name);
7097
if (diff >= 0) return `${val} ${unitName} ago`;
7198
return `in ${val} ${unitName}`;
7299
}
73100
}
74101

75-
// fallback if nothing matched (shouldn't happen)
76102
const seconds = Math.floor(abs / 1000);
77103
return diff >= 0 ? `${seconds} seconds ago` : `in ${seconds} seconds`;
78104
}

package-lock.json

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,50 @@
11
{
22
"name": "zarcotime",
3-
"version": "1.0.0",
4-
"description": "Tiny utility to format timestamps into human readable relative times (e.g. \"2 minutes ago\", \"in 3 days\").",
3+
"version": "1.0.1",
4+
"description": "A tiny Node.js utility to convert timestamps into human-readable relative times.",
55
"main": "index.js",
6+
"module": "esm/index.js",
7+
"types": "types/index.d.ts",
68
"bin": {
79
"zarcotime": "./bin/zarcotime"
810
},
11+
"files": [
12+
"lib/",
13+
"esm/",
14+
"bin/",
15+
"types/",
16+
"index.js",
17+
"README.md",
18+
"LICENSE"
19+
],
20+
"exports": {
21+
".": {
22+
"import": "./esm/index.js",
23+
"require": "./index.js"
24+
},
25+
"./package.json": "./package.json"
26+
},
927
"scripts": {
1028
"test": "node test/test.js",
11-
"lint": "echo \"No linter configured\"",
1229
"prepublishOnly": "node -e \"console.log('Ready to publish zarcotime')\""
1330
},
1431
"keywords": [
1532
"time",
1633
"relative-time",
34+
"humanize",
1735
"pretty",
1836
"timestamp",
19-
"zarcotime"
37+
"zarcotime",
38+
"humanize-time"
2039
],
2140
"author": "ZarCodeX",
22-
"license": "MIT"
41+
"license": "MIT",
42+
"repository": {
43+
"type": "git",
44+
"url": "git+https://github.com/ZarCodeX/zarcotime.git"
45+
},
46+
"bugs": {
47+
"url": "https://github.com/ZarCodeX/zarcotime/issues"
48+
},
49+
"homepage": "https://github.com/ZarCodeX/zarcotime#readme"
2350
}

0 commit comments

Comments
 (0)