├── lint.log ├── scripts ├── add-license-header └── license-header.py ├── .gitignore ├── glide.yaml ├── .build ├── install-icu4c.sh ├── cover.sh └── lint.mk ├── .travis.yml ├── glide.lock ├── Makefile ├── LICENSE.txt ├── number ├── bridge.h ├── number_formatter_test.go ├── number_formatter.go └── bridge.c ├── constants └── constants.go ├── currency ├── bridge.h ├── currency_formatter_test.go ├── currency_formatter.go └── bridge.c ├── locale ├── bridge.h ├── locale_test.go ├── bridge.c └── locale.go ├── datetime ├── style.go ├── bridge.h ├── datetime_formatter.go ├── util.go ├── datetime_formatter_test.go └── bridge.c └── README.md /lint.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/add-license-header: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -e 4 | 5 | python ./script/license-headers.py -t LICENSE.txt -d . 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.out 2 | *.test 3 | *.xml 4 | *.swp 5 | .idea/ 6 | *.iml 7 | *.cov 8 | *.html 9 | .tmp/ 10 | .gen/ 11 | /vendor 12 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/uber-go/icu4go 2 | testImport: 3 | - package: github.com/stretchr/testify 4 | subpackages: 5 | - assert 6 | -------------------------------------------------------------------------------- /.build/install-icu4c.sh: -------------------------------------------------------------------------------- 1 | cwd=$(pwd) 2 | cd /tmp 3 | wget http://download.icu-project.org/files/icu4c/58.2/icu4c-58_2-src.tgz 4 | tar -xf icu4c-58_2-src.tgz 5 | cd icu/source 6 | ./configure --prefix=/usr && make 7 | sudo make install 8 | cd $cwd 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - ./.build/install-icu4c.sh 3 | 4 | sudo: required 5 | language: go 6 | 7 | env: 8 | ICU_LIB: /usr 9 | 10 | go: 11 | - 1.7 12 | - 1.8 13 | - tip 14 | 15 | cache: 16 | directories: 17 | - vendor 18 | 19 | install: 20 | - make install_ci 21 | 22 | script: 23 | - make test_ci 24 | - make lint 25 | - travis_retry goveralls -coverprofile=cover.out -service=travis-ci 26 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: 8ac921bbcb3da5b3bfe9c2ad28fa1a64fa4809d6b97449819eb73269f563cd40 2 | updated: 2017-05-08T15:53:41.679497162-07:00 3 | imports: [] 4 | testImports: 5 | - name: github.com/davecgh/go-spew 6 | version: 346938d642f2ec3594ed81d874461961cd0faa76 7 | subpackages: 8 | - spew 9 | - name: github.com/pmezard/go-difflib 10 | version: 792786c7400a136282c1664665ae0a8db921c6c2 11 | subpackages: 12 | - difflib 13 | - name: github.com/stretchr/testify 14 | version: 4d4bfba8f1d1027c4fdbe371823030df51419987 15 | subpackages: 16 | - assert 17 | - require 18 | - suite 19 | -------------------------------------------------------------------------------- /.build/cover.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | COVER=cover 6 | ROOT_PKG=github.com/uber-go/icu4go 7 | 8 | if [[ -d "$COVER" ]]; then 9 | rm -rf "$COVER" 10 | fi 11 | mkdir -p "$COVER" 12 | 13 | i=0 14 | for pkg in "$@"; do 15 | i=$((i + 1)) 16 | 17 | extracoverpkg="" 18 | if [[ -f "$GOPATH/src/$pkg/.extra-coverpkg" ]]; then 19 | extracoverpkg=$( \ 20 | sed -e "s|^|$pkg/|g" < "$GOPATH/src/$pkg/.extra-coverpkg" \ 21 | | tr '\n' ',') 22 | fi 23 | 24 | coverpkg=$(go list -json "$pkg" | jq -r ' 25 | .Deps 26 | | map(select(startswith("'"$ROOT_PKG"'"))) 27 | | map(select(contains("/vendor/") | not)) 28 | | . + ["'"$pkg"'"] 29 | | join(",") 30 | ') 31 | if [[ -n "$extracoverpkg" ]]; then 32 | coverpkg="$extracoverpkg$coverpkg" 33 | fi 34 | 35 | go test \ 36 | -coverprofile "$COVER/cover.${i}.out" -coverpkg "$coverpkg" \ 37 | -v "$pkg" 38 | done 39 | 40 | gocovmerge "$COVER"/*.out > cover.out 41 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Below line is for Mac 2 | # export ICU_LIB = /usr/local/opt/icu4c 3 | export CGO_CFLAGS += -I${ICU_LIB}/include 4 | export CGO_LDFLAGS += -L${ICU_LIB}/lib -licui18n -licuuc -licudata 5 | 6 | ALL_SRC := $(shell find . -name "*.go" | grep -v -e vendor \ 7 | -e ".*/\..*" \ 8 | -e ".*/_.*") 9 | 10 | PACKAGES := $(shell glide nv) 11 | 12 | include .build/lint.mk 13 | 14 | .DEFAULT_GOAL := all 15 | 16 | .PHONY: build 17 | build: 18 | go build -i $(PACKAGES) 19 | 20 | .PHONY: install 21 | install: 22 | glide --version || go get github.com/Masterminds/glide 23 | glide install 24 | 25 | 26 | .PHONY: all 27 | all: lint test 28 | 29 | 30 | .PHONY: test 31 | test: 32 | go test -cover $(PACKAGES) 33 | 34 | 35 | .PHONY: install_ci 36 | install_ci: install 37 | go get github.com/wadey/gocovmerge 38 | go get github.com/mattn/goveralls 39 | go get golang.org/x/tools/cmd/cover 40 | go get github.com/golang/lint/golint 41 | go get golang.org/x/tools/cmd/goimports 42 | 43 | 44 | .PHONY: test_ci 45 | test_ci: install_ci build test 46 | ./.build/cover.sh $(shell go list $(PACKAGES)) 47 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Uber Technologies, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /.build/lint.mk: -------------------------------------------------------------------------------- 1 | LINT_EXCLUDES = _string.go mocks 2 | # Create a pipeline filter for go vet/golint. Patterns specified in LINT_EXCLUDES are 3 | # converted to a grep -v pipeline. If there are no filters, cat is used. 4 | FILTER_LINT := $(if $(LINT_EXCLUDES), grep -v $(foreach file, $(LINT_EXCLUDES),-e $(file)),cat) 5 | 6 | LINT_LOG := lint.log 7 | 8 | _THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST)) 9 | _THIS_DIR := $(dir $(_THIS_MAKEFILE)) 10 | 11 | ERRCHECK_FLAGS := -ignore "bytes:Write*" -ignoretests 12 | 13 | .PHONY: lint 14 | lint: vendor 15 | $(ECHO_V)rm -rf $(LINT_LOG) 16 | @echo "Checking formatting..." 17 | @$(MAKE) fmt 18 | @echo "Checking vet..." 19 | $(ECHO_V)$(foreach file,$(ALL_SRC),go tool vet $(file) 2>&1 | $(FILTER_LINT) | tee -a $(LINT_LOG);) 20 | @echo "Checking lint..." 21 | $(ECHO_V)$(foreach file,$(ALL_SRC),golint $(file) 2>&1 | $(FILTER_LINT) | tee -a $(LINT_LOG);) 22 | @echo "Checking for unresolved FIXMEs..." 23 | $(ECHO_V)git grep -i fixme | grep -v -e $(_THIS_MAKEFILE) -e CONTRIBUTING.md | tee -a $(LINT_LOG) 24 | @echo "Checking for imports of log package" 25 | $(ECHO_V)go list -f '{{ .ImportPath }}: {{ .Imports }}' $(shell glide nv) | grep -e "\blog\b" | tee -a $(LINT_LOG) 26 | $(ECHO_V)[ ! -s $(LINT_LOG) ] 27 | 28 | .PHONY: fmt 29 | fmt: 30 | $(ECHO_V)gofmt -s -w $(ALL_SRC) 31 | $(ECHO_V)goimports -w $(ALL_SRC) 32 | 33 | -------------------------------------------------------------------------------- /number/bridge.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef __C_NUMBER_BRIDGE_H__ 22 | #define __C_NUMBER_BRIDGE_H__ 23 | 24 | #include 25 | #include 26 | #include "unicode/utypes.h" 27 | 28 | // NOTE: UErrorCode is defined in unicode/utypes.h @396, with 0 means no error. 29 | 30 | UErrorCode formatNumber( 31 | const double a, 32 | const char* locale, 33 | char* result, 34 | const size_t resultLength 35 | ); 36 | 37 | #endif //__C_NUMBER_BRIDGE_H__ 38 | -------------------------------------------------------------------------------- /constants/constants.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package constants 22 | 23 | // All constants that are needed across packages go here 24 | const ( 25 | // BufSize512 Buffer size of 512 used in number and currency 26 | BufSize512 = 512 27 | // Utc timezone 28 | Utc = "UTC" 29 | // Hyphen 30 | Hyphen = "-" 31 | // DefaultLocaleCode we default to English at Uber 32 | DefaultLocaleCode = "en" 33 | // DefaultLanguageCode we default the language to en 34 | DefaultLanguageCode = "en" 35 | // DefaultCountryCode we default the country to US 36 | DefaultCountryCode = "US" 37 | // EmptyString just to externalize 38 | EmptyString = "" 39 | ) 40 | -------------------------------------------------------------------------------- /currency/bridge.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef __C_NUMBER_BRIDGE_H__ 22 | #define __C_NUMBER_BRIDGE_H__ 23 | 24 | #include 25 | #include 26 | #include "unicode/utypes.h" 27 | 28 | // NOTE: UErrorCode is defined in unicode/utypes.h @396, with 0 means no error. 29 | 30 | UErrorCode getCurrencySymbol( 31 | const char *currencyCode, 32 | const char* locale, 33 | char* result, 34 | const size_t resultLength 35 | ); 36 | 37 | UErrorCode formatCurrency( 38 | const double a, 39 | const char *currencyCode, 40 | const char* locale, 41 | char* result, 42 | const size_t resultLength 43 | ); 44 | 45 | #endif //__C_NUMBER_BRIDGE_H__ 46 | -------------------------------------------------------------------------------- /currency/currency_formatter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package numeric 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestCurrencyFormatter_Format(t *testing.T) { 30 | assertions := assert.New(t) 31 | locale := "fr-FR" 32 | actual, err := Format(locale, 1222333.4434, "USD") 33 | assertions.NoError(err) 34 | assertions.Equal("1\u00a0222\u00a0333,44\u00a0$US", actual) 35 | } 36 | 37 | func TestCurrencyFormatter_GetCurrencySymbol(t *testing.T) { 38 | assertions := assert.New(t) 39 | locale := "fr-FR" 40 | actual, err := GetSymbol(locale, "USD") 41 | assertions.NoError(err) 42 | assertions.Equal("$US", actual) 43 | } 44 | -------------------------------------------------------------------------------- /number/number_formatter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package numeric 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestNumberFormatter_FormatNumber_en_US(t *testing.T) { 30 | assertions := assert.New(t) 31 | locale := "en-US" 32 | actual, err := Format(locale, 1222333.44) 33 | assertions.NoError(err) 34 | assertions.Equal("1,222,333.44", actual) 35 | } 36 | 37 | func TestNumberFormatter_FormatNumber_fr_CA(t *testing.T) { 38 | assertions := assert.New(t) 39 | locale := "fr-CA" 40 | actual, err := Format(locale, 1222333.44) 41 | assertions.NoError(err) 42 | assertions.Equal("1\u00a0222\u00a0333,44", actual) 43 | } 44 | -------------------------------------------------------------------------------- /locale/bridge.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef __C_NUMBER_BRIDGE_H__ 22 | #define __C_NUMBER_BRIDGE_H__ 23 | 24 | #include 25 | #include 26 | 27 | // gets the language code from locale as per ICU standard 28 | // `result` is expected to be a fully constructed array of size `resultLength`. 29 | UErrorCode getLanguageCode( 30 | const char* localeID, 31 | char* result, 32 | int32_t resultLength); 33 | 34 | // gets the country code from locale as per ICU standard 35 | // `result` is expected to be a fully constructed array of size `resultLength`. 36 | UErrorCode getCountryCode( 37 | const char* localeID, 38 | char* result, 39 | int32_t resultLength); 40 | 41 | #endif //__C_NUMBER_BRIDGE_H__ 42 | -------------------------------------------------------------------------------- /number/number_formatter.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package numeric 22 | 23 | // #include "unicode/utypes.h" 24 | // #include "bridge.h" 25 | import "C" 26 | import ( 27 | "fmt" 28 | "unsafe" 29 | 30 | "github.com/uber-go/icu4go/constants" 31 | ) 32 | 33 | // Format is function to format a number for a locale 34 | func Format(l string, n float64) (string, error) { 35 | a := C.double(n) 36 | locale := C.CString(l) 37 | defer func() { C.free(unsafe.Pointer(locale)) }() 38 | 39 | resSize := C.size_t(constants.BufSize512 * C.sizeof_char) 40 | res := (*C.char)(C.malloc(resSize)) 41 | defer func() { C.free(unsafe.Pointer(res)) }() 42 | 43 | err := C.formatNumber(a, locale, res, resSize) 44 | if err > 0 { 45 | return "", fmt.Errorf("error (%d) formatting a number %f", err, n) 46 | } 47 | return C.GoString(res), nil 48 | } 49 | -------------------------------------------------------------------------------- /datetime/style.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package datetime 22 | 23 | // #include "bridge.h" 24 | import "C" 25 | 26 | // FormatType is one of [Full, Mid, Short, Long] 27 | type FormatType struct { 28 | Name string 29 | ICUStyle C.UDateFormatStyle 30 | } 31 | 32 | var ( 33 | // Full format type 34 | Full = FormatType{"Full", C.UDAT_FULL} 35 | // Mid format type 36 | Mid = FormatType{"Mid", C.UDAT_MEDIUM} 37 | // Short format type 38 | Short = FormatType{"Short", C.UDAT_SHORT} 39 | // Long format type 40 | Long = FormatType{"Long", C.UDAT_LONG} 41 | 42 | lookup = map[string]FormatType{ 43 | "Full": Full, 44 | "Mid": Mid, 45 | "Short": Short, 46 | "Long": Long, 47 | } 48 | ) 49 | 50 | // GetFormatType gets the format type for a string 51 | func GetFormatType(style string) FormatType { 52 | return lookup[style] 53 | } 54 | -------------------------------------------------------------------------------- /datetime/bridge.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #ifndef __C_BRIDGE_H__ 22 | #define __C_BRIDGE_H__ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | const UErrorCode formatDateTime(double epochMillis, 30 | const char* localeId, 31 | const char* pattern, 32 | const char* tz, 33 | char* const result, 34 | const size_t resultCapacity); 35 | 36 | const UErrorCode getDateTimePattern(const char* localeId, 37 | UDateFormatStyle style, 38 | bool localized, 39 | char* const result, 40 | const size_t resultCapacity); 41 | 42 | #endif //__C_BRIDGE_H__ 43 | -------------------------------------------------------------------------------- /datetime/datetime_formatter.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package datetime 22 | 23 | import ( 24 | "time" 25 | ) 26 | 27 | // GetPattern is exposed as an override to default ICU pattern 28 | var GetPattern func(string, FormatType, bool) (string, error) = GetPatternFromICU 29 | 30 | // Format to format a given datetime into string based on locale and format type 31 | func Format(locale string, t time.Time, formatType FormatType, timeZone string) (string, error) { 32 | pattern, err := GetPattern(locale, formatType, false) 33 | if err != nil { 34 | return "", err 35 | } 36 | 37 | return formatDateTime(t, pattern, timeZone, locale) 38 | } 39 | 40 | // GetPatternFromICU returns datetime pattern used for a locale and format style 41 | func GetPatternFromICU(locale string, formatType FormatType, localized bool) (string, error) { 42 | return getDateTimePattern(locale, formatType.ICUStyle, localized) 43 | } 44 | -------------------------------------------------------------------------------- /locale/locale_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package locale 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | "github.com/stretchr/testify/suite" 28 | ) 29 | 30 | // The Test Suite 31 | type LocaleTestSuite struct { 32 | suite.Suite 33 | } 34 | 35 | func TestLocales(t *testing.T) { 36 | suite.Run(t, new(LocaleTestSuite)) 37 | } 38 | 39 | func (s *LocaleTestSuite) TestNormalizeLocale() { 40 | assert := assert.New(s.T()) 41 | l := Normalized("en") 42 | assert.Equal("en", l) 43 | 44 | l = Normalized("en_US") 45 | assert.Equal("en-US", l) 46 | 47 | l = Normalized("en_us") 48 | assert.Equal("en-US", l) 49 | 50 | l = Normalized("en_Latn_US_REVISED") 51 | assert.Equal("en-US", l) 52 | 53 | l = Normalized("en@collation=phonebook;calendar=islamic-civil") 54 | assert.Equal("en", l) 55 | 56 | l = Normalized("en_Latn_US_REVISED@currency=USD") 57 | assert.Equal("en-US", l) 58 | 59 | l = Normalized("en_Latn") 60 | assert.Equal("en", l) 61 | } 62 | 63 | func (s *LocaleTestSuite) TestGetCountrycode() { 64 | assert := assert.New(s.T()) 65 | assert.Equal(GetCountryCode("en-US"), "US") 66 | } 67 | 68 | func (s *LocaleTestSuite) TestGetLanguageCode() { 69 | assert := assert.New(s.T()) 70 | assert.Equal(GetLanguageCode("en-US"), "en") 71 | } 72 | -------------------------------------------------------------------------------- /locale/bridge.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #include "unicode/uloc.h" 22 | #include "unicode/ustring.h" 23 | #include 24 | #include 25 | 26 | #include "bridge.h" 27 | 28 | 29 | UErrorCode getLanguageCode( 30 | const char* localeID, 31 | char* result, 32 | int32_t resultLength) 33 | { 34 | UErrorCode status = U_ZERO_ERROR; 35 | int32_t needed; 36 | 37 | needed = uloc_getLanguage( 38 | localeID, // The localeID 39 | result, // The output result buffer 40 | resultLength, // The capacity of the output result buffer 41 | &status // The output error status 42 | ); 43 | 44 | if (!U_FAILURE(status)) { 45 | // Just to be sure 46 | result[needed] = '\0'; 47 | } 48 | 49 | return status; 50 | } 51 | 52 | UErrorCode getCountryCode( 53 | const char* localeID, 54 | char* result, 55 | int32_t resultLength) 56 | { 57 | UErrorCode status = U_ZERO_ERROR; 58 | int32_t needed; 59 | 60 | needed = uloc_getCountry( 61 | localeID, // The localeID 62 | result, // The output result buffer 63 | resultLength, // The capacity of the output result buffer 64 | &status // The output error status 65 | ); 66 | 67 | if (!U_FAILURE(status)) { 68 | // Just to be sure 69 | result[needed] = '\0'; 70 | } 71 | 72 | return status; 73 | } 74 | -------------------------------------------------------------------------------- /number/bridge.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #include 22 | #include 23 | #include 24 | #include "unicode/unum.h" 25 | #include "unicode/ucurr.h" 26 | #include "unicode/ustring.h" 27 | 28 | #include "bridge.h" 29 | 30 | UErrorCode formatNumber(const double a, const char* locale, char* result, const size_t resultLength) 31 | { 32 | UErrorCode status = U_ZERO_ERROR; 33 | const int32_t bufSize = resultLength / sizeof(UChar); // considering it's UNICODE. 34 | int32_t needed; 35 | 36 | /* Create a formatter for the US locale */ 37 | UNumberFormat *fmt = unum_open( 38 | UNUM_DECIMAL, /* style */ 39 | 0, /* pattern */ 40 | 0, /* patternLength */ 41 | locale, /* locale */ 42 | 0, /* parseErr */ 43 | &status); 44 | 45 | if (!U_FAILURE(status)) { 46 | /* Use the formatter to format the number as a string 47 | in the given locale. The return value is the buffer size needed. 48 | We pass in NULL for the UFieldPosition pointer because we don't 49 | care to receive that data. */ 50 | UChar buf[bufSize]; 51 | needed = unum_formatDouble(fmt, a, buf, bufSize, NULL, &status); 52 | /** 53 | * u_austrcpy docs from the header: 54 | * 55 | * Copy ustring to a byte string encoded in the default codepage. 56 | * Adds a null terminator. 57 | * Performs a UChar to host byte conversion 58 | */ 59 | u_austrcpy(result, buf); 60 | 61 | /* Release the storage used by the formatter */ 62 | unum_close(fmt); 63 | } 64 | 65 | return status; 66 | } 67 | -------------------------------------------------------------------------------- /currency/currency_formatter.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package numeric 22 | 23 | // #include "bridge.h" 24 | import "C" 25 | import ( 26 | "fmt" 27 | "unsafe" 28 | 29 | "github.com/uber-go/icu4go/constants" 30 | ) 31 | 32 | // Format is function to format the input currency value 33 | func Format(locale string, value float64, currencyCode string) (string, error) { 34 | if currencyCode == "" { 35 | return "", fmt.Errorf("error (empty currency code) formatting a currency for value %f", value) 36 | } 37 | 38 | a := C.double(value) 39 | l := C.CString(locale) 40 | defer func() { C.free(unsafe.Pointer(l)) }() 41 | cc := C.CString(currencyCode) 42 | defer func() { C.free(unsafe.Pointer(cc)) }() 43 | resSize := C.size_t(constants.BufSize512 * C.sizeof_char) 44 | res := (*C.char)(C.malloc(resSize)) 45 | defer func() { C.free(unsafe.Pointer(res)) }() 46 | 47 | err := C.formatCurrency(a, cc, l, res, resSize) 48 | if err > 0 { 49 | return "", fmt.Errorf("error (%d) formatting a currency for value %f", err, value) 50 | } 51 | return C.GoString(res), nil 52 | } 53 | 54 | // GetSymbol is function to get currency symbol for a locale 55 | func GetSymbol(locale string, currencyCode string) (string, error) { 56 | l := C.CString(locale) 57 | defer func() { C.free(unsafe.Pointer(l)) }() 58 | cc := C.CString(currencyCode) 59 | defer func() { C.free(unsafe.Pointer(cc)) }() 60 | resSize := C.size_t(constants.BufSize512 * C.sizeof_char) 61 | res := (*C.char)(C.malloc(resSize)) 62 | err := C.getCurrencySymbol(cc, l, res, resSize) 63 | if err > 0 { 64 | return "", fmt.Errorf("error (%d) getting the currency symbol", err) 65 | } 66 | return C.GoString(res), nil 67 | } 68 | -------------------------------------------------------------------------------- /datetime/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package datetime 22 | 23 | // #include "bridge.h" 24 | import "C" 25 | 26 | import ( 27 | "fmt" 28 | "time" 29 | "unsafe" 30 | 31 | "github.com/uber-go/icu4go/constants" 32 | ) 33 | 34 | // formatDateTime to format a given date/time into string based on locale and format type 35 | func formatDateTime(t time.Time, format string, timeZone string, localeCode string) (string, error) { 36 | localeID := C.CString(localeCode) 37 | epochMillis := C.double(t.UnixNano() / int64(time.Millisecond)) 38 | resultSize := C.size_t(constants.BufSize512 * C.sizeof_char) 39 | result := (*C.char)(C.malloc(resultSize)) 40 | pattern := C.CString(format) 41 | tz := C.CString(timeZone) 42 | 43 | defer func() { 44 | C.free(unsafe.Pointer(localeID)) 45 | C.free(unsafe.Pointer(result)) 46 | C.free(unsafe.Pointer(pattern)) 47 | C.free(unsafe.Pointer(tz)) 48 | }() 49 | 50 | if err := C.formatDateTime( 51 | epochMillis, 52 | localeID, 53 | pattern, 54 | tz, 55 | result, 56 | resultSize, 57 | ); err > 0 { 58 | return "", fmt.Errorf("errno %d occurred formatting the date for locale %s", err, localeCode) 59 | } 60 | 61 | return C.GoString(result), nil 62 | } 63 | 64 | // getDateTimeFormat to retrieve a date time format for a locale and style 65 | func getDateTimePattern(localeCode string, style C.UDateFormatStyle, localized bool) (string, error) { 66 | localeID := C.CString(localeCode) 67 | resultSize := C.size_t(constants.BufSize512 * C.sizeof_char) 68 | result := (*C.char)(C.malloc(resultSize)) 69 | 70 | defer func() { 71 | C.free(unsafe.Pointer(localeID)) 72 | C.free(unsafe.Pointer(result)) 73 | }() 74 | 75 | if err := C.getDateTimePattern( 76 | localeID, 77 | style, 78 | C.bool(localized), 79 | result, 80 | resultSize, 81 | ); err > 0 { 82 | return "", fmt.Errorf("errno %d occurred getting date time pattern for locale %s", err, localeCode) 83 | } 84 | 85 | return C.GoString(result), nil 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # icu4go [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] 2 | 3 | ## A go binding for icu4c library 4 | 5 | This library provides functions for locale, date, time, currency and number formatting using icu4c. 6 | We will add more functions as and when needed. In order to use this library you must install icu4c library 7 | for your platform and define CGO_CFLAGS and CGO_LDFLAGS in your Makefile. You can take a look at this library's 8 | [Makefile](Makefile) for example 9 | 10 | 11 | ### Install ICU4C 12 | 1. On Mac: 13 | http://macappstore.org/icu4c/ 14 | 15 | 2. On Linux: 16 | http://www.linuxfromscratch.org/blfs/view/8.0/general/icu.html 17 | 18 | 19 | 20 | ## Usage 21 | #### Locale 22 | All locale specific functions reside in the `locale` package. 23 | 24 | ``` 25 | import "github.com/uber-go/icu4go/locale" 26 | 27 | ``` 28 | 29 | To normalize a locale use Normalized function as below: 30 | 31 | ``` 32 | locale.Normalized("en_US") 33 | > en-US 34 | ``` 35 | 36 | To get a country code from a locale use GetCountryCode as below: 37 | 38 | ``` 39 | locale.GetCountrycode("en-US") 40 | > US 41 | ``` 42 | 43 | To get a language code from locale use GetLanguageCode as below: 44 | 45 | ``` 46 | locale.GetLanguageCode("en-US") 47 | > en 48 | ``` 49 | 50 | #### Number formats 51 | All number formatting functions reside in `number` package. 52 | 53 | ``` 54 | import "github.com/uber-go/icu4go/number" 55 | ``` 56 | 57 | To format a number into a locale use Format function as below: 58 | ``` 59 | number.Format("en-US", 123456.78) 60 | > 123,456.78 61 | ``` 62 | 63 | #### Currency formats 64 | All currency formatting functions reside in `currency` package. 65 | 66 | ``` 67 | import "github.com/uber-go/icu4go/currency" 68 | ``` 69 | 70 | To format a currency into a locale use Format function as below: 71 | ``` 72 | currency.Format("en-US", 123456.78, "USD") 73 | > $123,456.78 74 | 75 | ``` 76 | 77 | #### DateTime Formats 78 | All date and time formatting functions reside in `datetime` package. The supported Styles are Short, Mid, Long, and Full. 79 | 80 | ``` 81 | import "github.com/uber-go/icu4go/datetime" 82 | 83 | ``` 84 | 85 | To format a date time into locale use Format function as below: 86 | 87 | ``` 88 | datetime.Format("de-DE", time.Now(), datetime.Short, "UTC") 89 | > 02.01.17, 12:35 90 | 91 | ``` 92 | 93 | To format a date time into locale with your pattern Override the GetPattern function as below: 94 | ``` 95 | // Override the func to return Short pattern always 96 | GetPattern = func(string, FormatType, bool) (string, error) { 97 | return "dd.MM.yy, HH:mm", nil 98 | } 99 | 100 | datetime.Format("de-DE", time.Now(), datetime.Full, "UTC") 101 | > 02.01.17, 12:35 102 | ``` 103 | 104 | ## Development Status 105 | Stable. 106 | 107 | ## Annotations 108 | Released under the [MIT License](LICENSE.txt). 109 | 110 | [ci-img]: https://travis-ci.org/uber-go/icu4go.svg?branch=master 111 | [ci]: https://travis-ci.org/uber-go/icu4go?branch=master 112 | [cov-img]: https://coveralls.io/repos/github/uber-go/icu4go/badge.svg?branch=master 113 | [cov]: https://coveralls.io/github/uber-go/icu4go?branch=master 114 | -------------------------------------------------------------------------------- /locale/locale.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package locale 22 | 23 | // #include "bridge.h" 24 | import "C" 25 | import ( 26 | "strings" 27 | "unsafe" 28 | 29 | "github.com/uber-go/icu4go/constants" 30 | ) 31 | 32 | // Normalized returns the normalized version of the input locale code. It will first try to get the 33 | // language code from the input locale. If it cant get a valid language then it will return "en" as a locale. 34 | // It will then try to get country and if it cant get country then it will just return that language as a locale. 35 | // At last, it will return language and country joined by hyphen. 36 | func Normalized(code string) string { 37 | cc := getCountryCode(code) 38 | if cc == constants.EmptyString { 39 | return getLanguageCode(code) 40 | } 41 | 42 | return strings.Join([]string{getLanguageCode(code), cc}, constants.Hyphen) 43 | } 44 | 45 | // GetCountryCode find the country code for a locale 46 | func GetCountryCode(code string) string { 47 | return getCountryCode(code) 48 | } 49 | 50 | // GetLanguageCode returns the language code for this locale 51 | func GetLanguageCode(code string) string { 52 | return getLanguageCode(code) 53 | } 54 | 55 | func getLanguageCode(code string) string { 56 | l := C.CString(code) 57 | defer func() { C.free(unsafe.Pointer(l)) }() 58 | 59 | resSize := C.size_t(constants.BufSize512 * C.sizeof_char) 60 | result := (*C.char)(C.malloc(resSize)) 61 | defer func() { C.free(unsafe.Pointer(result)) }() 62 | 63 | err := C.getLanguageCode(l, result, constants.BufSize512*C.sizeof_char) 64 | if err > C.U_ZERO_ERROR { 65 | return constants.DefaultLanguageCode 66 | } 67 | 68 | return C.GoString(result) 69 | } 70 | 71 | func getCountryCode(code string) string { 72 | l := C.CString(code) 73 | defer func() { 74 | C.free(unsafe.Pointer(l)) 75 | }() 76 | 77 | resSize := C.size_t(constants.BufSize512 * C.sizeof_char) 78 | result := (*C.char)(C.malloc(resSize)) 79 | defer func() { 80 | C.free(unsafe.Pointer(result)) 81 | }() 82 | 83 | err := C.getCountryCode(l, result, constants.BufSize512*C.sizeof_char) 84 | if err > C.U_ZERO_ERROR { 85 | return constants.DefaultCountryCode 86 | } 87 | 88 | return C.GoString(result) 89 | } 90 | -------------------------------------------------------------------------------- /datetime/datetime_formatter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package datetime 22 | 23 | import ( 24 | "testing" 25 | "time" 26 | 27 | "github.com/stretchr/testify/assert" 28 | "github.com/uber-go/icu4go/constants" 29 | ) 30 | 31 | var ( 32 | testEpochSecs = int64(1483360520) 33 | testLocale = "de-DE" 34 | ) 35 | 36 | func TestDateTimeFormatter_FormatTime(t *testing.T) { 37 | assert := assert.New(t) 38 | formattedDateTime, err := Format(testLocale, time.Unix(testEpochSecs, 0), Full, constants.Utc) 39 | assert.NoError(err) 40 | assert.Equal("Montag, 2. Januar 2017 um 12:35:20 GMT", formattedDateTime) 41 | 42 | formattedDateTime, err = Format(testLocale, time.Unix(testEpochSecs, 0), Mid, constants.Utc) 43 | assert.NoError(err) 44 | assert.Equal("02.01.2017, 12:35:20", formattedDateTime) 45 | 46 | formattedDateTime, err = Format(testLocale, time.Unix(testEpochSecs, 0), Long, constants.Utc) 47 | assert.NoError(err) 48 | assert.Equal("2. Januar 2017 um 12:35:20 GMT", formattedDateTime) 49 | 50 | formattedDateTime, err = Format(testLocale, time.Unix(testEpochSecs, 0), Short, constants.Utc) 51 | assert.NoError(err) 52 | assert.Equal("02.01.17, 12:35", formattedDateTime) 53 | } 54 | 55 | func TestDateTimeFormatter_GetPattern(t *testing.T) { 56 | assert := assert.New(t) 57 | pattern, err := GetPatternFromICU(testLocale, Full, false) 58 | assert.NoError(err) 59 | assert.Equal("EEEE, d. MMMM y 'um' HH:mm:ss zzzz", pattern) 60 | 61 | pattern, err = GetPatternFromICU(testLocale, Mid, false) 62 | assert.NoError(err) 63 | assert.Equal("dd.MM.y, HH:mm:ss", pattern) 64 | 65 | pattern, err = GetPatternFromICU(testLocale, Long, false) 66 | assert.NoError(err) 67 | assert.Equal("d. MMMM y 'um' HH:mm:ss z", pattern) 68 | 69 | pattern, err = GetPatternFromICU(testLocale, Short, false) 70 | assert.NoError(err) 71 | assert.Equal("dd.MM.yy, HH:mm", pattern) 72 | } 73 | 74 | func TestDateTimeFormatter_GetPatternFuncOverride(t *testing.T) { 75 | 76 | oldValue := GetPattern 77 | defer func() { 78 | GetPattern = oldValue 79 | }() 80 | 81 | // Override the func to return Short pattern always 82 | GetPattern = func(string, FormatType, bool) (string, error) { 83 | return "dd.MM.yy, HH:mm", nil 84 | } 85 | 86 | assert := assert.New(t) 87 | formattedDateTime, err := Format(testLocale, time.Unix(testEpochSecs, 0), Full, constants.Utc) 88 | assert.NoError(err) 89 | // We should get Short format even if we ask for Full as override is in place 90 | assert.Equal("02.01.17, 12:35", formattedDateTime) 91 | 92 | } 93 | -------------------------------------------------------------------------------- /currency/bridge.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #include 22 | #include 23 | #include 24 | #include "unicode/unum.h" 25 | #include "unicode/ucurr.h" 26 | #include "unicode/ustring.h" 27 | 28 | #include "bridge.h" 29 | 30 | UErrorCode getCurrencySymbol(const char *currencyCode, const char* locale, char* result, const size_t resultLength) 31 | { 32 | UErrorCode status = U_ZERO_ERROR; 33 | const int32_t bufSize = resultLength / sizeof(UChar); // considering it's UNICODE. 34 | int32_t needed; 35 | 36 | UChar cc[strlen(currencyCode) * sizeof(UChar)]; 37 | u_uastrncpy(cc, currencyCode, strlen(currencyCode)); 38 | 39 | UBool isChoiceFormat = FALSE; 40 | int32_t resSize; 41 | const UChar *res = ucurr_getName(cc, locale, UCURR_SYMBOL_NAME, &isChoiceFormat, &resSize, &status); 42 | if (!U_FAILURE(status)) { 43 | u_austrcpy(result, res); 44 | } 45 | 46 | return status; 47 | } 48 | 49 | UErrorCode formatCurrency(const double a, const char *currencyCode, const char* locale, char* result, const size_t resultLength) 50 | { 51 | UErrorCode status = U_ZERO_ERROR; 52 | const int32_t bufSize = resultLength / sizeof(UChar); // considering it's UNICODE. 53 | int32_t needed; 54 | 55 | // TODO (ds): understand how expensive this is. 56 | /* Create a formatter for the US locale */ 57 | UNumberFormat *fmt = unum_open( 58 | UNUM_CURRENCY, /* style */ 59 | 0, /* pattern */ 60 | 0, /* patternLength */ 61 | locale, /* locale */ 62 | 0, /* parseErr */ 63 | &status); 64 | 65 | if (!U_FAILURE(status)) { 66 | UChar buf[strlen(currencyCode) * sizeof(UChar)]; 67 | u_uastrncpy(buf, currencyCode, strlen(currencyCode)); 68 | unum_setTextAttribute(fmt, UNUM_CURRENCY_CODE, buf, u_strlen(buf), &status); 69 | } 70 | if (!U_FAILURE(status)) { 71 | /* Use the formatter to format the number as a string 72 | in the given locale. The return value is the buffer size needed. 73 | We pass in NULL for the UFieldPosition pointer because we don't 74 | care to receive that data. */ 75 | UChar buf[bufSize]; 76 | needed = unum_formatDouble(fmt, a, buf, bufSize, NULL, &status); 77 | /** 78 | * u_austrcpy docs from the header: 79 | * 80 | * Copy ustring to a byte string encoded in the default codepage. 81 | * Adds a null terminator. 82 | * Performs a UChar to host byte conversion 83 | */ 84 | u_austrcpy(result, buf); 85 | 86 | /* Release the storage used by the formatter */ 87 | unum_close(fmt); 88 | } 89 | 90 | return status; 91 | } 92 | 93 | -------------------------------------------------------------------------------- /datetime/bridge.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Uber Technologies, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | #include "bridge.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | const UErrorCode formatDateTime(double epochMillis, 29 | const char* localeId, 30 | const char* pattern, 31 | const char* tz, 32 | char* const result, 33 | const size_t resultCapacity) { 34 | 35 | UErrorCode status = U_ZERO_ERROR; 36 | UChar outBuffer[resultCapacity]; 37 | 38 | UChar tzUchar[strlen(tz) * sizeof(UChar)]; 39 | u_uastrcpy(tzUchar, tz); 40 | 41 | UChar patternUchar[strlen(pattern) * sizeof(UChar)]; 42 | u_uastrcpy(patternUchar, pattern); 43 | 44 | UDateFormat* dfmt = udat_open( 45 | #if U_ICU_VERSION_MAJOR_NUM >= 5 46 | UDAT_PATTERN, // The dateStyle, UDAT_PATTERN means take it from the pattern supplied 47 | UDAT_PATTERN, // The timeStyle, UDAT_PATTERN means take it from the pattern supplied 48 | #else 49 | UDAT_IGNORE, 50 | UDAT_IGNORE, 51 | #endif 52 | localeId, // The localeId 53 | tzUchar, // The timezone Id 54 | -1, // The timezone Id len ( -1 because tzUchar is null terminated ) 55 | patternUchar, // The custom datetime pattern 56 | -1, // The pattern len ( -1 because patternUchar is null terminated ) 57 | &status // The output error status 58 | ); 59 | if (U_FAILURE(status)) { 60 | return status; 61 | } 62 | 63 | udat_format( 64 | dfmt, // The datetime formatter 65 | (UDate)epochMillis, // Millseconds since epoch 66 | outBuffer, // The output buffer to store the result 67 | resultCapacity, // Capacity of the output buffer 68 | NULL, // Position ( NULL because we want it from start ) 69 | &status // The output error status 70 | ); 71 | if (U_FAILURE(status)) { 72 | return status; 73 | } 74 | 75 | u_austrcpy(result, outBuffer); 76 | return status; 77 | } 78 | 79 | const UErrorCode getDateTimePattern(const char* localeId, 80 | UDateFormatStyle style, 81 | bool localized, 82 | char* const result, 83 | const size_t resultCapacity) { 84 | 85 | UErrorCode status = U_ZERO_ERROR; 86 | UChar outBuffer[resultCapacity]; 87 | 88 | UDateFormat* dfmt = udat_open( 89 | style, // The dateStyle 90 | style, // The timeStyle 91 | localeId, // The localeId 92 | 0, // The timezone Id 0 meaning use the default timezone 93 | -1, // The timezone Id len 94 | NULL, // The datetime pattern 95 | -1, // The pattern len 96 | &status // The output error status 97 | ); 98 | if (U_FAILURE(status)) { 99 | return status; 100 | } 101 | 102 | udat_toPattern( 103 | dfmt, // The datetime formatter 104 | localized, // localized 105 | outBuffer, // The output buffer to store the result 106 | resultCapacity, // Capacity of the output buffer 107 | &status // The output error status 108 | ); 109 | if (U_FAILURE(status)) { 110 | return status; 111 | } 112 | 113 | u_austrcpy(result, outBuffer); 114 | return status; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /scripts/license-header.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | """A tool to change or add license headers in all supported files in or below a directory.""" 5 | 6 | from __future__ import unicode_literals 7 | from __future__ import print_function 8 | 9 | 10 | import os 11 | import shutil 12 | import sys 13 | import logging 14 | import argparse 15 | import re 16 | import fnmatch 17 | from string import Template 18 | from shutil import copyfile 19 | import io 20 | import subprocess 21 | import os.path 22 | 23 | 24 | __version__ = '0.1' 25 | 26 | 27 | log = logging.getLogger(__name__) 28 | 29 | 30 | try: 31 | unicode 32 | except NameError: 33 | unicode = str 34 | 35 | 36 | 37 | ## for each processing type, the detailed settings of how to process files of that type 38 | typeSettings = { 39 | "go": { 40 | "extensions": [".go"], 41 | "keepFirst": None, 42 | "blockCommentStartPattern": None, ## used to find the beginning of a header bloc 43 | "blockCommentEndPattern": None, ## used to find the end of a header block 44 | "lineCommentStartPattern": re.compile(r'\s*//'), ## used to find header blocks made by line comments 45 | "lineCommentEndPattern": None, 46 | "headerStartLine": "", ## inserted before the first header text line 47 | "headerEndLine": "", ## inserted after the last header text line 48 | "headerLinePrefix": "// ", ## inserted before each header text line 49 | "headerLineSuffix": None, ## inserted after each header text line, but before the new line 50 | }, 51 | "java": { 52 | "extensions": [".java",".scala",".groovy",".jape"], 53 | "keepFirst": None, 54 | "blockCommentStartPattern": re.compile('^\s*/\*'), ## used to find the beginning of a header bloc 55 | "blockCommentEndPattern": re.compile(r'\*/\s*$'), ## used to find the end of a header block 56 | "lineCommentStartPattern": re.compile(r'\s*//'), ## used to find header blocks made by line comments 57 | "lineCommentEndPattern": None, 58 | "headerStartLine": "/*\n", ## inserted before the first header text line 59 | "headerEndLine": " */\n", ## inserted after the last header text line 60 | "headerLinePrefix": " * ", ## inserted before each header text line 61 | "headerLineSuffix": None, ## inserted after each header text line, but before the new line 62 | }, 63 | "C": { 64 | "extensions": [".c", ".h"], 65 | "keepFirst": None, 66 | "blockCommentStartPattern": None, ## used to find the beginning of a header bloc 67 | "blockCommentEndPattern": None, ## used to find the end of a header block 68 | "lineCommentStartPattern": re.compile(r'\s*//'), ## used to find header blocks made by line comments 69 | "lineCommentEndPattern": None, 70 | "headerStartLine": "", ## inserted before the first header text line 71 | "headerEndLine": "", ## inserted after the last header text line 72 | "headerLinePrefix": "// ", ## inserted before each header text line 73 | "headerLineSuffix": None, ## inserted after each header text line, but before the new line 74 | }, 75 | } 76 | 77 | yearsPattern = re.compile("Copyright\s*(?:\(\s*[C|c|©]\s*\)\s*)?([0-9][0-9][0-9][0-9](?:-[0-9][0-9]?[0-9]?[0-9]?))",re.IGNORECASE) 78 | licensePattern = re.compile("license",re.IGNORECASE) 79 | emptyPattern = re.compile(r'^\s*$') 80 | 81 | ## ----------------------- 82 | 83 | ## maps each extension to its processing type. Filled from tpeSettings during initialization 84 | ext2type = {} 85 | patterns = [] 86 | 87 | def parse_command_line(argv): 88 | """Parse command line argument. See -h option. 89 | 90 | Arguments: 91 | argv: arguments on the command line must include caller file name. 92 | 93 | """ 94 | import textwrap 95 | 96 | example = textwrap.dedent(""" 97 | ## Some examples of how to use this command! 98 | """).format(os.path.basename(argv[0])) 99 | formatter_class = argparse.RawDescriptionHelpFormatter 100 | parser = argparse.ArgumentParser(description="Python license header updater", 101 | epilog=example, 102 | formatter_class=formatter_class) 103 | parser.add_argument("-V", "--version", action="version", 104 | version="%(prog)s {}".format(__version__)) 105 | parser.add_argument("-v", "--verbose", dest="verbose_count", 106 | action="count", default=0, 107 | help="increases log verbosity (can be specified " 108 | "multiple times)") 109 | parser.add_argument("-d", "--dir", dest="dir", nargs=1, 110 | help="The directory to recursively process.") 111 | parser.add_argument("-t", "--tmpl", dest="tmpl", nargs=1, 112 | help="Template name or file to use.") 113 | parser.add_argument("-y", "--years", dest="years", nargs=1, 114 | help="Year or year range to use.") 115 | parser.add_argument("-o", "--owner", dest="owner", nargs=1, 116 | help="Name of copyright owner to use.") 117 | parser.add_argument("-n", "--projname", dest="projectname", nargs=1, 118 | help="Name of project to use.") 119 | parser.add_argument("-u", "--projurl", dest="projecturl", nargs=1, 120 | help="Url of project to use.") 121 | arguments = parser.parse_args(argv[1:]) 122 | 123 | # Sets log level to WARN going more verbose for each new -V. 124 | log.setLevel(max(3 - arguments.verbose_count, 0) * 10) 125 | return arguments 126 | 127 | exclude = set(["vendor", ".git"]) 128 | def get_paths(patterns, start_dir="."): 129 | """Retrieve files that match any of the glob patterns from the start_dir and below.""" 130 | for root, dirs, files in os.walk(start_dir): 131 | names = [] 132 | dirs[:] = [d for d in dirs if d not in exclude] 133 | for pattern in patterns: 134 | names += fnmatch.filter(files, pattern) 135 | for name in names: 136 | path = os.path.join(root, name) 137 | yield path 138 | 139 | ## return an array of lines, with all the variables replaced 140 | ## throws an error if a variable cannot be replaced 141 | def read_template(templateFile,dict): 142 | with open(templateFile,'r') as f: 143 | lines = f.readlines() 144 | lines = [Template(line).substitute(dict) for line in lines] ## use safe_substitute if we do not want an error 145 | return lines 146 | 147 | ## format the template lines for the given type 148 | def for_type(templatelines,type): 149 | lines = [] 150 | settings = typeSettings[type] 151 | headerStartLine = settings["headerStartLine"] 152 | headerEndLine = settings["headerEndLine"] 153 | headerLinePrefix = settings["headerLinePrefix"] 154 | headerLineSuffix = settings["headerLineSuffix"] 155 | if headerStartLine is not None: 156 | lines.append(headerStartLine) 157 | for l in templatelines: 158 | tmp = l 159 | if headerLinePrefix is not None: 160 | if len(tmp.strip()) == 0: 161 | tmp = headerLinePrefix.rstrip() + tmp 162 | else: 163 | tmp = headerLinePrefix + tmp 164 | if headerLineSuffix is not None: 165 | tmp = tmp + headerLineSuffix 166 | lines.append(tmp) 167 | if headerEndLine is not None: 168 | lines.append(headerEndLine) 169 | return lines 170 | 171 | 172 | ## read a file and return a dictionary with the following elements: 173 | ## lines: array of lines 174 | ## skip: number of lines at the beginning to skip (always keep them when replacing or adding something) 175 | ## can also be seen as the index of the first line not to skip 176 | ## headStart: index of first line of detected header, or None if non header detected 177 | ## headEnd: index of last line of detected header, or None 178 | ## yearsLine: index of line which contains the copyright years, or None 179 | ## haveLicense: found a line that matches a pattern that indicates this could be a license header 180 | ## settings: the type settings 181 | ## If the file is not supported, return None 182 | def read_file(file): 183 | skip = 0 184 | headStart = None 185 | headEnd = None 186 | yearsLine = None 187 | haveLicense = False 188 | extension = os.path.splitext(file)[1] 189 | logging.debug("File extension is %s",extension) 190 | ## if we have no entry in the mapping from extensions to processing type, return None 191 | type = ext2type.get(extension) 192 | logging.debug("Type for this file is %s",type) 193 | if not type: 194 | return (None, None) 195 | settings = typeSettings.get(type) 196 | with open(file,'r') as f: 197 | lines = f.readlines() 198 | ## now iterate throw the lines and try to determine the various indies 199 | ## first try to find the start of the header: skip over shebang or empty lines 200 | keepFirst = settings.get("keepFirst") 201 | blockCommentStartPattern = settings.get("blockCommentStartPattern") 202 | blockCommentEndPattern = settings.get("blockCommentEndPattern") 203 | lineCommentStartPattern = settings.get("lineCommentStartPattern") 204 | i = 0 205 | for line in lines: 206 | if i==0 and keepFirst and keepFirst.findall(line): 207 | skip = i+1 208 | elif emptyPattern.findall(line): 209 | pass 210 | elif blockCommentStartPattern and blockCommentStartPattern.findall(line): 211 | headStart = i 212 | break 213 | elif blockCommentStartPattern and lineCommentStartPattern.findall(line): 214 | pass 215 | elif not blockCommentStartPattern and lineCommentStartPattern.findall(line): 216 | headStart = i 217 | break 218 | else: 219 | ## we have reached something else, so no header in this file 220 | #logging.debug("Did not find the start giving up at lien %s, line is >%s<",i,line) 221 | return (type, {"lines":lines, "skip":skip, "headStart":None, "headEnd":None, "yearsLine": None, "settings":settings, "haveLicense": haveLicense}) 222 | i = i+1 223 | #logging.debug("Found preliminary start at %s",headStart) 224 | ## now we have either reached the end, or we are at a line where a block start or line comment occurred 225 | # if we have reached the end, return default dictionary without info 226 | if i == len(lines): 227 | #logging.debug("We have reached the end, did not find anything really") 228 | return (type, {"lines":lines, "skip":skip, "headStart":headStart, "headEnd":headEnd, "yearsLine": yearsLine, "settings":settings, "haveLicense": haveLicense}) 229 | # otherwise process the comment block until it ends 230 | if blockCommentStartPattern: 231 | for j in range(i,len(lines)): 232 | #logging.debug("Checking line %s",j) 233 | if licensePattern.findall(lines[j]): 234 | haveLicense = True 235 | elif blockCommentEndPattern.findall(lines[j]): 236 | return (type, {"lines":lines, "skip":skip, "headStart":headStart, "headEnd":j, "yearsLine": yearsLine, "settings":settings, "haveLicense": haveLicense}) 237 | elif yearsPattern.findall(lines[j]): 238 | haveLicense = True 239 | yearsLine = j 240 | # if we went through all the lines without finding an end, maybe we have some syntax error or some other 241 | # unusual situation, so lets return no header 242 | #logging.debug("Did not find the end of a block comment, returning no header") 243 | return (type, {"lines":lines, "skip":skip, "headStart":None, "headEnd":None, "yearsLine": None, "settings":settings, "haveLicense": haveLicense}) 244 | else: 245 | for j in range(i,len(lines)-1): 246 | if lineCommentStartPattern.findall(lines[j]) and licensePattern.findall(lines[j]): 247 | haveLicense = True 248 | elif not lineCommentStartPattern.findall(lines[j]): 249 | return (type, {"lines":lines, "skip":skip, "headStart":i, "headEnd":j-1, "yearsLine": yearsLine, "settings":settings, "haveLicense": haveLicense}) 250 | elif yearsPattern.findall(lines[j]): 251 | haveLicense = True 252 | yearsLine = j 253 | ## if we went through all the lines without finding the end of the block, it could be that the whole 254 | ## file only consisted of the header, so lets return the last line index 255 | return (type, {"lines":lines, "skip":skip, "headStart":i, "headEnd":len(lines)-1, "yearsLine": yearsLine, "settings":settings, "haveLicense": haveLicense}) 256 | 257 | def make_backup(file): 258 | copyfile(file,file+".bak") 259 | 260 | def main(): 261 | """Main function.""" 262 | logging.basicConfig(stream=sys.stderr, level=logging.INFO) 263 | ## init: create the ext2type mappings 264 | for type in typeSettings: 265 | settings = typeSettings[type] 266 | exts = settings["extensions"] 267 | for ext in exts: 268 | ext2type[ext] = type 269 | patterns.append("*"+ext) 270 | 271 | try: 272 | error = False 273 | settings = { 274 | } 275 | templateLines = None 276 | arguments = parse_command_line(sys.argv) 277 | if arguments.dir: 278 | start_dir = arguments.dir[0] 279 | else: 280 | start_dir = "." 281 | if arguments.years: 282 | settings["years"] = arguments.years[0] 283 | if arguments.owner: 284 | settings["owner"] = arguments.owner[0] 285 | if arguments.projectname: 286 | settings["projectname"] = arguments.projectname[0] 287 | if arguments.projecturl: 288 | settings["projecturl"] = arguments.projecturl[0] 289 | ## if we have a template name specified, try to get or load the template 290 | if arguments.tmpl: 291 | opt_tmpl = arguments.tmpl[0] 292 | ## first get all the names of our own templates 293 | ## for this get first the path of this file 294 | templatesDir = os.path.join(os.path.dirname(os.path.abspath(__file__)),"templates") 295 | ## get all the templates in the templates directory 296 | templates = [f for f in get_paths("*.tmpl",templatesDir)] 297 | templates = [(os.path.splitext(os.path.basename(t))[0],t) for t in templates] 298 | ## filter by trying to match the name against what was specified 299 | tmpls = [t for t in templates if opt_tmpl in t[0]] 300 | if len(tmpls) == 1: 301 | tmplName = tmpls[0][0] 302 | tmplFile = tmpls[0][1] 303 | templateLines = read_template(tmplFile,settings) 304 | else: 305 | if len(tmpls) == 0: 306 | ## check if we can interpret the option as file 307 | if os.path.isfile(opt_tmpl): 308 | templateLines = read_template(os.path.abspath(opt_tmpl),settings) 309 | else: 310 | error = True 311 | else: 312 | ## notify that there are multiple matching templates 313 | error = True 314 | else: # no tmpl parameter 315 | if not arguments.years: 316 | error = True 317 | if not error: 318 | #logging.debug("Got template lines: %s",templateLines) 319 | ## now do the actual processing: if we did not get some error, we have a template loaded or no template at all 320 | ## if we have no template, then we will have the years. 321 | ## now process all the files and either replace the years or replace/add the header 322 | logging.debug("Processing directory %s",start_dir) 323 | logging.debug("Patterns: %s",patterns) 324 | for file in get_paths(patterns,start_dir): 325 | logging.debug("Processing file: %s",file) 326 | (type, dict) = read_file(file) 327 | if not dict: 328 | logging.debug("File not supported %s",file) 329 | continue 330 | # logging.debug("DICT for the file: %s",dict) 331 | logging.debug("Info for the file: headStart=%s, headEnd=%s, haveLicense=%s, skip=%s",dict["headStart"],dict["headEnd"],dict["haveLicense"],dict["skip"]) 332 | lines = dict["lines"] 333 | ## if we have a template: replace or add 334 | if templateLines: 335 | # make_backup(file) 336 | with open(file,'w') as fw: 337 | ## if we found a header, replace it 338 | ## otherwise, add it after the lines to skip 339 | headStart = dict["headStart"] 340 | headEnd = dict["headEnd"] 341 | haveLicense = dict["haveLicense"] 342 | skip = dict["skip"] 343 | if headStart is not None and headEnd is not None and haveLicense: 344 | ## first write the lines before the header 345 | fw.writelines(lines[0:headStart]) 346 | ## now write the new header from the template lines 347 | fw.writelines(for_type(templateLines,type)) 348 | ## now write the rest of the lines 349 | fw.writelines(lines[headEnd+1:]) 350 | else: 351 | fw.writelines(lines[0:skip]) 352 | fw.writelines(for_type(templateLines, type)) 353 | fw.write("\n") 354 | fw.writelines(lines[skip:]) 355 | else: ## no template lines, just update the line with the year, if we found a year 356 | yearsLine = dict["yearsLine"] 357 | if yearsLine is not None: 358 | # make_backup(file) 359 | with open(file,'w') as fw: 360 | fw.writelines(lines[0:yearsLine]) 361 | fw.write(yearsPattern.sub(arguments.years,lines[yearsLine])) 362 | finally: 363 | logging.shutdown() 364 | 365 | 366 | if __name__ == "__main__": 367 | sys.exit(main()) 368 | --------------------------------------------------------------------------------