// Copyright (c) 2026 Charles KWON OhJun. All rights reserved. // Extended date/time RTL functions. package hbrtl import ( "five/hbrt" "fmt" "strings" "time" ) // HB_DATE(nYear, nMonth, nDay) → dDate func HbDate(t *hbrt.Thread) { t.Frame(3, 0); defer t.EndProc() y := t.Local(1).AsInt() m := t.Local(2).AsInt() d := t.Local(3).AsInt() t.RetVal(hbrt.MakeDate(dateToJulian(int(y), int(m), int(d)))) } // HB_CTOD(cDate [, cFormat]) → dDate (extended CTOD with format) func HbCToD(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0); defer t.EndProc() s := strings.TrimSpace(t.Local(1).AsString()) if s == "" { t.RetVal(hbrt.MakeDate(0)); return } // Default format: "YYYY-MM-DD" or "YYYYMMDD" var gt time.Time var err error for _, layout := range []string{"2006-01-02", "20060102", "01/02/2006", "02/01/2006", "2006.01.02"} { gt, err = time.Parse(layout, s) if err == nil { break } } if err != nil { t.RetVal(hbrt.MakeDate(0)); return } y, m, d := gt.Date() t.RetVal(hbrt.MakeDate(dateToJulian(y, int(m), d))) } // HB_DTOC(dDate [, cFormat]) → cString (extended DTOC with format) func HbDToC(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0); defer t.EndProc() j := t.Local(1).AsJulian() if j == 0 { t.RetString(""); return } y, m, d := julianToDate(j) gt := time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Local) layout := "01/02/2006" if nParams >= 2 && !t.Local(2).IsNil() { layout = harbourFmtToGo(t.Local(2).AsString()) } t.RetString(gt.Format(layout)) } // HB_STOD(cYYYYMMDD) → dDate (alias — STOD already exists, this is hb_ prefixed) func HbSToD(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() s := t.Local(1).AsString() if len(s) < 8 { t.RetVal(hbrt.MakeDate(0)); return } gt, err := time.Parse("20060102", s[:8]) if err != nil { t.RetVal(hbrt.MakeDate(0)); return } y, m, d := gt.Date() t.RetVal(hbrt.MakeDate(dateToJulian(y, int(m), d))) } // HB_DTOT(dDate) → tTimestamp (Date → Timestamp at midnight) func HbDToT(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetVal(hbrt.MakeTimestamp(t.Local(1).AsJulian(), 0)) } // HB_TTOD(tTimestamp) → dDate (Timestamp → Date, discard time) func HbTToD(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetVal(hbrt.MakeDate(t.Local(1).AsJulian())) } // HB_TTOHOUR(tTS) → nHour func HbTToHour(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() ms := t.Local(1).AsTimeMs() t.RetInt(int64(ms / 3600000)) } // HB_TTOMIN(tTS) → nMinute func HbTToMin(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() ms := t.Local(1).AsTimeMs() t.RetInt(int64(ms / 60000 % 60)) } // HB_TTOSEC(tTS) → nSecond func HbTToSec(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() ms := t.Local(1).AsTimeMs() t.RetInt(int64(ms / 1000 % 60)) } // HB_TTOMSEC(tTS) → nMillisecond func HbTToMsec(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() ms := t.Local(1).AsTimeMs() t.RetInt(int64(ms % 1000)) } // HB_TTON(tTS) → nSeconds (seconds since midnight) func HbTToN(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() ms := t.Local(1).AsTimeMs() t.RetVal(hbrt.MakeDoubleAuto(float64(ms) / 1000.0)) } // HB_NTOT(dDate, nSeconds) → tTimestamp func HbNToT(t *hbrt.Thread) { t.Frame(2, 0); defer t.EndProc() j := t.Local(1).AsJulian() sec := t.Local(2).AsNumDouble() ms := int32(sec * 1000) t.RetVal(hbrt.MakeTimestamp(j, ms)) } // HB_NTOHOUR(nSeconds) → nHour func HbNToHour(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetInt(int64(t.Local(1).AsNumDouble()) / 3600) } // HB_NTOMIN(nSeconds) → nMinute func HbNToMin(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetInt(int64(t.Local(1).AsNumDouble()) / 60 % 60) } // HB_NTOSEC(nSeconds) → nSecond (mod 60) func HbNToSec(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetInt(int64(t.Local(1).AsNumDouble()) % 60) } // HB_WEEK(dDate) → nWeek (ISO week number) func HbWeek(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() j := t.Local(1).AsJulian() if j == 0 { t.RetInt(0); return } y, m, d := julianToDate(j) gt := time.Date(y, time.Month(m), d, 0, 0, 0, 0, time.Local) _, w := gt.ISOWeek() t.RetInt(int64(w)) } // HB_CDAY(nDow) → cDayName (1=Sunday) func HbCDay(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() n := t.Local(1).AsInt() days := []string{"", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} if n >= 1 && n <= 7 { t.RetString(days[n]) } else { t.RetString("") } } // DAYS(nSeconds) → nDays func Days(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetInt(int64(t.Local(1).AsNumDouble()) / 86400) } // ELAPTIME(cStart, cEnd) → cElapsed "HH:MM:SS" func ElapTime(t *hbrt.Thread) { t.Frame(2, 0); defer t.EndProc() s1 := parseHMS(t.Local(1).AsString()) s2 := parseHMS(t.Local(2).AsString()) diff := s2 - s1 if diff < 0 { diff += 86400 } h := diff / 3600; m := diff / 60 % 60; s := diff % 60 t.RetString(fmt.Sprintf("%02d:%02d:%02d", h, m, s)) } // AMPM(cTime) → cTime12h ("13:30" → "01:30 PM") func AMPM(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() s := t.Local(1).AsString() sec := parseHMS(s) h := sec / 3600 m := sec / 60 % 60 sc := sec % 60 suffix := "AM" if h >= 12 { suffix = "PM" } if h > 12 { h -= 12 } if h == 0 { h = 12 } t.RetString(fmt.Sprintf("%02d:%02d:%02d %s", h, m, sc, suffix)) } // SECS(cTime) → nSeconds ("01:30:00" → 5400) func Secs(t *hbrt.Thread) { t.Frame(1, 0); defer t.EndProc() t.RetInt(int64(parseHMS(t.Local(1).AsString()))) } // --- helpers --- func parseHMS(s string) int { s = strings.TrimSpace(s) var h, m, sc int fmt.Sscanf(s, "%d:%d:%d", &h, &m, &sc) return h*3600 + m*60 + sc } func harbourFmtToGo(hFmt string) string { r := strings.NewReplacer( "YYYY", "2006", "YY", "06", "MM", "01", "DD", "02", "HH", "15", "mm", "04", "SS", "05", ) return r.Replace(hFmt) }