Skip to content

Instantly share code, notes, and snippets.

@lpenaud
Last active August 12, 2025 19:27
Show Gist options
  • Select an option

  • Save lpenaud/4bc8dce6c69d80a3a65bf4c59b4c4e5a to your computer and use it in GitHub Desktop.

Select an option

Save lpenaud/4bc8dce6c69d80a3a65bf4c59b4c4e5a to your computer and use it in GitHub Desktop.
Copy file with the modified time

Activity

A script to sync activities *.FIT from my Garmin smartwatch.

Usage

Strict

deno run https://gist.githubusercontent.com/lpenaud/4bc8dce6c69d80a3a65bf4c59b4c4e5a/raw/activity.ts INDIR OUTDIR

Allow flags

deno run https://gist.githubusercontent.com/lpenaud/4bc8dce6c69d80a3a65bf4c59b4c4e5a/raw/activity.ts --allow-read=INDIR --allow-write=INDIR,OUTDIR INDIR OUTDIR 

With executable rights

You need to download it to run the script like that.

./activity.ts INDIR OUTDIR
#!/usr/bin/env -S deno run --allow-read --allow-write
import * as stdPath from "jsr:@std/[email protected]";
interface ListEntry {
stat: Deno.FileInfo;
name: string;
pathname: stdPath.ParsedPath;
}
interface CopyArgs {
src: string;
dest: string;
}
async function* list(indir: string): AsyncGenerator<ListEntry> {
for await (const { name: basename } of Deno.readDir(indir)) {
const resolve = stdPath.resolve(indir, basename);
yield {
stat: await Deno.stat(resolve),
name: resolve,
pathname: stdPath.parse(resolve),
};
}
}
function padNumber(n: number): string {
return n.toString().padStart(2, "0");
}
async function* paths(
indir: string,
outdir: string,
): AsyncGenerator<CopyArgs, void> {
for await (const { stat, name, pathname } of list(indir)) {
if (stat.birthtime === null) {
console.error("atime null:", name);
continue;
}
const year = stat.birthtime.getFullYear().toString();
const month = padNumber(stat.birthtime.getMonth() + 1);
const date = padNumber(stat.birthtime.getDate());
const hours = padNumber(stat.birthtime.getHours());
const minutes = padNumber(stat.birthtime.getMinutes());
const dest = stdPath.resolve(
outdir,
year,
month,
`${year}-${month}-${date}-${hours}h${minutes}m${pathname.ext}`,
);
yield {
src: name,
dest,
};
}
}
async function cpRecursive(src: string, dest: string) {
await Deno.mkdir(stdPath.dirname(dest), {
recursive: true,
});
await Deno.copyFile(src, dest);
console.log(src, "->", dest);
}
async function unlink(f: string) {
await Deno.remove(f);
console.log("Removed", f);
}
function usage() {
console.error(
"Usage: %s INDIR OUTDIR",
import.meta.filename ?? import.meta.url,
);
}
async function main(args: string[]): Promise<number> {
if (args.length < 2) {
usage();
return 1;
}
try {
const [indir, outdir] = args;
const infiles: string[] = [];
for await (const { src, dest } of paths(indir, outdir)) {
await cpRecursive(src, dest);
infiles.push(src);
}
if (infiles.length > 0 && confirm("Remove source files?")) {
await Promise.all(infiles.map((i) => unlink(i)));
}
} catch (error) {
console.error(error);
return 2;
}
return 0;
}
if (import.meta.main) {
Deno.exit(await main(Deno.args.slice()));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment