In this lesson, a number of data-related topics will be covered. In previous lessons, you've imported and used packages, such as fmt
to do printing and the time
package to pause your program for a period of time. You'll dig into a number of other packages provided in Go and learn how to use them to do sorting, to work with dates and times, and to perform searches in strings with regular expressions.
Go includes the sort
package, which allows you to perform sorting operations on comparable data types such as numbers and strings. In Listing 19.1 you define a slice storing integers. You then use the Ints
function from the sort
package to sort the values in the slice.
In this listing, we use the sort
package to execute the Ints
function, which sorts the slice in ascending order. This is done by passing the name of the slice to the function:
sort.Ints(numbers)
The sorted values are stored back into the original slice, replacing the original order of the values. The result is a sorted slice, as you can see in the output:
Original Numbers: [67 18 62 60 25 64 75 5 17 55]
Sorted Numbers: [5 17 18 25 55 60 62 64 67 75]
You can also use the sort
package to sort strings alphabetically, as shown in Listing 19.2.
This program uses the same logic as the previous example, but with strings instead of numbers. To sort strings, the Strings
function of the sort
package is used. Again, the sorted values replace the original values in the slice. The output is as follows:
Original slice: [camel zebra horse dog elephant giraffe]
Sorted slice: [camel dog elephant giraffe horse zebra]
To determine whether a slice or an array of strings is sorted, you can use the StringsAreSorted
function from the sort
package. StringsAreSorted
will return true
if the input is sorted or false
otherwise. The example in Listing 19.3 shows how to use the StringsAreSorted
function.
In this listing, you create an array of words, which you print to the screen. You then check using sort.StringsAreSorted
to see if the words are already sorted. In this case, you know they are not, so the returned value will be false
. Using sort.Strings
, you then sort the words before again printing them and checking to see if they are sorted.
Note that you checked to see if the words are sorted twice, once for the original slice and again after you have sorted the values in the slice. The final output looks like this:
Original slice: [camel zebra horse dog elephant giraffe]
The original values are sorted: false
Sorted slice: [camel dog elephant giraffe horse zebra]
The values are sorted: true
To determine if other data types are sorted, you can use a function similar to StringsAreSorted
. The function would be named similarly, using the following format:
sort.DatatypeAreSorted( slice )
where Datatype
is replaced with the type of the data to be sorted. For example, integers would use sort.IntsAreSorted
and float64
values would use sort.Float64sAreSorted
. The slice
passed would need to contain values of the corresponding type.
If you want to sort data based on a specific criterion, you can build your own functions and embed them in the sort
package for your use. For instance, imagine that you want to sort a set of words based on the number of characters in each word rather than alphabetically. The sort
package includes the Sort
interface, which you can implement with your own logic to create custom sorting algorithms.
The sort
interface includes three methods that you need to implement:
Len
: The Len
function returns the length of the data type in the context of sorting. In this case, you want to sort words based on their length so that the Len
function will return the length of the input words.Swap
: The sort
package uses the Swap
function internally to swap items in a slice or array during sorting. The function takes as input two indexes, and it swaps the values in those indexes.Less
: The Less
function provides the logic for comparing two items in a slice or array. Because you want to compare based on length, this function will take the indexes of the two items you want to compare in the slice or array, and it will return true
if the length of the first word (stored in the first index) is higher than the length of the second word (stored in the second index).Listing 19.4 implements the three methods of the sort
package to sort the string values based on the length of the string.
In order to implement the interface, you need your own custom type, so the first thing you do is create your own alias type, mytype
, based on a slice of strings:
type mytype []string
Remember that in Go, the definition of the receiver type must be in the same package as the method. This means that you cannot use an array of strings as the receiver type for the methods since it is not in the same package as the method. Thus, you need to create your own alias type.
Next, you implement the three methods Len
, Swap
, and Less
, with each function performing a specific action on the array. In the main
function, you create a slice of strings that you then convert into your own data type (mytype
).
Finally, you use the Sort
function from the sort
package to sort the list of words based on length. When you run the listing, you can see that the set of strings is indeed sorted by length:
Original slice: [pear pineapple mango banana fig]
Sorted by length: [fig pear mango banana pineapple]
Go also provides a method for reversing the sort order of a slice that has used the sort
interface. The method for reversing the sort order is sort.Reverse
. Listing 19.5 shows the sort order of an array of integers being reversed.
In this listing, you use the numbers slice that you used in Listing 19.1. The primary difference is that you added the last two lines to the main
function, with the key line of code being:
sort.Sort(sort.Reverse(sort.IntSlice(numbers)))
In this line of code, the numbers slice is sorted into reverse order. Specifically, you are passing your slice, numbers
, to sort.IntSlice
. This in turn is passed to sort.Reverse
, which is the function that will reverse the sort order. To do the sort, however, you pass all of this to sort.Sort
. The end result is that the order of the numbers is reversed, as shown in the output:
Original Numbers: [67 18 62 60 25 64 75 5 17 55]
Sorted Numbers: [5 17 18 25 55 60 62 64 67 75]
Reversed Numbers: [75 67 64 62 60 55 25 18 17 5]
If you want to reverse the order of data of a different data type, you can swap out IntSlice
with an interface based on the type you want to sort. The format of the interface would be:
sort.datatypeSlice( slice )
For example, to reverse the order of slice of strings called MyStrings
, you'd use the following:
sort.Sort(sort.Reverse(sort.StringSlice(MyStrings)))
Go includes a robust time
package that you can use to manipulate date and time values. As an example, you can use the Now
functions shown in Listing 19.6 to retrieve the current date and time, based on system values.
Listing 19.6 imports the time
package to gain access to a number of date and time functions. Using a variety of functions, the program prints the requested information based on the local system clock. While the actual outputs will change, it should look something like the following:
Today's date and time: 2022-04-13 12:27:36.0247625 -0400 EDT m=+0.006465401
Current year: 2022
Current month: April
Current day: 13
Current hour: 12
Current minute: 27
Current second: 36
Current nanosecond: 24762500
Current location: Local
Local: 2022-04-13 12:27:36.0247625 -0400 EDT
Current zone: EDT
Current zone offset: -14400
Current weekday: Wednesday
In the first line of output, now
is printed. As you can see, it contains the date, time, and more. This is followed by calls to individual functions that return various pieces of the current date and time. You can review the listing to see the functions that are used. Several of the available functions are listed in Table 19.1.
Table 19.1 Time functions
Function | Description |
---|---|
Year() | Displays the year as a four-digit value |
Month() | Displays the textual representation of a month, such as “January” |
Day() | Displays the numeric day of the month |
Hour() | Displays the numeric representation of the hour |
Minute() | Displays the numeric representation of the minutes |
Second() | Displays the numeric representation of the seconds |
Nanosecond() | Displays the numeric representation of the nanoseconds |
Weekday() | Displays the textual representation of the day of the week, such as “Monday” |
YearDay() | Displays the numeric representation for the day within the year |
Local() | Displays the current time value adjusted to local time |
Location() | Displays information about the time zone associated with the current time variable |
Zone() | Returns two values: first, a textual representation of the time zone such as “EST,” and second, a numeric value for the duration offset (in seconds) from GMT |
It is often useful to retrieve date and time values from the system, but you can also define a date/time value and analyze that value. Listing 19.7 defines a specific date and time and then retrieves values from that definition.
In this listing, instead of using the current time (now
), you are creating a date and time, which you assign to a variable called customTime
. You initialize your custom time with values when you create it:
customTime := time.Date( 2025, 05, 15, 15, 20, 00, 0, time.Local)
The arguments you pass also include time.Local
to indicate that the values should be based on the local location. Once your custom time has been created, you can display the pieces in the same manner you did for the current time in the previous listing. The output showing customTime
and the various pieces is as follows:
Custom date and time: 2025-05-15 15:20:00 -0400 EDT
Custom year: 2025
Custom month: May
Custom day: 15
Custom weekday: Thursday
Custom hour: 15
Custom minute: 20
Custom second: 0
Custom nanosecond: 0
Custom location: Local
Custom zone: EDT
Custom zone offset: -4
Note that if you are located in a different time zone than EDT, then you will have slightly different values showing for the Custom date and time
, Custom zone
, and Custom zone offset
values. The output shown here is based on the program being run in the EDT time zone. Also notice that the code divided the zone offset by 3,600. This is simply taking the return value saved in zoneOffset
and converting it to hours instead of seconds. There are 3,600 seconds in an hour.
You can ask Go to compare two times and determine which one occurs earlier. In Listing 19.8 you compare a custom time to the current time to determine which one is earlier. You can do this using functions provided by the time
package.
This listing creates two time variables. The first is called now
and contains the current time. The second is called customTime
and contains a date in 2025. The last three lines of code call three functions to compare the times. The Before
, After
, and Equal
functions return Boolean values based on comparing the associated time (customTime
in this case) to the time passed to the function (now
in this case). The output from this listing at the time this lesson was written is as follows:
Current date and time: 2022-01-21 16:30:24.8709676 -0500 EST m=+0.010002901
Custom date and time: 2025-05-15 15:20:00 -0400 EDT
The custom time is before now: false
The custom time is after now: true
The custom time is equal to now: false
Note that you could reverse what's done in Listing 19.8 and call these functions on now
with the customTime
being passed, as shown in Listing 19.9.
The result of flipping the time variables is what should be expected. The before and after results flip:
Current date and time: 2022-01-21 16:35:10.8787301 -0500 EST m=+0.016997501
Custom date and time: 2025-05-15 15:20:00 -0400 EDT
The current time is before the custom time: true
The current time is after the custom time: false
The current time is equal to the custom time: false
You can also use the time
package to perform calculations such as subtracting dates from each other, adding dates together, and adding values to a date.
Using subtraction, you can determine the difference between two dates. When you subtract one time value from another time value, Go returns the duration of time between those two values. You can see this in action in Listing 19.10.
The use of the Sub
function allows one time that is passed as an argument to be subtracted from the current time. In the listing, the instruction:
diff := now.Sub(customTime)
subtracts the customTime
value from now
and stores the difference in the diff
variable. In this example, the custom date is after the current date, so the result is negative. If you are running this listing after May 15, 2025, then the numbers will be positive.
The diff
variable contains the overall difference in time between the two dates. The third Println
statement prints the value of diff
:
Time between now and custom time: -27074h19m13.3341849s
You can see that the value contains several components, including hours, minutes, and seconds, with a decimal place. Each of these components of the time difference can be accessed by using functions with the diff
variable. This is done in the last four Println
statements within the listing. These lines get the components using diff.Hours()
, diff.Minutes()
, diff.Seconds()
, and diff.Nanoseconds()
. The full output is dependent on the current date but should be similar to the following:
Current date and time: 2022-04-13 13:00:46.6658151 -0400 EDT m=+0.007016301
Custom date and time: 2025-05-15 15:20:00 -0400 EDT
Time between now and custom time: -27074h19m13.3341849s
Hours between now and custom time: -27074.320370606918
Minutes between now and custom time: -1.624459222236415e+06
Seconds between now and custom time: -9.74675533341849e+07
Nanoseconds between now and custom time: -97467553334184900
You can also add a specific amount of time to a date/time value and return the date/value that results. For example, you may want to calculate a date two weeks later or one month earlier than a given date. Listing 19.11 builds on the earlier example and uses the duration between the current time and the custom time as the value in an Add
function.
In this listing, the duration between the current date (now
) and the custom date, which is May 15, 2025, is determined using the Sub
function, as shown in the previous listing. The result of this duration of time is stored in the diff
variable. When this lesson was written, the custom time was in the future, so diff
was a negative number:
Time between now and custom time: -27074h9m47.2688778s
The value for the difference is then added to the custom time and printed, followed by subtracting the difference:
fmt.Println(customTime.Add(diff))
fmt.Println(customTime.Add(-diff))
If you run this listing prior to May 15, 2025, then when you add the difference, it will result in the current date and time, whereas adding the negative of the difference will result in a date in the future past May 15, 2025. If you run the listing after May 15, 2025, then adding the difference will result in a date in the future, and adding the negative of the difference will result in the current date and time.
Using as the current date the date this lesson was written (which is prior to May 15, 2025), the output looks like this:
Current date and time: 2022-04-13 13:10:12.7311222 -0400 EDT m=+0.006706501
Custom date and time: 2025-05-15 15:20:00 -0400 EDT
Time between now and custom time: -27074h9m47.2688778s
2022-04-13 13:10:12.7311222 -0400 EDT
2028-06-16 17:29:47.2688778 -0400 EDT
While the previous listings have shown how to get the difference between two dates and how to use that difference to adjust an existing date, that's not always practical. Sometimes we just want to add a given number of hours, minutes, or seconds. This can also be done using the Add
function along with constants, as shown in Listing 19.12.
In this listing, you create a Time type called myTime
that is initialized to May 15, 2025, at 12:00:00. This date is then used to create four new dates that will have the time component adjusted.
For date1
the Add
function is used to add 6 seconds to the date and time stored in myTime
. To add the 6 seconds, you multiply 6 by the constant time.Seconds
. You then create date2
and date3
in a similar manner; however, with date2
you are adding 6 minutes, which is done by multiplying 6 by the constant time.Minute
. With date3
you are adding 6 hours using 6 multiplied by the constant time.Hour
.
For date4
, you are adding a week. There is no function for adding a week, so instead you add the number of hours that are in a week, which would be 24 (the number of hours in a day) times 7 (the number of days in a week). You again multiply this by the time.Hour
constant.
With our new date variables created, you print each to the screen to verify that the original date was indeed changed as expected:
2025-05-15 12:00:00 -0400 EDT
2025-05-15 12:00:06 -0400 EDT
2025-05-15 12:06:00 -0400 EDT
2025-05-15 18:00:00 -0400 EDT
2025-05-22 12:00:00 -0400 EDT
While the previous listings have shown how to get the difference between two dates and how to add time components, sometimes you just want to add a given number of days, months, or years to a date and you don't want to have to calculate the number of hours as you did in the previous listing to add a week. Fortunately, you can do that with the AddDate
function.
The AddDate
function can be applied to a time variable. The format of the function is:
AddDate(years, months, days)
Each of the arguments is a variable of type int
. To add two weeks to a time variable called customDate
, as you do in Listing 19.13, use this:
customDate.AddDate(0, 0, 14)
In this listing, you create a variable called now
that holds the current date and time. You then use the AddDate
function to add two weeks (14 days) to now
and save the resulting date in a new variable called newDate
. This is followed by printing the newDate
, which will be two weeks from the current time.
The listing then does the same thing again, but it starts with a custom date that was created. The approach works the same and the result is that a date two weeks in the future from your custom date is printed.
With Go, you can parse strings into time values using the time.Parse
function. This would allow you, for example, to accept date values as strings through user input or from a data file and then be able to convert those values into time values as needed. Listing 19.14 takes a string representing a time and parses it into individual parts of the date/time the string represents.
This listing creates a variable called myDate
and assigns it a string value that contains what appears to be a date. This string is then passed to the time.Parse
function to be converted into an actual date, which will be placed in t1
. The Parse
function also does error handling, so you include the variable e
to catch any errors that might occur.
The Parse
function also takes two parameters. The first one indicates the format of the date you will be parsing. The second parameter is the string to be parsed.
In the listing, once you call Parse
, you print the resulting date followed by the day and the month. You also print the value captured by e
, which will show an error if one happened during the parsing or nil
if there was no error. In our case, there was no error, as you can see in the output:
2025-05-21 12:50:41 +0000 +0000
21
May
<nil>
As mentioned, the first parameter passed to Parse
indicates the format the date within the string is expected to follow. In this case, we used RFC3339, which assumes a string uses the following format:
"2006-01-02T15:04:05Z07:00"
Table 19.2 lists many of the formats that are defined by the time
package. You can pass to Parse
either the constant, as we did in Listing 19.14, or the literal form.
Table 19.2 Date format constants
Constant | Literal format |
---|---|
ANSIC | "Mon Jan _2 15:04:05 2006" |
UnixDate | "Mon Jan _2 15:04:05 MST 2006" |
RubyDate | "Mon Jan 02 15:04:05 -0700 2006" |
RFC822 | "02 Jan 06 15:04 MST" |
RFC822Z | "02 Jan 06 15:04 -0700" |
RFC850 | "Monday, 02-Jan-06 15:04:05 MST" |
RFC1123 | "Mon, 02 Jan 2006 15:04:05 MST" |
RFC1123Z | "Mon, 02 Jan 2006 15:04:05 -0700" |
RFC3339 | "2006-01-02T15:04:05Z07:00" |
RFC339Nano | "2006-01-02T15:04:05.999999999Z07:00" |
Kitchen | "3:04PM" |
Stamp | "Jan _2 15:04:05" |
StampMilli | "Jan _2 15:04:05.000" |
StampMicro | "Jan _2 15:04:05.000000" |
StampNano | "Jan _2 15:04:05.000000000" |
You can also use a Unix representation of time in Go time functions. You can learn more about Unix time at https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16
. Listing 19.15 shows two ways to display time in a Unix format.
This listing starts by creating a variable and assigning the current time using the Now
function as you've done before. It then uses the Unix
and UnixNano
functions from the time
package to create two variables containing the current Unix times. The output looks like this:
2020-06-02 13:00:33.5876249 -0400 EDT m=+0.005984001
1591117233
1591117233587624900
Another option for displaying dates is to use standard time formats that are user-friendlier. Listing 19.16 formats the date using the RFC 1123 Z standard.
In this listing, you use the Format
function to format a time. In this case you are formatting the time stored in your variable now
, which is the current time. You are formatting it based on the pattern in the time.RFC1123Z
constant. This is the same constant presented earlier in Table 19.2. The output is:
Sun, 02 Jan 2022 13:01:53 -0400
In addition to using the time.RFC1123Z
format, any of the other formats presented in Table 19.2 can be used. For example, if the Println
statement in Listing 19.15 is changed to the following:
fmt.Println(now.Format(time.Kitchen))
then the output would be:
1:01PM
Regular expressions (also commonly referred to as regex or regexp) is a general tool that is widely used across computer languages. It includes standard search patterns that allow us to search for specific string values inside larger string patterns.
Go includes a package dedicated to regular expressions, regexp
, that allows us to perform regex
searches in strings. Listing 19.17 shows two examples of using the MatchString
function within basic regexp
within searches.
In the first example, you use the following pattern as the search string:
C([a-z]+)n
The regexp
package interprets this as a string that starts with C
, contains any number of letters, and ends in n
. The name Catelyn
meets this pattern, so the program returns true
. You can see that the MatchString
function includes error checking, so you capture the return value of true
or false
in m1
as well as any error conditions returned to err1
.
In the second example, you use this pattern:
[0-9]
This tells the program to search for any numeric character anywhere in the string. Because the username jonathan6smith
includes a numeric character, this also returns true
. The full output from running the listing is:
true
<nil>
true
<nil>
Instead of using the MatchString
function, you can use the Compile
function to parse a regular expression and then use the result to perform matching. Listing 19.18 shows how the Compile
function can be used.
You use Compile
to define the search term separately from the search operation, and you save the operation to a variable. You can then reuse the variable with any string, without having to redefine the search for each operation. In this case, you pass the following expression to Compile
:
[0-9]
As mentioned earlier, this will match any numeric digit from 0 to 9. This expression is assigned to the variable r
. When you print r
, you see that it contains this range as its search term.
The MatchString
function returns a Boolean based on whether the search is successful. In this listing, you use MatchString
with r
. Unlike in the previous listing, you only include the string to be searched. The search pattern is already known as a part of r
. When you search the string "S54366456SDfhdgstf7986"
, you will find a number, so the returned value is true
. When you search the string "It's five o'clock now"
, you do not find a number, so false
is returned.
You also use the FindString
function in the listing. The FindString
function returns the first matching instance of the search, regardless of the number of times the search term may appear in the original string, and it returns nothing if the search is not successful. In the output, you see the results from the searches using the MatchString
and FindString
functions with the search pattern in r
:
Search term: [0-9]
S54366456SDfhdgstf7986: true
It's five o'clock now: false
The phone number is 555-9980: 5
Alexander Hamilton:
In this lesson, we covered a number of prebuilt routines that can be used by importing the sort
, time
, or regex
packages. By importing these packages, you gain access to functions that will let data be easily sorted, allow you to use a variety of date and time functions, and allow you to incorporate regular expression searches.
The following exercises are provided to allow you to experiment with the tools and concepts presented in this lesson. For each exercise, write a program that meets the specified requirements and verify that the program runs as expected. The exercises are:
You learned in the lesson that you can use sort.Ints
to sort integers and sort.Strings
to sort strings. You can use the sort method Float64s
to sort Float64
values. Create a program that uses sort.Float64s
to sort 10 floating-point numbers. Only call the sort
function if the values are not already in sorted order.
The following is a struct called Student
that contains fields for the student's name and a student grade:
Type Student struct {
Name string
Grade int
}
Create a program that includes the Student
struct and declares a collection of Students
:
type Students []Student
Within the program, create an array of students in a class and assign each a name and a grade. Create a custom sort that sorts the students based on their grades, sorting from highest to lowest. The sort should be on the slice of students you create.
Write a program to display the following:
Write a program that starts with a date and then does each of the following:
Starting with a string of your choice, create a program that searches for at least five different string patterns in the original string. Include the following patterns:
Be sure to test for strings that don't exist in the original string as well as for strings that do exist. Feel free to include additional features, such as prompting the user for the original string as well as for search terms and including appropriate feedback messages if the search fails.
Write a program that lets the user enter a string. Using regular expressions, check the string and print a response indicating which of the following cases are true for the string:
This exercise is a bigger challenge that will require you to use much of what you've learned up to this point from this book. In this exercise, you should build several different date-time calculators, each of which performs a different calculation. Each calculator should accept appropriate user input for that calculator and perform the specified calculation using that input.
For each calculator:
User input can be simplified by breaking it up into discrete values. For example, instead of asking the user to enter a complete date (which could have a variety of formats), the program can prompt for separate date, month, and year values. This creates more work on the backend because your program will have to be able to convert the distinct values into a date, but it can make the program less error-prone.
Put all the following calculators in a single program and allow the user to choose the one they wish to use.
Add or subtract two different lengths of time.
For example, if the user enters one value of 3 days, 5 hours, 15 minutes, and 0 seconds, and adds a value of 7 days, 20 hours, 50 minutes, and 10 seconds, the result will be:
As a challenge, after the initial version of the calculator works as expected, include weeks as an additional unit for the input values and the output result.
Given a date and time, add or subtract an input length of time and display the date and time of the result.
For example, if the user enters December 1, 2021, 12:04:00 PM, and wants to subtract 5 days, 3 hours, and 30 minutes, the result would be November 25, 2021, 08:34:00 PM.
As a challenge, include the day of the week (Monday, Tuesday, etc.) in the output.
Given a start date and an end date, calculate the amount of time that has passed between the dates, displayed in years, months, days, hours, minutes, and seconds. For example, given the start date September 1, 1994 and the end date December 1, 2021, the results would be:
As a challenge, update the calculator to include a specific time with each date.