Compare commits

...
Author SHA1 Message Date
Dylan M. Taylor bfb23db2ca Use chrono instead of hand-rolled UTC conversion (#2457)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled Howard Hinnant civil/days-from-civil copies and the
leap-year/days-in-month table with chrono as a workspace dependency:
- apps/lightcraft/src/logging.rs: timestamp (log line time)
- crates/catalog/src/dates.rs: civil + weekday
- crates/catalog/src/stacks.rs: iso_seconds (date -> seconds)
- crates/engine/src/cmd/face_recognize.rs: capture_secs (native only)
- crates/meta/src/datetime.rs: DateTime::unix_seconds and the valid() day check
Output formats and semantics are unchanged (pre-epoch clamp, day rollover that
normalize_iso relies on, leap-second tolerance, EXIF/XMP parsing). chrono uses
only std, so wasm stays safe. AGENTS.md/CLAUDE.md now say time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Never panic on a clock outside chrono's range; keep 5-digit years unprefixed

chrono's From<SystemTime> unwraps, so a clock past year 262143 panicked
inside the logger. timestamp now uses the fallible DateTime::from_timestamp
and reads such a clock as the epoch, keeping the old pre-epoch clamp.

timestamp and catalog's civil write years past 9999 with {:04} like the old
{y:04} did, because %Y would add a '+'. Tests cover the out-of-range and
5-digit-year cases.

* Don't build an unrepresentable SystemTime in the far-future test

UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.

* time: preserve old out-of-range date conversion (no epoch fallback)

`DateTime::from_timestamp(...).unwrap_or_default()` silently reset any instant
past chrono's year range to 1970-01-01, changing output the old hand-rolled
Howard-Hinnant code converted exactly (e.g. 10_000_000_000_000 s was
318857-05-20 17:46:40, not the epoch). Restore the old conversion domain with a
cycle-based helper that routes the calendar through chrono (the calendar
repeats every 400 years = 146_097 days, so month/day/time come from chrono and
only the year is carried in i128, keeping every i64 exact).

- apps/lightcraft/src/logging.rs: `timestamp` via `utc_parts`; keep the
  pre-1970 epoch clamp; u64 secs past i64::MAX saturate, not epoch.
- crates/catalog/src/dates.rs: `civil` cycle-based (the callers stay inside
  0000-9999, but a far clock no longer resets to the epoch).

Pinned old outputs in tests:
  logging u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch),
          10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z
  civil 10_000_000_000_000 -> 318857-05-20T17:46:40,
        -10_000_000_000_000 -> -314918-08-13T06:13:20, i64 extremes

* Don't build a year-318857 SystemTime unchecked in the logging test

Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-21 19:36:36 -04:00
Dylan M. Taylor d79b1f33e3 security: enforce HTTPS on curl downloads (SonarCloud S6506) (#2493)
Add --proto '=https' to curl invocations so redirects cannot downgrade to
plaintext HTTP. Resolves SonarCloud S6506 (shell/docker/githubactions).
2026-10-21 19:35:59 -04:00
15 changed files with 116 additions and 78 deletions

No files matched your search

+1
View File
@@ -62,6 +62,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
- **Layering** (`plan/architecture.md` §3, enforced by `cargo xtask layers`): nothing below L5 depends on egui/eframe/winit/rfd.
- **Everything is a command** (`crates/engine`): id, label, menu path, shortcut, params, enabled(), run(). UI, CLI, control channel and MCP all dispatch by id. Every slider is a `develop` control spec.
- **Resolution independence:** settings use normalized image coordinates and relative radii; previews and exports must match.
- **Time goes through `chrono`** (workspace dependency). Never hand-roll UTC/calendar conversion (days-to-date algorithms, leap-year math); a crate owns only its output format string. The one exception is range extension: past the years chrono can represent, shift the date by whole 400-year Gregorian cycles (146,097 days, after which the calendar repeats), let chrono compute the month and day, and add the cycles back to the year (splitting seconds into hours, minutes and seconds is plain arithmetic).
- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests, layers, assets, wasm).
- **Commits:** one task id per commit (`M2.3: local Laplacian highlights/shadows`). Only green states. End messages with the attribution line required by the environment.
+1
View File
@@ -67,6 +67,7 @@ of it. Full standard: `../craftrules/standards/never-crash.md`
label, cancel)` for its whole run. It adopts the cancel flag the job already checks (`Cancel::Flag`), or uses
`Cancel::No` when the job can't stop. No progress window, panel or progress toast of its own; its tests assert the row.
- **Resolution independence:** settings use normalized image coordinates and relative radii; previews and exports must match.
- **Time goes through `chrono`** (workspace dependency). Never hand-roll UTC/calendar conversion (days-to-date algorithms, leap-year math); a crate owns only its output format string. The one exception is range extension: past the years chrono can represent, shift the date by whole 400-year Gregorian cycles (146,097 days, after which the calendar repeats), let chrono compute the month and day, and add the cycles back to the year (splitting seconds into hours, minutes and seconds is plain arithmetic).
- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests, layers, assets, wasm).
- **Commits:** one task id per commit (`M2.3: local Laplacian highlights/shadows`). Only green states. End messages with the attribution line required by the environment.
Generated
+13
View File
@@ -936,6 +936,15 @@ dependencies = [
"zeroize",
]
[[package]]
name = "chrono"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"num-traits",
]
[[package]]
name = "cipher"
version = "0.4.4"
@@ -3080,6 +3089,7 @@ dependencies = [
name = "lightcraft"
version = "0.11.0"
dependencies = [
"chrono",
"eframe",
"egui",
"lightcraft-codecs",
@@ -3105,6 +3115,7 @@ dependencies = [
name = "lightcraft-catalog"
version = "0.11.0"
dependencies = [
"chrono",
"crc32fast",
"lightcraft-develop",
"lightcraft-geom",
@@ -3223,6 +3234,7 @@ name = "lightcraft-engine"
version = "0.11.0"
dependencies = [
"ab_glyph",
"chrono",
"craft-docpath",
"harfrust",
"jxl-oxide",
@@ -3378,6 +3390,7 @@ dependencies = [
name = "lightcraft-meta"
version = "0.11.0"
dependencies = [
"chrono",
"lightcraft-geom",
"lightcraft-tiff",
"proptest",
+2
View File
@@ -49,6 +49,8 @@ sha2 = "0.10"
serde = { version = "1", features = ["derive", "rc"] }
serde_json = { version = "1", features = ["float_roundtrip"] }
log = "0.4"
# All date/time conversion goes through chrono (no hand-rolled calendar code beyond the 400-year range extension; AGENTS.md).
chrono = { version = "0.4.45", default-features = false, features = ["std"] }
rayon = "1"
proptest = "1"
bytemuck = { version = "1", features = ["derive"] }
+1
View File
@@ -26,6 +26,7 @@ lightcraft-raster = { workspace = true }
eframe = { workspace = true, features = ["default_fonts", "wgpu", "glow", "accesskit", "wayland", "x11"] }
egui = { workspace = true }
serde_json = { workspace = true }
chrono = { workspace = true }
# probing the window's renderer before it opens (`window_gpu`)
pollster = { workspace = true }
log = { workspace = true }
+40 -18
View File
@@ -113,28 +113,36 @@ fn matches(name: &str, target: &str) -> bool {
}
/// `2026-10-08T07:59:17.728Z` (UTC, milliseconds). A clock before 1970 reads as the epoch.
/// A clock far past chrono's year range still converts (the calendar repeats every 400 years), so
/// logging never panics and never silently resets to the epoch for a convertible instant.
pub fn timestamp(t: SystemTime) -> String {
let since = t.duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
let secs = since.as_secs();
let (days, rem) = (secs / 86_400, secs % 86_400);
let (y, m, d) = civil_from_days(days);
format!("{y:04}-{m:02}-{d:02}T{:02}:{:02}:{:02}.{:03}Z", rem / 3600, rem % 3600 / 60, rem % 60, since.subsec_millis())
// Pre-1970 is clamped to the epoch by `unwrap_or_default` above. `as_secs` is `u64`; a value
// past `i64::MAX` seconds saturates rather than falling back to the epoch.
let secs = i64::try_from(since.as_secs()).unwrap_or(i64::MAX);
let (y, t) = utc_parts(secs, since.subsec_nanos());
// `{:04}` rather than `%Y`, which writes a `+` before years past 9999.
format!("{y:04}-{}", t.format("%m-%dT%H:%M:%S%.3fZ"))
}
/// Days since 1970-01-01 to a proleptic Gregorian (year, month, day) (Howard Hinnant's
/// `civil_from_days`, unsigned because `timestamp` clamps at the epoch). Saturating, so no clock
/// value can overflow it.
fn civil_from_days(days: u64) -> (u64, u64, u64) {
let z = days.saturating_add(719_468);
let era = z / 146_097;
let doe = z % 146_097;
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = era.saturating_mul(400).saturating_add(yoe).saturating_add(u64::from(m <= 2));
(y, m, d)
/// Unix seconds (+ nanos) → (proleptic Gregorian year, the same wall-clock instant moved into
/// chrono's range by whole 400-year cycles). The calendar repeats every 400 years (146_097 days),
/// so month, day and time of day come from chrono and only the year is carried, which keeps every
/// `i64` exact. Replaces the old hand-rolled `civil_from_days`; chrono is the only calendar logic.
fn utc_parts(secs: i64, nanos: u32) -> (i64, chrono::NaiveDateTime) {
use chrono::Datelike;
let (days, sod) = (secs.div_euclid(86_400), secs.rem_euclid(86_400));
let (cycles, rest) = (days.div_euclid(146_097), days.rem_euclid(146_097));
// `rest + 719_163` (1970-01-01 is CE day 719_163) is always a valid CE day, and `sod` < 86_400.
let date = i32::try_from(rest + 719_163).ok().and_then(chrono::NaiveDate::from_num_days_from_ce_opt);
let time = u32::try_from(sod).ok().and_then(|s| chrono::NaiveTime::from_num_seconds_from_midnight_opt(s, nanos.min(999_999_999)));
match (date, time) {
(Some(d), Some(t)) => {
let y = i128::from(d.year()) + i128::from(cycles) * 400;
(i64::try_from(y).unwrap_or(if y < 0 { i64::MIN } else { i64::MAX }), d.and_time(t))
}
_ => (1970, chrono::NaiveDateTime::default()), // unreachable
}
}
fn numbered(dir: &Path, n: usize) -> PathBuf {
@@ -483,6 +491,20 @@ mod tests {
assert_eq!(timestamp(UNIX_EPOCH + Duration::from_secs(4_107_542_400)), "2100-03-01T00:00:00.000Z");
// Before the epoch (a clock set wrong) clamps instead of panicking.
assert_eq!(timestamp(UNIX_EPOCH - Duration::from_secs(5)), "1970-01-01T00:00:00.000Z");
// Past chrono's year range still converts via the 400-year cycle (the old code did too,
// through `civil_from_days`): never a silent reset to the epoch. Platform-safe: Windows'
// SystemTime can't hold this instant, so it is only checked where it exists.
// `u64::MAX / 2` seconds == `i64::MAX` seconds → year 292277026596 (old output).
if let Some(far) = UNIX_EPOCH.checked_add(Duration::from_secs(u64::MAX / 2)) {
assert_eq!(timestamp(far), "292277026596-12-04T15:30:07.000Z");
}
// Years past 9999 keep the plain zero-padded year (no `+`).
assert_eq!(timestamp(UNIX_EPOCH + Duration::from_secs(253_402_300_800)), "10000-01-01T00:00:00.000Z");
// 10_000_000_000_000 s is far beyond chrono's range; old output is year 318857.
// Windows' SystemTime holds only up to about year 30828, so this is checked where it fits.
if let Some(far) = UNIX_EPOCH.checked_add(Duration::from_secs(10_000_000_000_000)) {
assert_eq!(timestamp(far), "318857-05-20T17:46:40.000Z");
}
}
#[test]
+1
View File
@@ -17,6 +17,7 @@ serde_json = { workspace = true }
thiserror = { workspace = true }
crc32fast = { workspace = true }
log = { workspace = true }
chrono = { workspace = true }
web-time = { workspace = true }
[dev-dependencies]
+29 -20
View File
@@ -2,6 +2,7 @@
//! weekday and month names, shifting a time by an offset, and grouping a photo list into runs of
//! the same day / month / year (the date headers of the photo grid).
use chrono::Datelike;
use serde::{Deserialize, Serialize};
pub use crate::stacks::iso_seconds;
@@ -11,32 +12,32 @@ pub const WEEKDAYS: [&str; 7] = ["Monday", "Tuesday", "Wednesday", "Thursday", "
pub const MONTHS: [&str; 12] =
["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
/// Unix seconds → `YYYY-MM-DDTHH:MM:SS` (proleptic Gregorian; H. Hinnant's algorithm).
/// Unix seconds → `YYYY-MM-DDTHH:MM:SS` (UTC, proleptic Gregorian). A time outside chrono's year
/// range still converts: the calendar repeats every 400 years, so the instant is moved into range
/// by whole cycles and only the year is carried (matching the old `civil_from_days`), rather than
/// resetting to the epoch. The callers here stay well inside years 0000–9999.
pub fn civil(secs: i64) -> String {
let days = secs.div_euclid(86_400);
let rem = secs.rem_euclid(86_400);
let (y, m, d) = civil_from_days(days);
format!("{y:04}-{m:02}-{d:02}T{:02}:{:02}:{:02}", rem / 3600, rem % 3600 / 60, rem % 60)
}
/// Days since 1970-01-01 → (year, month 1..=12, day 1..=31).
pub fn civil_from_days(days: i64) -> (i64, i64, i64) {
let z = days + 719_468;
let era = z.div_euclid(146_097);
let doe = z - era * 146_097;
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
(yoe + era * 400 + i64::from(m <= 2), m, d)
let (days, sod) = (secs.div_euclid(86_400), secs.rem_euclid(86_400));
let (cycles, rest) = (days.div_euclid(146_097), days.rem_euclid(146_097));
// `rest + 719_163` (1970-01-01 is CE day 719_163) is always a valid CE day, and `sod` < 86_400.
let date = i32::try_from(rest + 719_163).ok().and_then(chrono::NaiveDate::from_num_days_from_ce_opt);
let time = u32::try_from(sod).ok().and_then(|s| chrono::NaiveTime::from_num_seconds_from_midnight_opt(s, 0));
match (date, time) {
(Some(d), Some(t)) => {
let y = i128::from(d.year()) + i128::from(cycles) * 400;
let y = i64::try_from(y).unwrap_or(if y < 0 { i64::MIN } else { i64::MAX });
// `{:04}` rather than `%Y`, which writes a `+` before years past 9999 (the old `{y:04}` did not).
format!("{y:04}-{}", d.and_time(t).format("%m-%dT%H:%M:%S"))
}
_ => "1970-01-01T00:00:00".to_string(), // unreachable
}
}
/// Weekday name of an ISO date (`None` if it doesn't parse).
pub fn weekday(iso: &str) -> Option<&'static str> {
let days = iso_seconds(iso.get(..10)?)?.div_euclid(86_400);
// 1970-01-01 was a Thursday
Some(WEEKDAYS[(days + 3).rem_euclid(7) as usize])
let date = chrono::NaiveDate::from_num_days_from_ce_opt(i32::try_from(days.checked_add(719_163)?).ok()?)?;
Some(WEEKDAYS[date.weekday().num_days_from_monday() as usize])
}
/// Shift an ISO time by `secs`, keeping anything after the seconds (fractions, zone) as it was.
@@ -199,6 +200,14 @@ mod tests {
fn calendar() {
assert_eq!(civil(0), "1970-01-01T00:00:00");
assert_eq!(civil(951_782_400), "2000-02-29T00:00:00");
// A year past 9999 keeps the plain zero-padded year (no `+`), as the old `{y:04}` did.
assert_eq!(civil(253_402_300_800), "10000-01-01T00:00:00");
// Beyond chrono's year range the old `civil_from_days` still converted; pin that output
// (never the epoch) in both directions and at the i64 extremes.
assert_eq!(civil(10_000_000_000_000), "318857-05-20T17:46:40");
assert_eq!(civil(-10_000_000_000_000), "-314918-08-13T06:13:20");
assert_eq!(civil(i64::MAX), "292277026596-12-04T15:30:07");
assert_eq!(civil(i64::MIN), "-292277022657-01-27T08:29:52");
assert_eq!(weekday("2026-09-30"), Some("Wednesday"));
assert_eq!(weekday("2026-10-01T09:00:00"), Some("Thursday"));
assert_eq!(weekday("2000-02-29"), Some("Tuesday"));
+6 -8
View File
@@ -299,14 +299,12 @@ pub fn iso_seconds(s: &str) -> Option<i64> {
if hh >= 24 || mm >= 60 || ss >= 60 {
return None;
}
// days from civil (proleptic Gregorian)
let y2 = if m <= 2 { y - 1 } else { y };
let era = y2.div_euclid(400);
let yoe = y2 - era * 400;
let mp = (m + 9) % 12;
let doy = (153 * mp + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
let days = era * 146_097 + doe - 719_468;
// Days since 1970-01-01 (proleptic Gregorian). A day past the month's length rolls over the
// way the old Hinnant arithmetic did (e.g. 2026-02-30 → 2026-03-02), which `normalize_iso`
// relies on to reject impossible dates.
let first = chrono::NaiveDate::from_ymd_opt(i32::try_from(y).ok()?, u32::try_from(m).ok()?, 1)?;
let date = first.checked_add_days(chrono::Days::new(u64::try_from(d - 1).ok()?))?;
let days = (date - chrono::NaiveDate::from_ymd_opt(1970, 1, 1)?).num_days();
Some(days * 86_400 + hh * 3600 + mm * 60 + ss)
}
+2
View File
@@ -46,6 +46,8 @@ lightcraft-segment = { workspace = true, optional = true }
# model downloads run natively only (the web build fetches no models)
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
lightcraft-fetch = { workspace = true }
# capture-time parsing for face recognition (native only)
chrono = { workspace = true }
[features]
# AI masks with SAM 3 (Object / Describe). Off for the web and CLI builds.
+6 -7
View File
@@ -509,13 +509,12 @@ mod imp {
if !(1..=12).contains(&m) || !(1..=31).contains(&d) {
return None;
}
// days from civil (proleptic Gregorian)
let y = if m <= 2 { y - 1 } else { y };
let era = y.div_euclid(400);
let yoe = y - era * 400;
let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
Some((era * 146_097 + doe - 719_468) * 86_400 + hh * 3600 + mm * 60 + ss)
// Days since 1970-01-01 (proleptic Gregorian); a day past the month's length rolls over as
// the old Hinnant arithmetic did.
let first = chrono::NaiveDate::from_ymd_opt(i32::try_from(y).ok()?, u32::try_from(m).ok()?, 1)?;
let date = first.checked_add_days(chrono::Days::new(u64::try_from(d - 1).ok()?))?;
let days = (date - chrono::NaiveDate::from_ymd_opt(1970, 1, 1)?).num_days();
Some(days * 86_400 + hh * 3600 + mm * 60 + ss)
}
/// `faces.evaluate`: leave-one-out over the library's named faces, to see how well recognition works on *your*
+1
View File
@@ -13,6 +13,7 @@ lightcraft-tiff = { workspace = true }
lightcraft-geom = { workspace = true }
serde = { workspace = true }
quick-xml = { workspace = true }
chrono = { workspace = true }
[dev-dependencies]
proptest = { workspace = true }
+10 -22
View File
@@ -28,21 +28,10 @@ impl DateTime {
// A timestamp must represent a real Gregorian calendar day, not just
// numbers in the range 1..=31. Invalid dates would otherwise be
// accepted from EXIF/XMP and silently normalized during time arithmetic.
let leap = self.year.is_multiple_of(4) && (!self.year.is_multiple_of(100) || self.year.is_multiple_of(400));
let max_day = match self.month {
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
4 | 6 | 9 | 11 => 30,
2 if leap => 29,
2 => 28,
_ => 0,
};
let ok = self.day >= 1
&& self.day <= max_day
&& self.hour < 24
&& self.minute < 60
&& self.second < 61
&& self.year > 0
&& self.offset_minutes.is_none_or(|o| o.abs() <= 18 * 60);
// chrono rejects impossible days (leap years and all); the time is checked
// by hand so a leap second (`second == 60`) stays acceptable.
let day_ok = self.year > 0 && chrono::NaiveDate::from_ymd_opt(i32::from(self.year), u32::from(self.month), u32::from(self.day)).is_some();
let ok = day_ok && self.hour < 24 && self.minute < 60 && self.second < 61 && self.offset_minutes.is_none_or(|o| o.abs() <= 18 * 60);
ok.then_some(self)
}
@@ -153,13 +142,12 @@ impl DateTime {
/// Seconds since 1970-01-01T00:00:00 of the civil time (UTC when the offset is known, else "local" seconds).
pub fn unix_seconds(&self) -> i64 {
// days from civil (proleptic Gregorian), Howard Hinnant's public-domain algorithm
let (y, m, d) = (self.year as i64 - if self.month <= 2 { 1 } else { 0 }, self.month as i64, self.day as i64);
let era = y.div_euclid(400);
let yoe = y - era * 400;
let doy = (153 * (m + if m > 2 { -3 } else { 9 }) + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
let days = era * 146097 + doe - 719468;
// Every `DateTime` reaching here is a real calendar day (`valid()` ran in the parsers);
// an out-of-range one (built by hand) reads as the epoch rather than panicking.
let days = chrono::NaiveDate::from_ymd_opt(i32::from(self.year), u32::from(self.month), u32::from(self.day))
.zip(chrono::NaiveDate::from_ymd_opt(1970, 1, 1))
.map(|(d, epoch)| (d - epoch).num_days())
.unwrap_or(0);
days * 86400 + self.hour as i64 * 3600 + self.minute as i64 * 60 + self.second as i64 - self.offset_minutes.unwrap_or(0) as i64 * 60
}
}
+1 -1
View File
@@ -114,7 +114,7 @@ if has appimage; then
if [ -z "$TOOL" ]; then
TOOL="$CARGO_TARGET_DIR/appimagetool-$ARCH.AppImage"
if [ ! -x "$TOOL" ]; then
curl -fsSL -o "$TOOL" "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$ARCH.AppImage"
curl --proto '=https' -fsSL -o "$TOOL" "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$ARCH.AppImage"
chmod +x "$TOOL"
fi
fi
+2 -2
View File
@@ -83,13 +83,13 @@ BASE="https://huggingface.co/$REPO/resolve/main"
fetch() {
set -- "$1" "$2" "${3:-}"
if [ -n "$TOKEN" ]; then auth="Authorization: Bearer $TOKEN"; else auth="X-No-Auth: 1"; fi
code=$(curl -fsSL ${3:+-C -} -H "$auth" -w '%{http_code}' -o "$2" "$BASE/$1") || true
code=$(curl --proto '=https' -fsSL ${3:+-C -} -H "$auth" -w '%{http_code}' -o "$2" "$BASE/$1") || true
case "$code" in
200|206) return 0 ;;
416)
echo "the partial download of $1 doesn't match the server's; starting over" >&2
rm -f "$2"
code=$(curl -fsSL -H "$auth" -w '%{http_code}' -o "$2" "$BASE/$1") || true
code=$(curl --proto '=https' -fsSL -H "$auth" -w '%{http_code}' -o "$2" "$BASE/$1") || true
[ "$code" = 200 ] && return 0
echo "download of $REPO/$1 failed ($code)" >&2; return 1 ;;
401|403) echo "access denied to $REPO/$1 (has your request for facebook/sam3 been approved?)" >&2; return 1 ;;