Setting the file. One moment.
Subchapter 46.4
scripts/date-utils.sh
Shell49 lines2 KB
# shellcheck shell=bash
# Portable date helpers shared by the daily-recap scripts. Sourced, not executed.
#
# Each helper tries GNU date syntax first and falls back to BSD date (macOS), so the
# scripts behave the same on Linux, macOS, and macOS with GNU coreutils on PATH.
# Local-time helpers honor the standard TZ environment variable.
# local_to_epoch "YYYY-MM-DD HH:MM:SS" -> epoch seconds, interpreting the input in local time.
# A time inside a spring-forward gap does not exist on the wall clock. BSD date reads it
# with the UTC offset in effect before the gap, so 02:30 becomes 03:30 after the change.
# GNU date rejects it, so the last fallback applies the previous day's offset to match.
local_to_epoch() {
date -d "$1" +%s 2>/dev/null && return
date -j -f '%Y-%m-%d %H:%M:%S' "$1" +%s 2>/dev/null && return
local offset
offset=$(date -d "${1%% *} 12:00:00 yesterday" +%z 2>/dev/null) || return 1
date -d "$1 ${offset}" +%s 2>/dev/null
}
# utc_to_epoch "YYYY-MM-DDTHH:MM:SSZ" -> epoch seconds.
utc_to_epoch() {
date -u -d "$1" +%s 2>/dev/null || date -j -u -f '%Y-%m-%dT%H:%M:%SZ' "$1" +%s 2>/dev/null
}
# epoch_fmt EPOCH FORMAT -> EPOCH formatted in local time.
epoch_fmt() {
date -d "@$1" "+$2" 2>/dev/null || date -r "$1" "+$2" 2>/dev/null
}
# epoch_fmt_utc EPOCH FORMAT -> EPOCH formatted in UTC.
epoch_fmt_utc() {
date -u -d "@$1" "+$2" 2>/dev/null || date -u -r "$1" "+$2" 2>/dev/null
}
# add_days YYYY-MM-DD N -> the date N days later (N may be negative).
# Anchors on local noon so DST transitions never shift the result across a date line.
add_days() {
local epoch
epoch=$(local_to_epoch "$1 12:00:00") || return 1
epoch_fmt "$((epoch + $2 * 86400))" '%Y-%m-%d'
}
# is_valid_date YYYY-MM-DD -> success only for a real calendar date in that exact form.
# BSD date silently normalizes impossible dates (2026-02-30 becomes 2026-03-02), so the
# round trip through add_days catches what parsing alone accepts.
is_valid_date() {
[[ "$1" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || return 1
[[ "$(add_days "$1" 0 2>/dev/null)" == "$1" ]]
}