Compare commits
	
		
			6 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 09932046f8 | |||
| 37e52595a2 | |||
| 95d7c90492 | |||
| 23a3235c7e | |||
| 506d276962 | |||
| 2a0cf84416 | 
| @@ -183,6 +183,10 @@ func (id {{.Name}}) CheckString() string { | |||||||
| 	return getCheckString(prefix{{.Name}}, string(id)) | 	return getCheckString(prefix{{.Name}}, string(id)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (id {{.Name}}) IsZero() bool { | ||||||
|  | 	return id == "" | ||||||
|  | } | ||||||
|  |  | ||||||
| func (id {{.Name}}) Regex() rext.Regex { | func (id {{.Name}}) Regex() rext.Regex { | ||||||
| 	return regex{{.Name}} | 	return regex{{.Name}} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,11 +6,11 @@ import ( | |||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"go/format" |  | ||||||
| 	"git.blackforestbytes.com/BlackForestBytes/goext" | 	"git.blackforestbytes.com/BlackForestBytes/goext" | ||||||
| 	"git.blackforestbytes.com/BlackForestBytes/goext/cryptext" | 	"git.blackforestbytes.com/BlackForestBytes/goext/cryptext" | ||||||
| 	"git.blackforestbytes.com/BlackForestBytes/goext/langext" | 	"git.blackforestbytes.com/BlackForestBytes/goext/langext" | ||||||
| 	"git.blackforestbytes.com/BlackForestBytes/goext/rext" | 	"git.blackforestbytes.com/BlackForestBytes/goext/rext" | ||||||
|  | 	"go/format" | ||||||
| 	"io" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
| @@ -46,7 +46,7 @@ var rexEnumPackage = rext.W(regexp.MustCompile(`^package\s+(?P<name>[A-Za-z0-9_] | |||||||
|  |  | ||||||
| var rexEnumDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*//\s*(@enum:type).*$`)) | var rexEnumDef = rext.W(regexp.MustCompile(`^\s*type\s+(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*//\s*(@enum:type).*$`)) | ||||||
|  |  | ||||||
| var rexEnumValueDef = rext.W(regexp.MustCompile(`^\s*(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*=\s*(?P<value>("[A-Za-z0-9_:\s\-.]*"|[0-9]+))\s*(//(?P<comm>.*))?.*$`)) | var rexEnumValueDef = rext.W(regexp.MustCompile(`^\s*(?P<name>[A-Za-z0-9_]+)\s+(?P<type>[A-Za-z0-9_]+)\s*=\s*(?P<value>("[@A-Za-z0-9_:\s\-.]*"|[0-9]+))\s*(//(?P<comm>.*))?.*$`)) | ||||||
|  |  | ||||||
| var rexEnumChecksumConst = rext.W(regexp.MustCompile(`const ChecksumEnumGenerator = "(?P<cs>[A-Za-z0-9_]*)"`)) | var rexEnumChecksumConst = rext.W(regexp.MustCompile(`const ChecksumEnumGenerator = "(?P<cs>[A-Za-z0-9_]*)"`)) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -45,6 +45,10 @@ func (i {{.Name}}) AsAnyPtr() *{{$.AnyDef.Name}} { | |||||||
| } | } | ||||||
| {{end}} | {{end}} | ||||||
|  |  | ||||||
|  | func (i {{.Name}}) IsZero() bool { | ||||||
|  | 	return i == "" | ||||||
|  | } | ||||||
|  |  | ||||||
| func New{{.Name}}() {{.Name}} { | func New{{.Name}}() {{.Name}} { | ||||||
| 	return {{.Name}}(primitive.NewObjectID().Hex()) | 	return {{.Name}}(primitive.NewObjectID().Hex()) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							| @@ -32,13 +32,13 @@ require ( | |||||||
| 	github.com/gin-contrib/sse v1.1.0 // indirect | 	github.com/gin-contrib/sse v1.1.0 // indirect | ||||||
| 	github.com/go-playground/locales v0.14.1 // indirect | 	github.com/go-playground/locales v0.14.1 // indirect | ||||||
| 	github.com/go-playground/universal-translator v0.18.1 // indirect | 	github.com/go-playground/universal-translator v0.18.1 // indirect | ||||||
| 	github.com/go-playground/validator/v10 v10.26.0 // indirect | 	github.com/go-playground/validator/v10 v10.27.0 // indirect | ||||||
| 	github.com/goccy/go-json v0.10.5 // indirect | 	github.com/goccy/go-json v0.10.5 // indirect | ||||||
| 	github.com/golang/snappy v1.0.0 // indirect | 	github.com/golang/snappy v1.0.0 // indirect | ||||||
| 	github.com/google/uuid v1.5.0 // indirect | 	github.com/google/uuid v1.5.0 // indirect | ||||||
| 	github.com/json-iterator/go v1.1.12 // indirect | 	github.com/json-iterator/go v1.1.12 // indirect | ||||||
| 	github.com/klauspost/compress v1.18.0 // indirect | 	github.com/klauspost/compress v1.18.0 // indirect | ||||||
| 	github.com/klauspost/cpuid/v2 v2.2.10 // indirect | 	github.com/klauspost/cpuid/v2 v2.2.11 // indirect | ||||||
| 	github.com/leodido/go-urn v1.4.0 // indirect | 	github.com/leodido/go-urn v1.4.0 // indirect | ||||||
| 	github.com/mattn/go-colorable v0.1.14 // indirect | 	github.com/mattn/go-colorable v0.1.14 // indirect | ||||||
| 	github.com/mattn/go-isatty v0.0.20 // indirect | 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||||
| @@ -48,7 +48,7 @@ require ( | |||||||
| 	github.com/pelletier/go-toml/v2 v2.2.4 // indirect | 	github.com/pelletier/go-toml/v2 v2.2.4 // indirect | ||||||
| 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | ||||||
| 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect | 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect | ||||||
| 	github.com/ugorji/go/codec v1.2.14 // indirect | 	github.com/ugorji/go/codec v1.3.0 // indirect | ||||||
| 	github.com/xdg-go/pbkdf2 v1.0.0 // indirect | 	github.com/xdg-go/pbkdf2 v1.0.0 // indirect | ||||||
| 	github.com/xdg-go/scram v1.1.2 // indirect | 	github.com/xdg-go/scram v1.1.2 // indirect | ||||||
| 	github.com/xdg-go/stringprep v1.0.4 // indirect | 	github.com/xdg-go/stringprep v1.0.4 // indirect | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
									
									
									
									
								
							| @@ -86,6 +86,8 @@ github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0 | |||||||
| github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= | github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= | ||||||
| github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= | github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= | ||||||
| github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= | github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= | ||||||
|  | github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= | ||||||
|  | github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= | ||||||
| github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= | github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= | ||||||
| github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= | github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= | ||||||
| github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= | github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= | ||||||
| @@ -126,6 +128,8 @@ github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kK | |||||||
| github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= | github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= | ||||||
| github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= | github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= | ||||||
| github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= | github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= | ||||||
|  | github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= | ||||||
|  | github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= | ||||||
| github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= | ||||||
| github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= | ||||||
| github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= | ||||||
| @@ -187,6 +191,8 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E | |||||||
| github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= | ||||||
| github.com/ugorji/go/codec v1.2.14 h1:yOQvXCBc3Ij46LRkRoh4Yd5qK6LVOgi0bYOXfb7ifjw= | github.com/ugorji/go/codec v1.2.14 h1:yOQvXCBc3Ij46LRkRoh4Yd5qK6LVOgi0bYOXfb7ifjw= | ||||||
| github.com/ugorji/go/codec v1.2.14/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= | github.com/ugorji/go/codec v1.2.14/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= | ||||||
|  | github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= | ||||||
|  | github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= | ||||||
| github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= | ||||||
| github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= | ||||||
| github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| package goext | package goext | ||||||
|  |  | ||||||
| const GoextVersion = "0.0.579" | const GoextVersion = "0.0.585" | ||||||
|  |  | ||||||
| const GoextVersionTimestamp = "2025-06-13T16:53:24+0200" | const GoextVersionTimestamp = "2025-07-04T11:46:00+0200" | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								langext/base64.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								langext/base64.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | package langext | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/base64" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // DecodeBase64Any decodes a base64 encoded string | ||||||
|  | // Works with all variants (std, url, imap), padded and unpadded and even ignores linrebreaks and indents | ||||||
|  | func DecodeBase64Any(data string) ([]byte, error) { | ||||||
|  | 	data = strings.ReplaceAll(data, "\n", "") // remove linebreaks and indents | ||||||
|  | 	data = strings.ReplaceAll(data, "\t", "") // remove linebreaks and indents | ||||||
|  | 	data = strings.ReplaceAll(data, " ", "")  // remove linebreaks and indents | ||||||
|  |  | ||||||
|  | 	data = strings.ReplaceAll(data, ",", "/") // base64_imap --> base64_std | ||||||
|  |  | ||||||
|  | 	data = strings.ReplaceAll(data, "_", "/") // base64_url --> base64_std | ||||||
|  | 	data = strings.ReplaceAll(data, "-", "+") // base64_url --> base64_std | ||||||
|  |  | ||||||
|  | 	data = strings.ReplaceAll(data, "=", "") // no padding | ||||||
|  |  | ||||||
|  | 	return base64.RawStdEncoding.DecodeString(data) | ||||||
|  | } | ||||||
							
								
								
									
										246
									
								
								langext/base64_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								langext/base64_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,246 @@ | |||||||
|  | package langext | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_StandardPadded(t *testing.T) { | ||||||
|  | 	input := "SGVsbG8gV29ybGQ=" // "Hello World" in standard Base64 (padded) | ||||||
|  | 	expected := "Hello World" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_StandardUnpadded(t *testing.T) { | ||||||
|  | 	input := "SGVsbG8gV29ybGQ" // "Hello World" in standard Base64 (unpadded) | ||||||
|  | 	expected := "Hello World" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_URLPadded(t *testing.T) { | ||||||
|  | 	input := "SGVsbG8tV29ybGQ=" // "Hello-World" in Base64 URL (padded) | ||||||
|  | 	expected := "Hello-World" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_URLUnpadded(t *testing.T) { | ||||||
|  | 	input := "SGVsbG8tV29ybGQ" // "Hello-World" in Base64 URL (unpadded) | ||||||
|  | 	expected := "Hello-World" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_IMAPPadded(t *testing.T) { | ||||||
|  | 	input := "SGVsbG8,V29ybGQ=" // "Hello/World" in Base64 IMAP (padded) | ||||||
|  | 	expected := "Hello?World" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_IMAPUnpadded(t *testing.T) { | ||||||
|  | 	input := "SGVsbG8,V29ybGQ" // "Hello/World" in Base64 IMAP (unpadded) | ||||||
|  | 	expected := "Hello?World" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_InvalidInput(t *testing.T) { | ||||||
|  | 	input := "Invalid@@Base64" // Invalid Base64 input | ||||||
|  |  | ||||||
|  | 	_, err := DecodeBase64Any(input) | ||||||
|  | 	if err == nil { | ||||||
|  | 		t.Fatal("expected an error, but got none") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_EmptyInput(t *testing.T) { | ||||||
|  | 	input := "" // Empty input | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(result) != 0 { | ||||||
|  | 		t.Errorf("expected empty result, got %q", string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_WhitespaceInput(t *testing.T) { | ||||||
|  | 	input := "  SGVsbG8gV29ybGQ=  " // Input with leading and trailing spaces | ||||||
|  | 	expected := "Hello World" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_LineBreaksInput(t *testing.T) { | ||||||
|  | 	input := "SGVs\nbG8g\nV29y\nbGQ=" // Input with line breaks | ||||||
|  | 	expected := "Hello World" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_TabCharactersInput(t *testing.T) { | ||||||
|  | 	input := "SGVsbG8g\tV29ybGQ=" // Input with tab characters | ||||||
|  | 	expected := "Hello World" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_SpecialCharactersIgnored(t *testing.T) { | ||||||
|  | 	input := "SGVsbG8gV29ybGQ=!!" // Input with ignored special characters | ||||||
|  |  | ||||||
|  | 	_, err := DecodeBase64Any(input) | ||||||
|  | 	if err == nil { | ||||||
|  | 		t.Fatal("expected an error, but got none") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_SingleCharacterInput(t *testing.T) { | ||||||
|  | 	input := "QQ==" // "A" in Base64 | ||||||
|  | 	expected := "A" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_LongInput(t *testing.T) { | ||||||
|  | 	input := "U29tZSB2ZXJ5IGxvbmcgc3RyaW5nIHdpdGggbXVsdGlwbGUgbGluZXMgYW5kIHNwYWNlcy4=" // Long Base64 string | ||||||
|  | 	expected := "Some very long string with multiple lines and spaces." | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_Standard63And64(t *testing.T) { | ||||||
|  | 	input := "Pz8/Pw==" // "???" in standard Base64 (63 = '+', 64 = '/') | ||||||
|  | 	expected := "????" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_Standard63And64_NoPad(t *testing.T) { | ||||||
|  | 	input := "Pz8/Pw" // "???" in standard Base64 (63 = '+', 64 = '/') | ||||||
|  | 	expected := "????" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_URL63And64(t *testing.T) { | ||||||
|  | 	input := "Pz8_Pw==" // "???" in Base64 URL-safe (63 = '_', 64 = '-') | ||||||
|  | 	expected := "????" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestDecodeBase64Any_URL63And64_NoPad(t *testing.T) { | ||||||
|  | 	input := "Pz8_Pw==" // "???" in Base64 URL-safe (63 = '_', 64 = '-') | ||||||
|  | 	expected := "????" | ||||||
|  |  | ||||||
|  | 	result, err := DecodeBase64Any(input) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("unexpected error: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if string(result) != expected { | ||||||
|  | 		t.Errorf("expected %q, got %q", expected, string(result)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -1,57 +1,51 @@ | |||||||
| package mongoext | package mongoext | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"go.mongodb.org/mongo-driver/bson" |  | ||||||
| 	"go.mongodb.org/mongo-driver/bson/bsoncodec" |  | ||||||
| 	"go.mongodb.org/mongo-driver/bson/bsontype" |  | ||||||
| 	"go.mongodb.org/mongo-driver/bson/primitive" |  | ||||||
| 	"git.blackforestbytes.com/BlackForestBytes/goext/exerr" | 	"git.blackforestbytes.com/BlackForestBytes/goext/exerr" | ||||||
| 	"git.blackforestbytes.com/BlackForestBytes/goext/langext" | 	"git.blackforestbytes.com/BlackForestBytes/goext/langext" | ||||||
| 	"git.blackforestbytes.com/BlackForestBytes/goext/rfctime" | 	"git.blackforestbytes.com/BlackForestBytes/goext/rfctime" | ||||||
|  | 	"go.mongodb.org/mongo-driver/bson" | ||||||
|  | 	"go.mongodb.org/mongo-driver/bson/bsoncodec" | ||||||
|  | 	"go.mongodb.org/mongo-driver/bson/primitive" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func CreateGoExtBsonRegistry() *bsoncodec.Registry { | func CreateGoExtBsonRegistry() *bsoncodec.Registry { | ||||||
| 	rb := bsoncodec.NewRegistryBuilder() | 	reg := bson.NewRegistry() | ||||||
|  |  | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.RFC3339Time{}), rfctime.RFC3339Time{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(rfctime.RFC3339Time{}), rfctime.RFC3339Time{}) | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.RFC3339Time{}), rfctime.RFC3339Time{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(&rfctime.RFC3339Time{}), rfctime.RFC3339Time{}) | ||||||
|  |  | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.RFC3339NanoTime{}), rfctime.RFC3339NanoTime{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(rfctime.RFC3339NanoTime{}), rfctime.RFC3339NanoTime{}) | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.RFC3339NanoTime{}), rfctime.RFC3339NanoTime{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(&rfctime.RFC3339NanoTime{}), rfctime.RFC3339NanoTime{}) | ||||||
|  |  | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.UnixTime{}), rfctime.UnixTime{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(rfctime.UnixTime{}), rfctime.UnixTime{}) | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.UnixTime{}), rfctime.UnixTime{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(&rfctime.UnixTime{}), rfctime.UnixTime{}) | ||||||
|  |  | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.UnixMilliTime{}), rfctime.UnixMilliTime{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(rfctime.UnixMilliTime{}), rfctime.UnixMilliTime{}) | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.UnixMilliTime{}), rfctime.UnixMilliTime{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(&rfctime.UnixMilliTime{}), rfctime.UnixMilliTime{}) | ||||||
|  |  | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.UnixNanoTime{}), rfctime.UnixNanoTime{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(rfctime.UnixNanoTime{}), rfctime.UnixNanoTime{}) | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.UnixNanoTime{}), rfctime.UnixNanoTime{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(&rfctime.UnixNanoTime{}), rfctime.UnixNanoTime{}) | ||||||
|  |  | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.Date{}), rfctime.Date{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(rfctime.Date{}), rfctime.Date{}) | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(&rfctime.Date{}), rfctime.Date{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(&rfctime.Date{}), rfctime.Date{}) | ||||||
|  |  | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(rfctime.SecondsF64(0)), rfctime.SecondsF64(0)) | 	reg.RegisterTypeDecoder(reflect.TypeOf(rfctime.SecondsF64(0)), rfctime.SecondsF64(0)) | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(rfctime.SecondsF64(0))), rfctime.SecondsF64(0)) | 	reg.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(rfctime.SecondsF64(0))), rfctime.SecondsF64(0)) | ||||||
|  |  | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(exerr.ErrorCategory{}), exerr.ErrorCategory{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(exerr.ErrorCategory{}), exerr.ErrorCategory{}) | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(exerr.ErrorCategory{})), exerr.ErrorCategory{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(exerr.ErrorCategory{})), exerr.ErrorCategory{}) | ||||||
|  |  | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(exerr.ErrorSeverity{}), exerr.ErrorSeverity{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(exerr.ErrorSeverity{}), exerr.ErrorSeverity{}) | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(exerr.ErrorSeverity{})), exerr.ErrorSeverity{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(exerr.ErrorSeverity{})), exerr.ErrorSeverity{}) | ||||||
|  |  | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(exerr.ErrorType{}), exerr.ErrorType{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(exerr.ErrorType{}), exerr.ErrorType{}) | ||||||
| 	rb.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(exerr.ErrorType{})), exerr.ErrorType{}) | 	reg.RegisterTypeDecoder(reflect.TypeOf(langext.Ptr(exerr.ErrorType{})), exerr.ErrorType{}) | ||||||
|  |  | ||||||
| 	bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb) |  | ||||||
| 	bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb) |  | ||||||
|  |  | ||||||
| 	bson.PrimitiveCodecs{}.RegisterPrimitiveCodecs(rb) |  | ||||||
|  |  | ||||||
| 	// otherwise we get []primitve.E when unmarshalling into any | 	// otherwise we get []primitve.E when unmarshalling into any | ||||||
| 	// which will result in {'key': .., 'value': ...}[] json when json-marshalling | 	// which will result in {'key': .., 'value': ...}[] json when json-marshalling | ||||||
| 	rb.RegisterTypeMapEntry(bsontype.EmbeddedDocument, reflect.TypeOf(primitive.M{})) | 	reg.RegisterTypeMapEntry(bson.TypeEmbeddedDocument, reflect.TypeOf(primitive.M{})) | ||||||
|  |  | ||||||
| 	return rb.Build() | 	return reg | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package timeext | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"math" | 	"math" | ||||||
|  | 	"sort" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -142,6 +143,38 @@ func Max(a time.Time, b time.Time) time.Time { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func Avg(v ...time.Time) time.Time { | ||||||
|  | 	if len(v) == 0 { | ||||||
|  | 		return time.Time{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var sum int64 | ||||||
|  | 	for _, t := range v { | ||||||
|  | 		sum += t.UnixNano() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return time.Unix(0, sum/int64(len(v))) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func Median(v ...time.Time) time.Time { | ||||||
|  | 	if len(v) == 0 { | ||||||
|  | 		return time.Time{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sorted := make([]time.Time, len(v)) | ||||||
|  | 	copy(sorted, v) | ||||||
|  | 	sort.Slice(sorted, func(i, j int) bool { | ||||||
|  | 		return sorted[i].UnixNano() < sorted[j].UnixNano() | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	mid := len(sorted) / 2 | ||||||
|  | 	if len(sorted)%2 == 0 { | ||||||
|  | 		return Avg(sorted[mid-1], sorted[mid]) | ||||||
|  | 	} else { | ||||||
|  | 		return sorted[mid] | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func UnixFloatSeconds(v float64) time.Time { | func UnixFloatSeconds(v float64) time.Time { | ||||||
| 	sec, dec := math.Modf(v) | 	sec, dec := math.Modf(v) | ||||||
| 	return time.Unix(int64(sec), int64(dec*(1e9))) | 	return time.Unix(int64(sec), int64(dec*(1e9))) | ||||||
|   | |||||||
| @@ -227,3 +227,173 @@ func TestDaysInMonth_FebruaryNonLeapYear(t *testing.T) { | |||||||
| 		t.Errorf("Expected %d but got %d", expected, result) | 		t.Errorf("Expected %d but got %d", expected, result) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestAvg_MultipleValues(t *testing.T) { | ||||||
|  | 	t1 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	t2 := time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	t3 := time.Date(2022, 1, 5, 0, 0, 0, 0, time.UTC) | ||||||
|  |  | ||||||
|  | 	// Average should be January 3, 2022 (middle date) | ||||||
|  | 	expected := time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	result := Avg(t1, t2, t3) | ||||||
|  |  | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestAvg_TwoValues(t *testing.T) { | ||||||
|  | 	t1 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	t2 := time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC) | ||||||
|  |  | ||||||
|  | 	expected := time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	result := Avg(t1, t2) | ||||||
|  |  | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestAvg_EmptySlice(t *testing.T) { | ||||||
|  | 	result := Avg() | ||||||
|  | 	expected := time.Time{} | ||||||
|  |  | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected zero time but got %v", result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestMedian_OddNumberOfValues(t *testing.T) { | ||||||
|  | 	t1 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	t2 := time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	t3 := time.Date(2022, 1, 5, 0, 0, 0, 0, time.UTC) | ||||||
|  |  | ||||||
|  | 	// Median should be the middle date | ||||||
|  | 	expected := t2 | ||||||
|  | 	result := Median(t1, t2, t3) | ||||||
|  |  | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestMedian_EvenNumberOfValues(t *testing.T) { | ||||||
|  | 	t1 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	t2 := time.Date(2022, 1, 2, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	t3 := time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	t4 := time.Date(2022, 1, 4, 0, 0, 0, 0, time.UTC) | ||||||
|  |  | ||||||
|  | 	// Median for even number of values should be average of middle two | ||||||
|  | 	expected := time.Date(2022, 1, 2, 12, 0, 0, 0, time.UTC) | ||||||
|  | 	result := Median(t1, t2, t3, t4) | ||||||
|  |  | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestMedian_UnsortedValues(t *testing.T) { | ||||||
|  | 	t1 := time.Date(2022, 1, 5, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	t2 := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) | ||||||
|  | 	t3 := time.Date(2022, 1, 3, 0, 0, 0, 0, time.UTC) | ||||||
|  |  | ||||||
|  | 	// Median should correctly sort values first | ||||||
|  | 	expected := t3 | ||||||
|  | 	result := Median(t1, t2, t3) | ||||||
|  |  | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestMedian_EmptySlice(t *testing.T) { | ||||||
|  | 	result := Median() | ||||||
|  | 	expected := time.Time{} | ||||||
|  |  | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected zero time but got %v", result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTimeToDatePart(t *testing.T) { | ||||||
|  | 	tz := TimezoneBerlin | ||||||
|  | 	tm := time.Date(2022, 1, 1, 13, 14, 15, 0, tz) | ||||||
|  | 	expected := time.Date(2022, 1, 1, 0, 0, 0, 0, tz) | ||||||
|  | 	result := TimeToDatePart(tm, tz) | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTimeToWeekStart(t *testing.T) { | ||||||
|  | 	tz := TimezoneBerlin | ||||||
|  | 	// January 5, 2022 was a Wednesday | ||||||
|  | 	tm := time.Date(2022, 1, 5, 13, 14, 15, 0, tz) | ||||||
|  | 	// Should return Monday, January 3, 2022 | ||||||
|  | 	expected := time.Date(2022, 1, 3, 0, 0, 0, 0, tz) | ||||||
|  | 	result := TimeToWeekStart(tm, tz) | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTimeToWeekStart_WhenMonday(t *testing.T) { | ||||||
|  | 	tz := TimezoneBerlin | ||||||
|  | 	// January 3, 2022 was a Monday | ||||||
|  | 	tm := time.Date(2022, 1, 3, 13, 14, 15, 0, tz) | ||||||
|  | 	expected := time.Date(2022, 1, 3, 0, 0, 0, 0, tz) | ||||||
|  | 	result := TimeToWeekStart(tm, tz) | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTimeToMonthStart(t *testing.T) { | ||||||
|  | 	tz := TimezoneBerlin | ||||||
|  | 	tm := time.Date(2022, 1, 15, 13, 14, 15, 0, tz) | ||||||
|  | 	expected := time.Date(2022, 1, 1, 0, 0, 0, 0, tz) | ||||||
|  | 	result := TimeToMonthStart(tm, tz) | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTimeToMonthEnd(t *testing.T) { | ||||||
|  | 	tz := TimezoneBerlin | ||||||
|  | 	tm := time.Date(2022, 1, 15, 13, 14, 15, 0, tz) | ||||||
|  | 	expected := time.Date(2022, 2, 1, 0, 0, 0, 0, tz).Add(-1) | ||||||
|  | 	result := TimeToMonthEnd(tm, tz) | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTimeToYearStart(t *testing.T) { | ||||||
|  | 	tz := TimezoneBerlin | ||||||
|  | 	tm := time.Date(2022, 5, 15, 13, 14, 15, 0, tz) | ||||||
|  | 	expected := time.Date(2022, 1, 1, 0, 0, 0, 0, tz) | ||||||
|  | 	result := TimeToYearStart(tm, tz) | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTimeToYearEnd(t *testing.T) { | ||||||
|  | 	tz := TimezoneBerlin | ||||||
|  | 	tm := time.Date(2022, 5, 15, 13, 14, 15, 0, tz) | ||||||
|  | 	expected := time.Date(2023, 1, 1, 0, 0, 0, 0, tz).Add(-1) | ||||||
|  | 	result := TimeToYearEnd(tm, tz) | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestTimeToNextYearStart(t *testing.T) { | ||||||
|  | 	tz := TimezoneBerlin | ||||||
|  | 	tm := time.Date(2022, 5, 15, 13, 14, 15, 0, tz) | ||||||
|  | 	expected := time.Date(2023, 1, 1, 0, 0, 0, 0, tz) | ||||||
|  | 	result := TimeToNextYearStart(tm, tz) | ||||||
|  | 	if !result.Equal(expected) { | ||||||
|  | 		t.Errorf("Expected %v but got %v", expected, result) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user