Major changes since last commit: - FiveSql2 SQL:1999 engine (10,458 LOC) — 43/43 ALL PASS - 21 compiler/runtime bugs fixed (short-circuit AND/OR, FOR LOOP, etc.) - @byref pass-by-reference via RefCell pattern - Mutable closure capture (EnsureLocalRef + RefCell sharing) - RTL: 400 → 479 functions (+79: file, string, datetime, hash, UTF-8) - DateTime/Timestamp fully working (hb_DateTime, hb_Hour/Min/Sec, display) - Reserved word guard (39 keywords blocked from function calls) - AEval arg order fix (element before index) - Closure capture redecl fix (unique _cap_ names per block) - Hash/string indexing in ArrayPush/ArrayPop - Harbour compat test suite: 51/51 - 4 docs: Porting Report, Implementation Plan, Optimization Plan, Commercialization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
214 lines
5.7 KiB
Go
214 lines
5.7 KiB
Go
// 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)
|
|
}
|