exiftool-vendored
Version:
Efficient, cross-platform access to ExifTool
313 lines • 12.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const node_crypto_1 = require("node:crypto");
const DateTime_1 = require("./DateTime");
const ExifDateTime_1 = require("./ExifDateTime");
const Object_1 = require("./Object");
const _chai_spec_1 = require("./_chai.spec");
describe("ExifDateTime", () => {
for (const ea of [
{
desc: "no zone suffix",
exif: "2016:09:12 07:28:50.768120",
exifExp: "2016:09:12 07:28:50.768",
iso: "2016-09-12T07:28:50.768",
d: {
hasZone: false,
zoneName: undefined,
year: 2016,
month: 9,
day: 12,
hour: 7,
minute: 28,
second: 50,
millisecond: 768,
},
},
{
desc: "with zulu suffix",
exif: "1999:01:02 03:04:05.67890Z",
exifExp: "1999:01:02 03:04:05.678+00:00",
iso: "1999-01-02T03:04:05.678Z",
d: {
hasZone: true,
zoneName: "UTC",
year: 1999,
month: 1,
day: 2,
hour: 3,
minute: 4,
second: 5,
millisecond: 678,
},
},
{
desc: "with timezone offset",
exif: "2002:10:11 13:14:15.789-03:00",
iso: "2002-10-11 13:14:15.789-03:00", // < no T!
isoExp: "2002-10-11T13:14:15.789-03:00",
d: {
hasZone: true,
zoneName: "UTC-3",
year: 2002,
month: 10,
day: 11,
hour: 13,
minute: 14,
second: 15,
millisecond: 789,
},
},
{
desc: "with no tzoffset and UTC default",
exif: "2011:01:23 18:19:20Z",
iso: "2011-01-23T18:19:20Z",
exifExp: "2011:01:23 18:19:20+00:00",
d: {
hasZone: true,
zoneName: "UTC",
year: 2011,
month: 1,
day: 23,
hour: 18,
minute: 19,
second: 20,
millisecond: undefined,
},
},
{
desc: "EXIF with ISO formatting and no tzoffset",
exif: "2016:08:12 07:28:50.768120",
exifExp: "2016:08:12 07:28:50.768",
iso: "2016-08-12T07:28:50.768",
d: {
hasZone: false,
zoneName: undefined,
year: 2016,
month: 8,
day: 12,
hour: 7,
minute: 28,
second: 50,
millisecond: 768,
},
},
{
desc: "with tzoffset and spurious timezone",
exif: "2015:07:17 08:46:27.123-09:00 DST",
exifExp: "2015:07:17 08:46:27.123-09:00",
iso: "2015-07-17T08:46:27.123-09:00",
isoExp: "2015-07-17T08:46:27.123-09:00",
d: {
hasZone: true,
zoneName: "UTC-9",
year: 2015,
month: 7,
day: 17,
hour: 8,
minute: 46,
second: 27,
millisecond: 123,
},
},
{
desc: "with tzoffset, no millis, and spurious timezone",
exif: "2014:07:17 08:46:27-07:00 DST",
exifExp: "2014:07:17 08:46:27-07:00",
iso: "2014-07-17T08:46:27-07:00",
isoExp: "2014-07-17T08:46:27-07:00",
d: {
hasZone: true,
zoneName: "UTC-7",
year: 2014,
month: 7,
day: 17,
hour: 8,
minute: 46,
second: 27,
millisecond: undefined,
},
},
{
desc: "with tzoffset, no millis or seconds",
exif: "2014:07:17 08:46-07:00",
exifExp: "2014:07:17 08:46:00-07:00",
iso: "2014-07-17T08:46-07:00",
isoExp: "2014-07-17T08:46:00-07:00",
d: {
hasZone: true,
zoneName: "UTC-7",
year: 2014,
month: 7,
day: 17,
hour: 8,
minute: 46,
second: 0,
millisecond: undefined,
},
},
// https://github.com/photostructure/exiftool-vendored.js/issues/318
{
desc: "with colon-less tzoffset (ISO 8601 basic format)",
exif: "2025:04:27 19:47:08-0300",
exifExp: "2025:04:27 19:47:08-03:00",
iso: "2025-04-27T19:47:08-0300",
isoExp: "2025-04-27T19:47:08-03:00",
d: {
hasZone: true,
zoneName: "UTC-3",
year: 2025,
month: 4,
day: 27,
hour: 19,
minute: 47,
second: 8,
millisecond: undefined,
},
},
]) {
describe(ea.desc + " (iso: " + ea.iso + ")", () => {
const fromExif = ExifDateTime_1.ExifDateTime.fromEXIF(ea.exif);
const expected = {
...ea.d,
inferredZone: false, // < because the .fromEXIF call doesn't have a default!
};
it("parses from EXIF", () => {
(0, _chai_spec_1.expect)(fromExif).to.containSubset(expected);
});
it("parses from EXIF with zone default", () => {
const edt = ExifDateTime_1.ExifDateTime.fromEXIF(ea.exif, "UTC+1");
(0, _chai_spec_1.expect)(edt).to.haveOwnProperty("inferredZone", !expected.hasZone);
(0, _chai_spec_1.expect)(edt?.zoneName).to.eql(expected.zoneName ?? "UTC+1");
});
const fromISO = ExifDateTime_1.ExifDateTime.fromISO(ea.iso);
it("parses from ISO", () => {
(0, _chai_spec_1.expect)(fromISO).to.containSubset(expected);
});
it("parses from ISO with zone default", () => {
const edt = ExifDateTime_1.ExifDateTime.fromISO(ea.iso, "UTC-5");
(0, _chai_spec_1.expect)(edt).to.haveOwnProperty("inferredZone", !expected.hasZone);
(0, _chai_spec_1.expect)(edt?.zoneName).to.eql(expected.zoneName ?? "UTC-5");
});
const jsDate = new Date(ea.iso);
it("renders correct epoch millis", () => {
const exp = jsDate.getTime();
(0, _chai_spec_1.expect)(fromExif?.toMillis()).to.eql(exp);
(0, _chai_spec_1.expect)(fromISO?.toMillis()).to.eql(exp);
});
it("renders correct JS Date", () => {
(0, _chai_spec_1.expect)(fromExif?.toDate()).to.eql(jsDate);
(0, _chai_spec_1.expect)(fromISO?.toDate()).to.eql(jsDate);
});
it("renders correct ISO string", () => {
const exp = ea.isoExp ?? ea.iso;
(0, _chai_spec_1.expect)(fromExif?.toISOString()).to.eql(exp);
(0, _chai_spec_1.expect)(fromISO?.toISOString()).to.eql(exp);
});
it("renders correct EXIF string", () => {
const exp = ea.exifExp ?? ea.exif;
(0, _chai_spec_1.expect)(fromExif?.toExifString()).to.eql(exp);
(0, _chai_spec_1.expect)(fromISO?.toExifString()).to.eql(exp);
});
});
}
describe("parsing empty/invalid input", () => {
const examples = [
"",
" ",
"0000",
"1958",
"2010_08",
"01:08:22",
"0000:00:00 00:00:00",
"0000:00:00",
"0001:01:01 00:00:00.00", // < actual value from a Fotofly image (SHAME!)
"0001:01:01",
" : : : : ",
];
examples.forEach((bad) => {
it(`.fromEXIF(${bad}) => undefined`, () => {
(0, _chai_spec_1.expect)(ExifDateTime_1.ExifDateTime.fromEXIF(bad)).to.eql(undefined);
});
it(`.fromISO(${bad}) => undefined`, () => {
(0, _chai_spec_1.expect)(ExifDateTime_1.ExifDateTime.fromISO(bad)).to.eql(undefined);
});
});
});
it("parses timestamps that end in DST", () => {
const raw = "2014:07:17 08:46:27-07:00 DST";
const dt = ExifDateTime_1.ExifDateTime.fromEXIF(raw);
(0, _chai_spec_1.expect)(dt.toISOString()).to.eql("2014-07-17T08:46:27-07:00");
(0, _chai_spec_1.expect)(dt.zone).to.eql("UTC-7");
});
it("try to repro issue #46", () => {
const edt = new ExifDateTime_1.ExifDateTime(2019, 3, 8, 14, 24, 54, 0, -480);
const dt = edt.toDateTime();
(0, _chai_spec_1.expect)(dt.toISO()).to.eql("2019-03-08T14:24:54.000-08:00");
});
it("try to repro issue #118", () => {
const edt = ExifDateTime_1.ExifDateTime.fromExifStrict("1970:01:01 00:00:00Z");
(0, _chai_spec_1.expect)(edt.toMillis()).to.eql(0);
});
it("parses non-standard timezone offset", () => {
// 1900-1923, Kyiv had an offset of UTC +2:02:04
const edt = ExifDateTime_1.ExifDateTime.fromExifStrict("1904:02:03 13:14:15", "Europe/Kiev");
(0, _chai_spec_1.expect)(edt.hasZone).to.eql(true);
(0, _chai_spec_1.expect)(edt.isValid).to.eql(true);
(0, _chai_spec_1.expect)(edt.toISOString()).to.eql("1904-02-03T13:14:15+02:02");
});
describe(".fromMillis()", () => {
it("round-trips now()", () => {
const e = ExifDateTime_1.ExifDateTime.now();
(0, _chai_spec_1.expect)(e.toDate().getTime()).to.be.closeTo(Date.now(), 2_000);
});
it("round-trips now() and retains raw value", () => {
const rawValue = (0, _chai_spec_1.randomChars)();
const e = ExifDateTime_1.ExifDateTime.now({ rawValue });
(0, _chai_spec_1.expect)(e.toDate().getTime()).to.be.closeTo(Date.now(), 2_000);
(0, _chai_spec_1.expect)(e.rawValue).to.eql(rawValue);
});
it("renders pre-epoch timestamp()", () => {
const e = ExifDateTime_1.ExifDateTime.fromMillis(-27686744322, { zone: "UTC" });
(0, _chai_spec_1.expect)(e.toISOString()).to.eql("1969-02-14T13:14:15.678Z");
});
it("renders UTC TWOSday timestamp()", () => {
const e = ExifDateTime_1.ExifDateTime.fromMillis(1643767342222, { zone: "UTC" });
(0, _chai_spec_1.expect)(e.toISOString()).to.eql("2022-02-02T02:02:22.222Z");
});
it("renders PST TWOSday timestamp()", () => {
const rawValue = (0, _chai_spec_1.randomChars)();
const e = ExifDateTime_1.ExifDateTime.fromMillis(1643796142345, {
zone: "America/Los_Angeles",
rawValue,
});
(0, _chai_spec_1.expect)(e.toISOString()).to.eql("2022-02-02T02:02:22.345-08:00");
(0, _chai_spec_1.expect)(e.rawValue).to.eql(rawValue);
});
});
describe(".plus()", () => {
const ts = Math.floor((0, node_crypto_1.randomInt)(0, Date.now()) / DateTime_1.MinuteMs) * DateTime_1.MinuteMs;
it("retains values with no zone", () => {
console.dir({ timestamp: ts });
// we want a timestamp where adding a second won't change the minute:
const e = ExifDateTime_1.ExifDateTime.fromMillis(ts);
(0, _chai_spec_1.expect)(e.hasZone).to.eql(false);
const json = e.toJSON();
const e2 = e.plus({ seconds: 1 });
(0, _chai_spec_1.expect)(e2.hasZone).to.eql(false);
(0, _chai_spec_1.expect)(e2.toJSON()).to.containSubset((0, Object_1.omit)(json, "second", "millisecond", "rawValue"));
});
it("retains values with zone", () => {
const zoneStr = "UTC+6";
// we want a timestamp where adding a second won't change the minute:
const e = ExifDateTime_1.ExifDateTime.fromMillis(ts).setZone(zoneStr);
(0, _chai_spec_1.expect)(e.hasZone).to.eql(true);
const json = e.toJSON();
const e2 = e.plus({ seconds: 1 });
(0, _chai_spec_1.expect)(e2.hasZone).to.eql(true);
(0, _chai_spec_1.expect)(e2.zone).to.eql(zoneStr);
(0, _chai_spec_1.expect)(e2.toJSON()).to.containSubset((0, Object_1.omit)(json, "second", "millisecond", "rawValue"));
});
});
});
//# sourceMappingURL=ExifDateTime.spec.js.map