Sign In
Sign In

Working with Date and Time in Go Using the time Package

Working with Date and Time in Go Using the time Package
Hostman Team
Technical writer
Go
28.01.2025
Reading time: 19 min

Go (Golang), like many other programming languages, has a built-in time package that provides special types and methods for working with dates and times.

You can find comprehensive information about the time package in the official documentation. This guide will cover the basic aspects of working with time in Go. 

All the examples shown were run on a cloud server provided by Hostman, using the Ubuntu 22.04 operating system and Go version 1.21.3.

It is assumed that you are already familiar with the basics of Go and know how to run scripts using the appropriate interpreter command:

go run script.go

Parsing, Formatting, and Creating Dates

Before getting started with time manipulation, it's important to understand a key feature of time formatting in Go.

In most programming languages, date and time formats are specified using special symbols, which are replaced by values representing day, month, year, hour, minute, and second.

However, Go approaches this differently. Instead of special symbols, it uses default date and time values represented by an increasing sequence of numbers:

01-02-03-04-05-06

This sequence of numbers represents:

  1. 1st month of the year (January)
  2. 2nd day of the month
  3. 3rd hour in 12-hour format (p.m.)
  4. 4th minute in 12-hour format (p.m.)
  5. 5th second in 12-hour format (p.m.)
  6. 6th year of the 21st century

Thus, this results in the following time format:

January 2nd, 3:04:05 PM, 2006

Or in another form: 02.01.2006 03:04:05 PM

It is important to remember that this value is nothing more than a regular increasing sequence of numbers without any special significance.

Therefore, this date and time act as a predefined layout for working with any explicitly specified date and time values.

For example, here’s an abstract (not Go-specific) pseudocode example:

currentTime = time.now()

console.write("Current date: ", currentTime.format("%D.%M.%Y"))  
console.write("Current time: ", currentTime.format("%H:%M"))  
console.write("Current date and time: ", currentTime.format("%D.%M.%Y %H:%M"))

In our pseudo-console, this would produce the following pseudo-output:

Current date: 26.11.2024  
Current time: 14:05  
Current date and time: 26.11.2024 14:05

This is how date and time formatting works in most programming languages.

In Go, however, the pseudocode would look like this:

currentTime = time.now()

console.write("Current date: ", currentTime.format("02.01.2006"))  
console.write("Current time: ", currentTime.format("03:04"))  
console.write("Current date and time: ", currentTime.format("02.01.2006 03:04"))

The console output would be similar:

Current date: 26.11.2024  
Current time: 14:05  
Current date and time: 26.11.2024 14:05

Here, the standard template values for date and time are automatically replaced with the actual date and time values.

Additionally, template values have certain variations. For instance, you can specify the month 01 as Jan.

Thanks to this approach, Go allows templates to be defined in a more intuitive and human-readable way.

Parsing

Working with time in Go starts by explicitly specifying it. This can be done using the time parsing function:

package main

import (
    "fmt"      // package for console I/O
    "time"     // package for working with time
    "reflect"  // package for determining variable types
)

func main() {
    timeLayout := "2006-01-02"        // time layout template
    timeValue := "2024-11-16"         // time value to be parsed

    timeVariable, err := time.Parse(timeLayout, timeValue)  // parsing time value using the template
    if err != nil { 
        panic(err)  // handling possible parsing errors
    }

    fmt.Println(timeVariable)                 // output the parsed time variable to the console
    fmt.Println(reflect.TypeOf(timeVariable)) // output the type of the time variable
}

When you run the script, the terminal will display the following output:

2024-11-16 00:00:00 +0000 UTC  
time.Time

Note that after parsing, a variable of type time.Time is created. This variable stores the parsed time value in its internal format.

In the example shown, the time layout and value could be replaced with another equivalent format.

func main() {
timeLayout := "2006-Jan-02"
timeValue:= "2024-Nov-16"
...

The final result would remain the same.

During parsing, an additional parameter can be specified to set the time zone, also known as the time offset or time zone:

package main

import (
	"fmt"
	"time"
)

func main() {
	// Local

	timeLocation, err := time.LoadLocation("Local")
	if err != nil { panic(err) }

	timeVariable, err := time.ParseInLocation("2006-01-02 15:04", "2024-11-16 07:45", timeLocation)
	if err != nil { panic(err) }

	fmt.Println("Local: ", timeVariable)

	// Asia/Bangkok

	timeLocation, err = time.LoadLocation("Asia/Bangkok")
	if err != nil { panic(err) }

	timeVariable, err = time.ParseInLocation("2006-01-02 15:04", "2024-11-16 07:45", timeLocation)
	if err != nil { panic(err) }
 
	fmt.Println("Asia/Bangkok: ", timeVariable)

	// Europe/Nicosia

	timeLocation, err = time.LoadLocation("Europe/Nicosia")
	if err != nil { panic(err) }

	timeVariable, err = time.ParseInLocation("2006-01-02 15:04", "2024-11-16 07:45", timeLocation)
	if err != nil { panic(err) }
 
	fmt.Println("Europe/Nicosia: ", timeVariable)
}

The console output of this script will be as follows:

Local:  2024-11-16 07:45:00 +0000 UTC
Asia/Bangkok:  2024-11-16 07:45:00 +0700 +07
Europe/Nicosia:  2024-11-16 07:45:00 +0300 EET

Instead of explicitly creating a time zone variable, you can use a predefined constant:

package main

import (
	"fmt"
	"time"
)

func main() {
	// time.LoadLocation("Local")

	timeLocation, err := time.LoadLocation("Local")
	if err != nil { panic(err) }

	timeVariable, err := time.ParseInLocation("2006-01-02 15:04", "2024-11-16 07:45", timeLocation)
	if err != nil { panic(err) }

	fmt.Println(timeVariable)

	// time.Local

	timeVariable, err = time.ParseInLocation("2006-01-02 15:04", "2024-11-16 07:45", time.Local)
	if err != nil { panic(err) }

	fmt.Println(timeVariable)
}

In this case, the complete date and time values in both variants will be identical.

2024-11-16 07:45:00 +0000 UTC
2024-11-16 07:45:00 +0000 UTC

You can find a complete list of available time zones in the so-called Time Zone Database (tz database).

Time zone identifiers are specified using two region names separated by a slash. For example:

  • Europe/Nicosia
  • Asia/Dubai
  • US/Alaska

Formatting

We can format an already created time variable to represent its value as a specific text string.

Thus, a variable of type time.Time has built-in methods for converting date and time into a string type.

package main

import (
	"fmt"
	"time"
)

func main() {
	timeLayout := "2006-01-02 15:04:05"
	timeValue := "2024-11-15 12:45:20"
 
	timeVariable, err := time.Parse(timeLayout, timeValue)
	if err != nil { panic(err) }

	fmt.Print("\r", "DATE", "\r\n")

	fmt.Println(timeVariable.Format("2006-01-02"))
	fmt.Println(timeVariable.Format("01/02/06"))
	fmt.Println(timeVariable.Format("01/02/2006"))
	fmt.Println(timeVariable.Format("20060102"))
	fmt.Println(timeVariable.Format("010206"))
	fmt.Println(timeVariable.Format("January 02, 2006"))
	fmt.Println(timeVariable.Format("02 January 2006"))
	fmt.Println(timeVariable.Format("02-Jan-2006"))
	fmt.Println(timeVariable.Format("Jan-02-06"))
	fmt.Println(timeVariable.Format("Jan-02-2006"))
	fmt.Println(timeVariable.Format("06"))
	fmt.Println(timeVariable.Format("Mon"))
	fmt.Println(timeVariable.Format("Monday"))
	fmt.Println(timeVariable.Format("Jan-06"))

	fmt.Print("\r", "TIME", "\r\n")

	fmt.Println(timeVariable.Format("15:04"))
	fmt.Println(timeVariable.Format("15:04:05"))
	fmt.Println(timeVariable.Format("3:04 PM"))
	fmt.Println(timeVariable.Format("03:04:05 PM"))

	fmt.Print("\r", "DATE and TIME", "\r\n")

	fmt.Println(timeVariable.Format("2006-01-02T15:04:05"))
	fmt.Println(timeVariable.Format("2 Jan 2006 15:04:05"))
	fmt.Println(timeVariable.Format("2 Jan 2006 15:04"))
	fmt.Println(timeVariable.Format("Mon, 2 Jan 2006 15:04:05 MST"))

	fmt.Print("\r", "PREDEFINED FORMATS", "\r\n")

	fmt.Println(timeVariable.Format(time.RFC1123)) // predefined format
	fmt.Println(timeVariable.Format(time.Kitchen)) // predefined format
	fmt.Println(timeVariable.Format(time.Stamp)) // predefined format
	fmt.Println(timeVariable.Format(time.DateOnly)) // predefined format
}

Running this script will output various possible date and time formats in the terminal:

DATE

2024-11-15
11/15/24
11/15/2024
20241115
111524
November 15, 2024
15 November 2024
15-Nov-2024
Nov-15-24
Nov-15-2024
24
Fri
Friday
Nov-24

TIME

12:45
12:45:20
12:45 PM
12:45:20 PM

DATE and TIME

2024-11-15T12:45:20
15 Nov 2024 12:45:20
15 Nov 2024 12:45
Fri, 15 Nov 2024 12:45:20 UTC

PREDEFINED FORMATS

Fri, 15 Nov 2024 12:45:20 UTC
12:45PM
Nov 15 12:45:20
2024-11-15

Pay attention to the last few formats, which are predefined as constant values. These constants provide commonly used date and time formats in a convenient, ready-to-use form.

You can find a complete list of these constants in the official documentation.

time.Layout

01/02 03:04:05PM '06 -0700

time.ANSIC

Mon Jan _2 15:04:05 2006

time.UnixDate

Mon Jan _2 15:04:05 MST 2006

time.RubyDate

Mon Jan 02 15:04:05 -0700 2006

time.RFC822

02 Jan 06 15:04 MST

time.RFC822Z

02 Jan 06 15:04 -0700

time.RFC850

Monday, 02-Jan-06 15:04:05 MST

time.RFC1123

Mon, 02 Jan 2006 15:04:05 MST

time.RFC1123Z

Mon, 02 Jan 2006 15:04:05 -0700

time.RFC3339

2006-01-02T15:04:05Z07:00

time.RFC3339Nano

2006-01-02T15:04:05.999999999Z07:00

time.Kitchen

3:04PM

time.Stamp

Jan _2 15:04:05

time.StampMilli

Jan _2 15:04:05.000

time.StampMicro

Jan _2 15:04:05.000000

time.StampNano

Jan _2 15:04:05.000000000

time.DateTime

2006-01-02 15:04:05

time.DateOnly

2006-01-02

time.TimeOnly

15:04:05

Another common method to format date and time in Go is by converting it to Unix time. 

package main

import (
	"fmt"
	"time"
	"reflect"
)
func main() {
    timeVariable := time.Unix(350, 50) // set Unix time to 350 seconds and 50 nanoseconds from January 1, 1970, 00:00:00

    fmt.Println("Time:", timeVariable) // display time in UTC format

    timeUnix := timeVariable.Unix()
    timeUnixNano := timeVariable.UnixNano()

    fmt.Println("Time (UNIX, seconds):", timeUnix) // display time in Unix format (seconds)
    fmt.Println("Time (UNIX, nanoseconds):", timeUnixNano) // display time in Unix format (nanoseconds)
    fmt.Println("Time (type):", reflect.TypeOf(timeUnix)) // display the variable type for Unix time
}

After running this script, the following output will appear in the terminal:

Time:  1970-01-01 00:05:50.00000005 +0000 UTC  
Time (UNIX, seconds):  350  
Time (UNIX, nanoseconds):  350000000050  
Time (type):  int64

Note that the variable created to store the Unix time value is of type int64, not time.Time.

Thus, by using formatting, you can perform conversions between string-based time and Unix time and vice versa:

package main

import (
	"fmt"
	"time"
)

func main() {
	timeString, _ := time.Parse("2006-01-02 15:04:05", "2024-11-15 12:45:20")
	fmt.Println(timeString.Unix())

	timeUnix := time.Unix(12345, 50)
	fmt.Println(timeUnix.Format("2006-01-02 15:04:05"))
}

The console output of this script will display the results of conversions to and from Unix time:

1731674720
1970-01-01 03:25:45

Creation

In Go, there is a more straightforward way to create a time.Time variable by explicitly setting the date and time parameters:

package main

import (
    "fmt"
    "time"
)

func main() {
    timeLocation, _ := time.LoadLocation("Europe/Vienna")

    // year, month, day, hour, minute, second, nanosecond, time zone
    timeVariable := time.Date(2024, 11, 20, 12, 30, 45, 50, timeLocation)

    fmt.Print(timeVariable)
}

After running this script, the following output will appear in the terminal:

2024-11-20 12:30:45.00000005 +0100 CET

Current Date and Time

In addition to manually setting arbitrary dates and times, you can set the current date and time:

package main

import (
    "fmt"
    "time"
    "reflect"
)

func main() {
    timeNow := time.Now()

    fmt.Println(timeNow)
    fmt.Println(timeNow.Format(time.DateTime))
    fmt.Println(timeNow.Unix())
    fmt.Println(reflect.TypeOf(timeNow))
}

After running this script, the following output will appear in the terminal:

2024-11-27 17:08:18.195495127 +0000 UTC m=+0.000035621
2024-11-27 17:08:18
1732727298
time.Time

As you can see, the time.Now() function creates the familiar time.Time variable, whose values can be formatted arbitrarily.

Extracting Parameters

The time.Time variable consists of several parameters that together form the date and time:

  • Year
  • Month
  • Day
  • Weekday
  • Hour
  • Minute
  • Second
  • Nanosecond
  • Time zone

Go provides a set of methods to extract and modify each of these parameters.

Most often, you will need to retrieve specific parameters from an already created time variable:

package main

import (
    "fmt"
    "time"
    "reflect"
)

func main() {
    timeLayout := "2006-01-02 15:04:05"
    timeValue := "2024-11-15 12:45:20"

    timeVariable, _ := time.Parse(timeLayout, timeValue)

    fmt.Println("Year:", timeVariable.Year())
    fmt.Println("Month:", timeVariable.Month())
    fmt.Println("Day:", timeVariable.Day())
    fmt.Println("Weekday:", timeVariable.Weekday())
    fmt.Println("Hour:", timeVariable.Hour())
    fmt.Println("Minute:", timeVariable.Minute())
    fmt.Println("Second:", timeVariable.Second())
    fmt.Println("Nanosecond:", timeVariable.Nanosecond())
    fmt.Println("Time zone:", timeVariable.Location())

    fmt.Println("")

    fmt.Println("Year (type):", reflect.TypeOf(timeVariable.Year()))
    fmt.Println("Month (type):", reflect.TypeOf(timeVariable.Month()))
    fmt.Println("Day (type):", reflect.TypeOf(timeVariable.Day()))
    fmt.Println("Weekday (type):", reflect.TypeOf(timeVariable.Weekday()))
    fmt.Println("Hour (type):", reflect.TypeOf(timeVariable.Hour()))
    fmt.Println("Minute (type):", reflect.TypeOf(timeVariable.Minute()))
    fmt.Println("Second (type):", reflect.TypeOf(timeVariable.Second()))
    fmt.Println("Nanosecond (type):", reflect.TypeOf(timeVariable.Nanosecond()))
    fmt.Println("Time zone (type):", reflect.TypeOf(timeVariable.Location()))
}

The console output of this script will be:

Year: 2024
Month: November
Day: 15
Weekday: Friday
Hour: 12
Minute: 45
Second: 20
Nanosecond: 0
Time zone: UTC

Year (type): int
Month (type): time.Month
Day (type): int
Weekday (type): time.Weekday
Hour (type): int
Minute (type): int
Second (type): int
Nanosecond (type): int
Time zone (type): *time.Location

Thus, you can individually retrieve specific information about the date and time without needing to format the output before displaying it in the console.

Note the types of the retrieved variables — all of them have the int type except for a few:

  • Month (time.Month)
  • Weekday (time.Weekday)
  • Time zone (*time.Location)

The last one (time zone) is a pointer.

Modification, Addition, and Subtraction

Modification

You cannot change the parameters of date and time directly in an already created time.Time variable. However, you can recreate the variable with updated values, thus changing the existing date and time:

package main

import (
	"fmt"
	"time"
)

func main() {
	timeVariable := time.Now()
	fmt.Println(timeVariable)

	// year, month, day, hour, minute, second, nanosecond, time zone
	timeChanged := time.Date(timeVariable.Year(), timeVariable.Month(), timeVariable.Day(), timeVariable.Hour() + 14, timeVariable.Minute(), timeVariable.Second(), timeVariable.Nanosecond(), timeVariable.Location())
	fmt.Println(timeChanged)
}

When running this script, the following output will appear:

2024-11-28 14:35:05.287957345 +0000 UTC m=+0.000039131
2024-11-29 04:35:05.287957345 +0000 UTC

In this example, 14 hours were added to the current time. This way, you can selectively update the time values in an existing time.Time variable.

Change by Time Zone

Sometimes, it is necessary to determine what the specified date and time will be in a different time zone. For this, Go provides a special method:

package main

import (
	"fmt"
	"time"
)

func main() {
	locationFirst, _ := time.LoadLocation("Europe/Nicosia")
	timeFirst := time.Date(2000, 1, 1, 0, 0, 0, 0, locationFirst)

	fmt.Println("Time (Europe/Nicosia)", timeFirst)

	locationSecond, _ := time.LoadLocation("America/Chicago")
	timeSecond := timeFirst.In(locationSecond) // changing the time zone and converting the date and time based on it

	fmt.Println("Time (America/Chicago)", timeSecond)
}

The result of running the script will produce the following console output:

Time (Europe/Nicosia) 2000-01-01 00:00:00 +0200 EET 
Time (America/Chicago) 1999-12-31 16:00:00 -0600 CST

Thus, we obtain new date and time values, updated according to the newly specified time zone.

Addition and Subtraction

Go does not have separate methods for date and time addition. Instead, you can add time intervals to an already created time.Time variable:

package main

import (
	"fmt"
	"time"
)

func main() {
	// current time
	timeVariable := time.Now()
	fmt.Println(timeVariable)

	// adding 5 days (24 hours * 5 days = 120 hours)
	timeChanged := timeVariable.Add(120 * time.Hour)
	fmt.Println(timeChanged)

	// subtracting 65 days (24 hours * 65 days = 1560 hours)
	timeChanged = timeVariable.Add(-1560 * time.Hour)
	fmt.Println(timeChanged)
}

Running this script will give the following output:

2024-12-05 08:42:01.927334604 +0000 UTC m=+0.000035141
2024-12-10 08:42:01.927334604 +0000 UTC m=+432000.000035141
2024-10-01 08:42:01.927334604 +0000 UTC m=-5615999.999964859

Note that when subtracting a sufficient number of days from the time.Time variable, the month is also modified.

Also, the time.Hour variable actually has a special type, time.Duration:

package main

import (
	"fmt"
	"time"
	"reflect"
)

func main() {
	fmt.Println(reflect.TypeOf(time.Hour))
	fmt.Println(reflect.TypeOf(120* time.Hour))
}

The output after running the script will be:

time.Duration
time.Duration

However, modifying the date and time by adding or subtracting a large number of hours is not very clear. In some cases, it is better to use more advanced methods for changing the time:

package main

import (
	"fmt"
	"time"
)

func main() {
	timeVariable := time.Now()
	fmt.Println(timeVariable)

	// year, month, day
	timeChanged := timeVariable.AddDate(3, 2, 1)
	fmt.Println(timeChanged)

	// day
	timeChanged = timeChanged.AddDate(0, 0, 15)
	fmt.Println(timeChanged)

	// year, month
	timeChanged = timeChanged.AddDate(5, 1, 0)
	fmt.Println(timeChanged)

	// -year, -day
	timeChanged = timeChanged.AddDate(-2, 0, -10)
	fmt.Println(timeChanged)
}

After running this script, the output will look like this:

2024-11-28 17:51:45.769245873 +0000 UTC m=+0.000024921
2028-01-29 17:51:45.769245873 +0000 UTC
2028-02-13 17:51:45.769245873 +0000 UTC
2033-03-13 17:51:45.769245873 +0000 UTC
2031-03-03 17:51:45.769245873 +0000 UTC

Subtraction

Unlike addition, Go has specialized methods for subtracting one time.Time variable from another.

package main

import (
	"fmt"
	"time"
	"reflect"
)

func main() {
	timeFirst := time.Date(2024, 6, 14, 0, 0, 0, 0, time.Local)
	timeSecond := time.Date(2010, 3, 26, 0, 0, 0, 0, time.Local)

	timeDeltaSub := timeFirst.Sub(timeSecond) // timeFirst - timeSecond
	timeDeltaSince := time.Since(timeFirst) // time.Now() - timeFirst
	timeDeltaUntil := time.Until(timeFirst) // timeFirst - time.Now()

	fmt.Println("timeFirst - timeSecond =", timeDeltaSub)
	fmt.Println("time.Now() - timeFirst =", timeDeltaSince)
	fmt.Println("timeFirst - time.Now() =", timeDeltaUntil)

	fmt.Println("")

	fmt.Println(reflect.TypeOf(timeDeltaSub))
	fmt.Println(reflect.TypeOf(timeDeltaSince))
	fmt.Println(reflect.TypeOf(timeDeltaUntil))
}

Console output:

timeFirst - timeSecond = 124656h0m0s
time.Now() - timeFirst = 4029h37m55.577746026s
timeFirst - time.Now() = -4029h37m55.577746176s

time.Duration
time.Duration
time.Duration

As you can see, the result of the subtraction is the familiar time.Duration type variable.

In fact, the main function for finding the difference is time.Time.Sub(), and the other two are just its derivatives:

package main

import (
	"fmt"
	"time"
)

func main() {
	timeVariable := time.Date(2024, 6, 14, 0, 0, 0, 0, time.Local)

	fmt.Println(time.Now().Sub(timeVariable))
	fmt.Println(time.Since(timeVariable))

	fmt.Println("")

	fmt.Println(timeVariable.Sub(time.Now()))
	fmt.Println(time.Until(timeVariable))
}

Console output:

4046h10m53.144212707s
4046h10m53.144254987s

-4046h10m53.144261117s
-4046h10m53.144267597s

You can see that the results of these described functions are identical.

  • time.Time.Since() = time.Now().Sub(timeVariable)
  • time.Time.Until() = timeVariable.Sub(time.Now())

Time Durations

Individual time intervals (durations) in the time package are represented as a special variable of type time.Duration. Unlike time.Time, they store not full date and time but time intervals.

With durations, you can perform some basic operations that modify their time parameters.

Parsing Durations

A duration is explicitly defined using a string containing time parameters:

package main

import (
	"fmt"
	"time"
)

func main() {
	// hours, minutes, seconds
	durationHMS, _ := time.ParseDuration("4h30m20s")
	fmt.Println("Duration (HMS):", durationHMS)

	// minutes, seconds
	durationMS, _ := time.ParseDuration("6m15s")
	fmt.Println("Duration (MS):", durationMS)

	// hours, minutes
	durationHM, _ := time.ParseDuration("2h45m")
	fmt.Println("Duration (HM):", durationHM)

	// hours, seconds
	durationHS, _ := time.ParseDuration("2h10s")
	fmt.Println("Duration (HS):", durationHS)

	// hours, minutes, seconds, milliseconds, microseconds, nanoseconds
	durationFULL, _ := time.ParseDuration("6h50m40s30ms4µs3ns")
	fmt.Println("Full Duration:", durationFULL)
}

Output of the script:

Duration (HMS): 4h30m20s  
Duration (MS): 6m15s  
Duration (HM): 2h45m0s  
Duration (HS): 2h0m10s  
Full Duration: 6h50m40.030004003s

Note the last duration, which contains all possible time parameters in decreasing order of magnitude—hours, minutes, seconds, milliseconds, microseconds, and nanoseconds.

During parsing, each parameter is specified using the following keywords:

  • Hours — h
  • Minutes — m
  • Seconds — s
  • Milliseconds — ms
  • Microseconds — µs
  • Nanoseconds — ns

Moreover, the order of specifying duration parameters does not affect it:

package main

import (
	"fmt"
	"time"
)

func main() {
	duration, _ := time.ParseDuration("7ms20s4h30m")
	fmt.Println("Duration:", duration)
}

Terminal output:

Duration: 4h30m20.007s

Formatting Durations

In Go, we can represent the same duration in different units of measurement:

package main

import (
	"fmt"
	"time"
	"reflect"
)

func main() {
	duration, _ := time.ParseDuration("4h30m20s")

	fmt.Println("Duration:", duration)

	fmt.Println("")

	fmt.Println("In hours:", duration.Hours())
	fmt.Println("In minutes:", duration.Minutes())
	fmt.Println("In seconds:", duration.Seconds())
	fmt.Println("In milliseconds:", duration.Milliseconds())
	fmt.Println("In microseconds:", duration.Microseconds())
	fmt.Println("In nanoseconds:", duration.Nanoseconds())

	fmt.Println("")

	fmt.Println(reflect.TypeOf(duration.Hours()))
	fmt.Println(reflect.TypeOf(duration.Minutes()))
	fmt.Println(reflect.TypeOf(duration.Seconds()))
	fmt.Println(reflect.TypeOf(duration.Milliseconds()))
	fmt.Println(reflect.TypeOf(duration.Microseconds()))
	fmt.Println(reflect.TypeOf(duration.Nanoseconds()))
}

Output of the script:

Duration: 4h30m20s

In hours: 4.5055555555555555  
In minutes: 270.3333333333333  
In seconds: 16220  
In milliseconds: 16220000  
In microseconds: 16220000000  
In nanoseconds: 16220000000000  

float64  
float64  
float64  
int64  
int64  
int64

As you can see, the parameters for hours, minutes, and seconds are of type float64, while the rest are of type int.

Conclusion

This guide covered the basic functions for working with dates and times in the Go programming language, all of which are part of the built-in time package.

Thus, Go allows you to:

  • Format dates and times
  • Convert dates and times
  • Set time zones
  • Extract specific date and time parameters
  • Set specific date and time parameters
  • Add and subtract dates and times
  • Execute code based on specific time settings

For more detailed information on working with the time package, refer to the official Go documentation.

In addition, you can deploy Go applications (such as Beego and Gin) on our app platform.

Go
28.01.2025
Reading time: 19 min

Similar

Go

Variables in Go

Variables are named values stored in specific areas of memory and used during program execution. Go (also known as Golang) is a statically typed programming language. This means that once a variable is declared, its type is fixed and cannot be changed. Variables can have various types, each with its own purpose and characteristics.Go provides several basic data types, which form the foundation of the language's logic: Integer Floating-point String Boolean Additionally, Go supports composite data types: Arrays Slices Structures Maps There are also several auxiliary types: Pointers Interfaces Besides these, Go (similar to C++) includes a Standard Library (std) containing many predefined types. You can find more detailed information about variable types in Go in a separate article. For instructions on installing Go on Linux, Windows, or macOS, refer to the Hostman guides.  All the code examples in this tutorial were tested using Go version 1.21.3. Compiling and Running Code All the code examples in this guide are run in separate files with the .go extension. First, create a new file: sudo nano example.go Next, fill it with code inside the main() function, including any necessary modules: package main import "fmt" func main() { // start of example var number int = 10 fmt.Println(number) // end of example } Then run the file: go run example.go Declaring a Variable There are different ways to declare a variable in Go before using it—ranging from a full form, explicitly specifying the parameters (or multiple parameters) of the variable, to a shorthand form that uses automatic type inference and initialization. The choice of declaration method depends on the context. However, it’s generally recommended to use the most concise and automatic form whenever possible, as this reduces the likelihood of programmer errors by shifting some of the responsibility to the language's interpreter. Using the var Keyword The most explicit way to declare a variable in Golang is by using the var keyword, followed by the variable name, type, and value: var some_variable int = 5 However, if the variable is initialized with a value, you can omit the explicit type: var some_variable = 5 You can also declare a variable without assigning a value, but in this case, you must specify the type: var some_variable intsome_variable = 5 In all of these examples: var — the keyword for declaring a variable some_variable — the variable's name int — the variable's type 5 — the variable's value For example, this is how you can declare string variables: var some_name string = "John" The following declaration will result in an error: // ERROR: no value or type specified during declarationvar some_namesome_name = "John" It’s important to note that type inference is only possible during the initial declaration of the variable when the interpreter allocates the appropriate amount of memory for its value. Short Form := Despite Go's strict static typing, it allows variables to be declared in a more concise form without explicitly specifying their parameters: some_variable := 5 In this case, the interpreter understands that it needs to automatically infer the variable type based on the assigned value. However, this shorthand declaration is only allowed inside a function (including main()); it cannot be used outside a function: package main // ERROR: short form declaration outside of a function some_variable := 5 func main() { // OK: short form declaration inside a function other_variable := 10 } It’s important to understand the distinction between declaring a variable (with initialization) and assigning a value to it: package main func main() { some_variable := 5 // this is declaration and initialization (colon is present) some_variable = 50 // this is assignment (no colon) other_variable = 7 // ERROR: this is assignment (no colon) to an undeclared variable } For example, you can declare (and initialize) several variables sequentially: age := 50 // variable of type int name := "John" // variable of type string occupation := "Just a guy" // variable of type string height := 190.5 // variable of type float32 You cannot use the := operator together with the var keyword. Doing so will result in an error: var someVariable int := 5 // ERRORvar someVariable := 5 // ERROR Excluding the var keyword but still explicitly specifying the type will still result in an error: someVariable int := 5 // ERROR Multiple Variables In Go, you can declare multiple variables in one line or block. For example, you can use the var keyword with a single type for all declared variables: var width, height, depth int = 100, 200, 300 You can also separate the declaration of variables and their assignment: var width, height, depth intwidth, height, depth = 100, 200, 300 If the variable types differ, the interpreter can automatically infer their types: var name, age, fired = "John", 50, false Similarly, you can use the short form for multiple variables: name, age, fired := "John", 50, false In this case, there is no var keyword, nor are the types of the variables specified. Another way to declare multiple variables is by using a block: var ( name string = "John" age int = 50 height float64 = 190 fired bool = false ) By the way, you can format block declarations using spaces in such a way that names, types, and values align in columns, improving code readability: var ( name string = "John" age int = 50 height float64 = 190.5 fired bool = false ) The block declaration has no particular utility significance. It’s just syntactic sugar that: Improves code readability by grouping important variables in one place. Improves code cleanliness by avoiding repeated use of the var keyword for each variable. Improves code maintainability by simplifying the search and modification of variable parameters. Thus, block declaration is justified only when you need to group several key variables, simplifying their visual perception in a code editor. No Initialization In Go, it is possible to create a variable without initializing it. In this case, the variable is assigned a zero value corresponding to the specified type: For int, float32, float64: 0, 0.0, 0.0 For bool: false For string: "" For pointers: nil We can demonstrate this behavior of Go regarding variable declaration and initialization in the following script: package main import "fmt" func main() { // Integer var numberInt int fmt.Println("Integer:", numberInt) // Floating-point number var numberFloat float32 fmt.Println("Floating-point number:", numberFloat) // String var text string fmt.Println("String:", text) // Boolean var condition bool fmt.Println("Boolean:", condition) // Array var array [5]int fmt.Println("Array:", array) // Slice var cut []int fmt.Println("Slice:", cut) // Struct type S struct { name string size int address string } var structure S fmt.Println("Struct:", structure) // Map var dictionary map[int]int fmt.Println("Map:", dictionary) // Pointer var pointer *int fmt.Println("Pointer:", pointer) } The console output will be as follows: Integer: 0 Floating-point number: 0 String: Boolean: false Array: [0 0 0 0 0] Slice: [] Struct: { 0} Map: map[] Pointer: <nil> As you can see, variables of different types are automatically initialized with zero (or empty) values wherever possible. Naming Conventions In Golang, variable names can either start with a Latin letter or an underscore (_): onething := 123 // OK Onething := 123 // OK _onething := 123 // OK __onething := 123 // OK 1thing := 123 // ERROR Additionally, variable names have a functional feature: names starting with an uppercase letter are visible in other packages, while names starting with a lowercase letter are not. There are also several universal naming conventions across programming languages, including Go: Snake Case Camel Case Pascal Case Kebab Case (not supported in Go) Snake Case In Snake Case, the variable name looks like this: some_random_variable := 123 // lowercaseSOME_RANDOM_VARIABLE := 123 // uppercase Camel Case In Camel Case, the variable name looks like this: someRandomVariable := 12 Pascal Case In Pascal Case, the variable name looks like this: SomeRandomVariable := 123 Kebab Case In Kebab Case, the variable name looks like this: // ERRORsome-random-variable := 123 // lowercaseSOME-RANDOM-VARIABLE := 123 // uppercase However, Go doesn't support the Kebab Case style due to the hyphen character, which is reserved for the subtraction operation. Example: Declaring Multiple Variables Let’s further explore all the aforementioned ways of declaring variables in Golang in this script example: package main import "fmt" func main() { // Explicit declaration with type specification var age int = 50 fmt.Println("Age:", age) // Explicit declaration with type inference var height = 190.5 fmt.Println("Height:", height) // Short declaration name := "John" fmt.Println("Name:", name) // Explicit declaration of multiple variables var width, depth int = 100, 200 fmt.Println("Width:", width, "Depth:", depth) // Explicit declaration without initialization var distance int fmt.Println("Distance:", distance) // Block declaration of multiple variables var ( occupation string = "Welder" category float32 = 3.4 license bool ) fmt.Println("Occupation:", occupation, "Category:", category, "License:", license) } The result of running this code will be the following output in the console: Age: 50 Height: 190.5 Name: John Width: 100 Depth: 200 Distance: 0 Occupation: Welder Category: 3.4 License: false The var keyword is required for explicit variable declaration, especially in the global scope. The := operator is used for short variable declarations, particularly within functions. The block () syntax is used for readable declaration of multiple variables. It's important to remember that Go emphasizes minimalism and concise syntax. Therefore, the most compact form of notation should be used wherever possible. This reduces errors and issues while maintaining the cleanliness and readability of the code. Variable Initialization Typically, when a variable is declared, it is manually initialized with a specific value. The initialization of different types has syntactic differences. Number Numerical variables are initialized by assigning a numerical value, which is syntactically simple: // int var someNumber int = 5 // float32 otherNumber := 10.0 A number can be initialized with another number: // int var someNumber int = 5 var otherNumber int = someNumber // int oneMoreNumber := someNumber String String variables are initialized by assigning a sequence of characters enclosed in double quotes: // stringvar someString string = "Some programmer was here" A string can also be initialized with another string: // string var someString string = "Some programmer was here" var otherString string = someString // string oneMoreString := someString Boolean Initializing boolean variables is similar to initializing numeric and string variables, except that the value used is the keyword true or false: // boolvar someBool bool = true Similarly, boolean variables can be initialized with other boolean variables: // bool var someBool bool = true var otherBool bool = someBool // bool oneMoreBool := someBool Array There are several ways to initialize an array. The simplest one is through sequential access to the elements: // array var languages [3]string languages[0] = "Golang" languages[1] = "Python" languages[2] = "Rust" A more complex method is using a composite literal. A composite literal is a compact syntax for initializing any composite (struct-like) type, which avoids assigning each element individually. Thus, the array can be initialized in one step: var languages = [3]string{"Golang", "Python", "Rust"} Or using the shorthand form: languages := [3]string{"Golang", "Python", "Rust"} You can also partially initialize array elements: // array size 5, but only 3 elements initialized languages := [5]string{"Golang", "Python", "Rust"} languages[3] = "Java" languages[4] = "C++" To make the initialization of a large array more readable, you can format it like this: languages := [5]string{ "Golang", "Python", "Rust", "Java", "C++", // the comma at the end is REQUIRED } By the way, an array can be initialized with another array, copying all of its elements: languages := [3]string{"Golang", "Python", "Rust"}otherLanguages := languages It’s important to understand that copying an array also occurs when it is passed to a function: package main import "fmt" func change(languages [5]string) { for i := range languages { languages[i] = "[" + languages[i] + "]" } } func main() { languages := [5]string{ "Golang", "Python", "Rust", "Java", "C++", } change(languages) fmt.Println(languages) } The output in the console will be: [Golang Python Rust Java C++] Thus, only the copy of the array inside the change() function was modified, not the original array from the main() function. However, explicit initialization of an array with another array is possible only if both arrays have the same length and type: languages := [3]string{"Golang", "Python", "Rust"} var otherLanguages [3]string = languages // OK var oneMoreLanguages [4]string = languages // ERROR Additionally, in Go, you can create arrays from an arbitrary number of other arrays. You can initialize elements of such arrays both sequentially: var matrix [2][2]string matrix[0][0] = "a" matrix[0][1] = "b" matrix[1][0] = "c" matrix[1][1] = "d" Or using a composite literal: var matrix = [2][2][2]string{{{"a", "b"}, {"c", "d"}}, {{"e", "f"}, {"g", "h"}}} As shown, the second option takes up less space, but the syntax is more complex. Slice A slice is initialized the same way as an array: var languages = []string{"Golang", "Python", "Rust"} However, unlike an array, a slice can be initialized with another slice of arbitrary length: var languages = []string{"Golang", "Python", "Rust"}var otherLanguages []string = languages Map Maps are initialized using a composite literal with the type of the key and value specified. The content is listed using commas and separated by a colon: var languages = map[string]string{"first": "Golang", "second": "Python", "third": "Rust"} You can also use the shorthand declaration and a more readable initialization format: languages := map[string]string{ "first": "Golang", "second": "Python", "third": "Rust", // the comma at the end is MANDATORY } However, initializing a map with another map does not copy the elements; instead, it makes them shared: package main import "fmt" func main() { languages := map[string]string{"first": "Golang", "second": "Python", "third": "Rust"} otherLanguages := languages fmt.Println(languages) fmt.Println(otherLanguages) otherLanguages["first"] = "C++" fmt.Println(languages) fmt.Println(otherLanguages) delete(otherLanguages, "second") fmt.Println(languages) fmt.Println(otherLanguages) } The console output of this example will be: map[first:Golang second:Python third:Rust] map[first:Golang second:Python third:Rust] map[first:C++ second:Python third:Rust] map[first:C++ second:Python third:Rust] map[first:C++ third:Rust] map[first:C++ third:Rust] Pointer Pointers can only be initialized with the address of a variable of the same type: var variable int = 15var pointer *int = &variable The ampersand (&) symbol is used to get the address of any variable: package main import "fmt" func main() { var variable int = 15 var pointer *int = &variable fmt.Println(pointer) } The console output of this example will look something like: 0xc000104040 You can also use shorthand notation to initialize pointers: variable := 15pointer := &variable To access the value stored at the address of a pointer, you need to dereference it using the asterisk (*): package main import "fmt" func main() { var variable int = 15 var pointer *int = &variable fmt.Println(*pointer) } In this case, the console output will show: 15 Thus, you can assign new values to a variable located at the address of the pointer: package main import "fmt" func main() { var variable int = 15 var pointer *int = &variable *pointer = 5 fmt.Println(*pointer) } The console will display: 5 Finally, a pointer can be initialized with an anonymous object in memory. This is done using the new() function, which returns the address of the allocated memory: variable := new(int)*variable = 15 You don't need to manually delete the allocated memory — the garbage collector automatically handles this. Structure A structure can be initialized either with explicitly specified values in order: type something struct { first string second int } var structure something = something{"John", 15} Or with explicitly specified values by key names: type something struct { first string second int } var structure something = something{second: 15, first: "John"} Alternatively, you can choose not to specify any values, which will automatically initialize all fields to their zero values: package main import "fmt" type something struct { first string second int } func main() { var structure something = something{} fmt.Println(structure) structure.first = "John" structure.second = 15 fmt.Println(structure) } In this case, the console output will be: { 0} {John 15} Branching Based on Variables Variables play a central role in branching. Different parts of the program's code are executed based on their values (conditions). if/else The most basic conditional construct is created using the if/else statements. Here's the simplest condition: a := 5 b := 10 if a < b { fmt.Println("A is less than B") } For example, you can use a simple condition to check a pointer: var pointer *int if pointer == nil { fmt.Println("No address") } A more complex form would look like this: a := 10 b := 5 if a < b { fmt.Println("A is less than B") } else { fmt.Println("A is greater than B") } You can create even more complex constructs by combining else and if: a := 10 b := 5 if a < b { fmt.Println("A is less than B") } else if a > b { fmt.Println("A is greater than B") } else { fmt.Println("A is equal to B") } Multiple if/else expressions can be used: a := 12 if a < 5 { fmt.Println("A is less than 5") } else if a < 10 { fmt.Println("A is less than 10") } else if a < 20 { fmt.Println("A is less than 20") } else { fmt.Println("A is in superposition") } switch Another way to branch is using the switch construct, where possible values of a variable are defined, and actions are performed if there's a match: a := 1 switch a { case 0: fmt.Println("A is 0") case 1: fmt.Println("A is 1") case 2: fmt.Println("A is 2") } The default section can be used to define an action that runs if no match occurs: a := 3 switch a { case 0: fmt.Println("A is 0") case 1: fmt.Println("A is 1") case 2: fmt.Println("A is 2") default: fmt.Println("A is in superposition") } You can also combine multiple possible matches into one section: a := 1 switch a { case 0, 1, 2: fmt.Println("A is either 0, 1, or 2") default: fmt.Println("A is in superposition") } Useful Functions Go has many utility functions for working with variables. In this guide, we'll cover just the basic ones. Environment Variables Go provides special system functions that allow you to set and get environment variables: package main import ( "fmt" "os" ) func main() { os.Setenv("SOMEVAR", "1") // Set an environment variable fmt.Println("SOMEVAR:", os.Getenv("SOMEVAR")) // Read an environment variable } Time Variables Often, the program logic requires measuring time. Go has a corresponding tool for this — the time type. Time is a broad topic by itself. To learn more about the time package, you can check the official documentation. This guide will show how to get the current time in different formats: package main import ( "fmt" "time" ) func main() { fmt.Println("Current time:", time.Now()) fmt.Println("Current time (UTC):", time.Now().UTC()) fmt.Println("Current time (Unix):", time.Now().Unix()) } The console output will look something like this: Current time: 2009-11-10 23:00:00 +0000 UTC m=+0.000000001 Current time (UTC): 2009-11-10 23:00:00 +0000 UTC Current time (Unix): 1257894000 You can also specify specific time parameters: package main import ( "fmt" "time" ) func main() { timeNow := time.Now() fmt.Println("Full time:", timeNow) fmt.Println("Year:", timeNow.Year()) fmt.Println("Month:", timeNow.Month()) fmt.Println("Day:", timeNow.Day()) fmt.Println("Hour:", timeNow.Hour()) fmt.Println("Minutes:", timeNow.Minute()) fmt.Println("Seconds:", timeNow.Second()) } In this case, the console output will be: Full time: 2024-11-15 23:46:09.157929822 +0000 UTC m=+0.000031801 Year: 2024 Month: November Day: 15 Hour: 23 Minutes: 23 Seconds: 9 Adding and Removing Elements from a Slice You can add elements to slices: var languages = []string{"Golang", "Python", "Rust"} languages = append(languages, "Java", "C++") fmt.Println(languages) This will append "Java" and "C++" to the languages slice. You can also remove elements from slices: var languages = []string{"Golang", "Python", "Rust"} // Remove the 2nd element (index 1) n := 1 languages = append(languages[:n], languages[n+1:]...) fmt.Println(languages) In this example, the second element is removed from the languages slice using slice operators, which create a new sequence from parts of the original slice. Here’s an example of slicing a sequence: package main import "fmt" func main() { var sequence = []string{"One", "Two", "Three", "Four", "Five"} newSequence := sequence[1:4] // Elements from index 1 to 3 become the new slice fmt.Println(newSequence) } The output in the console will be: [Two Three Four] Checking the Type of a Variable You can check the type of a variable using the TypeOf() function from the reflect package: package main import ( "fmt" "reflect" // Package to determine the type ) func main() { variableString := "string" variableInt := 5 variableFloat64 := 1.5 variableBool := true fmt.Println(reflect.TypeOf(variableString)) fmt.Println(reflect.TypeOf(variableInt)) fmt.Println(reflect.TypeOf(variableFloat64)) fmt.Println(reflect.TypeOf(variableBool)) } The console output for this example will be: string int float64 bool Variables in Strings Often, you need to insert a variable into a string. There are several ways to do this: package main import "fmt" func main() { // METHOD 1 stringPre := "human-readable" stringEnd1 := fmt.Sprintf("This is a %s string", stringPre) fmt.Println(stringEnd1) // METHOD 2 stringEnd2 := "This is " + stringPre + " string" fmt.Println(stringEnd2) } The output in the console will be: This is a human-readable stringThis is a human-readable string You can also combine numeric variables with strings: package main import "fmt" func main() { name := "John" age := 50 fmt.Printf("Hi, my name is %v and I'm %v years old.\n", name, age) } The output will be: Hi, my name is John and I'm 50 years old. Conclusion Like in most other programming languages, variables in Go are essential for storing data. Since data types differ from each other, Golang variables have several basic types, each having a specific representation in the computer's memory. In this guide, we only covered the basic ways to work with variables. You can find more detailed (and comprehensive) information about types and their specifics in the official Golang documentation. Additionally, the official Go package manager catalog provides information on many useful modules available for import into your project. One such module is the Standard Library. Check out our app platform to deploy Go applications (such as Beego and Gin). 
27 January 2025 · 19 min to read
Go

How to Create and Deploy a Gin App on Hostman App Platform

Gin is a highly efficient HTTP web framework written in the Go programming language, providing developers with powerful tools for building web applications, RESTful APIs, and microservices. It stands out among other frameworks due to its high request processing speed, flexible configuration, and ease of use. One of Gin’s key advantages is its performance. Gin uses a minimalist approach to handling HTTP requests, making it one of the fastest frameworks on the market. It is built on the net/http module from Golang’s standard library, ensuring excellent integration with Go’s ecosystem and enabling the use of Go’s concurrency features to handle a large number of simultaneous requests. Another important advantage of Gin is its simplicity. The syntax and structure of Gin are intuitive, reducing the learning curve for developers and speeding up the development process. Its built-in routing system makes it easy to define and handle routes, while its powerful middleware system allows flexible request handling. Gin’s flexibility is also worth mentioning. It allows you to extend functionality  through plugins and middleware, enabling adaptation to specific project requirements. Built-in support for JSON and other data formats simplifies the creation of RESTful APIs, and tools for handling requests and responses make data management straightforward. In addition, Gin has an active community and solid documentation, making it an excellent choice for developers looking for a reliable and well-supported framework. There are plenty of resources, including code examples, guides, and libraries, that make the learning and development process easier. Creating the Application Functionality Overview Our application will support basic CRUD operations (Create, Read, Update, Delete) for notes through a RESTful API. During development, we will discuss key aspects of integrating Gin with the GORM ORM library and demonstrate how to ensure the security and performance of our web application. The main features of our application include: Creating a New Note The user can add a new note by sending a POST request with the note’s title and content. The application will save the new note in the database and return its unique identifier. Retrieving All Notes The user can request a list of all notes by sending a GET request. The application will return all notes from the database in JSON format. Retrieving a Note by ID The user can retrieve a specific note by its ID by sending a GET request with the specified ID. The application will find the note in the database and return it in JSON format. Updating an Existing Note The user can update an existing note by sending a PUT request with a new title and content. The application will update the note’s data in the database and return the updated note. Deleting a Note The user can delete a note by its ID by sending a DELETE request with the specified ID. The application will remove the note from the database and return a status indicating the successful completion of the operation. Project Setup It is assumed that you have Go version 1.22 installed (you can install it using one of these guides: Windows, Ubuntu, MacOS). If you use an earlier version, errors may occur during the project setup and launch process. Additionally, you should have a basic understanding of Git and an account on one of the Git repository hosting services (GitHub, GitLab, Bitbucket, Gitea, etc.). Step 1: Create a Project Directory Run the following command to create the project directory: mkdir GinApp Navigate into the newly created directory: cd GinApp Step 2: Initialize a New Go Module Run the following command to initialize a new Golang module: go mod init gin-notes-api Step 3: Install Required Packages We will install the necessary packages for the project: Gin, GORM, and SQLite (for database interaction) using the following commands: go get -u github.com/gin-gonic/gin go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite Step 4: Create the Project Structure The project structure should look like this: GinApp/ ├── go.mod ├── main.go ├── models/ │ └── note.go ├── handlers/ │ └── note_handlers.go ├── storage/ │ ├── storage.go │ └── database.go You can create this structure using your IDE’s file explorer or by running the following command in the terminal: mkdir -p models handlers storage && touch go.mod main.go models/note.go handlers/note_handlers.go storage/storage.go storage/database.go Application Structure models/note.go Defines the data structure for notes. The Note model describes the fields of a note and is used to interact with the database through the GORM ORM library. package models // Definition of the Note structure type Note struct { ID int `json:"id" gorm:"primaryKey;autoIncrement"` // Unique identifier, auto-incremented Title string `json:"title"` // Note title Content string `json:"content"` // Note content } storage/database.go This file contains functions for initializing the database and retrieving the database instance. GORM is used to work with the SQLite database. package storage import ( "gorm.io/driver/sqlite" // Driver for SQLite "gorm.io/gorm" // GORM ORM library "gin-notes-api/models" // Importing the package with data models ) // Declare a global variable to store the database instance var db *gorm.DB // Function to initialize the database func InitDatabase() error { var err error db, err = gorm.Open(sqlite.Open("notes.db"), &gorm.Config{}) // Connect to SQLite using GORM if err != nil { return err // Return an error if the connection fails } return db.AutoMigrate(&models.Note{}) // Automatically create the Note table if it doesn’t exist } // Function to retrieve the database instance func GetDB() *gorm.DB { return db // Return the global db variable containing the database connection } storage/storage.go This file provides CRUD (Create, Read, Update, Delete) operations for the Note model using GORM to interact with the SQLite database. package storage import ( "gin-notes-api/models" // Importing the package with data models ) // Function to retrieve all notes from the database func GetAllNotes() []models.Note { var notes []models.Note db.Find(¬es) // Use GORM to execute a SELECT query and fill the notes slice return notes // Return all retrieved notes } // Function to retrieve a note by ID func GetNoteByID(id int) *models.Note { var note models.Note if result := db.First(¬e, id); result.Error != nil { return nil // Return nil if the note with the specified ID is not found } return ¬e // Return the found note } // Function to create a new note func CreateNote(title, content string) models.Note { note := models.Note{ Title: title, Content: content, } db.Create(¬e) // Use GORM to execute an INSERT query and save the new note return note // Return the created note } // Function to update an existing note by ID func UpdateNote(id int, title, content string) *models.Note { var note models.Note if result := db.First(¬e, id); result.Error != nil { return nil // Return nil if the note with the specified ID is not found } note.Title = title note.Content = content db.Save(¬e) // Use GORM to execute an UPDATE query and save the updated note return ¬e // Return the updated note } // Function to delete a note by ID func DeleteNoteByID(id int) bool { if result := db.Delete(&models.Note{}, id); result.Error != nil { return false // Return false if deletion fails } return true // Return true if the note is successfully deleted } handlers/note_handlers.go This file contains handler functions for processing HTTP requests. These functions are triggered in response to different routes and perform actions such as creating, retrieving, updating, and deleting notes. package handlers import ( "net/http" // HTTP package "strconv" // For converting strings to other data types "github.com/gin-gonic/gin" // Gin web framework "gin-notes-api/storage" // Import the storage module for database operations ) // Handler for retrieving all notes func GetNotes(c *gin.Context) { notes := storage.GetAllNotes() // Fetch all notes from storage c.JSON(http.StatusOK, notes) // Return notes in JSON format with a 200 OK status } // Handler for retrieving a note by ID func GetNoteByID(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) // Convert the ID parameter from string to integer if err != nil { c.JSON(http.StatusBadRequest, gin.H{ // Return 400 Bad Request if the ID is invalid "error": "Invalid note ID", }) return } note := storage.GetNoteByID(id) // Fetch the note by ID from storage if note == nil { c.JSON(http.StatusNotFound, gin.H{ // Return 404 Not Found if the note is not found "error": "Note not found", }) return } c.JSON(http.StatusOK, note) // Return the found note in JSON format with a 200 OK status } // Handler for creating a new note func CreateNote(c *gin.Context) { var input struct { Title string `json:"title" binding:"required"` Content string `json:"content" binding:"required"` } if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{ // Return 400 Bad Request if the input data is invalid "error": err.Error(), }) return } note := storage.CreateNote(input.Title, input.Content) // Create a new note in storage c.JSON(http.StatusCreated, note) // Return the created note in JSON format with a 201 Created status } // Handler for updating an existing note by ID func UpdateNoteByID(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) // Convert the ID parameter from string to integer if err != nil { c.JSON(http.StatusBadRequest, gin.H{ // Return 400 Bad Request if the ID is invalid "error": "Invalid note ID", }) return } var input struct { Title string `json:"title" binding:"required"` Content string `json:"content" binding:"required"` } if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{ // Return 400 Bad Request if the input data is invalid "error": err.Error(), }) return } note := storage.UpdateNote(id, input.Title, input.Content) // Update the note in storage if note == nil { c.JSON(http.StatusNotFound, gin.H{ // Return 404 Not Found if the note is not found "error": "Note not found", }) return } c.JSON(http.StatusOK, note) // Return the updated note in JSON format with a 200 OK status } // Handler for deleting a note by ID func DeleteNoteByID(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) // Convert the ID parameter from string to integer if err != nil { c.JSON(http.StatusBadRequest, gin.H{ // Return 400 Bad Request if the ID is invalid "error": "Invalid note ID", }) return } if success := storage.DeleteNoteByID(id); !success { c.JSON(http.StatusNotFound, gin.H{ // Return 404 Not Found if the note is not found "error": "Note not found", }) return } c.Status(http.StatusNoContent) // Return 204 No Content on successful deletion } main.go This file serves as the main entry point of the application. It initializes the database and sets up routes for handling HTTP requests using the Gin web framework. package main import ( "log" // Package for logging "github.com/gin-gonic/gin" // Gin web framework "gin-notes-api/handlers" // Importing the module with request handlers "gin-notes-api/storage" // Importing the module for database operations ) func main() { // Initialize the database if err := storage.InitDatabase(); err != nil { log.Fatalf("Failed to initialize database: %v", err) // Log the error and terminate the program if database initialization fails } // Create a new Gin router with default settings router := gin.Default() // Define routes and bind them to their respective handlers router.GET("/notes", handlers.GetNotes) // Route for retrieving all notes router.GET("/notes/:id", handlers.GetNoteByID) // Route for retrieving a note by ID router.POST("/notes", handlers.CreateNote) // Route for creating a new note router.PUT("/notes/:id", handlers.UpdateNoteByID) // Route for updating a note by ID router.DELETE("/notes/:id", handlers.DeleteNoteByID) // Route for deleting a note by ID // Start the web server on port 8080 router.Run(":8080") } Now we can run the application locally and test its functionality. To start the application, use the following command: go run main.go Examples of curl Requests for Testing Functionality Create a New Note This request creates a new note with a specified title and content. curl -X POST http://localhost:8080/notes \ -H "Content-Type: application/json" \ -d '{"title":"Title","content":"Note body"}' Get All Notes This request retrieves a list of all notes stored in the database. curl -X GET http://localhost:8080/notes Get a Note by ID This request fetches a specific note by its unique ID. curl -X GET http://localhost:8080/notes/1 Update a Note by ID This request updates an existing note by its ID, providing a new title and content. curl -X PUT http://localhost:8080/notes/1 \ -H "Content-Type: application/json" \ -d '{"title":"Updated Title","content":"Updated note body"}' Delete a Note by ID This request deletes a note with a specific ID. curl -X DELETE http://localhost:8080/notes/1 Deploying the Gin Application on Hostman App Platform Creating and Uploading the Repository To deploy the application using Hostman App Platform, first ensure your project is hosted in a Git repository. This example uses GitHub. Initialize a Git repository locally in your project directory: git init -b main git add . git commit -m 'First commit' Push the repository to a remote server using the commands provided when creating a new GitHub repository: git remote add origin [email protected]:your_user/your_repository.git git push -u origin main Setting Up Hostman App Platform Go to the App Platform section in Hostman and click Create app. Under the Type section, choose the Backend tab and select the Gin framework. Connect your GitHub account by granting access to the repositories, or manually select the necessary repository. After connecting your GitHub account, select the repository containing your application in the Repository section. Choose a region where your application will be hosted. In the Configuration section, select the minimum settings; they are sufficient for this project. You can modify them later if needed. Leave the default values in the App settings section. For more complex projects, you may specify environment variables and custom build commands. Specify a name for your application and click Start deploy. Deployment Process The deployment process can take up to 10 minutes. Once it’s completed, you will see the message “Deployment successfully completed” in the deployment logs. Navigate to the Settings tab on the application page to view the domain assigned to your app.In the same section, you can modify the server configuration, edit deployment settings, and update the domain binding. If you connect a custom domain, a Let’s Encrypt SSL certificate will be automatically issued and renewed 7 days before expiration. Testing the Application To verify that the application is working correctly, execute a curl request, replacing localhost with the assigned domain: curl -X GET https://your_domain/notes Conclusion In this tutorial, we have developed a basic web application for managing notes using the Gin framework and GORM library. The created RESTful API supports basic CRUD operations, making the application simple and user-friendly. Gin proved to be an efficient and easy-to-learn tool. Its routing system and support for concurrent requests made development smoother. GORM facilitated database interaction by automating many tasks. The application was successfully deployed on the Hostman App Platform, providing a fast and reliable deployment process.  In the future, we can enhance the application by adding new features such as user authentication and advanced note search capabilities. This project demonstrated how modern development tools like Gin and GORM simplify web application creation.
16 January 2025 · 14 min to read
Go

For Loops in the Go Programming Language

A loop is a block of code that runs until a specified condition is met or a required number of repetitions is completed. Loops are convenient for solving tasks where a program needs to repeat the same actions multiple times. For example, imagine you have a list of directors. You need to extract each director's last name and display it on the screen. Instead of manually accessing each element of the list, it's easier to use a loop. A loop will iterate through the list and display each last name on the screen. Loops in Go In Go, there are only for loops. There are no while or do while loops like in some other languages. Similar concepts are implemented using the same for loop. This design choice makes the code more readable. Developers don't have to decide on a looping strategy — if you need to repeat actions, there's for, which can be used in various ways. Let's explore how to create loops in Golang to solve specific tasks. ForClause The structure of a ForClause is simple. It consists of a condition and a body. The code inside the body executes if the condition is evaluated as true. for i := 0; i < 6; i++ { fmt.Println(i) } Here: i := 0 is the initializer. It sets the starting value of the loop. i < 6 is the condition. If it is evaluated as true, the code inside the loop is executed. fmt.Println(i) sequentially prints numbers from 0 to 5. i++ is the post-operation that increments i by 1 after each iteration. The code starts with i = 0. Since 0 < 6, the condition is true, and 0 is printed. Then, i++ increments i by 1, making i = 1. The loop continues as long as i < 6. When i becomes 6, the condition i < 6 is false, and the loop stops. The number 6 is not printed. Output: 0 1 2 3 4 5 You don't have to start at zero or stop at a fixed value. The for loop in Go allows you to adjust the logic as needed. for i := 100; i < 150; i = i + 10 { fmt.Println(i) } Output: 100 110 120 130 140 If you modify the condition slightly, you can include the number 150: for i := 100; i <= 150; i = i + 10 { fmt.Println(i) } Output: 100 110 120 130 140 150 You can also iterate in reverse, from the end to the beginning, by modifying the condition and the post-operation. for i := 50; i > 0; i -= 10 { fmt.Println(i) } Here, the loop starts with i = 50. On each iteration, it checks if i > 0. If the condition is true, it subtracts 10 from the current value of i. Output: 50 40 30 20 10 Note that 0 is not printed because the condition requires i > 0. Loop with a Condition If you remove the initializer and post-operator from the syntax, you get a simple construct that works based on a condition. The loop declaration in this case looks like this: i := 0 for i < 6 { fmt.Println(i) i++ } If you are familiar with other programming languages, you might recognize this as similar to a while loop. In this example, i is defined outside the loop. The for loop only has a condition, which keeps the loop running while i is less than 6. Note that the increment operation (i++), previously specified as a post-operator, is now inside the body. Sometimes, the number of iterations is unknown in advance. You can't specify a condition for ending the loop in such cases. To avoid infinite loops, Go supports the break keyword. Here's a simple example: func main() { i := 0 for { fmt.Println("Hello") if i == 5 { break } i++ } } Initially, i = 0. The loop runs indefinitely, printing "Hello" each time. However, when i reaches 5, the break statement is executed, and the program stops. RangeClause Go also provides another type of loop — the RangeClause. It is similar to ForClause, but it returns two values by default: the index of an element and its value. package main import "fmt" func main() { words := []string{"host", "man", "hostman", "cloud"} for i, word := range words { fmt.Println(i, word) } } Output: 0 host 1 man 2 hostman 3 cloud To omit the index, use an underscore _ as a placeholder: package main import "fmt" func main() { words := []string{"host", "man", "hostman", "cloud"} for _, word := range words { fmt.Println(word) } } Output: host man hostman cloud You can also use range to add elements to a list: package main import "fmt" func main() { words := []string{"host", "man", "hostman", "cloud"} for range words { words = append(words, "great") } fmt.Printf("%q\n", words) } Output: ["host" "man" "hostman" "cloud" "great" "great" "great" "great"] In this example, the word "great" is added for each element in the original length of the words slice. Suppose you have a slice of 10 zeros and need to populate it with numbers from 0 to 9: package main import "fmt" func main() { integers := make([]int, 10) fmt.Println(integers) for i := range integers { integers[i] = i } fmt.Println(integers) } [0 0 0 0 0 0 0 0 0 0] [0 1 2 3 4 5 6 7 8 9] You can use range to iterate over each character in a string: package main import "fmt" func main() { hostman := "Hostman" for _, letter := range hostman { fmt.Printf("%c\n", letter) } } Output: H o s t m a n This allows you to process each character in a string individually. Nested Constructs A for loop can be created inside another construct, making it nested. We can represent its syntax as: for { [Action] for { [Action] } } First, the outer loop starts running. It executes and then triggers the inner loop. After the inner loop finishes, the program returns to the outer loop. This process repeats as long as the given condition holds or until the program encounters a break statement. There is also a risk of creating an infinite loop, which even the powerful resources of Hostman wouldn’t handle, as the program would never terminate. To avoid this, always ensure the condition is properly checked or use the break operator. Here’s a simple example to demonstrate nested loops: package main import "fmt" func main() { numList := []int{1, 2} alphaList := []string{"a", "b", "c"} for _, i := range numList { fmt.Println(i) for _, letter := range alphaList { fmt.Println(letter) } } } Output: 1 a b c 2 a b c This example clearly demonstrates the order of operations: The first value from numList (1) is printed. The inner loop executes, printing each value from alphaList (a, b, c). The program returns to the outer loop and prints the next value from numList (2). The inner loop runs again, printing the values of alphaList (a, b, c) a second time. Conclusion Using for loops in Go is straightforward. Depending on the task, you can choose one of the three main forms of for or combine them to create nested constructs. You can control the loop's behavior by modifying the condition, initializer, and post-operator or by using break and continue statements. Nested loops provide flexibility and power but should be used carefully to avoid infinite loops or excessive computational overhead. You can deploy Go applications (such as Beego and Gin) on our app platform.
11 December 2024 · 6 min to read

Do you have questions,
comments, or concerns?

Our professionals are available to assist you at any moment,
whether you need help or are just unsure of where to start.
Email us
Hostman's Support