obrimbasecli/essential/visible/service/helper/datetime/datetime.go

330 lines
7.3 KiB
Go

/*
|--------------------------------------------------------------------------
| Metadata
|--------------------------------------------------------------------------
|
| Name:
| - Datetime Utility
|
| Purpose:
| - Retrieve the current date and time from either the host system
| clock or a trusted framework-managed NTP source and return the
| result using a framework-supported date/time format pattern.
|
| Guideline:
| - Use ObrimDatetime() as the primary entry point.
| - Always provide a valid datetime type.
| - Supported types are "local" and "trusted".
| - Always provide the "format" configuration key.
| - Use only framework-supported format patterns.
| - Expect all successful results to be returned as strings.
| - Use "local" when host system time is sufficient.
| - Use "trusted" when synchronized network time is required.
| - Handle both success and failure response codes.
|
| Example:
| - ObrimDatetime(
| "local",
| map[string]any{
| "format": "yyyy-MM-dd",
| },
| )
|
| - ObrimDatetime(
| "trusted",
| map[string]any{
| "format": "yyyy-MM-dd HH:mm:ss z",
| },
| )
|
|--------------------------------------------------------------------------
*/
/*
|--------------------------------------------------------------------------
| Credit
|--------------------------------------------------------------------------
|
| Contributor:
| - Rajon Ahmed
| - Scionite
|
|--------------------------------------------------------------------------
*/
package datetime
import (
"encoding/binary"
"fmt"
"log"
"net"
"strconv"
"time"
)
const (
obrimDatetimeTypeLocal = "local"
obrimDatetimeTypeTrusted = "trusted"
ObrimDatetimeCodeSuccessRetrieved = "SUCCESS_DATETIME_RETRIEVED"
ObrimDatetimeCodeFailedInvalidType = "FAILED_INVALID_TYPE"
ObrimDatetimeCodeFailedInvalidFormat = "FAILED_INVALID_FORMAT"
ObrimDatetimeCodeFailedTrustedTimeRetrieval = "FAILED_TRUSTED_TIME_RETRIEVAL"
)
const obrimDatetimeTrustedNtpPool = "pool.ntp.org:123"
// ObrimDatetimePayload defines the success payload structure.
type ObrimDatetimePayload struct {
Value string `json:"value"`
}
// ObrimDatetimeResponse defines the utility response structure.
type ObrimDatetimeResponse struct {
Status bool `json:"status"`
Code string `json:"code"`
Payload *ObrimDatetimePayload `json:"payload"`
}
// obrimDatetimePatternMap stores framework-supported format mappings.
var obrimDatetimePatternMap = map[string]string{
"yyyy-MM-dd": "2006-01-02",
"MMM dd, yyyy": "Jan 02, 2006",
"MMMM dd, yyyy": "January 02, 2006",
"EEE, MMM dd, yyyy": "Mon, Jan 02, 2006",
"EEE, MMMM dd, yyyy": "Mon, January 02, 2006",
"EEEE, MMM dd, yyyy": "Monday, Jan 02, 2006",
"EEEE, MMMM dd, yyyy": "Monday, January 02, 2006",
"HH:mm:ss": "15:04:05",
"hh:mm:ss a": "03:04:05 PM",
"yyyy-MM-dd HH:mm:ss": "2006-01-02 15:04:05",
"yyyy-MM-dd HH:mm:ss z": "2006-01-02 15:04:05 MST",
"yyyy-MM-dd'T'HH:mm:ssXXX": time.RFC3339,
}
// ObrimDatetime retrieves and formats current date and time.
func ObrimDatetime(
datetimeType string,
config map[string]any,
) map[string]any {
log.Println("[datetime] initialization started")
format, code := obrimDatetimeValidate(datetimeType, config)
if code != "" {
log.Printf("[datetime] validation failed: %s", code)
return obrimDatetimeResponse(
false,
code,
"",
)
}
var datetimeValue time.Time
var err error
switch datetimeType {
case obrimDatetimeTypeLocal:
datetimeValue = obrimDatetimeLocal()
case obrimDatetimeTypeTrusted:
datetimeValue, err = obrimDatetimeTrusted()
if err != nil {
log.Printf(
"[datetime] trusted time retrieval failed: %v",
err,
)
return obrimDatetimeResponse(
false,
ObrimDatetimeCodeFailedTrustedTimeRetrieval,
"",
)
}
}
formattedValue := obrimDatetimeFormatApply(
datetimeValue,
format,
)
log.Println("[datetime] datetime retrieved successfully")
return obrimDatetimeResponse(
true,
ObrimDatetimeCodeSuccessRetrieved,
formattedValue,
)
}
// obrimDatetimeValidate validates utility inputs.
func obrimDatetimeValidate(
datetimeType string,
config map[string]any,
) (string, string) {
switch datetimeType {
case obrimDatetimeTypeLocal:
case obrimDatetimeTypeTrusted:
default:
return "", ObrimDatetimeCodeFailedInvalidType
}
formatValue, exists := config["format"]
if !exists {
return "", ObrimDatetimeCodeFailedInvalidFormat
}
format, ok := formatValue.(string)
if !ok || format == "" {
return "", ObrimDatetimeCodeFailedInvalidFormat
}
if !obrimDatetimePatternValidate(format) {
return "", ObrimDatetimeCodeFailedInvalidFormat
}
return format, ""
}
// obrimDatetimePatternValidate validates supported patterns.
func obrimDatetimePatternValidate(pattern string) bool {
switch pattern {
case "timestampsec":
return true
case "timestampmil":
return true
}
_, exists := obrimDatetimePatternMap[pattern]
return exists
}
// obrimDatetimePatternResolve resolves pattern mappings.
func obrimDatetimePatternResolve(
pattern string,
) (string, bool) {
layout, exists := obrimDatetimePatternMap[pattern]
return layout, exists
}
// obrimDatetimeFormatApply formats datetime values.
func obrimDatetimeFormatApply(
datetimeValue time.Time,
pattern string,
) string {
switch pattern {
case "timestampsec":
return strconv.FormatInt(
datetimeValue.Unix(),
10,
)
case "timestampmil":
return strconv.FormatInt(
datetimeValue.UnixMilli(),
10,
)
}
layout, _ := obrimDatetimePatternResolve(pattern)
return datetimeValue.Format(layout)
}
// obrimDatetimeResponse builds utility responses.
func obrimDatetimeResponse(
status bool,
code string,
value string,
) map[string]any {
if !status {
return map[string]any{
"status": false,
"code": code,
"payload": nil,
}
}
return map[string]any{
"status": true,
"code": code,
"payload": map[string]any{
"value": value,
},
}
}
// obrimDatetimeLocal retrieves host system time.
func obrimDatetimeLocal() time.Time {
return time.Now()
}
// obrimDatetimeTrusted retrieves trusted NTP time.
func obrimDatetimeTrusted() (time.Time, error) {
return obrimDatetimeTrustedSync()
}
// obrimDatetimeTrustedSync synchronizes trusted time from NTP.
func obrimDatetimeTrustedSync() (time.Time, error) {
connection, err := net.DialTimeout(
"udp",
obrimDatetimeTrustedNtpPool,
5*time.Second,
)
if err != nil {
return time.Time{}, err
}
defer connection.Close()
var request = make([]byte, 48)
request[0] = 0x1B
if _, err = connection.Write(request); err != nil {
return time.Time{}, err
}
if err = connection.SetDeadline(
time.Now().Add(5 * time.Second),
); err != nil {
return time.Time{}, err
}
var response = make([]byte, 48)
if _, err = connection.Read(response); err != nil {
return time.Time{}, err
}
var ntpSeconds = binary.BigEndian.Uint32(
response[40:44],
)
var ntpFraction = binary.BigEndian.Uint32(
response[44:48],
)
const ntpEpochOffset = 2208988800
var unixSeconds = int64(ntpSeconds) - ntpEpochOffset
var nanoseconds = (int64(ntpFraction) * 1e9) >> 32
if unixSeconds < 0 {
return time.Time{}, fmt.Errorf(
"invalid ntp timestamp",
)
}
return time.Unix(
unixSeconds,
nanoseconds,
).UTC(), nil
}