@@ -151,6 +151,18 @@ func TestParseGeminiAppsIDsDisambiguateSameTimestampOccurrences(t *testing.T) {
151151 assert .Equal (t , ids , repeated )
152152}
153153
154+ func TestParseGeminiAppsEquivalentNumericZonesKeepStableIdentity (t * testing.T ) {
155+ cell := func (zone string ) string {
156+ return geminiAppsProductCellHTML (
157+ "Gemini Apps" , "Prompted" ,
158+ "Jan 2, 2025, 3:04:05 PM " + zone , "<p>prompt</p>" ,
159+ )
160+ }
161+ ids := parseGeminiAppsIDs (t , geminiAppsPromptedDocument (cell ("GMT+8" )))
162+ padded := parseGeminiAppsIDs (t , geminiAppsPromptedDocument (cell ("GMT+08:00" )))
163+ assert .Equal (t , ids ["prompt" ], padded ["prompt" ])
164+ }
165+
154166func TestParseGeminiAppsRejectsNotPromptedActivityLabel (t * testing.T ) {
155167 fixture := strings .ReplaceAll (
156168 sanitizedGeminiAppsHTML ,
@@ -1027,6 +1039,36 @@ func TestParseGeminiAppsPreflightsUnknownZoneFileBeforeCallback(t *testing.T) {
10271039 assert .Zero (t , callbacks )
10281040}
10291041
1042+ func TestParseGeminiAppsPreflightsMalformedZoneBeforeCallback (t * testing.T ) {
1043+ supported := geminiAppsSingleCellHTML (
1044+ "" , "My Activity History" , "Prompted" ,
1045+ "Jan 2, 2025, 3:04:05 PM EDT" , "<p>prompt</p>" ,
1046+ )
1047+ for _ , zone := range []string {"GMT+8:3" , "GMT+8junk" , "GMT+8:30junk" , "GMT+24" , "GMT+8:60" } {
1048+ t .Run (zone , func (t * testing.T ) {
1049+ unsupported := geminiAppsSingleCellHTML (
1050+ "" , "My Activity History" , "Prompted" ,
1051+ "Jan 3, 2025, 3:04:05 PM " + zone , "<p>prompt</p>" ,
1052+ )
1053+ path := filepath .Join (t .TempDir (), "malformed-zone.html" )
1054+ require .NoError (t , os .WriteFile (path , []byte (strings .Replace (
1055+ supported , "</body>" , unsupported + "</body>" , 1 ),
1056+ ), 0o644 ))
1057+
1058+ provider , ok := NewProvider (AgentGeminiApps , ProviderConfig {})
1059+ require .True (t , ok )
1060+ exporter := provider .(GeminiAppsExportParser )
1061+ callbacks := 0
1062+ _ , err := exporter .ParseGeminiAppsExport (path , func (ParseResult ) error {
1063+ callbacks ++
1064+ return nil
1065+ })
1066+ assert .Error (t , err )
1067+ assert .Zero (t , callbacks )
1068+ })
1069+ }
1070+ }
1071+
10301072func TestParseGeminiAppsSkipsUnknownCompatibleActivityLabels (t * testing.T ) {
10311073 for _ , label := range []string {"Nicht Prompted" , "Unknown Ereignis" , "Prompted extra" } {
10321074 t .Run (label , func (t * testing.T ) {
@@ -1123,6 +1165,9 @@ func TestParseGeminiAppsTimestampUsesExplicitZones(t *testing.T) {
11231165 }{
11241166 {"edt" , "Jan 2, 2025, 3:04:05 PM EDT" , "2025-01-02T19:04:05Z" },
11251167 {"pst" , "Jan 2, 2025, 3:04:05 PM PST" , "2025-01-02T23:04:05Z" },
1168+ {"whole-hour" , "Jan 2, 2025, 3:04:05 PM GMT+8" , "2025-01-02T07:04:05Z" },
1169+ {"whole-hour-padded" , "Jan 2, 2025, 3:04:05 PM GMT+08:00" , "2025-01-02T07:04:05Z" },
1170+ {"whole-hour-negative" , "Jan 2, 2025, 3:04:05 PM GMT-8" , "2025-01-02T23:04:05Z" },
11261171 {"numeric" , "Jan 2, 2025, 3:04:05 PM GMT+05:30" , "2025-01-02T09:34:05Z" },
11271172 {"negative-zero" , "Jan 2, 2025, 3:04:05 PM GMT-00:30" , "2025-01-02T15:34:05Z" },
11281173 }
@@ -1136,12 +1181,18 @@ func TestParseGeminiAppsTimestampUsesExplicitZones(t *testing.T) {
11361181 })
11371182 }
11381183
1139- match := geminiAppsTimestampRE .FindStringSubmatch (
1140- "Jan 2, 2025, 3:04:05 PM XYZ" ,
1141- )
1142- require .Len (t , match , 2 )
1143- _ , err := parseGeminiAppsTimestamp (match [0 ], match [1 ])
1144- assert .ErrorContains (t , err , "unsupported" )
1184+ for _ , zone := range []string {
1185+ "GMT+8:3" , "GMT+8junk" , "GMT+8:30junk" , "GMT+24" , "GMT+8:60" , "XYZ" ,
1186+ } {
1187+ t .Run ("reject-" + zone , func (t * testing.T ) {
1188+ match := geminiAppsTimestampRE .FindStringSubmatch (
1189+ "Jan 2, 2025, 3:04:05 PM " + zone ,
1190+ )
1191+ require .Len (t , match , 2 )
1192+ _ , err := parseGeminiAppsTimestamp (match [0 ], match [1 ])
1193+ assert .ErrorContains (t , err , "unsupported" )
1194+ })
1195+ }
11451196}
11461197
11471198func TestParseGeminiAppsMissingContentCellIsCountedError (t * testing.T ) {
0 commit comments