Why 1904?
Apple picked 1904-01-01 because it starts a clean leap-year cycle (unlike 1900, which wasn’t a leap year). It kept calendar math tidy for the classic Mac.
2,082,844,800 seconds (to/from 1970-01-01 UTC)HFS+ stores timestamps as whole seconds since the Mac epoch 1904-01-01T00:00:00Z. Compared to UNIX (1970-01-01T00:00:00Z), the exact gap is 2,082,844,800 seconds.
unix = hfs − 2,082,844,800hfs = unix + 2,082,844,8002^32 (≈ 2106-02-07 for UNIX).0x is accepted. Negative values supported.11 22 33 44 read little-endian, toggle swap.Copy-ready examples for converting between HFS+ seconds (since 1904-01-01 UTC) and human time.
let HFS_OFFSET: TimeInterval = 2_082_844_800 // seconds
func hfsToDate(_ hfs: TimeInterval) -> Date { Date(timeIntervalSince1970: hfs - HFS_OFFSET) }
func dateToHfs(_ date: Date) -> TimeInterval { date.timeIntervalSince1970 + HFS_OFFSET }
// Hex helper
func parseHexOrDec(_ s: String) -> Int64 {
let t = s.trimmingCharacters(in: .whitespacesAndNewlines)
if t.lowercased().hasPrefix("0x") { return Int64(t.dropFirst(2), radix:16)! }
if t.range(of: "[a-fA-F]", options:.regularExpression) != nil { return Int64(t, radix:16)! }
return Int64(t)!
}
static const NSTimeInterval kHFSOffset = 2082844800.0;
NSDate * HFSPlusToDate(long long hfs){ return [NSDate dateWithTimeIntervalSince1970:(hfs - kHFSOffset)]; }
long long DateToHFSPlus(NSDate *d){ return llround([d timeIntervalSince1970] + kHFSOffset); }
const HFS_OFFSET = 2082844800n; // seconds
function floorDiv(a,b){ const q=a/b, r=a%b; return r===0n || (a>=0n)===(b>=0n) ? q : q-1n; }
function parseIntFlexible(s){
s = s.trim();
if (/^-?0x[0-9a-f]+$/i.test(s)) return BigInt(s);
if (/^-?[0-9a-f]+$/i.test(s) && /[a-f]/i.test(s)) return BigInt((s[0]==='-'?'-':'') + '0x' + s.replace(/^-/,''));
if (/^-?\d+$/.test(s)) return BigInt(s);
throw new Error('bad int');
}
function hfsToDate(s){ const hfs = parseIntFlexible(s); const ms = (hfs - HFS_OFFSET) * 1000n; return new Date(Number(ms)); }
function dateToHfs(d=new Date()){ const ms=BigInt(d.getTime()); const sec=floorDiv(ms,1000n); return (sec + HFS_OFFSET).toString(); }
import datetime, re
HFS_OFFSET = 2_082_844_800
def parse_int_flexible(s:str)->int:
s=s.strip()
if s.lower().startswith('-0x'): return -int(s[3:],16)
if s.lower().startswith('0x'): return int(s[2:],16)
if re.search(r'[a-f]', s, re.I): return int(s,16)
return int(s,10)
def hfs_to_datetime(v:int)->datetime.datetime:
return datetime.datetime.fromtimestamp(v - HFS_OFFSET, tz=datetime.timezone.utc)
def datetime_to_hfs(dt:datetime.datetime)->int:
if dt.tzinfo is None: dt = dt.astimezone()
return int(dt.timestamp()) + HFS_OFFSET
#include <time.h> #include <stdint.h>
#define HFS_OFFSET 2082844800u
time_t hfs_to_unix(uint32_t hfs){ return (time_t)((uint32_t)hfs - HFS_OFFSET); }
uint32_t unix_to_hfs(time_t unix){ return (uint32_t)(unix + HFS_OFFSET); }
If a 4-byte hex was read little-endian (e.g., 44 33 22 11) but HFS+ expects big-endian, swap to 11223344 before converting.
Apple picked 1904-01-01 because it starts a clean leap-year cycle (unlike 1900, which wasn’t a leap year). It kept calendar math tidy for the classic Mac.
The Unix ↔ HFS+ offset is 2,082,844,800 seconds, or 0x7C25B080. Subtract/add it to jump between epochs.
A 32-bit unsigned HFS+ counter rolls over on 2040-02-06 06:28:16 UTC. That’s the HFS+ cousin to the 2038 problem.
The original Macintosh launch day 1984-01-24 equals 2526595200 HFS+ seconds (0x9698C880), a handy sanity check.
HFS+ stores timestamps big-endian. Accidentally reading 11223344 little-endian flips it to 0x44332211—a completely different year.